Skip to main content

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