diff --git a/Cargo.lock b/Cargo.lock index 1a2227d..40947db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -180,6 +180,12 @@ dependencies = [ "alloc-stdlib", ] +[[package]] +name = "build-print" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8e6738dfb11354886f890621b4a34c0b177f75538023f7100b608ab9adbd66b" + [[package]] name = "bumpalo" version = "3.19.0" @@ -2418,6 +2424,7 @@ dependencies = [ name = "trusted-server-js" version = "0.1.0" dependencies = [ + "build-print", "hex", "sha2 0.10.9", "which", diff --git a/Cargo.toml b/Cargo.toml index 9545c84..387fe3b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ debug = 1 async-trait = "0.1" base64 = "0.22" brotli = "8.0" +build-print = "1.0.1" bytes = "1.11" chacha20poly1305 = "0.10" chrono = "0.4.42" diff --git a/SEQUENCE.md b/SEQUENCE.md index 235ef0e..c4bc28d 100644 --- a/SEQUENCE.md +++ b/SEQUENCE.md @@ -1,4 +1,4 @@ -# 🛡️ Trusted Server — First-Party Proxying Flow +# 🛡️ Trusted Server — Proxying Flow ## 🔄 System Flow Diagram @@ -80,7 +80,7 @@ sequenceDiagram activate TS activate PBS activate DSP - JS->>TS: GET /first-party/ad
(with signals) + JS->>TS: GET /ad/render
(with signals) TS->>PBS: POST /openrtb2/auction
(OpenRTB 2.x) PBS->>DSP: POST bid request DSP-->>PBS: 200 bid response diff --git a/crates/common/README.md b/crates/common/README.md index 4fa5fad..50ad8bc 100644 --- a/crates/common/README.md +++ b/crates/common/README.md @@ -1,6 +1,6 @@ # trusted-server-common -Utilities shared by Trusted Server components. This crate contains HTML/CSS rewriting helpers used to normalize ad creative assets to first‑party proxy endpoints. +Utilities shared by Trusted Server components. This crate contains HTML/CSS rewriting helpers used to normalize ad creative assets to proxy endpoints. ## Creative Rewriting diff --git a/crates/common/src/backend.rs b/crates/common/src/backend.rs index ba5a1c8..2dbedb0 100644 --- a/crates/common/src/backend.rs +++ b/crates/common/src/backend.rs @@ -6,15 +6,42 @@ use url::Url; use crate::error::TrustedServerError; +/// Compute the Host header value for a backend request. +/// +/// For standard ports (443 for HTTPS, 80 for HTTP), returns just the hostname. +/// For non-standard ports, returns "hostname:port" to ensure backends that +/// generate URLs based on the Host header include the port. +/// +/// This fixes the issue where backends behind reverse proxies (like Caddy) +/// would generate URLs without the port when the Host header didn't include it. +#[inline] +fn compute_host_header(scheme: &str, host: &str, port: u16) -> String { + let is_https = scheme.eq_ignore_ascii_case("https"); + let default_port = if is_https { 443 } else { 80 }; + if port != default_port { + format!("{}:{}", host, port) + } else { + host.to_string() + } +} + /// Ensure a dynamic backend exists for the given origin and return its name. /// /// The backend name is derived from the scheme and `host[:port]` to avoid collisions across /// http/https or different ports. If a backend with the derived name already exists, /// this function logs and reuses it. +/// +/// # Arguments +/// +/// * `scheme` - The URL scheme ("http" or "https") +/// * `host` - The hostname +/// * `port` - Optional port number +/// * `certificate_check` - If true, enables TLS certificate verification (default for production) pub fn ensure_origin_backend( scheme: &str, host: &str, port: Option, + certificate_check: bool, ) -> Result> { if host.is_empty() { return Err(Report::new(TrustedServerError::Proxy { @@ -32,20 +59,33 @@ pub fn ensure_origin_backend( let host_with_port = format!("{}:{}", host, target_port); // Name: iframe___ (sanitize '.' and ':') + // Include cert setting in name to avoid reusing a backend with different cert settings let name_base = format!("{}_{}_{}", scheme, host, target_port); - let backend_name = format!("backend_{}", name_base.replace(['.', ':'], "_")); + let cert_suffix = if certificate_check { "" } else { "_nocert" }; + let backend_name = format!( + "backend_{}{}", + name_base.replace(['.', ':'], "_"), + cert_suffix + ); + + let host_header = compute_host_header(scheme, host, target_port); // Target base is host[:port]; SSL is enabled only for https scheme let mut builder = Backend::builder(&backend_name, &host_with_port) - .override_host(host) + .override_host(&host_header) .connect_timeout(Duration::from_secs(1)) .first_byte_timeout(Duration::from_secs(15)) .between_bytes_timeout(Duration::from_secs(10)); if scheme.eq_ignore_ascii_case("https") { - builder = builder - .enable_ssl() - .sni_hostname(host) - .check_certificate(host); + builder = builder.enable_ssl().sni_hostname(host); + if certificate_check { + builder = builder.check_certificate(host); + } else { + log::warn!( + "INSECURE: certificate check disabled for backend: {}", + backend_name + ); + } log::info!("enable ssl for backend: {}", backend_name); } @@ -75,7 +115,10 @@ pub fn ensure_origin_backend( } } -pub fn ensure_backend_from_url(origin_url: &str) -> Result> { +pub fn ensure_backend_from_url( + origin_url: &str, + certificate_check: bool, +) -> Result> { let parsed_url = Url::parse(origin_url).change_context(TrustedServerError::Proxy { message: format!("Invalid origin_url: {}", origin_url), })?; @@ -88,22 +131,69 @@ pub fn ensure_backend_from_url(origin_url: &str) -> Result String { // Image src + data-src element!("img", |el| { if let Some(src) = el.get_attribute("src") { + log::debug!("creative rewrite: img src input = {}", src); if let Some(p) = proxy_if_abs(settings, &src) { + log::debug!("creative rewrite: img src output = {}", p); let _ = el.set_attribute("src", &p); } } @@ -539,6 +542,43 @@ mod tests { assert_eq!(to_abs("mailto:test@example.com", &settings), None); } + #[test] + fn to_abs_preserves_port_in_protocol_relative() { + let settings = crate::test_support::tests::create_test_settings(); + // Protocol-relative URL with explicit port should preserve the port + assert_eq!( + to_abs("//cdn.example.com:8080/asset.js", &settings), + Some("https://cdn.example.com:8080/asset.js".to_string()) + ); + assert_eq!( + to_abs("//cdn.example.com:9443/img.png", &settings), + Some("https://cdn.example.com:9443/img.png".to_string()) + ); + } + + #[test] + fn rewrite_creative_preserves_non_standard_port() { + // Verify creative rewriting preserves non-standard ports in URLs + let settings = crate::test_support::tests::create_test_settings(); + let html = r#" + + + + + + + +"#; + let out = rewrite_creative_html(html, &settings); + + // Port 9443 should be preserved (URL-encoded as %3A9443) + assert!( + out.contains("cdn.example.com%3A9443"), + "Port 9443 should be preserved in rewritten URLs: {}", + out + ); + } + #[test] fn rewrite_style_urls_handles_absolute_and_relative() { let settings = crate::test_support::tests::create_test_settings(); diff --git a/crates/common/src/fastly_storage.rs b/crates/common/src/fastly_storage.rs index c4a8241..0136326 100644 --- a/crates/common/src/fastly_storage.rs +++ b/crates/common/src/fastly_storage.rs @@ -84,7 +84,7 @@ impl FastlyApiClient { } pub fn from_secret_store(store_name: &str, key_name: &str) -> Result { - ensure_backend_from_url("https://api.fastly.com").map_err(|e| { + ensure_backend_from_url("https://api.fastly.com", true).map_err(|e| { TrustedServerError::Configuration { message: format!("Failed to ensure API backend: {}", e), } diff --git a/crates/common/src/html_processor.rs b/crates/common/src/html_processor.rs index 1803436..c7cfe25 100644 --- a/crates/common/src/html_processor.rs +++ b/crates/common/src/html_processor.rs @@ -1,4 +1,4 @@ -//! Simplified HTML processor that combines URL replacement and Prebid injection +//! Simplified HTML processor that combines URL replacement and integration injection //! //! This module provides a StreamProcessor implementation for HTML content. use std::cell::Cell; @@ -189,10 +189,26 @@ pub fn create_html_processor(config: HtmlProcessorConfig) -> impl StreamProcesso // Inject unified tsjs bundle once at the start of element!("head", { let injected_tsjs = injected_tsjs.clone(); + let integrations = integration_registry.clone(); + let patterns = patterns.clone(); + let document_state = document_state.clone(); move |el| { if !injected_tsjs.get() { - let loader = tsjs::unified_script_tag(); - el.prepend(&loader, ContentType::Html); + let mut snippet = String::new(); + let ctx = IntegrationHtmlContext { + request_host: &patterns.request_host, + request_scheme: &patterns.request_scheme, + origin_host: &patterns.origin_host, + document_state: &document_state, + }; + // First inject the unified TSJS bundle (defines tsjs.setConfig, etc.) + snippet.push_str(&tsjs::unified_script_tag()); + // Then add any integration-specific head inserts (e.g., mode config) + // These run after the bundle so tsjs API is available + for insert in integrations.head_inserts(&ctx) { + snippet.push_str(&insert); + } + el.prepend(&snippet, ContentType::Html); injected_tsjs.set(true); } Ok(()) @@ -454,8 +470,10 @@ pub fn create_html_processor(config: HtmlProcessorConfig) -> impl StreamProcesso #[cfg(test)] mod tests { use super::*; + use crate::integrations::prebid::{tsjs_config_script_tag, Mode as PrebidMode}; use crate::integrations::{ AttributeRewriteAction, IntegrationAttributeContext, IntegrationAttributeRewriter, + IntegrationHeadInjector, }; use crate::streaming_processor::{Compression, PipelineConfig, StreamingPipeline}; use crate::test_support::tests::create_test_settings; @@ -582,6 +600,59 @@ mod tests { assert_eq!(config.request_scheme, "https"); } + #[test] + fn injects_tsjs_config_when_mode_set() { + struct ModeInjector; + + impl IntegrationHeadInjector for ModeInjector { + fn integration_id(&self) -> &'static str { + "mode-injector" + } + + fn head_inserts(&self, _ctx: &IntegrationHtmlContext<'_>) -> Vec { + vec![tsjs_config_script_tag(PrebidMode::Auction)] + } + } + + let mut config = create_test_config(); + config.integrations = IntegrationRegistry::from_rewriters_with_head_injectors( + Vec::new(), + Vec::new(), + vec![Arc::new(ModeInjector)], + ); + let processor = create_html_processor(config); + + let pipeline_config = PipelineConfig { + input_compression: Compression::None, + output_compression: Compression::None, + chunk_size: 8192, + }; + let mut pipeline = StreamingPipeline::new(pipeline_config, processor); + + let html = ""; + let mut output = Vec::new(); + pipeline + .process(Cursor::new(html.as_bytes()), &mut output) + .unwrap(); + let result = String::from_utf8(output).unwrap(); + + let config_pos = result.find("setConfig({mode:\"auction\"})"); + let script_pos = result.find("trustedserver-js"); + assert!( + config_pos.is_some(), + "should inject tsjs mode config when configured" + ); + assert!( + script_pos.is_some(), + "should inject unified tsjs script when processing HTML" + ); + // Config must come AFTER the bundle so tsjs.setConfig is defined when called + assert!( + config_pos.unwrap() > script_pos.unwrap(), + "should place tsjs config after the unified bundle (so tsjs.setConfig is available)" + ); + } + #[test] fn test_real_publisher_html() { // Test with publisher HTML from test_publisher.html diff --git a/crates/common/src/http_util.rs b/crates/common/src/http_util.rs index 132d6bd..c830aab 100644 --- a/crates/common/src/http_util.rs +++ b/crates/common/src/http_util.rs @@ -6,6 +6,157 @@ use sha2::{Digest, Sha256}; use crate::settings::Settings; +/// Extracted request information for host rewriting. +/// +/// This struct captures the effective host and scheme from an incoming request, +/// accounting for proxy headers like `X-Forwarded-Host` and `X-Forwarded-Proto`. +#[derive(Debug, Clone)] +pub struct RequestInfo { + /// The effective host for URL rewriting (from Forwarded, X-Forwarded-Host, or Host header) + pub host: String, + /// The effective scheme (from TLS detection, Forwarded, X-Forwarded-Proto, or default) + pub scheme: String, +} + +impl RequestInfo { + /// Extract request info from a Fastly request. + /// + /// Host priority: + /// 1. `Forwarded` header (RFC 7239, `host=...`) + /// 2. `X-Forwarded-Host` header (for chained proxy setups) + /// 3. `Host` header + /// + /// Scheme priority: + /// 1. Fastly SDK TLS detection (most reliable) + /// 2. `Forwarded` header (RFC 7239, `proto=https`) + /// 3. `X-Forwarded-Proto` header + /// 4. `Fastly-SSL` header + /// 5. Default to `http` + pub fn from_request(req: &Request) -> Self { + let host = extract_request_host(req); + let scheme = detect_request_scheme(req); + + Self { host, scheme } + } +} + +fn extract_request_host(req: &Request) -> String { + req.get_header("forwarded") + .and_then(|h| h.to_str().ok()) + .and_then(|value| parse_forwarded_param(value, "host")) + .or_else(|| { + req.get_header("x-forwarded-host") + .and_then(|h| h.to_str().ok()) + .and_then(parse_list_header_value) + }) + .or_else(|| req.get_header(header::HOST).and_then(|h| h.to_str().ok())) + .unwrap_or_default() + .to_string() +} + +fn parse_forwarded_param<'a>(forwarded: &'a str, param: &str) -> Option<&'a str> { + for entry in forwarded.split(',') { + for part in entry.split(';') { + let mut iter = part.splitn(2, '='); + let key = iter.next().unwrap_or("").trim(); + let value = iter.next().unwrap_or("").trim(); + if key.is_empty() || value.is_empty() { + continue; + } + if key.eq_ignore_ascii_case(param) { + let value = strip_quotes(value); + if !value.is_empty() { + return Some(value); + } + } + } + } + None +} + +fn parse_list_header_value(value: &str) -> Option<&str> { + value + .split(',') + .map(|part| part.trim()) + .find(|part| !part.is_empty()) + .map(strip_quotes) + .filter(|part| !part.is_empty()) +} + +fn strip_quotes(value: &str) -> &str { + let trimmed = value.trim(); + if trimmed.len() >= 2 && trimmed.starts_with('"') && trimmed.ends_with('"') { + &trimmed[1..trimmed.len() - 1] + } else { + trimmed + } +} + +fn normalize_scheme(value: &str) -> Option { + let scheme = value.trim().to_ascii_lowercase(); + if scheme == "https" || scheme == "http" { + Some(scheme) + } else { + None + } +} + +/// Detects the request scheme (HTTP or HTTPS) using Fastly SDK methods and headers. +/// +/// Tries multiple methods in order of reliability: +/// 1. Fastly SDK TLS detection methods (most reliable) +/// 2. Forwarded header (RFC 7239) +/// 3. X-Forwarded-Proto header +/// 4. Fastly-SSL header (least reliable, can be spoofed) +/// 5. Default to HTTP +fn detect_request_scheme(req: &Request) -> String { + // 1. First try Fastly SDK's built-in TLS detection methods + if let Some(tls_protocol) = req.get_tls_protocol() { + log::debug!("TLS protocol detected: {}", tls_protocol); + return "https".to_string(); + } + + // Also check TLS cipher - if present, connection is HTTPS + if req.get_tls_cipher_openssl_name().is_some() { + log::debug!("TLS cipher detected, using HTTPS"); + return "https".to_string(); + } + + // 2. Try the Forwarded header (RFC 7239) + if let Some(forwarded) = req.get_header("forwarded") { + if let Ok(forwarded_str) = forwarded.to_str() { + if let Some(proto) = parse_forwarded_param(forwarded_str, "proto") { + if let Some(scheme) = normalize_scheme(proto) { + return scheme; + } + } + } + } + + // 3. Try X-Forwarded-Proto header + if let Some(proto) = req.get_header("x-forwarded-proto") { + if let Ok(proto_str) = proto.to_str() { + if let Some(value) = parse_list_header_value(proto_str) { + if let Some(scheme) = normalize_scheme(value) { + return scheme; + } + } + } + } + + // 4. Check Fastly-SSL header (can be spoofed by clients, use as last resort) + if let Some(ssl) = req.get_header("fastly-ssl") { + if let Ok(ssl_str) = ssl.to_str() { + if ssl_str == "1" || ssl_str.to_lowercase() == "true" { + return "https".to_string(); + } + } + } + + // Default to HTTP + "http".to_string() +} + /// Build a static text response with strong ETag and standard caching headers. /// Handles If-None-Match to return 304 when appropriate. pub fn serve_static_with_etag(body: &str, req: &Request, content_type: &str) -> Response { @@ -166,4 +317,118 @@ mod tests { &t1 )); } + + // RequestInfo tests + + #[test] + fn test_request_info_from_host_header() { + let mut req = Request::new(fastly::http::Method::GET, "https://test.example.com/page"); + req.set_header("host", "test.example.com"); + + let info = RequestInfo::from_request(&req); + assert_eq!( + info.host, "test.example.com", + "Host should use Host header when forwarded headers are missing" + ); + // No TLS or forwarded headers, defaults to http. + assert_eq!( + info.scheme, "http", + "Scheme should default to http without TLS or forwarded headers" + ); + } + + #[test] + fn test_request_info_x_forwarded_host_precedence() { + let mut req = Request::new(fastly::http::Method::GET, "https://test.example.com/page"); + req.set_header("host", "internal-proxy.local"); + req.set_header("x-forwarded-host", "public.example.com, proxy.local"); + + let info = RequestInfo::from_request(&req); + assert_eq!( + info.host, "public.example.com", + "Host should prefer X-Forwarded-Host over Host" + ); + } + + #[test] + fn test_request_info_scheme_from_x_forwarded_proto() { + let mut req = Request::new(fastly::http::Method::GET, "https://test.example.com/page"); + req.set_header("host", "test.example.com"); + req.set_header("x-forwarded-proto", "https, http"); + + let info = RequestInfo::from_request(&req); + assert_eq!( + info.scheme, "https", + "Scheme should prefer the first X-Forwarded-Proto value" + ); + + // Test HTTP + let mut req = Request::new(fastly::http::Method::GET, "http://test.example.com/page"); + req.set_header("host", "test.example.com"); + req.set_header("x-forwarded-proto", "http"); + + let info = RequestInfo::from_request(&req); + assert_eq!( + info.scheme, "http", + "Scheme should use the X-Forwarded-Proto value when present" + ); + } + + #[test] + fn request_info_forwarded_header_precedence() { + // Forwarded header takes precedence over X-Forwarded-Proto + let mut req = Request::new(fastly::http::Method::GET, "https://test.example.com/page"); + req.set_header( + "forwarded", + "for=192.0.2.60;proto=\"HTTPS\";host=\"public.example.com:443\"", + ); + req.set_header("host", "internal-proxy.local"); + req.set_header("x-forwarded-host", "proxy.local"); + req.set_header("x-forwarded-proto", "http"); + + let info = RequestInfo::from_request(&req); + assert_eq!( + info.host, "public.example.com:443", + "Host should prefer Forwarded host over X-Forwarded-Host" + ); + assert_eq!( + info.scheme, "https", + "Scheme should prefer Forwarded proto over X-Forwarded-Proto" + ); + } + + #[test] + fn test_request_info_scheme_from_fastly_ssl() { + let mut req = Request::new(fastly::http::Method::GET, "https://test.example.com/page"); + req.set_header("fastly-ssl", "1"); + + let info = RequestInfo::from_request(&req); + assert_eq!( + info.scheme, "https", + "Scheme should fall back to Fastly-SSL when other signals are missing" + ); + } + + #[test] + fn test_request_info_chained_proxy_scenario() { + // Simulate: Client (HTTPS) -> Proxy A -> Trusted Server (HTTP internally) + // Proxy A sets X-Forwarded-Host and X-Forwarded-Proto + let mut req = Request::new( + fastly::http::Method::GET, + "http://trusted-server.internal/page", + ); + req.set_header("host", "trusted-server.internal"); + req.set_header("x-forwarded-host", "public.example.com"); + req.set_header("x-forwarded-proto", "https"); + + let info = RequestInfo::from_request(&req); + assert_eq!( + info.host, "public.example.com", + "Host should use X-Forwarded-Host in chained proxy scenarios" + ); + assert_eq!( + info.scheme, "https", + "Scheme should use X-Forwarded-Proto in chained proxy scenarios" + ); + } } diff --git a/crates/common/src/integrations/didomi.rs b/crates/common/src/integrations/didomi.rs index 6c1a2ed..ab601ad 100644 --- a/crates/common/src/integrations/didomi.rs +++ b/crates/common/src/integrations/didomi.rs @@ -202,7 +202,7 @@ impl IntegrationProxy for DidomiIntegration { let target_url = self .build_target_url(base_origin, consent_path, req.get_query_str()) .change_context(Self::error("Failed to build Didomi target URL"))?; - let backend_name = ensure_backend_from_url(base_origin) + let backend_name = ensure_backend_from_url(base_origin, true) .change_context(Self::error("Failed to configure Didomi backend"))?; let mut proxy_req = Request::new(req.get_method().clone(), &target_url); diff --git a/crates/common/src/integrations/gam.rs b/crates/common/src/integrations/gam.rs new file mode 100644 index 0000000..ae39ccd --- /dev/null +++ b/crates/common/src/integrations/gam.rs @@ -0,0 +1,157 @@ +//! GAM (Google Ad Manager) Interceptor Integration +//! +//! This integration forces Prebid creatives to render when GAM doesn't have +//! matching line items configured. It's a client-side only integration that +//! works by intercepting GPT's `slotRenderEnded` event. +//! +//! # Configuration +//! +//! ```toml +//! [integrations.gam] +//! enabled = true +//! bidders = ["mocktioneer"] # Only intercept these bidders, empty = all +//! force_render = false # Force render even if GAM has a line item +//! ``` +//! +//! # Environment Variables +//! +//! ```bash +//! TRUSTED_SERVER__INTEGRATIONS__GAM__ENABLED=true +//! TRUSTED_SERVER__INTEGRATIONS__GAM__BIDDERS="mocktioneer,appnexus" +//! TRUSTED_SERVER__INTEGRATIONS__GAM__FORCE_RENDER=false +//! ``` + +use serde::{Deserialize, Serialize}; +use validator::Validate; + +use crate::settings::IntegrationConfig; + +use super::{IntegrationHeadInjector, IntegrationHtmlContext, IntegrationRegistration}; + +const GAM_INTEGRATION_ID: &str = "gam"; + +/// GAM interceptor configuration. +#[derive(Debug, Clone, Default, Deserialize, Serialize, Validate)] +pub struct GamIntegrationConfig { + /// Enable the GAM interceptor. Defaults to false. + #[serde(default)] + pub enabled: bool, + + /// Only intercept bids from these bidders. Empty = all bidders. + #[serde(default, deserialize_with = "crate::settings::vec_from_seq_or_map")] + pub bidders: Vec, + + /// Force render Prebid creative even if GAM returned a line item. + #[serde(default)] + pub force_render: bool, +} + +impl IntegrationConfig for GamIntegrationConfig { + fn is_enabled(&self) -> bool { + self.enabled + } +} + +/// Generate the JavaScript config script tag for GAM integration. +/// Sets window.tsGamConfig which is picked up by the GAM integration on init. +pub fn gam_config_script_tag(config: &GamIntegrationConfig) -> String { + let bidders_json = if config.bidders.is_empty() { + "[]".to_string() + } else { + format!( + "[{}]", + config + .bidders + .iter() + .map(|b| format!("\"{}\"", b)) + .collect::>() + .join(",") + ) + }; + + format!( + r#""#, + bidders_json, config.force_render + ) +} + +pub struct GamIntegration { + config: GamIntegrationConfig, +} + +impl GamIntegration { + pub fn new(config: GamIntegrationConfig) -> Self { + Self { config } + } +} + +impl IntegrationHeadInjector for GamIntegration { + fn integration_id(&self) -> &'static str { + GAM_INTEGRATION_ID + } + + fn head_inserts(&self, _ctx: &IntegrationHtmlContext<'_>) -> Vec { + vec![gam_config_script_tag(&self.config)] + } +} + +/// Register the GAM integration if enabled. +pub fn register(settings: &crate::settings::Settings) -> Option { + use std::sync::Arc; + + let config: GamIntegrationConfig = + settings.integrations.get_typed(GAM_INTEGRATION_ID).ok()??; + + log::info!( + "GAM integration enabled: bidders={:?}, force_render={}", + config.bidders, + config.force_render + ); + + let integration = Arc::new(GamIntegration::new(config)); + + Some( + IntegrationRegistration::builder(GAM_INTEGRATION_ID) + .with_head_injector(integration) + .build(), + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn gam_config_script_tag_with_bidders() { + let config = GamIntegrationConfig { + enabled: true, + bidders: vec!["mocktioneer".to_string(), "appnexus".to_string()], + force_render: false, + }; + let tag = gam_config_script_tag(&config); + assert!(tag.contains("window.tsGamConfig=")); + assert!(tag.contains("enabled:true")); + assert!(tag.contains(r#"bidders:["mocktioneer","appnexus"]"#)); + assert!(tag.contains("forceRender:false")); + } + + #[test] + fn gam_config_script_tag_empty_bidders() { + let config = GamIntegrationConfig { + enabled: true, + bidders: vec![], + force_render: true, + }; + let tag = gam_config_script_tag(&config); + assert!(tag.contains("bidders:[]")); + assert!(tag.contains("forceRender:true")); + } + + #[test] + fn gam_config_disabled_by_default() { + let config = GamIntegrationConfig::default(); + assert!(!config.enabled); + assert!(config.bidders.is_empty()); + assert!(!config.force_render); + } +} diff --git a/crates/common/src/integrations/lockr.rs b/crates/common/src/integrations/lockr.rs index 014e226..84a0335 100644 --- a/crates/common/src/integrations/lockr.rs +++ b/crates/common/src/integrations/lockr.rs @@ -141,7 +141,7 @@ impl LockrIntegration { lockr_req.set_header(header::USER_AGENT, "TrustedServer/1.0"); lockr_req.set_header(header::ACCEPT, "application/javascript, */*"); - let backend_name = ensure_backend_from_url(sdk_url) + let backend_name = ensure_backend_from_url(sdk_url, true) .change_context(Self::error("Failed to determine backend for SDK fetch"))?; let mut lockr_response = @@ -235,7 +235,7 @@ impl LockrIntegration { } // Get backend and forward - let backend_name = ensure_backend_from_url(&self.config.api_endpoint) + let backend_name = ensure_backend_from_url(&self.config.api_endpoint, true) .change_context(Self::error("Failed to determine backend for API proxy"))?; let response = match target_req.send(backend_name) { @@ -265,6 +265,7 @@ impl LockrIntegration { header::ACCEPT_LANGUAGE, header::ACCEPT_ENCODING, header::COOKIE, + header::ORIGIN, ]; for header_name in &headers_to_copy { diff --git a/crates/common/src/integrations/mod.rs b/crates/common/src/integrations/mod.rs index 076d5cb..8e7e940 100644 --- a/crates/common/src/integrations/mod.rs +++ b/crates/common/src/integrations/mod.rs @@ -3,6 +3,7 @@ use crate::settings::Settings; pub mod didomi; +pub mod gam; pub mod lockr; pub mod nextjs; pub mod permutive; @@ -13,9 +14,9 @@ pub mod testlight; pub use registry::{ AttributeRewriteAction, AttributeRewriteOutcome, IntegrationAttributeContext, IntegrationAttributeRewriter, IntegrationDocumentState, IntegrationEndpoint, - IntegrationHtmlContext, IntegrationHtmlPostProcessor, IntegrationMetadata, IntegrationProxy, - IntegrationRegistration, IntegrationRegistrationBuilder, IntegrationRegistry, - IntegrationScriptContext, IntegrationScriptRewriter, ScriptRewriteAction, + IntegrationHeadInjector, IntegrationHtmlContext, IntegrationHtmlPostProcessor, + IntegrationMetadata, IntegrationProxy, IntegrationRegistration, IntegrationRegistrationBuilder, + IntegrationRegistry, IntegrationScriptContext, IntegrationScriptRewriter, ScriptRewriteAction, }; type IntegrationBuilder = fn(&Settings) -> Option; @@ -28,5 +29,6 @@ pub(crate) fn builders() -> &'static [IntegrationBuilder] { permutive::register, lockr::register, didomi::register, + gam::register, ] } diff --git a/crates/common/src/integrations/permutive.rs b/crates/common/src/integrations/permutive.rs index 6d565e8..986948c 100644 --- a/crates/common/src/integrations/permutive.rs +++ b/crates/common/src/integrations/permutive.rs @@ -118,7 +118,7 @@ impl PermutiveIntegration { permutive_req.set_header(header::USER_AGENT, "TrustedServer/1.0"); permutive_req.set_header(header::ACCEPT, "application/javascript, */*"); - let backend_name = ensure_backend_from_url(&sdk_url) + let backend_name = ensure_backend_from_url(&sdk_url, true) .change_context(Self::error("Failed to determine backend for SDK fetch"))?; let mut permutive_response = @@ -208,7 +208,7 @@ impl PermutiveIntegration { } // Get backend and forward - let backend_name = ensure_backend_from_url(&self.config.api_endpoint) + let backend_name = ensure_backend_from_url(&self.config.api_endpoint, true) .change_context(Self::error("Failed to determine backend for API proxy"))?; let response = target_req @@ -277,7 +277,7 @@ impl PermutiveIntegration { } // Get backend and forward - let backend_name = ensure_backend_from_url(&self.config.secure_signals_endpoint) + let backend_name = ensure_backend_from_url(&self.config.secure_signals_endpoint, true) .change_context(Self::error( "Failed to determine backend for Secure Signals proxy", ))?; @@ -342,7 +342,7 @@ impl PermutiveIntegration { } // Get backend and forward - let backend_name = ensure_backend_from_url("https://events.permutive.app") + let backend_name = ensure_backend_from_url("https://events.permutive.app", true) .change_context(Self::error("Failed to determine backend for Events proxy"))?; let response = target_req @@ -405,7 +405,7 @@ impl PermutiveIntegration { } // Get backend and forward - let backend_name = ensure_backend_from_url("https://sync.permutive.com") + let backend_name = ensure_backend_from_url("https://sync.permutive.com", true) .change_context(Self::error("Failed to determine backend for Sync proxy"))?; let response = target_req @@ -460,7 +460,7 @@ impl PermutiveIntegration { self.copy_request_headers(&req, &mut target_req); // Get backend and forward - let backend_name = ensure_backend_from_url("https://cdn.permutive.com") + let backend_name = ensure_backend_from_url("https://cdn.permutive.com", true) .change_context(Self::error("Failed to determine backend for CDN proxy"))?; let response = target_req diff --git a/crates/common/src/integrations/prebid.rs b/crates/common/src/integrations/prebid.rs index 51b1b9f..485cafd 100644 --- a/crates/common/src/integrations/prebid.rs +++ b/crates/common/src/integrations/prebid.rs @@ -2,13 +2,12 @@ use std::collections::HashMap; use std::sync::Arc; use async_trait::async_trait; -use base64::{engine::general_purpose::STANDARD as BASE64, Engine}; use error_stack::{Report, ResultExt}; use fastly::http::{header, Method, StatusCode}; use fastly::{Request, Response}; use serde::{Deserialize, Serialize}; -use serde_json::{json, Value as Json, Value as JsonValue}; -use url::Url; +use serde_json::{Value as Json, Value as JsonValue}; +use url::{form_urlencoded, Url}; use validator::Validate; use crate::backend::ensure_backend_from_url; @@ -16,18 +15,50 @@ use crate::constants::{HEADER_SYNTHETIC_FRESH, HEADER_SYNTHETIC_TRUSTED_SERVER}; use crate::creative; use crate::error::TrustedServerError; use crate::geo::GeoInfo; +use crate::http_util::compute_encrypted_sha256_token; +use crate::http_util::RequestInfo; use crate::integrations::{ AttributeRewriteAction, IntegrationAttributeContext, IntegrationAttributeRewriter, - IntegrationEndpoint, IntegrationProxy, IntegrationRegistration, + IntegrationEndpoint, IntegrationHeadInjector, IntegrationHtmlContext, IntegrationProxy, + IntegrationRegistration, +}; +use crate::openrtb::{ + Banner, Device, Format, Geo, Imp, ImpExt, OpenRtbRequest, OpenRtbResponse, PrebidImpExt, Regs, + RegsExt, RequestExt, Site, TrustedServerExt, User, UserExt, }; -use crate::openrtb::{Banner, Format, Imp, ImpExt, OpenRtbRequest, PrebidImpExt, Site}; use crate::request_signing::RequestSigner; use crate::settings::{IntegrationConfig, Settings}; use crate::synthetic::{generate_synthetic_id, get_or_generate_synthetic_id}; const PREBID_INTEGRATION_ID: &str = "prebid"; -const ROUTE_FIRST_PARTY_AD: &str = "/first-party/ad"; -const ROUTE_THIRD_PARTY_AD: &str = "/third-party/ad"; +const ROUTE_RENDER: &str = "/ad/render"; +const ROUTE_AUCTION: &str = "/ad/auction"; + +/// Mode determines how TSJS handles ad requests. +/// +/// - `Render`: Uses iframe-based server-side rendering. +/// The server handles the full auction and returns ready-to-display HTML. +/// - `Auction`: Uses client-side OpenRTB auctions. +/// Clients (for example, Prebid.js) send OpenRTB to /ad/auction. +#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum Mode { + Render, + Auction, +} + +/// Generate TSJS config script tag based on mode. +/// This script runs after the unified TSJS bundle has loaded. +pub fn tsjs_config_script_tag(mode: Mode) -> String { + match mode { + Mode::Render => { + r#""#.to_string() + } + Mode::Auction => { + r#""#.to_string() + } + } +} #[derive(Debug, Clone, Deserialize, Serialize, Validate)] pub struct PrebidIntegrationConfig { @@ -41,12 +72,19 @@ pub struct PrebidIntegrationConfig { deserialize_with = "crate::settings::vec_from_seq_or_map" )] pub bidders: Vec, - #[serde(default = "default_auto_configure")] - pub auto_configure: bool, #[serde(default)] pub debug: bool, + /// Optional default mode to enqueue when injecting the unified bundle. #[serde(default)] - pub script_handler: Option, + pub mode: Option, + /// Patterns to match Prebid script URLs for serving empty JS. + /// Supports suffix matching (e.g., "/prebid.min.js" matches any path ending with that) + /// and wildcard patterns (e.g., "/static/prebid/*" matches paths under that prefix). + #[serde( + default = "default_script_patterns", + deserialize_with = "crate::settings::vec_from_seq_or_map" + )] + pub script_patterns: Vec, } impl IntegrationConfig for PrebidIntegrationConfig { @@ -63,12 +101,29 @@ fn default_bidders() -> Vec { vec!["mocktioneer".to_string()] } -fn default_auto_configure() -> bool { +fn default_enabled() -> bool { true } -fn default_enabled() -> bool { - true +/// Default suffixes that identify Prebid scripts +const PREBID_SCRIPT_SUFFIXES: &[&str] = &[ + "/prebid.js", + "/prebid.min.js", + "/prebidjs.js", + "/prebidjs.min.js", +]; + +fn default_script_patterns() -> Vec { + // Default patterns to intercept Prebid scripts and serve empty JS + // - Exact paths like "/prebid.min.js" match only that path + // - Wildcard paths like "/static/prebid/*" match anything under that prefix + // and are filtered by PREBID_SCRIPT_SUFFIXES in matches_script_pattern() + vec![ + "/prebid.js".to_string(), + "/prebid.min.js".to_string(), + "/prebidjs.js".to_string(), + "/prebidjs.min.js".to_string(), + ] } #[derive(Debug, Deserialize)] @@ -118,6 +173,92 @@ impl PrebidIntegration { Arc::new(Self { config }) } + fn matches_script_url(&self, attr_value: &str) -> bool { + let trimmed = attr_value.trim(); + let without_query = trimmed.split(['?', '#']).next().unwrap_or(trimmed); + + if self.matches_script_pattern(without_query) { + return true; + } + + if !without_query.starts_with('/') + && !without_query.starts_with("//") + && !without_query.contains("://") + { + let with_slash = format!("/{without_query}"); + if self.matches_script_pattern(&with_slash) { + return true; + } + } + + let parsed = if without_query.starts_with("//") { + Url::parse(&format!("https:{without_query}")) + } else { + Url::parse(without_query) + }; + + parsed + .ok() + .is_some_and(|url| self.matches_script_pattern(url.path())) + } + + fn matches_script_pattern(&self, path: &str) -> bool { + // Normalize path to lowercase for case-insensitive matching + let path_lower = path.to_ascii_lowercase(); + + log::debug!( + "matches_script_pattern: path='{}', patterns={:?}", + path, + self.config.script_patterns + ); + + // Check if path matches any configured pattern + for pattern in &self.config.script_patterns { + let pattern_lower = pattern.to_ascii_lowercase(); + + // Check for wildcard patterns: /* or {*name} + if pattern_lower.ends_with("/*") || pattern_lower.contains("{*") { + // Extract prefix before the wildcard + let prefix = if pattern_lower.ends_with("/*") { + &pattern_lower[..pattern_lower.len() - 1] // Remove trailing * + } else { + // Find {* and extract prefix before it + pattern_lower.split("{*").next().unwrap_or("") + }; + + log::debug!( + " wildcard pattern='{}', prefix='{}', path_starts_with_prefix={}", + pattern, + prefix, + path_lower.starts_with(prefix) + ); + + if path_lower.starts_with(prefix) { + // Check if it ends with a known Prebid script suffix + let has_suffix = PREBID_SCRIPT_SUFFIXES + .iter() + .any(|suffix| path_lower.ends_with(suffix)); + log::debug!( + " checking suffixes: path ends with known suffix={}", + has_suffix + ); + if has_suffix { + return true; + } + } + } else { + // Exact match or suffix match + let matches = path_lower.ends_with(&pattern_lower); + log::debug!(" exact/suffix pattern='{}', matches={}", pattern, matches); + if matches { + return true; + } + } + } + log::debug!(" no pattern matched, returning false"); + false + } + fn error(message: impl Into) -> TrustedServerError { TrustedServerError::Integration { integration: PREBID_INTEGRATION_ID.to_string(), @@ -125,62 +266,35 @@ impl PrebidIntegration { } } - async fn handle_third_party_ad( + async fn handle_auction( &self, settings: &Settings, - mut req: Request, + req: Request, ) -> Result> { - let body: AdRequest = serde_json::from_slice(&req.take_body_bytes()).change_context( - TrustedServerError::Prebid { - message: "Failed to parse tsjs auction request".to_string(), - }, - )?; - - log::info!("/third-party/ad: received {} adUnits", body.ad_units.len()); - for unit in &body.ad_units { - if let Some(mt) = &unit.media_types { - if let Some(banner) = &mt.banner { - log::debug!("unit={} sizes={:?}", unit.code, banner.sizes); - } - } - } - - let openrtb = build_openrtb_from_ts(&body, settings, &self.config); - if let Ok(preview) = serde_json::to_string(&openrtb) { - log::debug!( - "OpenRTB payload (truncated): {}", - &preview.chars().take(512).collect::() - ); - } - - req.set_body_json(&openrtb) - .change_context(TrustedServerError::Prebid { - message: "Failed to set OpenRTB body".to_string(), - })?; - handle_prebid_auction(settings, req, &self.config).await } fn handle_script_handler(&self) -> Result> { - let body = "// Script overridden by Trusted Server\n"; - + // Serve empty JS - Prebid.js is already bundled in the unified TSJS bundle + // that gets injected into the page. We just need to prevent the original + // Prebid script from loading/executing. Ok(Response::from_status(StatusCode::OK) .with_header( header::CONTENT_TYPE, "application/javascript; charset=utf-8", ) .with_header(header::CACHE_CONTROL, "public, max-age=31536000, immutable") - .with_body(body)) + .with_body("/* prebid.js replaced by tsjs-unified */")) } - async fn handle_first_party_ad( + async fn handle_render( &self, settings: &Settings, mut req: Request, ) -> Result> { let url = req.get_url_str(); let parsed = Url::parse(url).change_context(TrustedServerError::Prebid { - message: "Invalid first-party serve-ad URL".to_string(), + message: "Invalid render URL".to_string(), })?; let qp = parsed .query_pairs() @@ -257,7 +371,8 @@ pub fn register(settings: &Settings) -> Option { Some( IntegrationRegistration::builder(PREBID_INTEGRATION_ID) .with_proxy(integration.clone()) - .with_attribute_rewriter(integration) + .with_attribute_rewriter(integration.clone()) + .with_head_injector(integration) .build(), ) } @@ -270,14 +385,15 @@ impl IntegrationProxy for PrebidIntegration { fn routes(&self) -> Vec { let mut routes = vec![ - IntegrationEndpoint::get(ROUTE_FIRST_PARTY_AD), - IntegrationEndpoint::post(ROUTE_THIRD_PARTY_AD), + IntegrationEndpoint::get(ROUTE_RENDER), + IntegrationEndpoint::post(ROUTE_AUCTION), ]; - if let Some(script_path) = &self.config.script_handler { - // We need to leak the string to get a 'static str for IntegrationEndpoint - // This is safe because the config lives for the lifetime of the application - let static_path: &'static str = Box::leak(script_path.clone().into_boxed_str()); + // Register routes for script removal patterns + // Patterns can be exact paths (e.g., "/prebid.min.js") or use matchit wildcards + // (e.g., "/static/prebid/{*rest}") + for pattern in &self.config.script_patterns { + let static_path: &'static str = Box::leak(pattern.clone().into_boxed_str()); routes.push(IntegrationEndpoint::get(static_path)); } @@ -292,19 +408,34 @@ impl IntegrationProxy for PrebidIntegration { let path = req.get_path().to_string(); let method = req.get_method().clone(); + log::debug!( + "Prebid handle: method={}, path='{}', script_patterns={:?}", + method, + path, + self.config.script_patterns + ); + match method { - Method::GET if self.config.script_handler.as_ref() == Some(&path) => { + Method::GET if path == ROUTE_RENDER => self.handle_render(settings, req).await, + Method::POST if path == ROUTE_AUCTION => self.handle_auction(settings, req).await, + // Serve empty JS for any other GET request that was routed here by matchit + // (i.e., matched one of our script_patterns wildcards). + // Prebid.js is already bundled in tsjs-unified, so we just need to + // prevent the original script from loading. + Method::GET => { + log::debug!("Prebid: serving empty JS stub for path '{}'", path); self.handle_script_handler() } - Method::GET if path == ROUTE_FIRST_PARTY_AD => { - self.handle_first_party_ad(settings, req).await - } - Method::POST if path == ROUTE_THIRD_PARTY_AD => { - self.handle_third_party_ad(settings, req).await + _ => { + log::debug!( + "Prebid: no handler matched for {} '{}', returning error", + method, + path + ); + Err(Report::new(Self::error(format!( + "Unsupported Prebid route: {path}" + )))) } - _ => Err(Report::new(Self::error(format!( - "Unsupported Prebid route: {path}" - )))), } } } @@ -315,7 +446,7 @@ impl IntegrationAttributeRewriter for PrebidIntegration { } fn handles_attribute(&self, attribute: &str) -> bool { - self.config.auto_configure && matches!(attribute, "src" | "href") + matches!(attribute, "src" | "href") } fn rewrite( @@ -324,7 +455,7 @@ impl IntegrationAttributeRewriter for PrebidIntegration { attr_value: &str, _ctx: &IntegrationAttributeContext<'_>, ) -> AttributeRewriteAction { - if self.config.auto_configure && is_prebid_script_url(attr_value) { + if self.matches_script_url(attr_value) { AttributeRewriteAction::remove_element() } else { AttributeRewriteAction::keep() @@ -332,6 +463,22 @@ impl IntegrationAttributeRewriter for PrebidIntegration { } } +impl IntegrationHeadInjector for PrebidIntegration { + fn integration_id(&self) -> &'static str { + PREBID_INTEGRATION_ID + } + + fn head_inserts(&self, _ctx: &IntegrationHtmlContext<'_>) -> Vec { + // Only inject TSJS mode config if mode is set + // The Prebid bundle (served via script interception) already has s2sConfig built-in + // GAM config is now handled by the separate GAM integration + self.config + .mode + .map(|mode| vec![tsjs_config_script_tag(mode)]) + .unwrap_or_default() + } +} + fn build_openrtb_from_ts( req: &AdRequest, settings: &Settings, @@ -370,10 +517,15 @@ fn build_openrtb_from_ts( Imp { id: unit.code.clone(), - banner: Some(Banner { format: formats }), + banner: Some(Banner { + format: formats, + extra: HashMap::new(), + }), ext: Some(ImpExt { - prebid: PrebidImpExt { bidder }, + prebid: Some(PrebidImpExt { bidder }), + extra: HashMap::new(), }), + extra: HashMap::new(), } }) .collect(); @@ -384,20 +536,16 @@ fn build_openrtb_from_ts( site: Some(Site { domain: Some(settings.publisher.domain.clone()), page: Some(format!("https://{}", settings.publisher.domain)), + extra: HashMap::new(), }), + user: None, + regs: None, + device: None, + ext: None, + extra: HashMap::new(), } } -fn is_prebid_script_url(url: &str) -> bool { - let lower = url.to_ascii_lowercase(); - let without_query = lower.split('?').next().unwrap_or(""); - let filename = without_query.rsplit('/').next().unwrap_or(""); - matches!( - filename, - "prebid.js" | "prebid.min.js" | "prebidjs.js" | "prebidjs.min.js" - ) -} - async fn pbs_auction_for_get( settings: &Settings, req: Request, @@ -438,11 +586,10 @@ async fn handle_prebid_auction( config: &PrebidIntegrationConfig, ) -> Result> { log::info!("Handling Prebid auction request"); - let mut openrtb_request: Json = serde_json::from_slice(&req.take_body_bytes()).change_context( - TrustedServerError::Prebid { + let mut openrtb_request: OpenRtbRequest = serde_json::from_slice(&req.take_body_bytes()) + .change_context(TrustedServerError::Prebid { message: "Failed to parse OpenRTB request".to_string(), - }, - )?; + })?; let synthetic_id = get_or_generate_synthetic_id(settings, &req)?; let fresh_id = generate_synthetic_id(settings, &req)?; @@ -460,6 +607,7 @@ async fn handle_prebid_auction( settings, &req, )?; + override_prebid_bidders(&mut openrtb_request, &config.bidders); let mut pbs_req = Request::new( Method::POST, @@ -473,7 +621,8 @@ async fn handle_prebid_auction( })?; log::info!("Sending request to Prebid Server"); - let backend_name = ensure_backend_from_url(&config.server_url)?; + let backend_name = + ensure_backend_from_url(&config.server_url, settings.proxy.certificate_check)?; let mut pbs_response = pbs_req .send(backend_name) @@ -483,18 +632,21 @@ async fn handle_prebid_auction( if pbs_response.get_status().is_success() { let response_body = pbs_response.take_body_bytes(); - match serde_json::from_slice::(&response_body) { - Ok(mut response_json) => { - let request_host = get_request_host(&req); - let request_scheme = get_request_scheme(&req); - transform_prebid_response(&mut response_json, &request_host, &request_scheme)?; - - let transformed_body = serde_json::to_vec(&response_json).change_context( - TrustedServerError::Prebid { - message: "Failed to serialize transformed response".to_string(), - }, + match serde_json::from_slice::(&response_body) { + Ok(mut response) => { + let request_info = RequestInfo::from_request(&req); + transform_prebid_response( + &mut response, + settings, + &request_info.host, + &request_info.scheme, )?; + let transformed_body = + serde_json::to_vec(&response).change_context(TrustedServerError::Prebid { + message: "Failed to serialize transformed response".to_string(), + })?; + Ok(Response::from_status(StatusCode::OK) .with_header(header::CONTENT_TYPE, "application/json") .with_header("X-Synthetic-ID", &synthetic_id) @@ -512,154 +664,264 @@ async fn handle_prebid_auction( } fn enhance_openrtb_request( - request: &mut Json, + request: &mut OpenRtbRequest, synthetic_id: &str, fresh_id: &str, settings: &Settings, req: &Request, ) -> Result<(), Report> { - if !request["user"].is_object() { - request["user"] = json!({}); - } - request["user"]["id"] = json!(synthetic_id); - - if !request["user"]["ext"].is_object() { - request["user"]["ext"] = json!({}); - } - request["user"]["ext"]["synthetic_fresh"] = json!(fresh_id); + let user = request.user.get_or_insert_with(User::default); + user.id = Some(synthetic_id.to_string()); + let user_ext = user.ext.get_or_insert_with(UserExt::default); + user_ext.synthetic_fresh = Some(fresh_id.to_string()); if req.get_header("Sec-GPC").is_some() { - if !request["regs"].is_object() { - request["regs"] = json!({}); - } - if !request["regs"]["ext"].is_object() { - request["regs"]["ext"] = json!({}); - } - request["regs"]["ext"]["us_privacy"] = json!("1YYN"); + let regs = request.regs.get_or_insert_with(Regs::default); + let regs_ext = regs.ext.get_or_insert_with(RegsExt::default); + regs_ext.us_privacy = Some("1YYN".to_string()); } if let Some(geo_info) = GeoInfo::from_request(req) { - let geo_obj = json!({ - "type": 2, - "country": geo_info.country, - "city": geo_info.city, - "region": geo_info.region, - }); - - if !request["device"].is_object() { - request["device"] = json!({}); - } - request["device"]["geo"] = geo_obj; + let device = request.device.get_or_insert_with(Device::default); + let geo = device.geo.get_or_insert_with(Geo::default); + geo.geo_type = Some(2); + geo.country = Some(geo_info.country); + geo.city = Some(geo_info.city); + geo.region = geo_info.region; } - if !request["site"].is_object() { - request["site"] = json!({ - "domain": settings.publisher.domain, - "page": format!("https://{}", settings.publisher.domain), + if request.site.is_none() { + request.site = Some(Site { + domain: Some(settings.publisher.domain.clone()), + page: Some(format!("https://{}", settings.publisher.domain)), + extra: HashMap::new(), }); } if let Some(request_signing_config) = &settings.request_signing { - if request_signing_config.enabled && request["id"].is_string() { - if !request["ext"].is_object() { - request["ext"] = json!({}); - } - - let id = request["id"] - .as_str() - .expect("should have string id when is_string checked"); + if request_signing_config.enabled { let signer = RequestSigner::from_config()?; - let signature = signer.sign(id.as_bytes())?; - request["ext"]["trusted_server"] = json!({ - "signature": signature, - "kid": signer.kid - }); + let signature = signer.sign(request.id.as_bytes())?; + let ext = request.ext.get_or_insert_with(RequestExt::default); + let trusted = ext + .trusted_server + .get_or_insert_with(TrustedServerExt::default); + trusted.signature = Some(signature); + trusted.kid = Some(signer.kid); } } Ok(()) } +/// Override bidders in the OpenRTB request with the server-configured bidder list. +/// +/// This replaces any client-provided bidder configuration with the bidders specified +/// in `[integrations.prebid].bidders` from trusted-server.toml. This ensures that: +/// +/// 1. Only approved bidders are used (security/compliance control) +/// 2. Bidder params are managed server-side, not exposed to clients +/// 3. The publisher's Prebid.js config doesn't need bidder-specific setup +/// +/// Note: Client-provided bidder params (e.g., `appnexus: { placementId: '123' }`) +/// are intentionally stripped. Bidder params should be configured in Prebid Server +/// or through trusted-server's bidder configuration. +fn override_prebid_bidders(request: &mut OpenRtbRequest, bidders: &[String]) { + let bidder_map = bidders + .iter() + .map(|bidder| (bidder.clone(), JsonValue::Object(serde_json::Map::new()))) + .collect::>(); + + log::debug!( + "Overriding OpenRTB bidders from settings: count={}", + bidder_map.len() + ); + + for imp in &mut request.imp { + let ext = imp.ext.get_or_insert_with(|| ImpExt { + prebid: None, + extra: HashMap::new(), + }); + let prebid = ext.prebid.get_or_insert_with(PrebidImpExt::default); + prebid.bidder = bidder_map.clone(); + } +} + fn transform_prebid_response( - response: &mut Json, + response: &mut OpenRtbResponse, + settings: &Settings, request_host: &str, request_scheme: &str, ) -> Result<(), Report> { - if let Some(seatbids) = response["seatbid"].as_array_mut() { - for seatbid in seatbids { - if let Some(bids) = seatbid["bid"].as_array_mut() { - for bid in bids { - if let Some(adm) = bid["adm"].as_str() { - bid["adm"] = json!(rewrite_ad_markup(adm, request_host, request_scheme)); - } + let Some(seatbids) = response.seatbid.as_mut() else { + return Ok(()); + }; - if let Some(nurl) = bid["nurl"].as_str() { - bid["nurl"] = json!(make_first_party_proxy_url( - nurl, - request_host, - request_scheme, - "track" - )); - } + for seatbid in seatbids { + let Some(bids) = seatbid.bid.as_mut() else { + continue; + }; - if let Some(burl) = bid["burl"].as_str() { - bid["burl"] = json!(make_first_party_proxy_url( - burl, - request_host, - request_scheme, - "track" - )); - } + for bid in bids { + if let Some(adm) = bid.adm.as_deref() { + if looks_like_html(adm) { + bid.adm = Some(creative::rewrite_creative_html(adm, settings)); } } + + if let Some(nurl) = bid.nurl.as_deref() { + bid.nurl = Some(first_party_proxy_url( + settings, + request_host, + request_scheme, + nurl, + )); + } + + if let Some(burl) = bid.burl.as_deref() { + bid.burl = Some(first_party_proxy_url( + settings, + request_host, + request_scheme, + burl, + )); + } } } Ok(()) } -fn rewrite_ad_markup(markup: &str, request_host: &str, request_scheme: &str) -> String { - let mut content = markup.to_string(); - let cdn_patterns = vec![ - ("https://cdn.adsrvr.org", "adsrvr"), - ("https://ib.adnxs.com", "adnxs"), - ("https://rtb.openx.net", "openx"), - ("https://as.casalemedia.com", "casale"), - ("https://eus.rubiconproject.com", "rubicon"), - ]; - - for (cdn_url, cdn_name) in cdn_patterns { - if content.contains(cdn_url) { - let proxy_base = format!( - "{}://{}/ad-proxy/{}", - request_scheme, request_host, cdn_name - ); - content = content.replace(cdn_url, &proxy_base); - } +fn first_party_proxy_url( + settings: &Settings, + request_host: &str, + request_scheme: &str, + clear_url: &str, +) -> String { + let trimmed = clear_url.trim(); + let Some(abs) = normalize_absolute_url(trimmed, request_scheme) else { + return clear_url.to_string(); + }; + if is_excluded_by_domain(settings, &abs) { + return clear_url.to_string(); } - - content = content.replace( - "//cdn.adsrvr.org", - &format!("//{}/ad-proxy/adsrvr", request_host), - ); - content = content.replace( - "//ib.adnxs.com", - &format!("//{}/ad-proxy/adnxs", request_host), - ); - content + let signed = creative::build_proxy_url(settings, &abs); + let proxy_path = if signed == abs { + build_proxy_path_for_raw_url(settings, &abs) + } else { + signed + }; + absolutize_proxy_path(settings, request_host, request_scheme, proxy_path) } -fn make_first_party_proxy_url( - third_party_url: &str, +fn absolutize_proxy_path( + settings: &Settings, request_host: &str, request_scheme: &str, - proxy_type: &str, + proxy_path: String, ) -> String { - let encoded = BASE64.encode(third_party_url.as_bytes()); - format!( - "{}://{}/ad-proxy/{}/{}", - request_scheme, request_host, proxy_type, encoded - ) + if proxy_path.starts_with('/') { + let host = if request_host.is_empty() { + settings.publisher.domain.as_str() + } else { + request_host + }; + if host.is_empty() { + return proxy_path; + } + return format!("{request_scheme}://{host}{proxy_path}"); + } + proxy_path +} + +fn looks_like_html(markup: &str) -> bool { + let trimmed = markup.trim_start(); + if trimmed.is_empty() { + return false; + } + let lower = trimmed.to_ascii_lowercase(); + if !lower.starts_with('<') { + return false; + } + if lower.starts_with(" Option { + let lower = url.to_ascii_lowercase(); + if lower.starts_with("http://") || lower.starts_with("https://") { + return Some(url.to_string()); + } + if url.starts_with("//") { + return Some(format!("{}:{}", request_scheme, url)); + } + None +} + +fn is_excluded_by_domain(settings: &Settings, abs_url: &str) -> bool { + if settings.rewrite.exclude_domains.is_empty() { + return false; + } + if let Ok(parsed) = Url::parse(abs_url) { + return settings.rewrite.is_excluded(parsed.as_str()); + } + let Some(host) = extract_host(abs_url) else { + return false; + }; + let check = format!("https://{}", host); + settings.rewrite.is_excluded(&check) +} + +fn extract_host(abs_url: &str) -> Option<&str> { + let lower = abs_url.to_ascii_lowercase(); + let rest = if lower.starts_with("https://") { + &abs_url["https://".len()..] + } else if lower.starts_with("http://") { + &abs_url["http://".len()..] + } else { + return None; + }; + let host = rest.split(['/', '?', '#']).next().unwrap_or(""); + if host.is_empty() { + return None; + } + Some(host) +} + +fn build_proxy_path_for_raw_url(settings: &Settings, clear_url: &str) -> String { + let token = compute_encrypted_sha256_token(settings, clear_url); + let mut qs = form_urlencoded::Serializer::new(String::new()); + qs.append_pair("tsurl", clear_url); + qs.append_pair("tstoken", &token); + format!("/first-party/proxy?{}", qs.finish()) } fn copy_request_headers(from: &Request, to: &mut Request) { @@ -678,38 +940,14 @@ fn copy_request_headers(from: &Request, to: &mut Request) { } } -fn get_request_host(req: &Request) -> String { - req.get_header(header::HOST) - .and_then(|h| h.to_str().ok()) - .unwrap_or("") - .to_string() -} - -fn get_request_scheme(req: &Request) -> String { - if req.get_tls_protocol().is_some() || req.get_tls_cipher_openssl_name().is_some() { - return "https".to_string(); - } - - if let Some(proto) = req.get_header("X-Forwarded-Proto") { - if let Ok(proto_str) = proto.to_str() { - return proto_str.to_lowercase(); - } - } - - "https".to_string() -} - #[cfg(test)] mod tests { use super::*; - use crate::html_processor::{create_html_processor, HtmlProcessorConfig}; - use crate::integrations::{AttributeRewriteAction, IntegrationRegistry}; + use crate::integrations::{AttributeRewriteAction, IntegrationAttributeContext}; use crate::settings::Settings; - use crate::streaming_processor::{Compression, PipelineConfig, StreamingPipeline}; use crate::test_support::tests::crate_test_settings_str; use fastly::http::Method; use serde_json::json; - use std::io::Cursor; fn make_settings() -> Settings { Settings::from_toml(&crate_test_settings_str()).expect("should parse settings") @@ -721,30 +959,15 @@ mod tests { server_url: "https://prebid.example".to_string(), timeout_ms: 1000, bidders: vec!["exampleBidder".to_string()], - auto_configure: true, debug: false, - script_handler: None, + mode: None, + script_patterns: default_script_patterns(), } } - fn config_from_settings( - settings: &Settings, - registry: &IntegrationRegistry, - ) -> HtmlProcessorConfig { - HtmlProcessorConfig::from_settings( - settings, - registry, - "origin.example.com", - "test.example.com", - "https", - ) - } - #[test] fn attribute_rewriter_removes_prebid_scripts() { - let integration = PrebidIntegration { - config: base_config(), - }; + let integration = PrebidIntegration::new(base_config()); let ctx = IntegrationAttributeContext { attribute_name: "src", request_host: "pub.example", @@ -753,17 +976,21 @@ mod tests { }; let rewritten = integration.rewrite("src", "https://cdn.prebid.org/prebid.min.js", &ctx); - assert!(matches!(rewritten, AttributeRewriteAction::RemoveElement)); + assert!( + matches!(rewritten, AttributeRewriteAction::RemoveElement), + "Prebid script tags should be removed" + ); let untouched = integration.rewrite("src", "https://cdn.example.com/app.js", &ctx); - assert!(matches!(untouched, AttributeRewriteAction::Keep)); + assert!( + matches!(untouched, AttributeRewriteAction::Keep), + "Non-Prebid scripts should remain" + ); } #[test] - fn attribute_rewriter_handles_query_strings_and_links() { - let integration = PrebidIntegration { - config: base_config(), - }; + fn attribute_rewriter_handles_query_strings() { + let integration = PrebidIntegration::new(base_config()); let ctx = IntegrationAttributeContext { attribute_name: "href", request_host: "pub.example", @@ -773,146 +1000,225 @@ mod tests { let rewritten = integration.rewrite("href", "https://cdn.prebid.org/prebid.js?v=1.2.3", &ctx); - assert!(matches!(rewritten, AttributeRewriteAction::RemoveElement)); + assert!( + matches!(rewritten, AttributeRewriteAction::RemoveElement), + "Prebid links with query strings should be removed" + ); } #[test] - fn html_processor_keeps_prebid_scripts_when_auto_config_disabled() { - let html = r#" - - - "#; - - let mut settings = make_settings(); - settings - .integrations - .insert_config( - "prebid", - &json!({ - "enabled": true, - "server_url": "https://test-prebid.com/openrtb2/auction", - "timeout_ms": 1000, - "bidders": ["mocktioneer"], - "auto_configure": false, - "debug": false - }), - ) - .expect("should update prebid config"); - let registry = IntegrationRegistry::new(&settings); - let config = config_from_settings(&settings, ®istry); - let processor = create_html_processor(config); - let pipeline_config = PipelineConfig { - input_compression: Compression::None, - output_compression: Compression::None, - chunk_size: 8192, + fn attribute_rewriter_matches_wildcard_patterns() { + let mut config = base_config(); + config.script_patterns = vec!["/static/prebid/*".to_string()]; + let integration = PrebidIntegration::new(config); + let ctx = IntegrationAttributeContext { + attribute_name: "src", + request_host: "pub.example", + request_scheme: "https", + origin_host: "origin.example", }; - let mut pipeline = StreamingPipeline::new(pipeline_config, processor); - let mut output = Vec::new(); - let result = pipeline.process(Cursor::new(html.as_bytes()), &mut output); - assert!(result.is_ok()); - let processed = String::from_utf8_lossy(&output); - assert!( - processed.contains("tsjs-unified"), - "Unified bundle should be injected" + let rewritten = integration.rewrite( + "src", + "https://cdn.example.com/static/prebid/v1/prebid.min.js", + &ctx, ); assert!( - processed.contains("prebid.min.js"), - "Prebid script should remain when auto-config is disabled" + matches!(rewritten, AttributeRewriteAction::RemoveElement), + "Wildcard patterns should match prebid assets on full URLs" ); + + let rewritten_relative = integration.rewrite("src", "static/prebid/prebid.min.js", &ctx); assert!( - processed.contains("cdn.prebid.org/prebid.js"), - "Prebid preload should remain when auto-config is disabled" + matches!(rewritten_relative, AttributeRewriteAction::RemoveElement), + "Wildcard patterns should match relative paths without a leading slash" ); } #[test] - fn html_processor_removes_prebid_scripts_when_auto_config_enabled() { - let html = r#" - - - "#; - - let mut settings = make_settings(); - settings - .integrations - .insert_config( - "prebid", - &json!({ - "enabled": true, - "server_url": "https://test-prebid.com/openrtb2/auction", - "timeout_ms": 1000, - "bidders": ["mocktioneer"], - "auto_configure": true, - "debug": false - }), - ) - .expect("should update prebid config"); - let registry = IntegrationRegistry::new(&settings); - let config = config_from_settings(&settings, ®istry); - let processor = create_html_processor(config); - let pipeline_config = PipelineConfig { - input_compression: Compression::None, - output_compression: Compression::None, - chunk_size: 8192, - }; - let mut pipeline = StreamingPipeline::new(pipeline_config, processor); + fn script_pattern_matching_exact_paths() { + let integration = PrebidIntegration::new(base_config()); + + // Should match default exact patterns (suffix matching) + assert!(integration.matches_script_pattern("/prebid.js")); + assert!(integration.matches_script_pattern("/prebid.min.js")); + assert!(integration.matches_script_pattern("/prebidjs.js")); + assert!(integration.matches_script_pattern("/prebidjs.min.js")); + + // Suffix matching means nested paths also match + assert!(integration.matches_script_pattern("/static/prebid.min.js")); + assert!(integration.matches_script_pattern("/static/prebid/v8.53.0/prebid.min.js")); + + // Should not match other scripts + assert!(!integration.matches_script_pattern("/app.js")); + assert!(!integration.matches_script_pattern("/static/bundle.min.js")); + } - let mut output = Vec::new(); - let result = pipeline.process(Cursor::new(html.as_bytes()), &mut output); - assert!(result.is_ok()); - let processed = String::from_utf8_lossy(&output); - assert!( - processed.contains("tsjs-unified"), - "Unified bundle should be injected" - ); - assert!( - !processed.contains("prebid.min.js"), - "Prebid script should be removed when auto-config is enabled" - ); + #[test] + fn script_pattern_matching_wildcard_slash_star() { + // Test /* wildcard pattern matching + let mut config = base_config(); + config.script_patterns = vec!["/static/prebid/*".to_string()]; + let integration = PrebidIntegration::new(config); + + // Should match paths under the prefix with known suffixes + assert!(integration.matches_script_pattern("/static/prebid/prebid.min.js")); + assert!(integration.matches_script_pattern("/static/prebid/v8.53.0/prebid.min.js")); + assert!(integration.matches_script_pattern("/static/prebid/prebidjs.js")); + + // Should not match paths outside prefix + assert!(!integration.matches_script_pattern("/prebid.min.js")); + assert!(!integration.matches_script_pattern("/other/prebid.min.js")); + + // Should not match non-prebid scripts even under prefix + assert!(!integration.matches_script_pattern("/static/prebid/app.js")); + } + + #[test] + fn script_pattern_matching_wildcard_matchit_syntax() { + // Test {*rest} matchit-style wildcard pattern matching + let mut config = base_config(); + config.script_patterns = vec!["/wp-content/plugins/prebidjs/{*rest}".to_string()]; + let integration = PrebidIntegration::new(config); + + // Should match paths under the prefix with known suffixes assert!( - !processed.contains("cdn.prebid.org/prebid.js"), - "Prebid preload should be removed when auto-config is enabled" + integration.matches_script_pattern("/wp-content/plugins/prebidjs/js/prebidjs.min.js") ); + assert!(integration.matches_script_pattern("/wp-content/plugins/prebidjs/prebid.min.js")); + assert!(integration.matches_script_pattern("/wp-content/plugins/prebidjs/v1/v2/prebid.js")); + + // Should not match paths outside prefix + assert!(!integration.matches_script_pattern("/prebid.min.js")); + assert!(!integration.matches_script_pattern("/wp-content/other/prebid.min.js")); + + // Should not match non-prebid scripts even under prefix + assert!(!integration.matches_script_pattern("/wp-content/plugins/prebidjs/app.js")); + } + + #[test] + fn script_pattern_matching_case_insensitive() { + let integration = PrebidIntegration::new(base_config()); + + assert!(integration.matches_script_pattern("/Prebid.JS")); + assert!(integration.matches_script_pattern("/PREBID.MIN.JS")); + assert!(integration.matches_script_pattern("/Static/Prebid.min.js")); + } + + #[test] + fn routes_include_script_patterns() { + let integration = PrebidIntegration::new(base_config()); + let routes = integration.routes(); + + // Should include the default ad routes + assert!(routes.iter().any(|r| r.path == "/ad/render")); + assert!(routes.iter().any(|r| r.path == "/ad/auction")); + + // Should include default script removal patterns + assert!(routes.iter().any(|r| r.path == "/prebid.js")); + assert!(routes.iter().any(|r| r.path == "/prebid.min.js")); + assert!(routes.iter().any(|r| r.path == "/prebidjs.js")); + assert!(routes.iter().any(|r| r.path == "/prebidjs.min.js")); } #[test] fn enhance_openrtb_request_adds_ids_and_regs() { let settings = make_settings(); - let mut request_json = json!({ - "id": "openrtb-request-id" - }); + let mut request = OpenRtbRequest { + id: "openrtb-request-id".to_string(), + imp: Vec::new(), + site: None, + user: None, + regs: None, + device: None, + ext: None, + extra: HashMap::new(), + }; let synthetic_id = "synthetic-123"; let fresh_id = "fresh-456"; - let mut req = Request::new(Method::POST, "https://edge.example/third-party/ad"); + let mut req = Request::new(Method::POST, "https://edge.example/auction"); req.set_header("Sec-GPC", "1"); - enhance_openrtb_request(&mut request_json, synthetic_id, fresh_id, &settings, &req) + enhance_openrtb_request(&mut request, synthetic_id, fresh_id, &settings, &req) .expect("should enhance request"); - assert_eq!(request_json["user"]["id"], synthetic_id); - assert_eq!(request_json["user"]["ext"]["synthetic_fresh"], fresh_id); + let user = request.user.as_ref().expect("should have user"); + assert_eq!(user.id.as_deref(), Some(synthetic_id), "should set user id"); + let user_ext = user.ext.as_ref().expect("should have user ext"); assert_eq!( - request_json["regs"]["ext"]["us_privacy"], "1YYN", - "GPC header should map to US privacy flag" + user_ext.synthetic_fresh.as_deref(), + Some(fresh_id), + "should set synthetic fresh id" ); + let regs = request.regs.as_ref().expect("should have regs"); + let regs_ext = regs.ext.as_ref().expect("should have regs ext"); assert_eq!( - request_json["site"]["domain"], settings.publisher.domain, - "site domain should match publisher domain" + regs_ext.us_privacy.as_deref(), + Some("1YYN"), + "should map GPC header to US privacy flag" ); - assert!( - request_json["site"]["page"] - .as_str() - .unwrap() - .starts_with("https://"), - "site page should be populated" + let site = request.site.as_ref().expect("should have site"); + assert_eq!( + site.domain.as_deref(), + Some(settings.publisher.domain.as_str()), + "should set site domain" + ); + let page = site.page.as_ref().expect("should have site page"); + assert!(page.starts_with("https://"), "should set site page"); + } + + #[test] + fn override_prebid_bidders_replaces_request_values() { + let mut request: OpenRtbRequest = serde_json::from_value(json!({ + "id": "openrtb-request-id", + "imp": [ + { + "id": "slot-a", + "ext": { + "prebid": { + "bidder": { "legacy": {} } + } + } + }, + { "id": "slot-b" } + ] + })) + .expect("should parse openrtb request"); + let bidders = vec!["appnexus".to_string(), "rubicon".to_string()]; + + override_prebid_bidders(&mut request, &bidders); + + let expected = bidders + .iter() + .map(|bidder| (bidder.clone(), JsonValue::Object(serde_json::Map::new()))) + .collect::>(); + let first = request.imp.first().expect("should have first imp"); + let first_prebid = first + .ext + .as_ref() + .and_then(|ext| ext.prebid.as_ref()) + .expect("should have prebid ext for first imp"); + assert_eq!( + first_prebid.bidder, expected, + "should replace bidders in first imp" + ); + let second = request.imp.get(1).expect("should have second imp"); + let second_prebid = second + .ext + .as_ref() + .and_then(|ext| ext.prebid.as_ref()) + .expect("should have prebid ext for second imp"); + assert_eq!( + second_prebid.bidder, expected, + "should replace bidders in second imp" ); } #[test] fn transform_prebid_response_rewrites_creatives_and_tracking() { - let mut response = json!({ + let settings = crate::test_support::tests::create_test_settings(); + let mut response: OpenRtbResponse = serde_json::from_value(json!({ "seatbid": [{ "bid": [{ "adm": r#""#, @@ -920,30 +1226,69 @@ mod tests { "burl": "https://notify.example/bill" }] }] - }); + })) + .expect("should parse openrtb response"); - transform_prebid_response(&mut response, "pub.example", "https") + transform_prebid_response(&mut response, &settings, "pub.example", "https") .expect("should rewrite response"); - let rewritten_adm = response["seatbid"][0]["bid"][0]["adm"] - .as_str() + let rewritten_adm = response + .seatbid + .as_ref() + .and_then(|seatbids| seatbids.first()) + .and_then(|seatbid| seatbid.bid.as_ref()) + .and_then(|bids| bids.first()) + .and_then(|bid| bid.adm.as_deref()) .expect("adm should be string"); assert!( - rewritten_adm.contains("/ad-proxy/adsrvr"), - "creative markup should proxy CDN urls" + rewritten_adm.contains("/first-party/proxy?tsurl="), + "creative markup should proxy asset urls" ); - for url_field in ["nurl", "burl"] { - let value = response["seatbid"][0]["bid"][0][url_field] - .as_str() - .unwrap(); + let bid = response + .seatbid + .as_ref() + .and_then(|seatbids| seatbids.first()) + .and_then(|seatbid| seatbid.bid.as_ref()) + .and_then(|bids| bids.first()) + .expect("should have bid"); + for value in [bid.nurl.as_deref(), bid.burl.as_deref()] { + let value = value.expect("should have tracking url"); assert!( - value.contains("/ad-proxy/track/"), + value.starts_with("https://pub.example/first-party/proxy?tsurl="), "tracking URLs should be proxied" ); } } + #[test] + fn transform_prebid_response_preserves_non_html_adm() { + let settings = crate::test_support::tests::create_test_settings(); + let adm = r#""#; + let mut response: OpenRtbResponse = serde_json::from_value(json!({ + "seatbid": [{ + "bid": [{ + "adm": adm, + "nurl": "https://notify.example/win" + }] + }] + })) + .expect("should parse openrtb response"); + + transform_prebid_response(&mut response, &settings, "pub.example", "https") + .expect("should rewrite response"); + + let rewritten_adm = response + .seatbid + .as_ref() + .and_then(|seatbids| seatbids.first()) + .and_then(|seatbid| seatbid.bid.as_ref()) + .and_then(|bids| bids.first()) + .and_then(|bid| bid.adm.as_deref()) + .expect("adm should be string"); + assert_eq!(rewritten_adm, adm, "non-html adm should remain unchanged"); + } + #[test] fn extract_adm_for_slot_prefers_exact_match() { let response = json!({ @@ -967,32 +1312,54 @@ mod tests { } #[test] - fn make_first_party_proxy_url_base64_encodes_target() { + fn first_party_proxy_url_signs_target() { + let settings = crate::test_support::tests::create_test_settings(); let url = "https://cdn.example/path?x=1"; - let rewritten = make_first_party_proxy_url(url, "pub.example", "https", "track"); + let rewritten = first_party_proxy_url(&settings, "pub.example", "https", url); assert!( - rewritten.starts_with("https://pub.example/ad-proxy/track/"), + rewritten.starts_with("https://pub.example/first-party/proxy?tsurl="), "proxy prefix should be applied" ); + assert!( + rewritten.contains("tstoken="), + "proxy url should include a signature" + ); + } - let encoded = rewritten.split("/ad-proxy/track/").nth(1).unwrap(); - let decoded = BASE64 - .decode(encoded.as_bytes()) - .expect("should decode base64 proxy payload"); - assert_eq!(String::from_utf8(decoded).unwrap(), url); + #[test] + fn first_party_proxy_url_handles_macros() { + let settings = crate::test_support::tests::create_test_settings(); + let url = "https://notify.example/win?price=${AUCTION_PRICE}&id=123"; + let rewritten = first_party_proxy_url(&settings, "pub.example", "https", url); + assert!( + rewritten.starts_with("https://pub.example/first-party/proxy?tsurl="), + "proxy prefix should be applied" + ); + assert!( + rewritten.contains("tstoken="), + "proxy url should include a signature" + ); + assert!( + rewritten.contains("notify.example"), + "proxy url should include target host" + ); + assert!( + rewritten.contains("AUCTION_PRICE"), + "proxy url should preserve macro tokens" + ); } #[test] - fn is_prebid_script_url_matches_common_variants() { - assert!(is_prebid_script_url("https://cdn.com/prebid.js")); - assert!(is_prebid_script_url( - "https://cdn.com/prebid.min.js?version=1" - )); - assert!(!is_prebid_script_url("https://cdn.com/app.js")); + fn first_party_proxy_url_respects_exclude_domains() { + let mut settings = crate::test_support::tests::create_test_settings(); + settings.rewrite.exclude_domains = vec!["notify.example".to_string()]; + let url = "https://notify.example/win"; + let rewritten = first_party_proxy_url(&settings, "pub.example", "https", url); + assert_eq!(rewritten, url, "excluded domains should not be proxied"); } #[test] - fn test_script_handler_config_parsing() { + fn test_script_patterns_config_parsing() { let toml_str = r#" [publisher] domain = "test-publisher.com" @@ -1009,7 +1376,7 @@ template = "{{client_ip}}:{{user_agent}}" [integrations.prebid] enabled = true server_url = "https://prebid.example" -script_handler = "/prebid.js" +script_patterns = ["/static/prebid/*"] "#; let settings = Settings::from_toml(toml_str).expect("should parse TOML"); @@ -1018,11 +1385,11 @@ script_handler = "/prebid.js" .expect("should get config") .expect("should be enabled"); - assert_eq!(config.script_handler, Some("/prebid.js".to_string())); + assert_eq!(config.script_patterns, vec!["/static/prebid/*"]); } #[test] - fn test_script_handler_none_by_default() { + fn test_script_patterns_default() { let toml_str = r#" [publisher] domain = "test-publisher.com" @@ -1047,21 +1414,13 @@ server_url = "https://prebid.example" .expect("should get config") .expect("should be enabled"); - assert_eq!(config.script_handler, None); + // Should have default patterns + assert_eq!(config.script_patterns, default_script_patterns()); } #[test] fn test_script_handler_returns_empty_js() { - let config = PrebidIntegrationConfig { - enabled: true, - server_url: "https://prebid.example".to_string(), - timeout_ms: 1000, - bidders: vec![], - auto_configure: false, - debug: false, - script_handler: Some("/prebid.js".to_string()), - }; - let integration = PrebidIntegration::new(config); + let integration = PrebidIntegration::new(base_config()); let response = integration .handle_script_handler() @@ -1081,41 +1440,104 @@ server_url = "https://prebid.example" assert!(cache_control.contains("immutable")); let body = response.into_body_str(); - assert!(body.contains("// Script overridden by Trusted Server")); + assert!( + body.contains("tsjs-unified"), + "should contain comment about replacement" + ); } #[test] - fn test_routes_includes_script_handler() { - let config = PrebidIntegrationConfig { - enabled: true, - server_url: "https://prebid.example".to_string(), - timeout_ms: 1000, - bidders: vec![], - auto_configure: false, - debug: false, - script_handler: Some("/prebid.js".to_string()), - }; + fn test_routes_with_default_patterns() { + let config = base_config(); // Has default script_patterns let integration = PrebidIntegration::new(config); let routes = integration.routes(); - // Should have 3 routes: first-party ad, third-party ad, and script handler - assert_eq!(routes.len(), 3); + // Should have 2 ad routes + 4 default script patterns + assert_eq!(routes.len(), 6); - let has_script_route = routes - .iter() - .any(|r| r.path == "/prebid.js" && r.method == Method::GET); - assert!(has_script_route, "should register script handler route"); + // Verify ad routes + assert!(routes.iter().any(|r| r.path == "/ad/render")); + assert!(routes.iter().any(|r| r.path == "/ad/auction")); + + // Verify script pattern routes + assert!(routes.iter().any(|r| r.path == "/prebid.js")); + assert!(routes.iter().any(|r| r.path == "/prebid.min.js")); + assert!(routes.iter().any(|r| r.path == "/prebidjs.js")); + assert!(routes.iter().any(|r| r.path == "/prebidjs.min.js")); + } + + #[test] + fn tsjs_config_script_tag_generates_render_mode() { + let tag = tsjs_config_script_tag(Mode::Render); + assert!(tag.starts_with("")); + assert!(tag.contains(r#"mode:"render""#)); + assert!(tag.contains("tsjs.setConfig")); + // Should have guard for tsjs existence + assert!(tag.contains("window.tsjs&&")); + } + + #[test] + fn tsjs_config_script_tag_generates_auction_mode() { + let tag = tsjs_config_script_tag(Mode::Auction); + assert!(tag.starts_with("")); + assert!(tag.contains(r#"mode:"auction""#)); + assert!(tag.contains("tsjs.setConfig")); + // Should have guard for tsjs existence + assert!(tag.contains("window.tsjs&&")); + } + + #[test] + fn head_injector_returns_empty_when_no_mode() { + // When no mode is set, no head inserts needed + // (s2sConfig is built into the Prebid bundle served via script interception) + let integration = PrebidIntegration::new(base_config()); + let ctx = IntegrationHtmlContext { + request_host: "pub.example", + request_scheme: "https", + origin_host: "origin.example", + document_state: &Default::default(), + }; + let inserts = integration.head_inserts(&ctx); + assert!(inserts.is_empty(), "no head inserts when mode not set"); } #[test] - fn test_routes_without_script_handler() { - let config = base_config(); // Has script_handler: None + fn head_injector_injects_mode_config_when_mode_set() { + let mut config = base_config(); + config.mode = Some(Mode::Auction); let integration = PrebidIntegration::new(config); + let ctx = IntegrationHtmlContext { + request_host: "pub.example", + request_scheme: "https", + origin_host: "origin.example", + document_state: &Default::default(), + }; + let inserts = integration.head_inserts(&ctx); + assert_eq!(inserts.len(), 1, "should inject mode config"); + assert!( + inserts[0].contains(r#"mode:"auction""#), + "should contain mode config" + ); + } - let routes = integration.routes(); + #[test] + fn script_handler_serves_empty_js_stub() { + let integration = PrebidIntegration::new(base_config()); - // Should only have 2 routes: first-party ad and third-party ad - assert_eq!(routes.len(), 2); + let response = integration + .handle_script_handler() + .expect("should return response"); + + assert_eq!(response.get_status(), StatusCode::OK); + let body = response.into_body_str(); + // Should be a small stub comment, not the full bundle + assert!( + body.len() < 100, + "should serve empty JS stub, not full bundle" + ); + assert!(body.starts_with("/*"), "should be a JS comment"); } } diff --git a/crates/common/src/integrations/registry.rs b/crates/common/src/integrations/registry.rs index 819890d..734586a 100644 --- a/crates/common/src/integrations/registry.rs +++ b/crates/common/src/integrations/registry.rs @@ -357,6 +357,14 @@ pub trait IntegrationHtmlPostProcessor: Send + Sync { fn post_process(&self, html: &mut String, ctx: &IntegrationHtmlContext<'_>) -> bool; } +/// Trait for integration-provided HTML head injections. +pub trait IntegrationHeadInjector: Send + Sync { + /// Identifier for logging/diagnostics. + fn integration_id(&self) -> &'static str; + /// Return HTML snippets to insert at the start of ``. + fn head_inserts(&self, ctx: &IntegrationHtmlContext<'_>) -> Vec; +} + /// Registration payload returned by integration builders. pub struct IntegrationRegistration { pub integration_id: &'static str, @@ -364,6 +372,7 @@ pub struct IntegrationRegistration { pub attribute_rewriters: Vec>, pub script_rewriters: Vec>, pub html_post_processors: Vec>, + pub head_injectors: Vec>, } impl IntegrationRegistration { @@ -386,6 +395,7 @@ impl IntegrationRegistrationBuilder { attribute_rewriters: Vec::new(), script_rewriters: Vec::new(), html_post_processors: Vec::new(), + head_injectors: Vec::new(), }, } } @@ -420,6 +430,12 @@ impl IntegrationRegistrationBuilder { self } + #[must_use] + pub fn with_head_injector(mut self, injector: Arc) -> Self { + self.registration.head_injectors.push(injector); + self + } + #[must_use] pub fn build(self) -> IntegrationRegistration { self.registration @@ -441,6 +457,7 @@ struct IntegrationRegistryInner { html_rewriters: Vec>, script_rewriters: Vec>, html_post_processors: Vec>, + head_injectors: Vec>, } impl Default for IntegrationRegistryInner { @@ -455,6 +472,7 @@ impl Default for IntegrationRegistryInner { html_rewriters: Vec::new(), script_rewriters: Vec::new(), html_post_processors: Vec::new(), + head_injectors: Vec::new(), } } } @@ -539,6 +557,9 @@ impl IntegrationRegistry { inner .html_post_processors .extend(registration.html_post_processors.into_iter()); + inner + .head_injectors + .extend(registration.head_injectors.into_iter()); } } @@ -622,6 +643,18 @@ impl IntegrationRegistry { self.inner.html_post_processors.clone() } + /// Collect HTML snippets for insertion at the start of ``. + pub fn head_inserts(&self, ctx: &IntegrationHtmlContext<'_>) -> Vec { + let mut inserts = Vec::new(); + for injector in &self.inner.head_injectors { + let mut next = injector.head_inserts(ctx); + if !next.is_empty() { + inserts.append(&mut next); + } + } + inserts + } + /// Provide a snapshot of registered integrations and their hooks. pub fn registered_integrations(&self) -> Vec { let mut map: BTreeMap<&'static str, IntegrationMetadata> = BTreeMap::new(); @@ -668,6 +701,29 @@ impl IntegrationRegistry { html_rewriters: attribute_rewriters, script_rewriters, html_post_processors: Vec::new(), + head_injectors: Vec::new(), + }), + } + } + + #[cfg(test)] + pub fn from_rewriters_with_head_injectors( + attribute_rewriters: Vec>, + script_rewriters: Vec>, + head_injectors: Vec>, + ) -> Self { + Self { + inner: Arc::new(IntegrationRegistryInner { + get_router: Router::new(), + post_router: Router::new(), + put_router: Router::new(), + delete_router: Router::new(), + patch_router: Router::new(), + routes: Vec::new(), + html_rewriters: attribute_rewriters, + script_rewriters, + html_post_processors: Vec::new(), + head_injectors, }), } } @@ -711,6 +767,7 @@ impl IntegrationRegistry { html_rewriters: Vec::new(), script_rewriters: Vec::new(), html_post_processors: Vec::new(), + head_injectors: Vec::new(), }), } } diff --git a/crates/common/src/openrtb.rs b/crates/common/src/openrtb.rs index c73f974..0fcaabd 100644 --- a/crates/common/src/openrtb.rs +++ b/crates/common/src/openrtb.rs @@ -1,8 +1,10 @@ -use serde::Serialize; +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; use serde_json::Value; /// Minimal subset of OpenRTB 2.x bid request used by Trusted Server. -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub struct OpenRtbRequest { /// Unique ID of the bid request, provided by the exchange. @@ -10,42 +12,168 @@ pub struct OpenRtbRequest { pub imp: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub site: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub user: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub regs: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub device: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub ext: Option, + #[serde(default, flatten)] + pub extra: HashMap, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct Imp { pub id: String, #[serde(skip_serializing_if = "Option::is_none")] pub banner: Option, #[serde(skip_serializing_if = "Option::is_none")] pub ext: Option, + #[serde(default, flatten)] + pub extra: HashMap, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct Banner { + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub format: Vec, + #[serde(default, flatten)] + pub extra: HashMap, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct Format { pub w: u32, pub h: u32, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct Site { #[serde(skip_serializing_if = "Option::is_none")] pub domain: Option, #[serde(skip_serializing_if = "Option::is_none")] pub page: Option, + #[serde(default, flatten)] + pub extra: HashMap, +} + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct User { + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub ext: Option, + #[serde(default, flatten)] + pub extra: HashMap, +} + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct UserExt { + #[serde(skip_serializing_if = "Option::is_none")] + pub synthetic_fresh: Option, + #[serde(default, flatten)] + pub extra: HashMap, +} + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct Regs { + #[serde(skip_serializing_if = "Option::is_none")] + pub ext: Option, + #[serde(default, flatten)] + pub extra: HashMap, +} + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct RegsExt { + #[serde(skip_serializing_if = "Option::is_none")] + pub us_privacy: Option, + #[serde(default, flatten)] + pub extra: HashMap, +} + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct Device { + #[serde(skip_serializing_if = "Option::is_none")] + pub geo: Option, + #[serde(default, flatten)] + pub extra: HashMap, +} + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct Geo { + #[serde(rename = "type", skip_serializing_if = "Option::is_none")] + pub geo_type: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub country: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub city: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub region: Option, + #[serde(default, flatten)] + pub extra: HashMap, +} + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct RequestExt { + #[serde(skip_serializing_if = "Option::is_none")] + pub trusted_server: Option, + #[serde(default, flatten)] + pub extra: HashMap, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct TrustedServerExt { + #[serde(skip_serializing_if = "Option::is_none")] + pub signature: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub kid: Option, + #[serde(default, flatten)] + pub extra: HashMap, +} + +#[derive(Debug, Serialize, Deserialize)] pub struct ImpExt { - pub prebid: PrebidImpExt, + #[serde(skip_serializing_if = "Option::is_none")] + pub prebid: Option, + #[serde(default, flatten)] + pub extra: HashMap, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, Deserialize, Default)] pub struct PrebidImpExt { - pub bidder: std::collections::HashMap, + #[serde(default, skip_serializing_if = "HashMap::is_empty")] + pub bidder: HashMap, +} + +/// Minimal subset of OpenRTB 2.x bid response used by Trusted Server. +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct OpenRtbResponse { + #[serde(skip_serializing_if = "Option::is_none")] + pub seatbid: Option>, + #[serde(default, flatten)] + pub extra: HashMap, +} + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct SeatBid { + #[serde(skip_serializing_if = "Option::is_none")] + pub bid: Option>, + #[serde(default, flatten)] + pub extra: HashMap, +} + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct Bid { + #[serde(skip_serializing_if = "Option::is_none")] + pub impid: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub adm: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub nurl: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub burl: Option, + #[serde(default, flatten)] + pub extra: HashMap, } diff --git a/crates/common/src/proxy.rs b/crates/common/src/proxy.rs index 64b72bc..6a7dbff 100644 --- a/crates/common/src/proxy.rs +++ b/crates/common/src/proxy.rs @@ -150,6 +150,11 @@ fn finalize_proxied_response( if ct.contains("text/html") { // HTML: rewrite and serve as HTML (safe to read as string) let body = beresp.take_body_str(); + // Debug: log first 500 chars of raw HTML to see if port is present + log::debug!( + "proxy: raw HTML body (first 500 chars): {}", + body.chars().take(500).collect::() + ); let rewritten = crate::creative::rewrite_creative_html(&body, settings); return rebuild_text_response(beresp, "text/html; charset=utf-8", rewritten); } @@ -411,10 +416,16 @@ async fn proxy_with_redirects( })); } - let backend_name = crate::backend::ensure_origin_backend(&scheme, host, parsed_url.port())?; + let backend_name = crate::backend::ensure_origin_backend( + &scheme, + host, + parsed_url.port(), + settings.proxy.certificate_check, + )?; let mut proxy_req = Request::new(current_method.clone(), ¤t_url); copy_proxy_forward_headers(req, &mut proxy_req); + if let Some(body_bytes) = body { proxy_req.set_body(body_bytes.to_vec()); } @@ -1086,6 +1097,28 @@ mod tests { assert_eq!(err.current_context().status_code(), StatusCode::BAD_GATEWAY); } + #[tokio::test] + async fn proxy_sign_preserves_non_standard_port() { + let settings = create_test_settings(); + // Test with non-standard port (e.g., 9443) + let body = serde_json::json!({ + "url": "https://cdn.example.com:9443/img/300x250.svg", + }); + let mut req = Request::new(Method::POST, "https://edge.example/first-party/sign"); + req.set_body(body.to_string()); + let mut resp = handle_first_party_proxy_sign(&settings, req) + .await + .expect("sign ok"); + assert_eq!(resp.get_status(), StatusCode::OK); + let json = resp.take_body_str(); + // Port 9443 should be preserved (URL-encoded as %3A9443) + assert!( + json.contains("%3A9443"), + "Port should be preserved in signed URL: {}", + json + ); + } + #[test] fn proxy_request_config_supports_streaming_and_headers() { let cfg = ProxyRequestConfig::new("https://example.com/asset") @@ -1464,6 +1497,44 @@ mod tests { assert_eq!(ct, "text/css; charset=utf-8"); } + #[test] + fn html_response_rewrite_preserves_non_standard_port() { + // Verify that HTML rewriting preserves non-standard ports in sub-resource URLs. + // This is the core test for the port preservation fix. + let settings = create_test_settings(); + + let html = r#" + + + + + + + +"#; + + let beresp = Response::from_status(StatusCode::OK) + .with_header(header::CONTENT_TYPE, "text/html; charset=utf-8") + .with_body(html); + + let req = Request::new(Method::GET, "https://edge.example/first-party/proxy"); + let mut out = finalize( + &settings, + &req, + "https://cdn.example.com:9443/creatives/300x250.html", + beresp, + ); + + let body = out.take_body_str(); + + // Port 9443 should be preserved (URL-encoded as %3A9443) + assert!( + body.contains("cdn.example.com%3A9443"), + "Port 9443 should be preserved in rewritten URLs. Body:\n{}", + body + ); + } + #[test] fn image_accept_sets_generic_content_type_when_missing() { let settings = create_test_settings(); diff --git a/crates/common/src/publisher.rs b/crates/common/src/publisher.rs index 6041536..09911ab 100644 --- a/crates/common/src/publisher.rs +++ b/crates/common/src/publisher.rs @@ -3,7 +3,7 @@ use fastly::http::{header, StatusCode}; use fastly::{Body, Request, Response}; use crate::backend::ensure_backend_from_url; -use crate::http_util::serve_static_with_etag; +use crate::http_util::{serve_static_with_etag, RequestInfo}; use crate::constants::{HEADER_SYNTHETIC_TRUSTED_SERVER, HEADER_X_COMPRESS_HINT}; use crate::cookies::create_synthetic_cookie; @@ -15,65 +15,6 @@ use crate::streaming_processor::{Compression, PipelineConfig, StreamProcessor, S use crate::streaming_replacer::create_url_replacer; use crate::synthetic::get_or_generate_synthetic_id; -/// Detects the request scheme (HTTP or HTTPS) using Fastly SDK methods and headers. -/// -/// Tries multiple methods in order of reliability: -/// 1. Fastly SDK TLS detection methods (most reliable) -/// 2. Forwarded header (RFC 7239) -/// 3. X-Forwarded-Proto header -/// 4. Fastly-SSL header (least reliable, can be spoofed) -/// 5. Default to HTTP -fn detect_request_scheme(req: &Request) -> String { - // 1. First try Fastly SDK's built-in TLS detection methods - // These are the most reliable as they check the actual connection - if let Some(tls_protocol) = req.get_tls_protocol() { - // If we have a TLS protocol, the connection is definitely HTTPS - log::debug!("TLS protocol detected: {}", tls_protocol); - return "https".to_string(); - } - - // Also check TLS cipher - if present, connection is HTTPS - if req.get_tls_cipher_openssl_name().is_some() { - log::debug!("TLS cipher detected, using HTTPS"); - return "https".to_string(); - } - - // 2. Try the Forwarded header (RFC 7239) - if let Some(forwarded) = req.get_header("forwarded") { - if let Ok(forwarded_str) = forwarded.to_str() { - // Parse the Forwarded header - // Format: Forwarded: for=192.0.2.60;proto=https;by=203.0.113.43 - if forwarded_str.contains("proto=https") { - return "https".to_string(); - } else if forwarded_str.contains("proto=http") { - return "http".to_string(); - } - } - } - - // 3. Try X-Forwarded-Proto header - if let Some(proto) = req.get_header("x-forwarded-proto") { - if let Ok(proto_str) = proto.to_str() { - let proto_lower = proto_str.to_lowercase(); - if proto_lower == "https" || proto_lower == "http" { - return proto_lower; - } - } - } - - // 4. Check Fastly-SSL header (can be spoofed by clients, use as last resort) - if let Some(ssl) = req.get_header("fastly-ssl") { - if let Ok(ssl_str) = ssl.to_str() { - if ssl_str == "1" || ssl_str.to_lowercase() == "true" { - return "https".to_string(); - } - } - } - - // Default to HTTP (changed from HTTPS based on your settings file) - "http".to_string() -} - /// Unified tsjs static serving: `/static/tsjs=` /// Accepts: `tsjs-core(.min).js`, `tsjs-ext(.min).js`, `tsjs-creative(.min).js` pub fn handle_tsjs_dynamic( @@ -238,29 +179,20 @@ pub fn handle_publisher_request( // Prebid.js requests are not intercepted here anymore. The HTML processor rewrites // any Prebid script references to `/static/tsjs-ext.min.js` when auto-configure is enabled. - // Extract the request host from the incoming request - let request_host = req - .get_header(header::HOST) - .map(|h| h.to_str().unwrap_or_default()) - .unwrap_or_default() - .to_string(); + // Extract request host and scheme from headers (supports X-Forwarded-Host/Proto for chained proxies) + let request_info = RequestInfo::from_request(&req); + let request_host = &request_info.host; + let request_scheme = &request_info.scheme; - // Detect the request scheme using multiple methods - let request_scheme = detect_request_scheme(&req); - - // Log detection details for debugging log::debug!( - "Scheme detection - TLS Protocol: {:?}, TLS Cipher: {:?}, Forwarded: {:?}, X-Forwarded-Proto: {:?}, Fastly-SSL: {:?}, Result: {}", - req.get_tls_protocol(), - req.get_tls_cipher_openssl_name(), - req.get_header("forwarded"), + "Request info: host={}, scheme={} (X-Forwarded-Host: {:?}, Host: {:?}, X-Forwarded-Proto: {:?})", + request_host, + request_scheme, + req.get_header("x-forwarded-host"), + req.get_header(header::HOST), req.get_header("x-forwarded-proto"), - req.get_header("fastly-ssl"), - request_scheme ); - log::debug!("Request host: {}, scheme: {}", request_host, request_scheme); - // Generate synthetic identifiers before the request body is consumed. let synthetic_id = get_or_generate_synthetic_id(settings, &req)?; let has_synthetic_cookie = req @@ -279,7 +211,10 @@ pub fn handle_publisher_request( has_synthetic_cookie ); - let backend_name = ensure_backend_from_url(&settings.publisher.origin_url)?; + let backend_name = ensure_backend_from_url( + &settings.publisher.origin_url, + settings.proxy.certificate_check, + )?; let origin_host = settings.publisher.origin_host(); log::debug!( @@ -334,8 +269,8 @@ pub fn handle_publisher_request( content_encoding: &content_encoding, origin_host: &origin_host, origin_url: &settings.publisher.origin_url, - request_host: &request_host, - request_scheme: &request_scheme, + request_host, + request_scheme, settings, content_type: &content_type, integration_registry, @@ -387,73 +322,6 @@ mod tests { use crate::test_support::tests::create_test_settings; use fastly::http::Method; - #[test] - fn test_detect_request_scheme() { - // Note: In tests, we can't mock the TLS methods on Request, so we test header fallbacks - - // Test Forwarded header with HTTPS - let mut req = Request::new(Method::GET, "https://test.example.com/page"); - req.set_header("forwarded", "for=192.0.2.60;proto=https;by=203.0.113.43"); - assert_eq!(detect_request_scheme(&req), "https"); - - // Test Forwarded header with HTTP - let mut req = Request::new(Method::GET, "http://test.example.com/page"); - req.set_header("forwarded", "for=192.0.2.60;proto=http;by=203.0.113.43"); - assert_eq!(detect_request_scheme(&req), "http"); - - // Test X-Forwarded-Proto with HTTPS - let mut req = Request::new(Method::GET, "https://test.example.com/page"); - req.set_header("x-forwarded-proto", "https"); - assert_eq!(detect_request_scheme(&req), "https"); - - // Test X-Forwarded-Proto with HTTP - let mut req = Request::new(Method::GET, "http://test.example.com/page"); - req.set_header("x-forwarded-proto", "http"); - assert_eq!(detect_request_scheme(&req), "http"); - - // Test Fastly-SSL header - let mut req = Request::new(Method::GET, "https://test.example.com/page"); - req.set_header("fastly-ssl", "1"); - assert_eq!(detect_request_scheme(&req), "https"); - - // Test default to HTTP when no headers present - let req = Request::new(Method::GET, "https://test.example.com/page"); - assert_eq!(detect_request_scheme(&req), "http"); - - // Test priority: Forwarded takes precedence over X-Forwarded-Proto - let mut req = Request::new(Method::GET, "https://test.example.com/page"); - req.set_header("forwarded", "proto=https"); - req.set_header("x-forwarded-proto", "http"); - assert_eq!(detect_request_scheme(&req), "https"); - } - - #[test] - fn test_handle_publisher_request_extracts_headers() { - // Test that the function correctly extracts host and scheme from request headers - let mut req = Request::new(Method::GET, "https://test.example.com/page"); - req.set_header("host", "test.example.com"); - req.set_header("x-forwarded-proto", "https"); - - // Extract headers like the function does - let request_host = req - .get_header("host") - .map(|h| h.to_str().unwrap_or_default()) - .unwrap_or_default() - .to_string(); - - let request_scheme = req - .get_header("x-forwarded-proto") - .and_then(|h| h.to_str().ok()) - .unwrap_or("https") - .to_string(); - - assert_eq!(request_host, "test.example.com"); - assert_eq!(request_scheme, "https"); - } - - // Note: test_handle_publisher_request_default_https_scheme and test_handle_publisher_request_http_scheme - // were removed as they're redundant with test_detect_request_scheme which covers all scheme detection cases - #[test] fn test_content_type_detection() { // Test which content types should be processed diff --git a/crates/common/src/settings.rs b/crates/common/src/settings.rs index 4a41b1e..2a2880a 100644 --- a/crates/common/src/settings.rs +++ b/crates/common/src/settings.rs @@ -259,6 +259,28 @@ fn default_request_signing_enabled() -> bool { false } +/// Proxy settings for `/first-party/proxy` endpoint +#[derive(Debug, Deserialize, Serialize)] +pub struct Proxy { + /// Enable TLS certificate verification when proxying to HTTPS origins. + /// Defaults to true for secure production use. + /// Set to false for local development with self-signed certificates. + #[serde(default = "default_certificate_check")] + pub certificate_check: bool, +} + +fn default_certificate_check() -> bool { + true +} + +impl Default for Proxy { + fn default() -> Self { + Self { + certificate_check: default_certificate_check(), + } + } +} + #[derive(Debug, Default, Deserialize, Serialize, Validate)] pub struct Settings { #[validate(nested)] @@ -277,6 +299,8 @@ pub struct Settings { #[serde(default)] #[validate(nested)] pub rewrite: Rewrite, + #[serde(default)] + pub proxy: Proxy, } #[allow(unused)] diff --git a/crates/common/src/synthetic.rs b/crates/common/src/synthetic.rs index d84a95e..b60736c 100644 --- a/crates/common/src/synthetic.rs +++ b/crates/common/src/synthetic.rs @@ -14,6 +14,7 @@ use sha2::Sha256; use crate::constants::{HEADER_SYNTHETIC_PUB_USER_ID, HEADER_SYNTHETIC_TRUSTED_SERVER}; use crate::cookies::handle_request_cookies; use crate::error::TrustedServerError; +use crate::http_util::RequestInfo; use crate::settings::Settings; type HmacSha256 = Hmac; @@ -41,9 +42,13 @@ pub fn generate_synthetic_id( let auth_user_id = req .get_header(HEADER_SYNTHETIC_PUB_USER_ID) .map(|h| h.to_str().unwrap_or("anonymous")); - let publisher_domain = req - .get_header(header::HOST) - .map(|h| h.to_str().unwrap_or("unknown")); + // Use RequestInfo for consistent host extraction (respects X-Forwarded-Host) + let request_info = RequestInfo::from_request(req); + let publisher_domain = if request_info.host.is_empty() { + None + } else { + Some(request_info.host.as_str()) + }; let client_ip = req.get_client_ip_addr().map(|ip| ip.to_string()); let accept_language = req .get_header(header::ACCEPT_LANGUAGE) diff --git a/crates/js/Cargo.toml b/crates/js/Cargo.toml index 1ebd755..3e16e47 100644 --- a/crates/js/Cargo.toml +++ b/crates/js/Cargo.toml @@ -15,6 +15,7 @@ repository = "https://example.invalid/trusted-server" readme = "README.md" [build-dependencies] +build-print = { workspace = true } which = { workspace = true } [dependencies] diff --git a/crates/js/build.rs b/crates/js/build.rs index 26d12a1..4058960 100644 --- a/crates/js/build.rs +++ b/crates/js/build.rs @@ -3,6 +3,8 @@ use std::fs; use std::path::{Path, PathBuf}; use std::process::Command; +use build_print::{error, info, warn}; + const UNIFIED_BUNDLE: &str = "tsjs-unified.js"; fn main() { @@ -32,7 +34,7 @@ fn main() { // If Node/npm is absent, keep going if dist exists let npm = which::which("npm").ok(); if npm.is_none() { - println!("cargo:warning=tsjs: npm not found; will use existing dist if available"); + warn!("tsjs: npm not found; will use existing dist if available"); } // Install deps if node_modules missing @@ -44,9 +46,7 @@ fn main() { .current_dir(&ts_dir) .status(); if !status.as_ref().map(|s| s.success()).unwrap_or(false) { - println!( - "cargo:warning=tsjs: npm install failed; using existing dist if available" - ); + error!("tsjs: npm install failed; using existing dist if available"); } } } @@ -60,24 +60,24 @@ fn main() { .status(); } - // Build unified bundle + // Build unified bundle (includes Prebid.js) if !skip { if let Some(npm_path) = npm.clone() { - println!("cargo:warning=tsjs: Building unified bundle"); + info!("tsjs: Building unified bundle"); let js_modules = env::var("TSJS_MODULES").unwrap_or("".to_string()); let status = Command::new(&npm_path) .env("TSJS_MODULES", js_modules) - .args(["run", "build:custom"]) + .args(["run", "build"]) .current_dir(&ts_dir) .status(); if !status.as_ref().map(|s| s.success()).unwrap_or(false) { - panic!("tsjs: npm run build:custom failed - refusing to use stale bundle"); + panic!("tsjs: npm run build failed - refusing to use stale bundle"); } } } - // Copy unified bundle into OUT_DIR for include_str! + // Copy bundle into OUT_DIR for include_str! copy_bundle(UNIFIED_BUNDLE, true, &crate_dir, &dist_dir, &out_dir); } diff --git a/crates/js/lib/package-lock.json b/crates/js/lib/package-lock.json index 83411a3..cac5ded 100644 --- a/crates/js/lib/package-lock.json +++ b/crates/js/lib/package-lock.json @@ -7,12 +7,16 @@ "": { "name": "tsjs", "version": "0.1.0", + "dependencies": { + "prebid.js": "^10.18.0" + }, "devDependencies": { "@eslint/js": "^9.13.0", "@types/jsdom": "^27.0.0", "@types/node": "^24.10.0", "@typescript-eslint/eslint-plugin": "^8.6.0", "@typescript-eslint/parser": "^8.6.0", + "baseline-browser-mapping": "^2.9.16", "eslint": "^9.10.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-import": "^2.29.1", @@ -26,58 +30,1655 @@ "vitest": "^4.0.8" } }, - "node_modules/@acemir/cssom": { - "version": "0.9.23", - "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.23.tgz", - "integrity": "sha512-2kJ1HxBKzPLbmhZpxBiTZggjtgCwKg1ma5RHShxvd6zgqhDEdEkzpiwe7jLkI2p2BrZvFCXIihdoMkl1H39VnA==", - "dev": true, - "license": "MIT" + "node_modules/@acemir/cssom": { + "version": "0.9.23", + "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.23.tgz", + "integrity": "sha512-2kJ1HxBKzPLbmhZpxBiTZggjtgCwKg1ma5RHShxvd6zgqhDEdEkzpiwe7jLkI2p2BrZvFCXIihdoMkl1H39VnA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@asamuzakjp/css-color": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.0.tgz", + "integrity": "sha512-9xiBAtLn4aNsa4mDnpovJvBn72tNEIACyvlqaNJ+ADemR+yeMJWnBudOi2qGDviJa7SwcDOU/TRh5dnET7qk0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "lru-cache": "^11.2.2" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "6.7.4", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.4.tgz", + "integrity": "sha512-buQDjkm+wDPXd6c13534URWZqbz0RP5PAhXZ+LIoa5LgwInT9HVJvGIJivg75vi8I13CxDGdTnz+aY5YUJlIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.1.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.2" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/code-frame": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", + "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", + "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", + "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", + "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", + "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "regexpu-core": "^6.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", + "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "debug": "^4.4.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.10" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz", + "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", + "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", + "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", + "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.6.tgz", + "integrity": "sha512-9knsChgsMzBV5Yh3kkhrZNxH3oCYAfMBkNNaVN4cP2RVlFPe8wYdwwcnOsAbkdDoV9UjFtOXWrWB52M8W4jNeA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz", + "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", + "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", + "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", + "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", + "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", + "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/template": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", + "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", + "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.28.6.tgz", + "integrity": "sha512-5suVoXjC14lUN6ZL9OLKIHCNVWCrqGqlmEp/ixdXjvgnEl/kauLvvMO/Xw9NyMc95Joj1AeLVPVMvibBgSoFlA==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz", + "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", + "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", + "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", + "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", + "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz", + "integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", + "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", + "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", + "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", + "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", + "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", + "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", + "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.6.tgz", + "integrity": "sha512-eZhoEZHYQLL5uc1gS5e9/oTknS0sSSAtd5TkKMUp3J+S/CaUjagc0kOUPsEbDmMeva0nC3WWl4SxVY6+OBuxfw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", + "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.5.tgz", + "integrity": "sha512-20NUVgOrinudkIBzQ2bNxP08YpKprUkRTiRSd2/Z5GOdPImJGkoN4Z7IQe1T5AdyKI1i5L6RBmluqdSzvaq9/w==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } }, - "node_modules/@asamuzakjp/css-color": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.0.tgz", - "integrity": "sha512-9xiBAtLn4aNsa4mDnpovJvBn72tNEIACyvlqaNJ+ADemR+yeMJWnBudOi2qGDviJa7SwcDOU/TRh5dnET7qk0w==", - "dev": true, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", "license": "MIT", "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "lru-cache": "^11.2.2" + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@asamuzakjp/dom-selector": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.4.tgz", - "integrity": "sha512-buQDjkm+wDPXd6c13534URWZqbz0RP5PAhXZ+LIoa5LgwInT9HVJvGIJivg75vi8I13CxDGdTnz+aY5YUJlIAA==", - "dev": true, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", + "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==", "license": "MIT", "dependencies": { - "@asamuzakjp/nwsapi": "^2.3.9", - "bidi-js": "^1.0.3", - "css-tree": "^3.1.0", - "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.2.2" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@asamuzakjp/nwsapi": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", - "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", - "dev": true, - "license": "MIT" + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "node_modules/@babel/helper-validator-identifier": { + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", + "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", + "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", + "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.6.tgz", + "integrity": "sha512-GaTI4nXDrs7l0qaJ6Rg06dtOXTBCG6TMDB44zbqofCIC4PqC7SEvmFFtpxzCDw9W5aJ7RKVshgXTLvLdBFV/qw==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.28.6", + "@babel/plugin-syntax-import-attributes": "^7.28.6", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.28.6", + "@babel/plugin-transform-async-to-generator": "^7.28.6", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.6", + "@babel/plugin-transform-class-properties": "^7.28.6", + "@babel/plugin-transform-class-static-block": "^7.28.6", + "@babel/plugin-transform-classes": "^7.28.6", + "@babel/plugin-transform-computed-properties": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-dotall-regex": "^7.28.6", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.28.6", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.6", + "@babel/plugin-transform-exponentiation-operator": "^7.28.6", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.28.6", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.28.6", + "@babel/plugin-transform-modules-systemjs": "^7.28.5", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", + "@babel/plugin-transform-numeric-separator": "^7.28.6", + "@babel/plugin-transform-object-rest-spread": "^7.28.6", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.28.6", + "@babel/plugin-transform-optional-chaining": "^7.28.6", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/plugin-transform-private-methods": "^7.28.6", + "@babel/plugin-transform-private-property-in-object": "^7.28.6", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.28.6", + "@babel/plugin-transform-regexp-modifiers": "^7.28.6", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.28.6", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.28.6", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "core-js-compat": "^3.43.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-typescript": { "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", + "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", + "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/@csstools/color-helpers": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", @@ -925,13 +2526,51 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, "license": "MIT" }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1298,6 +2937,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, "node_modules/@standard-schema/spec": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", @@ -1316,6 +2961,15 @@ "assertion-error": "^2.0.1" } }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/deep-eql": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", @@ -1346,7 +3000,6 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, "license": "MIT" }, "node_modules/@types/json5": { @@ -1360,7 +3013,6 @@ "version": "24.10.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~7.16.0" @@ -1720,6 +3372,19 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -1771,11 +3436,70 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "license": "MIT", + "dependencies": { + "ansi-wrap": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -1787,6 +3511,28 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/are-docs-informative": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", @@ -1804,6 +3550,24 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/array-buffer-byte-length": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", @@ -1821,6 +3585,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, "node_modules/array-includes": { "version": "3.1.9", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", @@ -1936,6 +3706,15 @@ "node": ">=12" } }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/async-function": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", @@ -1962,18 +3741,73 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", + "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.7", + "@babel/helper-define-polyfill-provider": "^0.6.5", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", + "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "license": "MIT", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, "node_modules/baseline-browser-mapping": { - "version": "2.8.30", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.30.tgz", - "integrity": "sha512-aTUKW4ptQhS64+v2d6IkPzymEzzhw+G0bA1g3uBRV3+ntkH+svttKseW5IOR4Ed6NUVKqnY7qT3dKvzQ7io4AA==", - "dev": true, + "version": "2.9.16", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.16.tgz", + "integrity": "sha512-KeUZdBuxngy825i8xvzaK1Ncnkx0tBmb3k8DkEuqjKRkmtvNTjey2ZsNeh8Dw4lfKvbCOu9oeNx2TKm2vHqcRw==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.js" @@ -1989,6 +3823,75 @@ "require-from-string": "^2.0.2" } }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -2003,7 +3906,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -2016,7 +3918,6 @@ "version": "4.28.0", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", - "dev": true, "funding": [ { "type": "opencollective", @@ -2047,6 +3948,41 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bufferstreams": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bufferstreams/-/bufferstreams-1.0.1.tgz", + "integrity": "sha512-LZmiIfQprMLS6/k42w/PTc7awhU8AdNNcUerxTgr01WlP9agR2SgMv0wjlYYFD6eDOi8WvofrTX8RayjR/AeUQ==", + "dependencies": { + "readable-stream": "^1.0.33" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/bufferstreams/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "license": "MIT" + }, + "node_modules/bufferstreams/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/bufferstreams/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "license": "MIT" + }, "node_modules/builtin-modules": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-5.0.0.tgz", @@ -2060,6 +3996,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -2083,7 +4028,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -2097,7 +4041,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -2124,7 +4067,6 @@ "version": "1.0.30001756", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001756.tgz", "integrity": "sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A==", - "dev": true, "funding": [ { "type": "opencollective", @@ -2175,6 +4117,42 @@ "dev": true, "license": "MIT" }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/ci-info": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", @@ -2214,11 +4192,21 @@ "node": ">=0.8.0" } }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -2231,7 +4219,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/comment-parser": { @@ -2248,14 +4235,156 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, "license": "MIT" }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/connect/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/connect/node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/connect/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/connect/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/consolidate": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.15.1.tgz", + "integrity": "sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==", + "deprecated": "Please upgrade to consolidate v1.0.0+ as it has been modernized with several long-awaited fixes implemented. Maintenance is supported by Forward Email at https://forwardemail.net ; follow/watch https://github.com/ladjs/consolidate for updates and release changelog", + "license": "MIT", + "dependencies": { + "bluebird": "^3.1.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/core-js": { + "version": "3.47.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.47.0.tgz", + "integrity": "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/core-js-compat": { "version": "3.47.0", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.47.0.tgz", "integrity": "sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ==", - "dev": true, "license": "MIT", "dependencies": { "browserslist": "^4.28.0" @@ -2265,6 +4394,25 @@ "url": "https://opencollective.com/core-js" } }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2280,6 +4428,12 @@ "node": ">= 8" } }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, "node_modules/css-tree": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", @@ -2309,6 +4463,12 @@ "node": ">=20" } }, + "node_modules/custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", + "license": "MIT" + }, "node_modules/data-urls": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz", @@ -2377,11 +4537,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/date-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", + "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2445,6 +4613,37 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", + "license": "MIT" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "license": "MIT" + }, "node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -2458,11 +4657,31 @@ "node": ">=0.10.0" } }, + "node_modules/dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==", + "license": "MIT", + "dependencies": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "node_modules/dset": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", + "integrity": "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -2473,13 +4692,83 @@ "node": ">= 0.4" } }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, "node_modules/electron-to-chromium": { "version": "1.5.259", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.259.tgz", "integrity": "sha512-I+oLXgpEJzD6Cwuwt1gYjxsDmu/S/Kd41mmLA3O+/uH2pFRO/DvOjUyGozL8j3KeLV6WyZ7ssPwELMsXCcsJAQ==", - "dev": true, "license": "ISC" }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/engine.io": { + "version": "6.6.5", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.5.tgz", + "integrity": "sha512-2RZdgEbXmp5+dVbRm0P7HQUImZpICccJy7rN7Tv+SFa55pH+lxnuw6/K1ZxxBfHoYpSkHLAO92oa8O4SwFXA2A==", + "license": "MIT", + "dependencies": { + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.4.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.18.3" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/ent": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.2.tgz", + "integrity": "sha512-kKvD1tO6BM+oK9HzCPpUdRb4vKFQY/FPTFmurMvh6LlN68VMrdj77w8yp51/kDbpkFOS9J8w5W6zIzgM2H8/hw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "punycode": "^1.4.1", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ent/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "license": "MIT" + }, "node_modules/entities": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", @@ -2566,7 +4855,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -2576,7 +4864,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -2593,7 +4880,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -2649,6 +4935,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "license": "MIT" + }, "node_modules/esbuild": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", @@ -2695,12 +4987,17 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -3105,6 +5402,19 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/esquery": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", @@ -3155,12 +5465,26 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, "node_modules/expect-type": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", @@ -3171,11 +5495,90 @@ "node": ">=12.0.0" } }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "license": "MIT", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -3222,6 +5625,22 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", @@ -3249,7 +5668,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -3258,6 +5676,39 @@ "node": ">=8" } }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -3306,9 +5757,28 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", @@ -3325,11 +5795,57 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs-readfile-promise": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fs-readfile-promise/-/fs-readfile-promise-3.0.1.tgz", + "integrity": "sha512-LsSxMeaJdYH27XrW7Dmq0Gx63mioULCRel63B5VeELYLavi1wF5s0XfsIdKDFdCL9hsfQ2qBvXJszQtQJ9h17A==", + "license": "ISC", + "dependencies": { + "graceful-fs": "^4.1.11" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -3340,11 +5856,19 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/fun-hooks": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fun-hooks/-/fun-hooks-1.1.0.tgz", + "integrity": "sha512-LTpm8ayq0ZgrrK0D/Fk+2cgCgkpLppsGKtQIdZBg/5ncKXTGPKuYz8DBG+z8FVCDsJM1DtnYGy4FfSYRLnWbsQ==", + "license": "MIT", + "dependencies": { + "typescript-tuple": "^2.2.1" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3391,11 +5915,28 @@ "node": ">= 0.4" } }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -3420,7 +5961,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -3448,6 +5988,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -3461,6 +6022,28 @@ "node": ">=10.13.0" } }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", @@ -3495,22 +6078,107 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true, + "license": "MIT" + }, + "node_modules/gulp-babel": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/gulp-babel/-/gulp-babel-8.0.0.tgz", + "integrity": "sha512-oomaIqDXxFkg7lbpBou/gnUkX51/Y/M2ZfSjL2hdqXTAlSWZcgZtd2o0cOH0r/eE8LWD0+Q/PsLsr2DKOoqToQ==", + "license": "MIT", + "dependencies": { + "plugin-error": "^1.0.1", + "replace-ext": "^1.0.0", + "through2": "^2.0.0", + "vinyl-sourcemaps-apply": "^0.2.0" + }, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/gulp-wrap": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/gulp-wrap/-/gulp-wrap-0.15.0.tgz", + "integrity": "sha512-f17zkGObA+hE/FThlg55gfA0nsXbdmHK1WqzjjB2Ytq1TuhLR7JiCBJ3K4AlMzCyoFaCjfowos+VkToUNE0WTQ==", + "dependencies": { + "consolidate": "^0.15.1", + "es6-promise": "^4.2.6", + "fs-readfile-promise": "^3.0.1", + "js-yaml": "^3.13.0", + "lodash": "^4.17.11", + "node.extend": "2.0.2", + "plugin-error": "^1.0.1", + "through2": "^3.0.1", + "tryit": "^1.0.1", + "vinyl-bufferstream": "^1.0.1" + }, + "engines": { + "node": ">=6.14", + "npm": ">=1.4.3" + } + }, + "node_modules/gulp-wrap/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/gulp-wrap/node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/gulp-wrap/node_modules/through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + }, + "node_modules/has": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", + "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", "license": "MIT", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 0.4.0" } }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -3567,7 +6235,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3580,7 +6247,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -3596,7 +6262,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -3635,6 +6300,40 @@ ], "license": "MIT" }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -3663,6 +6362,36 @@ "node": ">= 14" } }, + "node_modules/iab-adcom": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/iab-adcom/-/iab-adcom-1.0.6.tgz", + "integrity": "sha512-XAJdidfrFgZNKmHqcXD3Zhqik2rdSmOs+PGgeVfPWgthxvzNBQxkZnKkW3QAau6mrLjtJc8yOQC6awcEv7gryA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/iab-native": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/iab-native/-/iab-native-1.0.0.tgz", + "integrity": "sha512-AxGYpKGRcyG5pbEAqj+ssxNwZAfxC0pRwyKc0MYoKjm0UeOoUNCWrZV0HGimcQii6ebe6MRqBQEeENyHM4qTdQ==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/iab-openrtb": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/iab-openrtb/-/iab-openrtb-1.0.1.tgz", + "integrity": "sha512-egawJx6+pMh/6uA/hak1y+R2+XCSH2jxteSkWlY98/XdQQftaMUMllUFNMKrHwq9lgCI70Me06g4JCCnV6E62g==", + "license": "MIT", + "dependencies": { + "iab-adcom": "1.0.6" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -3726,6 +6455,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -3741,6 +6487,24 @@ "node": ">= 0.4" } }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/is/-/is-3.3.2.tgz", + "integrity": "sha512-a2xr4E3s1PjDS8ORcGgXpWx6V+liNs+O3JRD2mb9aeugD7rtkkZ0zgLdYgw0tWsKhsdiezGYptSiMlVazCBTuQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -3795,6 +6559,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-boolean-object": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", @@ -3845,7 +6621,6 @@ "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -3892,11 +6667,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -3918,6 +6704,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-generator-function": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", @@ -3942,7 +6737,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -3981,7 +6775,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -4004,6 +6797,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -4015,7 +6820,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -4163,6 +6967,18 @@ "dev": true, "license": "MIT" }, + "node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "license": "MIT", + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -4170,6 +6986,21 @@ "dev": true, "license": "ISC" }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, "node_modules/js-yaml": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", @@ -4250,7 +7081,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -4293,6 +7123,84 @@ "json5": "lib/cli.js" } }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/karma": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz", + "integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==", + "license": "MIT", + "dependencies": { + "@colors/colors": "1.5.0", + "body-parser": "^1.19.0", + "braces": "^3.0.2", + "chokidar": "^3.5.1", + "connect": "^3.7.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.1", + "glob": "^7.1.7", + "graceful-fs": "^4.2.6", + "http-proxy": "^1.18.1", + "isbinaryfile": "^4.0.8", + "lodash": "^4.17.21", + "log4js": "^6.4.1", + "mime": "^2.5.2", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.5", + "qjobs": "^1.2.0", + "range-parser": "^1.2.1", + "rimraf": "^3.0.2", + "socket.io": "^4.7.2", + "source-map": "^0.6.1", + "tmp": "^0.2.1", + "ua-parser-js": "^0.7.30", + "yargs": "^16.1.1" + }, + "bin": { + "karma": "bin/karma" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/karma-safarinative-launcher": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/karma-safarinative-launcher/-/karma-safarinative-launcher-1.1.0.tgz", + "integrity": "sha512-vdMjdQDHkSUbOZc8Zq2K5bBC0yJGFEgfrKRJTqt0Um0SC1Rt8drS2wcN6UA3h4LgsL3f1pMcmRSvKucbJE8Qdg==", + "license": "MIT", + "peerDependencies": { + "karma": ">=0.9" + } + }, + "node_modules/karma/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/karma/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -4303,6 +7211,15 @@ "json-buffer": "3.0.1" } }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -4317,6 +7234,28 @@ "node": ">= 0.8.0" } }, + "node_modules/live-connect-common": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/live-connect-common/-/live-connect-common-4.1.0.tgz", + "integrity": "sha512-sRklgbe13377aR+G0qCBiZPayQw5oZZozkuxKEoyipxscLbVzwe9gtA7CPpbmo6UcOdQxdCE6A7J1tI0wTSmqw==", + "license": "Apache-2.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/live-connect-js": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-7.2.0.tgz", + "integrity": "sha512-oZY4KqwrG1C+CDKApcsdDdMG4j2d44lhmvbNy4ZE6sPFr+W8R3m0+V+JxXB8p6tgSePJ8X/uhzAGos0lDg/MAg==", + "license": "Apache-2.0", + "dependencies": { + "live-connect-common": "^v4.1.0", + "tiny-hashes": "1.0.1" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -4333,6 +7272,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -4340,6 +7291,22 @@ "dev": true, "license": "MIT" }, + "node_modules/log4js": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", + "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", + "license": "Apache-2.0", + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "flatted": "^3.2.7", + "rfdc": "^1.3.0", + "streamroller": "^3.1.5" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/lru-cache": { "version": "11.2.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", @@ -4364,7 +7331,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4377,6 +7343,24 @@ "dev": true, "license": "CC0-1.0" }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -4387,6 +7371,15 @@ "node": ">= 8" } }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -4401,6 +7394,39 @@ "node": ">=8.6" } }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -4421,17 +7447,27 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { @@ -4453,19 +7489,58 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", - "dev": true, - "license": "MIT" + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "license": "MIT" + }, + "node_modules/node.extend": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node.extend/-/node.extend-2.0.2.tgz", + "integrity": "sha512-pDT4Dchl94/+kkgdwyS2PauDFjZG0Hk0IcHIB+LkW27HLDtdoeMxHTxZh39DYbPP8UflWXWj9JcdDozF+YDOpQ==", + "license": "(MIT OR GPL-2.0)", + "dependencies": { + "has": "^1.0.3", + "is": "^3.2.1" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, "node_modules/object-deep-merge": { "version": "2.0.0", @@ -4478,7 +7553,6 @@ "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4571,6 +7645,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -4682,6 +7777,15 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -4692,6 +7796,15 @@ "node": ">=8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -4706,7 +7819,12 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, "node_modules/pathe": { @@ -4720,14 +7838,12 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -4736,6 +7852,21 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/plugin-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", + "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", + "license": "MIT", + "dependencies": { + "ansi-colors": "^1.0.1", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/pluralize": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", @@ -4785,6 +7916,42 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/prebid.js": { + "version": "10.20.0", + "resolved": "https://registry.npmjs.org/prebid.js/-/prebid.js-10.20.0.tgz", + "integrity": "sha512-k+tBZos6n9zPyb74B4UtQRJAd78gh0JZ56PUJaMcJ9c3mOM6LNaadLgeNp/o2pjEWYyGzYg48mf4L67bh/RL+g==", + "license": "Apache-2.0", + "dependencies": { + "@babel/core": "^7.28.4", + "@babel/plugin-transform-runtime": "^7.18.9", + "@babel/preset-env": "^7.27.2", + "@babel/preset-typescript": "^7.26.0", + "@babel/runtime": "^7.28.3", + "core-js": "^3.45.1", + "crypto-js": "^4.2.0", + "dlv": "^1.1.3", + "dset": "^3.1.4", + "express": "^4.15.4", + "fun-hooks": "^1.1.0", + "gulp-babel": "^8.0.0", + "gulp-wrap": "^0.15.0", + "iab-adcom": "^1.0.6", + "iab-native": "^1.0.0", + "iab-openrtb": "^1.0.1", + "karma-safarinative-launcher": "^1.1.0", + "klona": "^2.0.6", + "live-connect-js": "^7.2.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + }, + "peerDependencies": { + "schema-utils": "^4.3.2" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -4811,6 +7978,25 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -4821,6 +8007,30 @@ "node": ">=6" } }, + "node_modules/qjobs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", + "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "license": "MIT", + "engines": { + "node": ">=0.9" + } + }, + "node_modules/qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -4842,6 +8052,81 @@ ], "license": "MIT" }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -4865,6 +8150,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/regexp-tree": { "version": "0.1.27", "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", @@ -4896,11 +8199,33 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regexpu-core": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.2", + "regjsgen": "^0.8.0", + "regjsparser": "^0.13.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.2.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "license": "MIT" + }, "node_modules/regjsparser": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "jsesc": "~3.1.0" @@ -4909,16 +8234,39 @@ "regjsparser": "bin/parser" } }, + "node_modules/replace-ext": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", + "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT" + }, "node_modules/reserved-identifiers": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/reserved-identifiers/-/reserved-identifiers-1.2.0.tgz", @@ -4936,7 +8284,6 @@ "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", - "dev": true, "license": "MIT", "dependencies": { "is-core-module": "^2.16.1", @@ -4974,6 +8321,28 @@ "node": ">=0.10.0" } }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/rollup": { "version": "4.53.3", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", @@ -5060,6 +8429,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/safe-push-apply": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", @@ -5081,7 +8470,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -5099,33 +8487,152 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/saxes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", - "dev": true, - "license": "ISC", - "dependencies": { - "xmlchars": "^2.2.0" + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" }, "engines": { - "node": ">=v12.22.7" + "node": ">=4" } }, - "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" }, "engines": { - "node": ">=10" + "node": ">= 0.8.0" } }, "node_modules/set-function-length": { @@ -5177,6 +8684,12 @@ "node": ">= 0.4" } }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -5204,7 +8717,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -5224,7 +8736,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -5241,7 +8752,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -5260,7 +8770,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -5283,6 +8792,56 @@ "dev": true, "license": "ISC" }, + "node_modules/socket.io": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.3.tgz", + "integrity": "sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.4.1", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.6.tgz", + "integrity": "sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==", + "license": "MIT", + "dependencies": { + "debug": "~4.4.1", + "ws": "~8.18.3" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.5.tgz", + "integrity": "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -5318,6 +8877,12 @@ "dev": true, "license": "CC0-1.0" }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -5325,6 +8890,15 @@ "dev": true, "license": "MIT" }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/std-env": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", @@ -5346,6 +8920,49 @@ "node": ">= 0.4" } }, + "node_modules/streamroller": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", + "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", + "license": "MIT", + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string.prototype.trim": { "version": "1.2.10", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", @@ -5405,6 +9022,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -5458,7 +9087,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5474,6 +9102,22 @@ "dev": true, "license": "MIT" }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/tiny-hashes": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tiny-hashes/-/tiny-hashes-1.0.1.tgz", + "integrity": "sha512-knIN5zj4fl7kW4EBU5sLP20DWUvi/rVouvJezV0UAym2DkQaqm365Nyc8F3QEiOvunNDMxR8UhcXd1d5g+Wg1g==", + "license": "MIT" + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -5567,11 +9211,19 @@ "dev": true, "license": "MIT" }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -5597,6 +9249,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, "node_modules/tough-cookie": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", @@ -5623,6 +9284,12 @@ "node": ">=20" } }, + "node_modules/tryit": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", + "integrity": "sha512-6C5h3CE+0qjGp+YKYTs74xR0k/Nw/ePtl/Lp6CCf44hqBQ66qnH1sDFR5mV/Gc48EsrHLB53lCFSffQCkka3kg==", + "license": "MIT" + }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -5662,6 +9329,19 @@ "node": ">= 0.8.0" } }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typed-array-buffer": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", @@ -5755,6 +9435,15 @@ "node": ">=14.17" } }, + "node_modules/typescript-compare": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/typescript-compare/-/typescript-compare-0.0.2.tgz", + "integrity": "sha512-8ja4j7pMHkfLJQO2/8tut7ub+J3Lw2S3061eJLFQcvs3tsmJKp8KG5NtpLn7KcY2w08edF74BSVN7qJS0U6oHA==", + "license": "MIT", + "dependencies": { + "typescript-logic": "^0.0.0" + } + }, "node_modules/typescript-eslint": { "version": "8.47.0", "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.47.0.tgz", @@ -5779,6 +9468,47 @@ "typescript": ">=4.8.4 <6.0.0" } }, + "node_modules/typescript-logic": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/typescript-logic/-/typescript-logic-0.0.0.tgz", + "integrity": "sha512-zXFars5LUkI3zP492ls0VskH3TtdeHCqu0i7/duGt60i5IGPIpAHE/DWo5FqJ6EjQ15YKXrt+AETjv60Dat34Q==", + "license": "MIT" + }, + "node_modules/typescript-tuple": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/typescript-tuple/-/typescript-tuple-2.2.1.tgz", + "integrity": "sha512-Zcr0lbt8z5ZdEzERHAMAniTiIKerFCMgd7yjq1fPnDJ43et/k9twIFQMUYff9k5oXcsQ0WpvFcgzK2ZKASoW6Q==", + "license": "MIT", + "dependencies": { + "typescript-compare": "^0.0.2" + } + }, + "node_modules/ua-parser-js": { + "version": "0.7.41", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.41.tgz", + "integrity": "sha512-O3oYyCMPYgNNHuO7Jjk3uacJWZF8loBgwrfd/5LE/HyZ3lUIOdniQ7DNXJcIgZbwioZxk0fLfI4EVnetdiX5jg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "license": "MIT", + "bin": { + "ua-parser-js": "script/cli.js" + }, + "engines": { + "node": "*" + } + }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", @@ -5802,14 +9532,70 @@ "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, "license": "MIT" }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", - "dev": true, "funding": [ { "type": "opencollective", @@ -5846,6 +9632,56 @@ "punycode": "^2.1.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vinyl-bufferstream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vinyl-bufferstream/-/vinyl-bufferstream-1.0.1.tgz", + "integrity": "sha512-yCCIoTf26Q9SQ0L9cDSavSL7Nt6wgQw8TU1B/bb9b9Z4A3XTypXCGdc5BvXl4ObQvVY8JrDkFnWa/UqBqwM2IA==", + "dependencies": { + "bufferstreams": "1.0.1" + } + }, + "node_modules/vinyl-sourcemaps-apply": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", + "integrity": "sha512-+oDh3KYZBoZC8hfocrbrxbLUeaYtQK7J5WU5Br9VqWqmCll3tFJqKp97GC9GmMsVIL0qnx2DgEDVxdo5EZ5sSw==", + "license": "ISC", + "dependencies": { + "source-map": "^0.5.1" + } + }, + "node_modules/vinyl-sourcemaps-apply/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/vite": { "version": "7.2.4", "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.4.tgz", @@ -6049,6 +9885,15 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", @@ -6241,11 +10086,33 @@ "node": ">=0.10.0" } }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, "node_modules/ws": { "version": "8.18.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "dev": true, "license": "MIT", "engines": { "node": ">=10.0.0" @@ -6280,6 +10147,57 @@ "dev": true, "license": "MIT" }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/crates/js/lib/package.json b/crates/js/lib/package.json index 93164d3..d6b9316 100644 --- a/crates/js/lib/package.json +++ b/crates/js/lib/package.json @@ -6,7 +6,6 @@ "description": "Trusted Server tsjs TypeScript library with queue and simple banner rendering.", "scripts": { "build": "vite build", - "build:custom": "vite build", "dev": "vite build --watch", "test": "vitest run", "test:watch": "vitest", @@ -15,12 +14,16 @@ "format": "prettier --check \"**/*.{ts,tsx,js,json,css,md}\"", "format:write": "prettier --write \"**/*.{ts,tsx,js,json,css,md}\"" }, + "dependencies": { + "prebid.js": "^10.18.0" + }, "devDependencies": { "@eslint/js": "^9.13.0", "@types/jsdom": "^27.0.0", "@types/node": "^24.10.0", "@typescript-eslint/eslint-plugin": "^8.6.0", "@typescript-eslint/parser": "^8.6.0", + "baseline-browser-mapping": "^2.9.16", "eslint": "^9.10.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-import": "^2.29.1", diff --git a/crates/js/lib/src/core/config.ts b/crates/js/lib/src/core/config.ts index c06f081..b0fdbf9 100644 --- a/crates/js/lib/src/core/config.ts +++ b/crates/js/lib/src/core/config.ts @@ -1,9 +1,26 @@ // Global configuration storage for the tsjs runtime (mode, logging, etc.). import { log, LogLevel } from './log'; -import type { Config } from './types'; -import { RequestMode } from './types'; +import type { Config, GamConfig } from './types'; -let CONFIG: Config = { mode: RequestMode.FirstParty }; +let CONFIG: Config = { mode: 'render' }; + +// Lazy import to avoid circular dependencies - GAM integration may not be present +let setGamConfigFn: ((cfg: GamConfig) => void) | null | undefined = undefined; + +function getSetGamConfig(): ((cfg: GamConfig) => void) | null { + if (setGamConfigFn === undefined) { + try { + // Dynamic import path - bundler will include if gam integration is present + // eslint-disable-next-line @typescript-eslint/no-require-imports + const gam = require('../integrations/gam/index'); + setGamConfigFn = gam.setGamConfig || null; + } catch { + // GAM integration not available + setGamConfigFn = null; + } + } + return setGamConfigFn ?? null; +} // Merge publisher-provided config and adjust the log level accordingly. export function setConfig(cfg: Config): void { @@ -12,6 +29,15 @@ export function setConfig(cfg: Config): void { const l = cfg.logLevel as LogLevel | undefined; if (typeof l === 'string') log.setLevel(l); else if (debugFlag === true) log.setLevel('debug'); + + // Forward GAM config to the GAM integration if present + if (cfg.gam) { + const setGam = getSetGamConfig(); + if (setGam) { + setGam(cfg.gam); + } + } + log.info('setConfig:', cfg); } diff --git a/crates/js/lib/src/core/global.d.ts b/crates/js/lib/src/core/global.d.ts index c7c8b08..ea91f97 100644 --- a/crates/js/lib/src/core/global.d.ts +++ b/crates/js/lib/src/core/global.d.ts @@ -3,7 +3,9 @@ import type { TsjsApi } from './types'; declare global { interface Window { tsjs?: TsjsApi; - pbjs?: TsjsApi; + // pbjs is Prebid.js which has its own types + // eslint-disable-next-line @typescript-eslint/no-explicit-any + pbjs?: any; } } diff --git a/crates/js/lib/src/core/index.ts b/crates/js/lib/src/core/index.ts index 227b1a3..41c8270 100644 --- a/crates/js/lib/src/core/index.ts +++ b/crates/js/lib/src/core/index.ts @@ -1,12 +1,8 @@ -// Public tsjs core bundle: sets up the global API, queue, and default methods. -export type { AdUnit, TsjsApi } from './types'; +// Public tsjs core bundle: sets up the global API. +export type { TsjsApi } from './types'; import type { TsjsApi } from './types'; -import { addAdUnits } from './registry'; -import { renderAdUnit, renderAllAdUnits } from './render'; import { log } from './log'; import { setConfig, getConfig } from './config'; -import { requestAds } from './request'; -import { installQueue } from './queue'; const VERSION = '0.1.0'; @@ -15,45 +11,12 @@ const w: Window & { tsjs?: TsjsApi } = tsjs?: TsjsApi; }) || ({} as Window & { tsjs?: TsjsApi }); -// Collect existing tsjs queued fns before we overwrite -const pending: Array<() => void> = Array.isArray(w.tsjs?.que) ? [...w.tsjs.que] : []; - // Create API and attach methods const api: TsjsApi = (w.tsjs ??= {} as TsjsApi); api.version = VERSION; -api.addAdUnits = addAdUnits; -api.renderAdUnit = renderAdUnit; -api.renderAllAdUnits = () => renderAllAdUnits(); api.log = log; api.setConfig = setConfig; api.getConfig = getConfig; -// Provide core requestAds API -api.requestAds = requestAds; -// Point global tsjs w.tsjs = api; -// Single shared queue -installQueue(api, w); - -// Flush prior queued callbacks -for (const fn of pending) { - try { - if (typeof fn === 'function') { - fn.call(api); - log.debug('queue: flushed callback'); - } - } catch { - /* ignore queued callback error */ - } -} - -log.info('tsjs initialized', { - methods: [ - 'setConfig', - 'getConfig', - 'requestAds', - 'addAdUnits', - 'renderAdUnit', - 'renderAllAdUnits', - ], -}); +log.info('tsjs initialized', { version: VERSION }); diff --git a/crates/js/lib/src/core/queue.ts b/crates/js/lib/src/core/queue.ts deleted file mode 100644 index 73c2741..0000000 --- a/crates/js/lib/src/core/queue.ts +++ /dev/null @@ -1,23 +0,0 @@ -// Minimal Prebid-style queue shim that executes callbacks immediately. -import { log } from './log'; - -// Replace the legacy Prebid-style queue with an immediate executor so queued work runs in order. -export function installQueue void> }>( - target: T, - w: Window & { tsjs?: T } -) { - const q: Array<() => void> = []; - q.push = ((fn: () => void) => { - if (typeof fn === 'function') { - try { - fn.call(target); - log.debug('queue: push executed immediately'); - } catch { - /* ignore queued fn error */ - } - } - return q.length; - }) as typeof q.push; - target.que = q; - if (w.tsjs) w.tsjs.que = q; -} diff --git a/crates/js/lib/src/core/registry.ts b/crates/js/lib/src/core/registry.ts deleted file mode 100644 index ea77937..0000000 --- a/crates/js/lib/src/core/registry.ts +++ /dev/null @@ -1,36 +0,0 @@ -// In-memory registry for ad units registered via tsjs (used by core + extensions). -import type { AdUnit, Size } from './types'; -import { toArray } from './util'; -import { log } from './log'; - -const registry = new Map(); - -// Merge ad unit definitions into the in-memory registry (supports array or single unit). -export function addAdUnits(units: AdUnit | AdUnit[]): void { - for (const u of toArray(units)) { - if (!u || !u.code) continue; - registry.set(u.code, { ...registry.get(u.code), ...u }); - } - log.info('addAdUnits:', { count: toArray(units).length }); -} - -// Convenience helper to grab the first banner size off an ad unit. -export function firstSize(unit: AdUnit): Size | null { - const sizes = unit.mediaTypes?.banner?.sizes; - return sizes && sizes.length ? sizes[0] : null; -} - -// Return a snapshot array of all registered ad units. -export function getAllUnits(): AdUnit[] { - return Array.from(registry.values()); -} - -// Look up a unit by its code. -export function getUnit(code: string): AdUnit | undefined { - return registry.get(code); -} - -// Extract just the ad unit codes for quick iteration. -export function getAllCodes(): string[] { - return Array.from(registry.keys()); -} diff --git a/crates/js/lib/src/core/render.ts b/crates/js/lib/src/core/render.ts deleted file mode 100644 index 5464eb5..0000000 --- a/crates/js/lib/src/core/render.ts +++ /dev/null @@ -1,176 +0,0 @@ -// Rendering utilities for Trusted Server demo placements: find slots, seed placeholders, -// and inject creatives into sandboxed iframes. -import { log } from './log'; -import type { AdUnit } from './types'; -import { getUnit, getAllUnits, firstSize } from './registry'; -import NORMALIZE_CSS from './styles/normalize.css?inline'; -import IFRAME_TEMPLATE from './templates/iframe.html?raw'; - -function normalizeId(raw: string): string { - const s = String(raw ?? '').trim(); - return s.startsWith('#') ? s.slice(1) : s; -} - -// Locate an ad slot element by id, tolerating funky selectors provided by tag managers. -export function findSlot(id: string): HTMLElement | null { - const nid = normalizeId(id); - // Fast path - const byId = document.getElementById(nid) as HTMLElement | null; - if (byId) return byId; - // Fallback for odd IDs (special chars) or if provided with quotes/etc. - try { - const selector = `[id="${nid.replace(/"/g, '\\"')}"]`; - const byAttr = document.querySelector(selector) as HTMLElement | null; - if (byAttr) return byAttr; - } catch { - // Ignore selector errors (e.g., invalid characters) - } - return null; -} - -function ensureSlot(id: string): HTMLElement { - const nid = normalizeId(id); - let el = document.getElementById(nid) as HTMLElement | null; - if (el) return el; - el = document.createElement('div'); - el.id = nid; - const body: HTMLElement | null = typeof document !== 'undefined' ? document.body : null; - if (body && typeof body.appendChild === 'function') { - body.appendChild(el); - } else { - // DOM not ready — attach once available - const element = el; - const onReady = () => { - const readyBody = document.body; - if (readyBody && !document.getElementById(nid) && element) readyBody.appendChild(element); - }; - document.addEventListener('DOMContentLoaded', onReady, { once: true }); - } - return el; -} - -// Drop a placeholder message into the slot so pages don't sit empty pre-render. -export function renderAdUnit(codeOrUnit: string | AdUnit): void { - const code = typeof codeOrUnit === 'string' ? codeOrUnit : codeOrUnit?.code; - if (!code) return; - const unit = typeof codeOrUnit === 'string' ? getUnit(code) : codeOrUnit; - const size = (unit && firstSize(unit)) || [300, 250]; - const el = ensureSlot(code); - try { - el.textContent = `Trusted Server — ${size[0]}x${size[1]}`; - log.info('renderAdUnit: rendered placeholder', { code, size }); - } catch { - log.warn('renderAdUnit: failed', { code }); - } -} - -// Render placeholders for every registered ad unit (used in simple publisher demos). -export function renderAllAdUnits(): void { - try { - const parentReady = - typeof document !== 'undefined' && (document.body || document.documentElement); - if (!parentReady) { - log.warn('renderAllAdUnits: DOM not ready; skipping'); - return; - } - const units = getAllUnits(); - for (const u of units) { - renderAdUnit(u); - } - log.info('renderAllAdUnits: rendered all placeholders', { count: units.length }); - } catch (e) { - log.warn('renderAllAdUnits: failed', e as unknown); - } -} - -// Swap the slot contents for a creative iframe and write HTML into it safely. -export function renderCreativeIntoSlot(slotId: string, html: string): void { - const el = findSlot(slotId); - if (!el) { - log.warn('renderCreativeIntoSlot: slot not found; skipping render', { slotId }); - return; - } - try { - // Clear previous content - el.innerHTML = ''; - // Determine size if available - const unit = getUnit(slotId); - const sz = (unit && firstSize(unit)) || [300, 250]; - const iframe = createAdIframe(el, { - name: `tsjs_iframe_${slotId}`, - title: 'Ad content', - width: sz[0], - height: sz[1], - }); - writeHtmlToIframe(iframe, html); - log.info('renderCreativeIntoSlot: rendered', { slotId, width: sz[0], height: sz[1] }); - } catch (err) { - log.warn('renderCreativeIntoSlot: failed', { slotId, err }); - } -} - -type IframeOptions = { name?: string; title?: string; width?: number; height?: number }; - -// Construct a sandboxed iframe sized for the ad so we can render arbitrary HTML. -export function createAdIframe( - container: HTMLElement, - opts: IframeOptions = {} -): HTMLIFrameElement { - const iframe = document.createElement('iframe'); - // Attributes - iframe.scrolling = 'no'; - iframe.frameBorder = '0'; - iframe.setAttribute('marginwidth', '0'); - iframe.setAttribute('marginheight', '0'); - if (opts.name) iframe.name = String(opts.name); - iframe.title = opts.title || 'Ad content'; - iframe.setAttribute('aria-label', 'Advertisement'); - // Sandbox permissions for creatives - try { - iframe.sandbox.add( - 'allow-forms', - 'allow-popups', - 'allow-popups-to-escape-sandbox', - 'allow-same-origin', - 'allow-scripts', - 'allow-top-navigation-by-user-activation' - ); - } catch (err) { - log.debug('createAdIframe: sandbox add failed', err); - } - // Sizing + style - const w = Math.max(0, Number(opts.width ?? 0) | 0); - const h = Math.max(0, Number(opts.height ?? 0) | 0); - if (w > 0) iframe.width = String(w); - if (h > 0) iframe.height = String(h); - const s = iframe.style; - s.setProperty('border', '0'); - s.setProperty('margin', '0'); - s.setProperty('overflow', 'hidden'); - s.setProperty('display', 'block'); - if (w > 0) s.setProperty('width', `${w}px`); - if (h > 0) s.setProperty('height', `${h}px`); - // Insert into container - container.appendChild(iframe); - return iframe; -} - -function writeHtmlToIframe(iframe: HTMLIFrameElement, creativeHtml: string): void { - try { - const doc = (iframe.contentDocument || iframe.contentWindow?.document) as Document | undefined; - if (!doc) return; - const html = buildIframeDocument(creativeHtml); - doc.open(); - doc.write(html); - doc.close(); - } catch (err) { - log.warn('renderCreativeIntoSlot: iframe write failed', { err }); - } -} - -function buildIframeDocument(creativeHtml: string): string { - return IFRAME_TEMPLATE.replace('%NORMALIZE_CSS%', NORMALIZE_CSS).replace( - '%CREATIVE_HTML%', - creativeHtml - ); -} diff --git a/crates/js/lib/src/core/request.ts b/crates/js/lib/src/core/request.ts deleted file mode 100644 index 98e04a3..0000000 --- a/crates/js/lib/src/core/request.ts +++ /dev/null @@ -1,156 +0,0 @@ -// Request orchestration for tsjs: fires first-party iframe loads or third-party fetches. -import { delay } from '../shared/async'; - -import { log } from './log'; -import { getAllUnits, firstSize } from './registry'; -import { renderCreativeIntoSlot, renderAllAdUnits, createAdIframe, findSlot } from './render'; -import { getConfig } from './config'; -import { RequestMode } from './types'; -import type { RequestAdsCallback, RequestAdsOptions } from './types'; - -// getHighestCpmBids is provided by the Prebid extension (shim) to mirror Prebid's API - -// Entry point matching Prebid's requestBids signature; decides first/third-party mode. -export function requestAds( - callbackOrOpts?: RequestAdsCallback | RequestAdsOptions, - maybeOpts?: RequestAdsOptions -): void { - let callback: RequestAdsCallback | undefined; - let opts: RequestAdsOptions | undefined; - if (typeof callbackOrOpts === 'function') { - callback = callbackOrOpts as RequestAdsCallback; - opts = maybeOpts; - } else { - opts = callbackOrOpts as RequestAdsOptions | undefined; - callback = opts?.bidsBackHandler; - } - - const mode: RequestMode = (getConfig().mode as RequestMode | undefined) ?? RequestMode.FirstParty; - log.info('requestAds: called', { hasCallback: typeof callback === 'function', mode }); - try { - const adUnits = getAllUnits(); - const payload = { adUnits, config: {} }; - log.debug('requestAds: payload', { units: adUnits.length }); - if (mode === RequestMode.FirstParty) void requestAdsFirstParty(adUnits); - else requestAdsThirdParty(payload); - // Synchronously invoke callback to match test expectations - try { - if (callback) callback(); - } catch { - /* ignore callback errors */ - } - // network handled in requestAdsThirdParty; no-op here - } catch { - log.warn('requestAds: failed to initiate'); - } -} - -// Create per-slot first-party iframe requests served directly from the edge. -async function requestAdsFirstParty(adUnits: ReadonlyArray<{ code: string }>) { - for (const unit of adUnits) { - const size = (firstSize(unit) ?? [300, 250]) as readonly [number, number]; - const slotId = unit.code; - - const attemptInsert = async (attemptsRemaining: number): Promise => { - const container = findSlot(slotId) as HTMLElement | null; - if (container) { - const iframe = createAdIframe(container, { - name: `tsjs_iframe_${slotId}`, - title: 'Ad content', - width: size[0], - height: size[1], - }); - iframe.src = `/first-party/ad?slot=${encodeURIComponent(slotId)}&w=${encodeURIComponent(String(size[0]))}&h=${encodeURIComponent(String(size[1]))}`; - return; - } - - if (attemptsRemaining <= 0) { - log.warn('requestAds(firstParty): slot not found; skipping iframe', { slotId }); - return; - } - - if (typeof document !== 'undefined' && document.readyState === 'loading') { - document.addEventListener( - 'DOMContentLoaded', - () => { - void attemptInsert(attemptsRemaining - 1); - }, - { once: true } - ); - return; - } - - await delay(50); - await attemptInsert(attemptsRemaining - 1); - }; - - void attemptInsert(10); - } -} - -// Fire a JSON POST to the third-party ad endpoint and render returned creatives. -function requestAdsThirdParty(payload: { adUnits: unknown[]; config: unknown }) { - // Render simple placeholders immediately so pages have content - renderAllAdUnits(); - if (typeof fetch !== 'function') { - log.warn('requestAds: fetch not available; nothing to render'); - return; - } - log.info('requestAds: sending request to /third-party/ad', { - units: (payload.adUnits || []).length, - }); - void fetch('/third-party/ad', { - method: 'POST', - headers: { 'content-type': 'application/json' }, - credentials: 'same-origin', - body: JSON.stringify(payload), - keepalive: true, - }) - .then(async (res) => { - log.debug('requestAds: sent'); - try { - const ct = res.headers.get('content-type') || ''; - if (res.ok && ct.includes('application/json')) { - const data: unknown = await res.json(); - for (const bid of parseSeatBids(data)) { - if (bid.impid && bid.adm) renderCreativeIntoSlot(String(bid.impid), bid.adm); - } - log.info('requestAds: rendered creatives from response'); - return; - } - log.warn('requestAds: unexpected response', { ok: res.ok, status: res.status, ct }); - } catch (err) { - log.warn('requestAds: failed to process response', err); - } - }) - .catch((e) => { - log.warn('requestAds: failed', e); - }); -} - -// Local minimal OpenRTB typing to keep core decoupled from Prebid extension types -type RtBid = { impid?: string; adm?: string }; -type RtSeatBid = { bid?: RtBid[] | null }; -type RtResponse = { seatbid?: RtSeatBid[] | null }; - -function isSeatBidArray(x: unknown): x is RtSeatBid[] { - return Array.isArray(x); -} - -// Minimal OpenRTB seatbid parser—just enough to render adm by impid. -function parseSeatBids(data: unknown): RtBid[] { - const out: RtBid[] = []; - const resp = data as Partial; - const seatbids = resp && resp.seatbid; - if (!seatbids || !isSeatBidArray(seatbids)) return out; - for (const sb of seatbids) { - const bids = sb && sb.bid; - if (!Array.isArray(bids)) continue; - for (const b of bids) { - const impid = typeof b?.impid === 'string' ? b!.impid : undefined; - const adm = typeof b?.adm === 'string' ? b!.adm : undefined; - out.push({ impid, adm }); - } - } - return out; -} diff --git a/crates/js/lib/src/core/styles/normalize.css b/crates/js/lib/src/core/styles/normalize.css deleted file mode 100644 index da77bad..0000000 --- a/crates/js/lib/src/core/styles/normalize.css +++ /dev/null @@ -1,169 +0,0 @@ -/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ -button, -hr, -input { - overflow: visible; -} - -progress, -sub, -sup { - vertical-align: baseline; -} - -[type='checkbox'], -[type='radio'], -legend { - box-sizing: border-box; - padding: 0; -} - -html { - line-height: 1.15; - -webkit-text-size-adjust: 100%; -} - -body { - margin: 0; -} - -details, -main { - display: block; -} - -h1 { - font-size: 2em; - margin: 0.67em 0; -} - -hr { - box-sizing: content-box; - height: 0; -} - -code, -kbd, -pre, -samp { - font-family: monospace, monospace; - font-size: 1em; -} - -a { - background-color: transparent; -} - -abbr[title] { - border-bottom: none; - text-decoration: underline dotted; -} - -b, -strong { - font-weight: bolder; -} - -small { - font-size: 80%; -} - -sub, -sup { - font-size: 75%; - line-height: 0; - position: relative; -} - -sub { - bottom: -0.25em; -} - -sup { - top: -0.5em; -} - -img { - border-style: none; -} - -button, -input, -optgroup, -select, -textarea { - font-family: inherit; - font-size: 100%; - line-height: 1.15; - margin: 0; -} - -button, -select { - text-transform: none; -} - -[type='button'], -[type='reset'], -[type='submit'], -button { - -webkit-appearance: button; -} - -[type='button']::-moz-focus-inner, -[type='reset']::-moz-focus-inner, -[type='submit']::-moz-focus-inner, -button::-moz-focus-inner { - border-style: none; - padding: 0; -} - -[type='button']:-moz-focusring, -[type='reset']:-moz-focusring, -[type='submit']:-moz-focusring, -button:-moz-focusring { - outline: 1px dotted ButtonText; -} - -fieldset { - padding: 0.35em 0.75em 0.625em; -} - -legend { - color: inherit; - display: table; - max-width: 100%; - white-space: normal; -} - -textarea { - overflow: auto; -} - -[type='number']::-webkit-inner-spin-button, -[type='number']::-webkit-outer-spin-button { - height: auto; -} - -[type='search'] { - -webkit-appearance: textfield; - outline-offset: -2px; -} - -[type='search']::-webkit-search-decoration { - -webkit-appearance: none; -} - -::-webkit-file-upload-button { - -webkit-appearance: button; - font: inherit; -} - -summary { - display: list-item; -} - -[hidden], -template { - display: none; -} diff --git a/crates/js/lib/src/core/templates/iframe.html b/crates/js/lib/src/core/templates/iframe.html deleted file mode 100644 index 55af0d5..0000000 --- a/crates/js/lib/src/core/templates/iframe.html +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - %CREATIVE_HTML% - diff --git a/crates/js/lib/src/core/types.ts b/crates/js/lib/src/core/types.ts index 3bf1b49..acbbc6a 100644 --- a/crates/js/lib/src/core/types.ts +++ b/crates/js/lib/src/core/types.ts @@ -1,75 +1,37 @@ -// Shared TypeScript types for the tsjs core API and extensions. -export type Size = readonly [number, number]; +// Shared TypeScript types for the tsjs core API. -export interface Banner { - sizes: ReadonlyArray; -} +export type LogLevel = 'silent' | 'error' | 'warn' | 'info' | 'debug'; -export interface MediaTypes { - banner?: Banner; +export interface GamConfig { + /** Enable the GAM interceptor. Defaults to false. */ + enabled?: boolean; + /** Only intercept bids from these bidders. Empty array = all bidders. */ + bidders?: string[]; + /** Force render Prebid creative even if GAM returned a line item. Defaults to false. */ + forceRender?: boolean; } -export interface AdUnit { - code: string; - mediaTypes?: MediaTypes; +export interface Config { + debug?: boolean; + logLevel?: LogLevel; + /** Select ad serving mode: 'render' or 'auction'. */ + mode?: 'render' | 'auction'; + /** GAM interceptor configuration. */ + gam?: GamConfig; + // Extendable for future fields + [key: string]: unknown; } export interface TsjsApi { version: string; - que: Array<() => void>; - addAdUnits(units: AdUnit | AdUnit[]): void; - renderAdUnit(codeOrUnit: string | AdUnit): void; - renderAllAdUnits(): void; - setConfig?(cfg: Config): void; - getConfig?(): Config; - // Core API: requestAds; accepts same signatures as Prebid's requestBids - requestAds?(opts?: RequestAdsOptions): void; - requestAds?(callback: RequestAdsCallback, opts?: RequestAdsOptions): void; - getHighestCpmBids?(adUnitCodes?: string | string[]): ReadonlyArray; - log?: { - setLevel(l: 'silent' | 'error' | 'warn' | 'info' | 'debug'): void; - getLevel(): 'silent' | 'error' | 'warn' | 'info' | 'debug'; + setConfig(cfg: Config): void; + getConfig(): Config; + log: { + setLevel(l: LogLevel): void; + getLevel(): LogLevel; info(...args: unknown[]): void; warn(...args: unknown[]): void; error(...args: unknown[]): void; debug(...args: unknown[]): void; }; } - -export enum RequestMode { - FirstParty = 'firstParty', - ThirdParty = 'thirdParty', -} - -export interface Config { - debug?: boolean; - logLevel?: 'silent' | 'error' | 'warn' | 'info' | 'debug'; - /** Select ad serving mode. Default is RequestMode.FirstParty. */ - mode?: RequestMode; - // Extendable for future fields - [key: string]: unknown; -} - -// Core-neutral request types -export type RequestAdsCallback = () => void; -export interface RequestAdsOptions { - bidsBackHandler?: RequestAdsCallback; - timeout?: number; -} - -// Back-compat aliases for Prebid-style naming (used by the extension shim) -export type RequestBidsCallback = RequestAdsCallback; - -export interface HighestCpmBid { - adUnitCode: string; - width: number; - height: number; - cpm: number; - currency: string; - bidderCode: string; - creativeId: string; - adserverTargeting: Record; -} - -// Minimal OpenRTB response typing -// OpenRTB response typing is specific to the Prebid extension and lives in src/ext/types.ts diff --git a/crates/js/lib/src/core/util.ts b/crates/js/lib/src/core/util.ts deleted file mode 100644 index be9a855..0000000 --- a/crates/js/lib/src/core/util.ts +++ /dev/null @@ -1,9 +0,0 @@ -// Tiny shared helpers used across core modules. -export function isArray(v: unknown): v is T[] { - return Array.isArray(v); -} - -// Normalise a single value into an array for simple iteration helpers. -export function toArray(v: T | T[]): T[] { - return isArray(v) ? v : [v]; -} diff --git a/crates/js/lib/src/index.ts b/crates/js/lib/src/index.ts index bfe6e69..9a5a505 100644 --- a/crates/js/lib/src/index.ts +++ b/crates/js/lib/src/index.ts @@ -35,7 +35,7 @@ for (const [moduleName, moduleExports] of Object.entries(modules)) { } // Re-export core types for convenience -export type { AdUnit, TsjsApi } from './core/types'; +export type { TsjsApi } from './core/types'; // Export the modules object for advanced use cases export { modules }; diff --git a/crates/js/lib/src/integrations/ext/index.ts b/crates/js/lib/src/integrations/ext/index.ts deleted file mode 100644 index 2cf7971..0000000 --- a/crates/js/lib/src/integrations/ext/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { installPrebidJsShim } from './prebidjs'; - -// Execute immediately on import; safe no-op if pbjs is not present. -void installPrebidJsShim(); diff --git a/crates/js/lib/src/integrations/ext/prebidjs.ts b/crates/js/lib/src/integrations/ext/prebidjs.ts deleted file mode 100644 index 242ebc0..0000000 --- a/crates/js/lib/src/integrations/ext/prebidjs.ts +++ /dev/null @@ -1,121 +0,0 @@ -// Prebid.js compatibility shim: exposes tsjs API through the legacy pbjs global. -import type { - TsjsApi, - HighestCpmBid, - RequestAdsCallback, - RequestAdsOptions, -} from '../../core/types'; -import { log } from '../../core/log'; -import { installQueue } from '../../core/queue'; -import { getAllCodes, getAllUnits, firstSize } from '../../core/registry'; -import { resolvePrebidWindow, PrebidWindow } from '../../shared/globals'; -type RequestBidsFunction = ( - callbackOrOpts?: RequestAdsCallback | RequestAdsOptions, - opts?: RequestAdsOptions -) => void; - -/** - * Shim implementation for pbjs.getHighestCpmBids that returns synthetic - * placeholder bids derived from the registered core ad units. - */ -function getHighestCpmBidsShim(adUnitCodes?: string | string[]): ReadonlyArray { - const codes: string[] = - typeof adUnitCodes === 'string' ? [adUnitCodes] : (adUnitCodes ?? getAllCodes()); - const results: HighestCpmBid[] = []; - for (const code of codes) { - const unit = getAllUnits().find((u) => u.code === code); - if (!unit) continue; - const size = (firstSize(unit) ?? [300, 250]) as readonly [number, number]; - results.push({ - adUnitCode: code, - width: size[0], - height: size[1], - cpm: 0, - currency: 'USD', - bidderCode: 'tsjs', - creativeId: 'tsjs-placeholder', - adserverTargeting: {}, - }); - } - return results; -} - -/** - * Shim implementation for pbjs.requestBids that forwards to core requestAds. - */ -function requestBidsShim(api: TsjsApi): RequestBidsFunction { - return (callbackOrOpts?: RequestAdsCallback | RequestAdsOptions, opts?: RequestAdsOptions) => { - const requestAds = api.requestAds as - | ((options?: RequestAdsOptions) => void) - | ((callback: RequestAdsCallback, options?: RequestAdsOptions) => void) - | undefined; - if (!requestAds) return; - if (typeof callbackOrOpts === 'function') { - requestAds(callbackOrOpts, opts); - } else { - requestAds(callbackOrOpts); - } - }; -} - -// Guarantee a tsjs API stub exists so we can alias pbjs onto it. -function ensureTsjsApi(win: PrebidWindow): TsjsApi { - if (win.tsjs) return win.tsjs; - const stub: TsjsApi = { - version: '0.0.0', - que: [], - addAdUnits: () => undefined, - renderAdUnit: () => undefined, - renderAllAdUnits: () => undefined, - }; - win.tsjs = stub; - return stub; -} - -// Bridge the minimal tsjs API onto the legacy pbjs global so existing tags keep working. -export function installPrebidJsShim(): boolean { - const w = resolvePrebidWindow(); - - // Ensure core exists - const api = ensureTsjsApi(w); - - // Capture any queued pbjs callbacks before aliasing - const pending: Array<() => void> = Array.isArray(w.pbjs?.que) ? [...(w.pbjs?.que ?? [])] : []; - - // Core provides requestAds/getHighestCpmBids; extension aliases pbjs and shims requestBids → requestAds - - // Alias pbjs to tsjs and ensure a single shared queue - w.pbjs = api; - if (!Array.isArray(api.que)) { - installQueue(api, w); - } - const pbjsApi = w.pbjs as TsjsApi & { requestBids?: RequestBidsFunction }; - // Make sure both globals share the same queue - if (Array.isArray(api.que)) { - pbjsApi.que = api.que; - } - // Shim Prebid-style API surface - pbjsApi.requestBids = requestBidsShim(api); - pbjsApi.getHighestCpmBids = getHighestCpmBidsShim; - - // Flush previously queued pbjs callbacks - for (const fn of pending) { - try { - if (typeof fn === 'function') { - fn.call(api); - log.debug('prebidjs extension: flushed callback'); - } - } catch (err) { - log.debug('prebidjs extension: queued callback failed', err); - } - } - - log.info('prebidjs extension installed', { - hasRequestBids: typeof pbjsApi.requestBids === 'function', - hasGetHighestCpmBids: typeof pbjsApi.getHighestCpmBids === 'function', - }); - - return true; -} - -export default installPrebidJsShim; diff --git a/crates/js/lib/src/integrations/ext/types.ts b/crates/js/lib/src/integrations/ext/types.ts deleted file mode 100644 index a83a499..0000000 --- a/crates/js/lib/src/integrations/ext/types.ts +++ /dev/null @@ -1,12 +0,0 @@ -// Minimal OpenRTB response typing (used by the Prebid extension) -export interface OpenRtbBid { - impid?: string; - adm?: string; - [key: string]: unknown; -} -export interface OpenRtbSeatBid { - bid?: OpenRtbBid[] | null; -} -export interface OpenRtbBidResponse { - seatbid?: OpenRtbSeatBid[] | null; -} diff --git a/crates/js/lib/src/integrations/gam/index.ts b/crates/js/lib/src/integrations/gam/index.ts new file mode 100644 index 0000000..2832067 --- /dev/null +++ b/crates/js/lib/src/integrations/gam/index.ts @@ -0,0 +1,396 @@ +// GAM (Google Ad Manager) Interceptor - forces Prebid creatives to render when +// GAM doesn't have matching line items configured. +// +// This integration intercepts GPT's slotRenderEnded event and replaces GAM's +// creative with the Prebid winning bid when: +// 1. A Prebid bid exists for the slot (hb_adid targeting is set) +// 2. The bid meets the configured criteria (specific bidder or any bidder) +// +// Configuration options: +// - enabled: boolean (default: false) - Master switch for the interceptor +// - bidders: string[] (default: []) - Only intercept for these bidders. Empty = all bidders +// - forceRender: boolean (default: false) - Render even if GAM has a line item +// +// Usage: +// window.tsGamConfig = { enabled: true, bidders: ['mocktioneer'] }; +// // or via tsjs.setConfig({ gam: { enabled: true, bidders: ['mocktioneer'] } }) + +import { log } from '../../core/log'; + +export interface TsGamConfig { + /** Enable the GAM interceptor. Defaults to false. */ + enabled?: boolean; + /** Only intercept bids from these bidders. Empty array = all bidders. */ + bidders?: string[]; + /** Force render Prebid creative even if GAM returned a line item. Defaults to false. */ + forceRender?: boolean; +} + +export interface TsGamApi { + setConfig(cfg: TsGamConfig): void; + getConfig(): TsGamConfig; + getStats(): GamInterceptStats; +} + +interface GamInterceptStats { + intercepted: number; + rendered: Array<{ + slotId: string; + adId: string; + bidder: string; + method: string; + timestamp: number; + }>; +} + +type GamWindow = Window & { + googletag?: { + pubads?: () => { + addEventListener: (event: string, callback: (e: SlotRenderEndedEvent) => void) => void; + getSlots?: () => GptSlot[]; + }; + }; + pbjs?: { + getBidResponsesForAdUnitCode?: (code: string) => { bids?: PrebidBid[] }; + renderAd?: (doc: Document, adId: string) => void; + }; + tsGamConfig?: TsGamConfig; + __tsGamInstalled?: boolean; +}; + +interface SlotRenderEndedEvent { + slot: GptSlot; + isEmpty: boolean; + lineItemId: number | null; +} + +interface GptSlot { + getSlotElementId(): string; + getTargeting(key: string): string[]; + getTargetingKeys(): string[]; +} + +interface PrebidBid { + adId?: string; + ad?: string; + adUrl?: string; + bidder?: string; + cpm?: number; +} + +interface IframeAttrs { + src: string; + width?: string; + height?: string; +} + +/** + * Extract iframe attributes from a creative that is just an iframe wrapper. + * Returns null if the creative is not a simple iframe tag. + * Exported for testing. + */ +export function extractIframeAttrs(html: string): IframeAttrs | null { + const trimmed = html.trim(); + // Check if it's a simple iframe tag (possibly with whitespace/newline after) + if (!trimmed.toLowerCase().startsWith(''; + expect(extractIframeSrc(html)).toBe('/first-party/proxy?tsurl=https://example.com'); + }); + + it('handles trailing newline (mocktioneer style)', async () => { + const { extractIframeSrc } = await import('../../../src/integrations/gam/index'); + + const html = + '\n'; + expect(extractIframeSrc(html)).toBe( + '/first-party/proxy?tsurl=https%3A%2F%2Flocal.mocktioneer.com' + ); + }); + + it('returns null for non-iframe content', async () => { + const { extractIframeSrc } = await import('../../../src/integrations/gam/index'); + + expect(extractIframeSrc('
not an iframe
')).toBeNull(); + expect(extractIframeSrc('')).toBeNull(); + }); + + it('returns null for iframe without src', async () => { + const { extractIframeSrc } = await import('../../../src/integrations/gam/index'); + + expect(extractIframeSrc('')).toBeNull(); + }); + + it('returns null for complex content with iframe', async () => { + const { extractIframeSrc } = await import('../../../src/integrations/gam/index'); + + expect(extractIframeSrc('
')).toBeNull(); + }); + }); +}); diff --git a/crates/js/lib/vite.config.ts b/crates/js/lib/vite.config.ts index 8b9ed86..a6cc700 100644 --- a/crates/js/lib/vite.config.ts +++ b/crates/js/lib/vite.config.ts @@ -84,8 +84,6 @@ export type ModuleName = ${finalModules.map((m) => `'${m}'`).join(' | ')}; export default defineConfig(() => { const distDir = path.resolve(__dirname, '../dist'); - const buildTimestamp = new Date().toISOString(); - const banner = `// build: ${buildTimestamp}\n`; return { build: { @@ -93,11 +91,11 @@ export default defineConfig(() => { outDir: distDir, assetsDir: '.', sourcemap: false, - minify: 'esbuild', + minify: 'esbuild' as const, rollupOptions: { input: path.resolve(__dirname, 'src/index.ts'), output: { - format: 'iife', + format: 'iife' as const, dir: distDir, entryFileNames: 'tsjs-unified.js', inlineDynamicImports: true, diff --git a/crates/js/src/bundle.rs b/crates/js/src/bundle.rs index ef29249..60daa6e 100644 --- a/crates/js/src/bundle.rs +++ b/crates/js/src/bundle.rs @@ -43,7 +43,8 @@ impl TsjsBundle { } } - pub(crate) const fn bundle(self) -> &'static str { + /// Returns the JavaScript bundle content. + pub const fn bundle(self) -> &'static str { METAS[self as usize].bundle } diff --git a/docs/guide/api-reference.md b/docs/guide/api-reference.md index b10d345..1bd35ec 100644 --- a/docs/guide/api-reference.md +++ b/docs/guide/api-reference.md @@ -11,9 +11,9 @@ Quick reference for all Trusted Server HTTP endpoints. --- -## First-Party Endpoints +## Ad and Proxy Endpoints -### GET /first-party/ad +### GET /ad/render Server-side ad rendering endpoint. Returns complete HTML for a single ad slot. @@ -26,11 +26,11 @@ Server-side ad rendering endpoint. Returns complete HTML for a single ad slot. **Response:** - **Content-Type:** `text/html; charset=utf-8` -- **Body:** Complete HTML creative with first-party proxying applied +- **Body:** Complete HTML creative with proxy rewrites applied **Example:** ```bash -curl "https://edge.example.com/first-party/ad?slot=header-banner&w=728&h=90" +curl "https://edge.example.com/ad/render?slot=header-banner&w=728&h=90" ``` **Response Headers:** @@ -44,25 +44,36 @@ curl "https://edge.example.com/first-party/ad?slot=header-banner&w=728&h=90" --- -### POST /third-party/ad +### POST /ad/auction -Client-side auction endpoint for TSJS library. +OpenRTB auction endpoint for client-side integrations (for example, Prebid.js S2S). **Request Body:** ```json { - "adUnits": [ + "id": "auction-1", + "imp": [ { - "code": "header-banner", - "mediaTypes": { - "banner": { - "sizes": [[728, 90], [970, 250]] + "id": "header-banner", + "banner": { + "format": [ + { "w": 728, "h": 90 }, + { "w": 970, "h": 250 } + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": {}, + "rubicon": {} + } } } } ], - "config": { - "debug": false + "site": { + "domain": "example.com", + "page": "https://example.com" } } ``` @@ -88,9 +99,17 @@ Client-side auction endpoint for TSJS library. **Example:** ```bash -curl -X POST https://edge.example.com/third-party/ad \ +curl -X POST https://edge.example.com/ad/auction \ -H "Content-Type: application/json" \ - -d '{"adUnits":[{"code":"banner","mediaTypes":{"banner":{"sizes":[[300,250]]}}}]}' + -d '{ + "id": "auction-1", + "imp": [{ + "id": "banner", + "banner": { "format": [{ "w": 300, "h": 250 }] }, + "ext": { "prebid": { "bidder": { "appnexus": {} } } } + }], + "site": { "domain": "example.com", "page": "https://example.com" } + }' ``` --- @@ -164,7 +183,7 @@ curl -I "https://edge.example.com/first-party/click?tsurl=https://advertiser.com ### GET/POST /first-party/sign -URL signing endpoint. Returns signed first-party proxy URL for a given target URL. +URL signing endpoint. Returns signed proxy URL for a given target URL. **Request Methods:** GET or POST @@ -415,24 +434,28 @@ See [Configuration](./configuration.md) for TSJS build options. ### Prebid Integration -#### GET /first-party/ad -See [First-Party Endpoints](#get-first-party-ad) above. +#### GET /ad/render +See [Ad and Proxy Endpoints](#get-ad-render) above. -#### POST /third-party/ad -See [First-Party Endpoints](#post-third-party-ad) above. +#### POST /ad/auction +See [Ad and Proxy Endpoints](#post-ad-auction) above. -#### GET /prebid.js (Optional) -Returns empty JavaScript to override Prebid.js when `script_handler` is configured. +#### GET /prebid.js, /prebid.min.js, etc. (Script Override) +Returns empty JavaScript to override Prebid.js scripts when the Prebid integration is enabled. By default, exact requests to `/prebid.js`, `/prebid.min.js`, `/prebidjs.js`, or `/prebidjs.min.js` will be intercepted and served an empty script. **Configuration:** ```toml [integrations.prebid] -script_handler = "/prebid.js" +# Default patterns (exact paths) +script_patterns = ["/prebid.js", "/prebid.min.js", "/prebidjs.js", "/prebidjs.min.js"] + +# Use wildcard patterns to match paths under a prefix +# script_patterns = ["/static/prebid/*"] ``` **Response:** - **Content-Type:** `application/javascript; charset=utf-8` -- **Body:** `// Prebid.js override by Trusted Server` +- **Body:** `// Script overridden by Trusted Server` - **Cache:** `immutable, max-age=31536000` --- diff --git a/docs/guide/configuration-reference.md b/docs/guide/configuration-reference.md index c820ca7..8370187 100644 --- a/docs/guide/configuration-reference.md +++ b/docs/guide/configuration-reference.md @@ -597,9 +597,16 @@ All integrations support: | `server_url` | String | Required | Prebid Server endpoint URL | | `timeout_ms` | Integer | `1000` | Request timeout in milliseconds | | `bidders` | Array[String] | `[]` | List of enabled bidders | -| `auto_configure` | Boolean | `false` | Auto-inject Prebid.js shim | | `debug` | Boolean | `false` | Enable debug logging | -| `script_handler` | String | Optional | Custom script endpoint path | +| `mode` | String | None | Default TSJS request mode when Prebid is enabled (`render` or `auction`); `auction` expects OpenRTB clients (for example, Prebid.js) calling `/ad/auction` | +| `script_patterns` | Array[String] | See below | Patterns for removing Prebid script tags and intercepting requests | + +**Default `script_patterns`**: +```toml +["/prebid.js", "/prebid.min.js", "/prebidjs.js", "/prebidjs.min.js"] +``` + +These patterns use suffix matching when stripping HTML, so `/static/prebid/v8/prebid.min.js` matches because it ends with `/prebid.min.js`. For request interception, exact paths are registered unless you use wildcard patterns (e.g., `/static/prebid/*`), which match paths under that prefix. **Example**: ```toml @@ -608,8 +615,9 @@ enabled = true server_url = "https://prebid-server.example/openrtb2/auction" timeout_ms = 1200 bidders = ["kargo", "rubicon", "appnexus", "openx"] -auto_configure = true debug = false +mode = "auction" # OpenRTB clients (for example, Prebid.js) +# script_patterns = ["/static/prebid/*"] # Optional: restrict to specific path ``` **Environment Override**: @@ -618,8 +626,10 @@ TRUSTED_SERVER__INTEGRATIONS__PREBID__ENABLED=true TRUSTED_SERVER__INTEGRATIONS__PREBID__SERVER_URL=https://prebid.example/auction TRUSTED_SERVER__INTEGRATIONS__PREBID__TIMEOUT_MS=1200 TRUSTED_SERVER__INTEGRATIONS__PREBID__BIDDERS=kargo,rubicon,appnexus -TRUSTED_SERVER__INTEGRATIONS__PREBID__AUTO_CONFIGURE=true TRUSTED_SERVER__INTEGRATIONS__PREBID__DEBUG=false +TRUSTED_SERVER__INTEGRATIONS__PREBID__MODE=auction +TRUSTED_SERVER__INTEGRATIONS__PREBID__SCRIPT_PATTERNS__0=/prebid.js +TRUSTED_SERVER__INTEGRATIONS__PREBID__SCRIPT_PATTERNS__1=/prebid.min.js ``` ### Next.js Integration diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index 962e440..d929a1c 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -95,7 +95,7 @@ enabled = true server_url = "https://prebid-server.com/openrtb2/auction" timeout_ms = 1200 bidders = ["kargo", "rubicon", "appnexus"] -auto_configure = false +# script_patterns = ["/static/prebid/*"] ``` ### `fastly.toml` @@ -264,7 +264,7 @@ enabled = true server_url = "https://prebid-server.com/openrtb2/auction" timeout_ms = 1200 bidders = ["kargo", "rubicon", "appnexus"] -auto_configure = false +# script_patterns = ["/static/prebid/*"] ``` **Next.js**: diff --git a/docs/guide/environment-variables.md b/docs/guide/environment-variables.md index 82a11f9..5784a78 100644 --- a/docs/guide/environment-variables.md +++ b/docs/guide/environment-variables.md @@ -201,14 +201,19 @@ TRUSTED_SERVER__INTEGRATIONS__PREBID__TIMEOUT_MS=1000 # Bidders (comma-separated) TRUSTED_SERVER__INTEGRATIONS__PREBID__BIDDERS="appnexus,rubicon,openx" -# Auto-remove Prebid.js scripts -TRUSTED_SERVER__INTEGRATIONS__PREBID__AUTO_CONFIGURE=true - # Enable debug logging TRUSTED_SERVER__INTEGRATIONS__PREBID__DEBUG=false -# Optional: Script handler path -TRUSTED_SERVER__INTEGRATIONS__PREBID__SCRIPT_HANDLER="/prebid.js" +# Default tsjs mode when Prebid integration is enabled (optional) +# "auction" expects OpenRTB clients (for example, Prebid.js) calling /ad/auction +TRUSTED_SERVER__INTEGRATIONS__PREBID__MODE="auction" # or "render" + +# Script patterns to remove Prebid tags and serve empty JS (indexed format) +# Default patterns match common Prebid filenames at exact paths +TRUSTED_SERVER__INTEGRATIONS__PREBID__SCRIPT_PATTERNS__0="/prebid.js" +TRUSTED_SERVER__INTEGRATIONS__PREBID__SCRIPT_PATTERNS__1="/prebid.min.js" +# For versioned paths, use wildcards: +# TRUSTED_SERVER__INTEGRATIONS__PREBID__SCRIPT_PATTERNS__0="/static/prebid/{*rest}" ``` **TOML Equivalent:** @@ -218,13 +223,50 @@ enabled = true server_url = "https://prebid-server.example.com" timeout_ms = 1000 bidders = ["appnexus", "rubicon", "openx"] -auto_configure = true debug = false -script_handler = "/prebid.js" +mode = "auction" # OpenRTB clients (for example, Prebid.js) +script_patterns = ["/prebid.js", "/prebid.min.js", "/prebidjs.js", "/prebidjs.min.js"] ``` --- +## GAM Integration + +The GAM (Google Ad Manager) interceptor forces Prebid creatives to render when GAM +doesn't have matching line items configured. This is useful for testing and development. + +```bash +# Enable GAM interceptor +TRUSTED_SERVER__INTEGRATIONS__GAM__ENABLED=true + +# Only intercept specific bidders (comma-separated, empty = all bidders) +TRUSTED_SERVER__INTEGRATIONS__GAM__BIDDERS="mocktioneer,appnexus" + +# Force render even if GAM returned a line item (default: false) +TRUSTED_SERVER__INTEGRATIONS__GAM__FORCE_RENDER=false +``` + +**TOML Equivalent:** +```toml +[integrations.gam] +enabled = true +bidders = ["mocktioneer"] +force_render = false +``` + +**How it works:** +1. When a GPT slot renders, the interceptor checks if there's a Prebid bid (`hb_adid` targeting) +2. If `bidders` is set, only those bidders' creatives are intercepted +3. If `force_render` is false (default), only renders when GAM has no matching line item +4. If `force_render` is true, always renders Prebid creative regardless of GAM + +**Use cases:** +- **Development/Testing**: Render mocktioneer test ads without GAM line items +- **A/B Testing**: Force Prebid creatives for specific bidders +- **Fallback**: Use Prebid as fallback when GAM has no matching ads + +--- + ## Next.js Integration ```bash @@ -642,7 +684,11 @@ export TRUSTED_SERVER__INTEGRATIONS__PREBID__ENABLED=true export TRUSTED_SERVER__INTEGRATIONS__PREBID__SERVER_URL="https://prebid-server.com" export TRUSTED_SERVER__INTEGRATIONS__PREBID__TIMEOUT_MS=2000 export TRUSTED_SERVER__INTEGRATIONS__PREBID__BIDDERS="appnexus,rubicon,openx" -export TRUSTED_SERVER__INTEGRATIONS__PREBID__AUTO_CONFIGURE=true + +# Optional: GAM Interceptor (forces Prebid creatives when GAM has no line items) +export TRUSTED_SERVER__INTEGRATIONS__GAM__ENABLED=true +export TRUSTED_SERVER__INTEGRATIONS__GAM__BIDDERS="mocktioneer" +export TRUSTED_SERVER__INTEGRATIONS__GAM__FORCE_RENDER=false # Optional: Security Headers export TRUSTED_SERVER__RESPONSE_HEADERS__STRICT_TRANSPORT_SECURITY="max-age=31536000" diff --git a/docs/guide/error-reference.md b/docs/guide/error-reference.md index a7c9c43..18f9297 100644 --- a/docs/guide/error-reference.md +++ b/docs/guide/error-reference.md @@ -595,7 +595,7 @@ fastly log-tail fastly compute serve # Test endpoint -curl http://localhost:7676/first-party/ad?slot=test&w=300&h=250 +curl http://localhost:7676/ad/render?slot=test&w=300&h=250 ``` --- diff --git a/docs/guide/integration-guide.md b/docs/guide/integration-guide.md index 74f46cf..1790d24 100644 --- a/docs/guide/integration-guide.md +++ b/docs/guide/integration-guide.md @@ -210,6 +210,8 @@ impl IntegrationScriptRewriter for MyIntegration { `html_processor.rs` calls these hooks after applying the standard origin→first-party rewrite, so you can simply swap URLs, append query parameters, or mutate inline JSON. Use this to point `