edgee_sdk/
lib.rs

1use serde::{Deserialize, Serialize};
2
3pub mod token;
4
5#[derive(Debug, Serialize, Deserialize, Clone)]
6pub struct Autocapture {
7    #[serde(default)]
8    pub pageview: bool,
9    #[serde(default)]
10    pub spa_pageview: bool,
11    #[serde(default)]
12    pub engagement: bool,
13    #[serde(default)]
14    pub click: bool,
15    #[serde(default)]
16    pub form: bool,
17    #[serde(default)]
18    pub scroll: bool,
19}
20
21impl Default for Autocapture {
22    fn default() -> Self {
23        Self {
24            pageview: true,
25            spa_pageview: false,
26            engagement: false,
27            click: false,
28            form: false,
29            scroll: false,
30        }
31    }
32}
33
34/// Retrieves and customizes the SDK content.
35///
36/// This function gets the SDK content based on the URL and customizes it for the given host and autocapture settings.
37/// For URLs ending in "sdk.js", it returns the latest version. Otherwise, it extracts and returns a specific version.
38///
39/// # Arguments
40///
41/// * `url` - A string slice containing the URL path to get the SDK from (e.g. "/_edgee/sdk.js" or "/_edgee/edgee.v1.5.0.js")
42/// * `host` - A string slice containing the host domain to customize the SDK for
43/// * `autocapture` - An Autocapture struct specifying which events to automatically capture
44/// * `edgee_cookie` - A string slice containing the name of the Edgee cookie
45///
46/// # Returns
47///
48/// * `Ok(String)` - The customized SDK content if successful
49/// * `Err(&'static str)` - An error message if the SDK content could not be retrieved
50///
51/// # Example
52///
53/// ```rust
54/// use edgee_sdk::Autocapture;
55/// let url = "/_edgee/edgee.v1.8.1.js";
56/// let host = "example.com";
57/// let sdk_content = edgee_sdk::get_sdk(url, host, Autocapture::default(), "edgee", "example.com", None);
58/// assert!(sdk_content.is_ok());
59/// ```
60pub fn get_sdk(
61    url: &str,
62    host: &str,
63    autocapture: Autocapture,
64    edgee_cookie: &str,
65    edgee_cookie_domain: &str,
66    cookies_for_sdk_injection: Option<String>,
67) -> Result<String, &'static str> {
68    let content = match url.contains("shopify.") {
69        true => get_shopify_sdk_content(url)?,
70        false => get_sdk_content(url)?,
71    };
72    Ok(dynamize_sdk(
73        content,
74        host,
75        autocapture,
76        edgee_cookie,
77        edgee_cookie_domain,
78        cookies_for_sdk_injection,
79    ))
80}
81
82pub fn get_sdk_for_unsafe_user(url: &str) -> Result<String, &'static str> {
83    let content = match url.contains("shopify.") {
84        true => get_shopify_sdk_content(url)?,
85        false => get_sdk_content(url)?,
86    };
87    Ok(content.replace("{{safe}}", "unsafe"))
88}
89
90fn get_sdk_content(url: &str) -> Result<&str, &'static str> {
91    let default_version = include_str!("../release/edgee.v1.8.1.js").trim();
92    if url.ends_with("sdk.js") {
93        return Ok(default_version);
94    }
95
96    let Some((_, part)) = url.rsplit_once("edgee.v") else {
97        return Ok(default_version);
98    };
99    let Some(part) = part.strip_suffix(".js") else {
100        return Ok(default_version);
101    };
102
103    let content = match part {
104        "1.7.6" => include_str!("../release/edgee.v1.7.6.js").trim(),
105        "1.7.7" => include_str!("../release/edgee.v1.7.7.js").trim(),
106        "1.7.8" => include_str!("../release/edgee.v1.7.8.js").trim(),
107        "1.7.9" => include_str!("../release/edgee.v1.7.9.js").trim(),
108        "1.8.0" => include_str!("../release/edgee.v1.8.0.js").trim(),
109        "1.8.1" => include_str!("../release/edgee.v1.8.1.js").trim(),
110        // Add more versions as needed
111        _ => return Ok(default_version),
112    };
113
114    Ok(content)
115}
116
117fn get_shopify_sdk_content(url: &str) -> Result<&str, &'static str> {
118    let default_version = include_str!("../release/shopify.v1.8.1.js").trim();
119    if url.ends_with("shopify.js") {
120        return Ok(default_version);
121    }
122
123    let Some((_, part)) = url.rsplit_once("shopify.v") else {
124        return Ok(default_version);
125    };
126    let Some(part) = part.strip_suffix(".js") else {
127        return Ok(default_version);
128    };
129
130    let content = match part {
131        "1.7.8" => include_str!("../release/shopify.v1.7.8.js").trim(),
132        "1.7.9" => include_str!("../release/shopify.v1.7.9.js").trim(),
133        "1.8.0" => include_str!("../release/shopify.v1.8.0.js").trim(),
134        "1.8.1" => include_str!("../release/shopify.v1.8.1.js").trim(),
135        // Add more versions as needed
136        _ => return Ok(default_version),
137    };
138    Ok(content)
139}
140
141fn dynamize_sdk(
142    sdk: &str,
143    host: &str,
144    autocapture: Autocapture,
145    edgee_cookie: &str,
146    edgee_cookie_domain: &str,
147    cookies_for_sdk_injection: Option<String>,
148) -> String {
149    let token = token::generate(host);
150    let mut sdk = sdk.replace("{{token}}", token.as_str());
151    sdk = sdk.replace("{{ecn}}", edgee_cookie);
152    sdk = sdk.replace("{{ecd}}", edgee_cookie_domain);
153
154    let cookies_for_sdk_injection = cookies_for_sdk_injection.unwrap_or("".to_string());
155    sdk = sdk.replace("{{ctf}}", cookies_for_sdk_injection.as_str());
156
157    if !autocapture.pageview {
158        sdk = sdk.replace("{{ac_pageview}}", "n");
159    }
160    if autocapture.spa_pageview {
161        sdk = sdk.replace("{{ac_spa_pageview}}", "y");
162    }
163    if autocapture.engagement {
164        sdk = sdk.replace("{{ac_engagement}}", "y");
165    }
166    if autocapture.click {
167        sdk = sdk.replace("{{ac_click}}", "y");
168    }
169    if autocapture.form {
170        sdk = sdk.replace("{{ac_form}}", "y");
171    }
172    if autocapture.scroll {
173        sdk = sdk.replace("{{ac_scroll}}", "y");
174    }
175    sdk.to_string()
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181
182    #[test]
183    fn retrieves_sdk_content_for_valid_url() {
184        let url = "/sdk.js";
185        let host = "example.com";
186        let result = get_sdk(
187            url,
188            host,
189            Autocapture::default(),
190            "edgee",
191            "example.com",
192            None,
193        );
194        assert!(result.is_ok());
195        assert!(result.unwrap().contains("{{side}}"));
196    }
197
198    #[test]
199    fn retrieves_sdk_with_custom_edgee_cookie_name() {
200        let url = "/sdk.js";
201        let host = "example.com";
202        let result = get_sdk(
203            url,
204            host,
205            Autocapture::default(),
206            "test_edgee_cookie_name",
207            "example.com",
208            None,
209        );
210        assert!(result.is_ok());
211        assert!(result.unwrap().contains("test_edgee_cookie_name"));
212    }
213
214    #[test]
215    fn retrieves_versioned_sdk_content() {
216        let url = "/edgee.v1.5.0.js";
217        let host = "example.com";
218        let result = get_sdk(
219            url,
220            host,
221            Autocapture::default(),
222            "edgee",
223            "example.com",
224            None,
225        );
226        assert!(result.is_ok());
227        assert!(result.unwrap().contains("{{side}}"));
228    }
229    #[test]
230    fn returns_error_for_invalid_url_format() {
231        let url = "/invalid.js";
232        let host = "example.com";
233        let result = get_sdk(
234            url,
235            host,
236            Autocapture::default(),
237            "edgee",
238            "example.com",
239            None,
240        );
241        assert!(result.is_ok());
242    }
243
244    #[test]
245    fn returns_error_for_unsupported_version() {
246        let url = "/edgee.v2.0.0.js";
247        let host = "example.com";
248        let result = get_sdk(
249            url,
250            host,
251            Autocapture::default(),
252            "edgee",
253            "example.com",
254            None,
255        );
256        assert!(result.is_ok());
257    }
258
259    #[test]
260    fn dynamizes_sdk_with_cookies_for_sdk_injection() {
261        let url = "/edgee.v2.0.0.js";
262        let host = "example.com";
263        let cookies_for_sdk_injection = Some("s,api_key_test".to_string());
264        let result = get_sdk(
265            url,
266            host,
267            Autocapture::default(),
268            "edgee",
269            "example.com",
270            cookies_for_sdk_injection,
271        );
272        assert!(result.is_ok());
273        assert!(result.unwrap().contains("s,api_key_test"));
274    }
275}