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