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.0.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.0.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        // Add more versions as needed
110        _ => return Ok(default_version),
111    };
112
113    Ok(content)
114}
115
116fn get_shopify_sdk_content(url: &str) -> Result<&str, &'static str> {
117    let default_version = include_str!("../release/shopify.v1.7.9.js").trim();
118    if url.ends_with("shopify.js") {
119        return Ok(default_version);
120    }
121
122    let Some((_, part)) = url.rsplit_once("shopify.v") else {
123        return Ok(default_version);
124    };
125    let Some(part) = part.strip_suffix(".js") else {
126        return Ok(default_version);
127    };
128
129    let content = match part {
130        "1.7.8" => include_str!("../release/shopify.v1.7.8.js").trim(),
131        "1.7.9" => include_str!("../release/shopify.v1.7.9.js").trim(),
132        "1.8.0" => include_str!("../release/shopify.v1.8.0.js").trim(),
133        // Add more versions as needed
134        _ => return Ok(default_version),
135    };
136    Ok(content)
137}
138
139fn dynamize_sdk(
140    sdk: &str,
141    host: &str,
142    autocapture: Autocapture,
143    edgee_cookie: &str,
144    edgee_cookie_domain: &str,
145    cookies_for_sdk_injection: Option<String>,
146) -> String {
147    let token = token::generate(host);
148    let mut sdk = sdk.replace("{{token}}", token.as_str());
149    sdk = sdk.replace("{{ecn}}", edgee_cookie);
150    sdk = sdk.replace("{{ecd}}", edgee_cookie_domain);
151
152    let cookies_for_sdk_injection = cookies_for_sdk_injection.unwrap_or("".to_string());
153    sdk = sdk.replace("{{ctf}}", cookies_for_sdk_injection.as_str());
154
155    if !autocapture.pageview {
156        sdk = sdk.replace("{{ac_pageview}}", "n");
157    }
158    if autocapture.spa_pageview {
159        sdk = sdk.replace("{{ac_spa_pageview}}", "y");
160    }
161    if autocapture.engagement {
162        sdk = sdk.replace("{{ac_engagement}}", "y");
163    }
164    if autocapture.click {
165        sdk = sdk.replace("{{ac_click}}", "y");
166    }
167    if autocapture.form {
168        sdk = sdk.replace("{{ac_form}}", "y");
169    }
170    if autocapture.scroll {
171        sdk = sdk.replace("{{ac_scroll}}", "y");
172    }
173    sdk.to_string()
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179
180    #[test]
181    fn retrieves_sdk_content_for_valid_url() {
182        let url = "/sdk.js";
183        let host = "example.com";
184        let result = get_sdk(
185            url,
186            host,
187            Autocapture::default(),
188            "edgee",
189            "example.com",
190            None,
191        );
192        assert!(result.is_ok());
193        assert!(result.unwrap().contains("{{side}}"));
194    }
195
196    #[test]
197    fn retrieves_sdk_with_custom_edgee_cookie_name() {
198        let url = "/sdk.js";
199        let host = "example.com";
200        let result = get_sdk(
201            url,
202            host,
203            Autocapture::default(),
204            "test_edgee_cookie_name",
205            "example.com",
206            None,
207        );
208        assert!(result.is_ok());
209        assert!(result.unwrap().contains("test_edgee_cookie_name"));
210    }
211
212    #[test]
213    fn retrieves_versioned_sdk_content() {
214        let url = "/edgee.v1.5.0.js";
215        let host = "example.com";
216        let result = get_sdk(
217            url,
218            host,
219            Autocapture::default(),
220            "edgee",
221            "example.com",
222            None,
223        );
224        assert!(result.is_ok());
225        assert!(result.unwrap().contains("{{side}}"));
226    }
227    #[test]
228    fn returns_error_for_invalid_url_format() {
229        let url = "/invalid.js";
230        let host = "example.com";
231        let result = get_sdk(
232            url,
233            host,
234            Autocapture::default(),
235            "edgee",
236            "example.com",
237            None,
238        );
239        assert!(result.is_ok());
240    }
241
242    #[test]
243    fn returns_error_for_unsupported_version() {
244        let url = "/edgee.v2.0.0.js";
245        let host = "example.com";
246        let result = get_sdk(
247            url,
248            host,
249            Autocapture::default(),
250            "edgee",
251            "example.com",
252            None,
253        );
254        assert!(result.is_ok());
255    }
256
257    #[test]
258    fn dynamizes_sdk_with_cookies_for_sdk_injection() {
259        let url = "/edgee.v2.0.0.js";
260        let host = "example.com";
261        let cookies_for_sdk_injection = Some("s,api_key_test".to_string());
262        let result = get_sdk(
263            url,
264            host,
265            Autocapture::default(),
266            "edgee",
267            "example.com",
268            cookies_for_sdk_injection,
269        );
270        assert!(result.is_ok());
271        assert!(result.unwrap().contains("s,api_key_test"));
272    }
273}