edgee-sdk 1.8.0

The full-stack edge platform for your edge oriented applications
Documentation
use serde::{Deserialize, Serialize};

pub mod token;

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Autocapture {
    #[serde(default)]
    pub pageview: bool,
    #[serde(default)]
    pub spa_pageview: bool,
    #[serde(default)]
    pub engagement: bool,
    #[serde(default)]
    pub click: bool,
    #[serde(default)]
    pub form: bool,
    #[serde(default)]
    pub scroll: bool,
}

impl Default for Autocapture {
    fn default() -> Self {
        Self {
            pageview: true,
            spa_pageview: false,
            engagement: false,
            click: false,
            form: false,
            scroll: false,
        }
    }
}

/// Retrieves and customizes the SDK content.
///
/// This function gets the SDK content based on the URL and customizes it for the given host and autocapture settings.
/// For URLs ending in "sdk.js", it returns the latest version. Otherwise, it extracts and returns a specific version.
///
/// # Arguments
///
/// * `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")
/// * `host` - A string slice containing the host domain to customize the SDK for
/// * `autocapture` - An Autocapture struct specifying which events to automatically capture
/// * `edgee_cookie` - A string slice containing the name of the Edgee cookie
///
/// # Returns
///
/// * `Ok(String)` - The customized SDK content if successful
/// * `Err(&'static str)` - An error message if the SDK content could not be retrieved
///
/// # Example
///
/// ```rust
/// use edgee_sdk::Autocapture;
/// let url = "/_edgee/edgee.v1.8.0.js";
/// let host = "example.com";
/// let sdk_content = edgee_sdk::get_sdk(url, host, Autocapture::default(), "edgee", "example.com", None);
/// assert!(sdk_content.is_ok());
/// ```
pub fn get_sdk(
    url: &str,
    host: &str,
    autocapture: Autocapture,
    edgee_cookie: &str,
    edgee_cookie_domain: &str,
    cookies_for_sdk_injection: Option<String>,
) -> Result<String, &'static str> {
    let content = match url.contains("shopify.") {
        true => get_shopify_sdk_content(url)?,
        false => get_sdk_content(url)?,
    };
    Ok(dynamize_sdk(
        content,
        host,
        autocapture,
        edgee_cookie,
        edgee_cookie_domain,
        cookies_for_sdk_injection,
    ))
}

pub fn get_sdk_for_unsafe_user(url: &str) -> Result<String, &'static str> {
    let content = match url.contains("shopify.") {
        true => get_shopify_sdk_content(url)?,
        false => get_sdk_content(url)?,
    };
    Ok(content.replace("{{safe}}", "unsafe"))
}

fn get_sdk_content(url: &str) -> Result<&str, &'static str> {
    let default_version = include_str!("../release/edgee.v1.8.0.js").trim();
    if url.ends_with("sdk.js") {
        return Ok(default_version);
    }

    let Some((_, part)) = url.rsplit_once("edgee.v") else {
        return Ok(default_version);
    };
    let Some(part) = part.strip_suffix(".js") else {
        return Ok(default_version);
    };

    let content = match part {
        "1.7.6" => include_str!("../release/edgee.v1.7.6.js").trim(),
        "1.7.7" => include_str!("../release/edgee.v1.7.7.js").trim(),
        "1.7.8" => include_str!("../release/edgee.v1.7.8.js").trim(),
        "1.7.9" => include_str!("../release/edgee.v1.7.9.js").trim(),
        "1.8.0" => include_str!("../release/edgee.v1.8.0.js").trim(),
        // Add more versions as needed
        _ => return Ok(default_version),
    };

    Ok(content)
}

fn get_shopify_sdk_content(url: &str) -> Result<&str, &'static str> {
    let default_version = include_str!("../release/shopify.v1.7.9.js").trim();
    if url.ends_with("shopify.js") {
        return Ok(default_version);
    }

    let Some((_, part)) = url.rsplit_once("shopify.v") else {
        return Ok(default_version);
    };
    let Some(part) = part.strip_suffix(".js") else {
        return Ok(default_version);
    };

    let content = match part {
        "1.7.8" => include_str!("../release/shopify.v1.7.8.js").trim(),
        "1.7.9" => include_str!("../release/shopify.v1.7.9.js").trim(),
        "1.8.0" => include_str!("../release/shopify.v1.8.0.js").trim(),
        // Add more versions as needed
        _ => return Ok(default_version),
    };
    Ok(content)
}

fn dynamize_sdk(
    sdk: &str,
    host: &str,
    autocapture: Autocapture,
    edgee_cookie: &str,
    edgee_cookie_domain: &str,
    cookies_for_sdk_injection: Option<String>,
) -> String {
    let token = token::generate(host);
    let mut sdk = sdk.replace("{{token}}", token.as_str());
    sdk = sdk.replace("{{ecn}}", edgee_cookie);
    sdk = sdk.replace("{{ecd}}", edgee_cookie_domain);

    let cookies_for_sdk_injection = cookies_for_sdk_injection.unwrap_or("".to_string());
    sdk = sdk.replace("{{ctf}}", cookies_for_sdk_injection.as_str());

    if !autocapture.pageview {
        sdk = sdk.replace("{{ac_pageview}}", "n");
    }
    if autocapture.spa_pageview {
        sdk = sdk.replace("{{ac_spa_pageview}}", "y");
    }
    if autocapture.engagement {
        sdk = sdk.replace("{{ac_engagement}}", "y");
    }
    if autocapture.click {
        sdk = sdk.replace("{{ac_click}}", "y");
    }
    if autocapture.form {
        sdk = sdk.replace("{{ac_form}}", "y");
    }
    if autocapture.scroll {
        sdk = sdk.replace("{{ac_scroll}}", "y");
    }
    sdk.to_string()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn retrieves_sdk_content_for_valid_url() {
        let url = "/sdk.js";
        let host = "example.com";
        let result = get_sdk(
            url,
            host,
            Autocapture::default(),
            "edgee",
            "example.com",
            None,
        );
        assert!(result.is_ok());
        assert!(result.unwrap().contains("{{side}}"));
    }

    #[test]
    fn retrieves_sdk_with_custom_edgee_cookie_name() {
        let url = "/sdk.js";
        let host = "example.com";
        let result = get_sdk(
            url,
            host,
            Autocapture::default(),
            "test_edgee_cookie_name",
            "example.com",
            None,
        );
        assert!(result.is_ok());
        assert!(result.unwrap().contains("test_edgee_cookie_name"));
    }

    #[test]
    fn retrieves_versioned_sdk_content() {
        let url = "/edgee.v1.5.0.js";
        let host = "example.com";
        let result = get_sdk(
            url,
            host,
            Autocapture::default(),
            "edgee",
            "example.com",
            None,
        );
        assert!(result.is_ok());
        assert!(result.unwrap().contains("{{side}}"));
    }
    #[test]
    fn returns_error_for_invalid_url_format() {
        let url = "/invalid.js";
        let host = "example.com";
        let result = get_sdk(
            url,
            host,
            Autocapture::default(),
            "edgee",
            "example.com",
            None,
        );
        assert!(result.is_ok());
    }

    #[test]
    fn returns_error_for_unsupported_version() {
        let url = "/edgee.v2.0.0.js";
        let host = "example.com";
        let result = get_sdk(
            url,
            host,
            Autocapture::default(),
            "edgee",
            "example.com",
            None,
        );
        assert!(result.is_ok());
    }

    #[test]
    fn dynamizes_sdk_with_cookies_for_sdk_injection() {
        let url = "/edgee.v2.0.0.js";
        let host = "example.com";
        let cookies_for_sdk_injection = Some("s,api_key_test".to_string());
        let result = get_sdk(
            url,
            host,
            Autocapture::default(),
            "edgee",
            "example.com",
            cookies_for_sdk_injection,
        );
        assert!(result.is_ok());
        assert!(result.unwrap().contains("s,api_key_test"));
    }
}