From 4038336da1800da2765f16466a75560dccb7a6dc Mon Sep 17 00:00:00 2001 From: Christian Date: Mon, 9 Feb 2026 11:45:46 -0600 Subject: [PATCH 1/3] maybe permutive work --- crates/common/src/auction/formats.rs | 16 ++- .../common/src/integrations/adserver_mock.rs | 117 +++++++++++++++++- crates/js/lib/src/core/request.ts | 45 ++++++- 3 files changed, 174 insertions(+), 4 deletions(-) diff --git a/crates/common/src/auction/formats.rs b/crates/common/src/auction/formats.rs index 6b446f05..abb9c480 100644 --- a/crates/common/src/auction/formats.rs +++ b/crates/common/src/auction/formats.rs @@ -135,6 +135,20 @@ pub fn convert_tsjs_to_auction_request( geo: Some(geo), }); + // Extract optional Permutive segments from the request config + let mut context = HashMap::new(); + if let Some(ref config) = body.config { + if let Some(segments) = config.get("permutive_segments") { + if segments.is_array() { + log::info!( + "Auction request includes {} Permutive segments", + segments.as_array().map_or(0, Vec::len) + ); + context.insert("permutive_segments".to_string(), segments.clone()); + } + } + } + Ok(AuctionRequest { id: Uuid::new_v4().to_string(), slots, @@ -152,7 +166,7 @@ pub fn convert_tsjs_to_auction_request( domain: settings.publisher.domain.clone(), page: format!("https://{}", settings.publisher.domain), }), - context: HashMap::new(), + context, }) } diff --git a/crates/common/src/integrations/adserver_mock.rs b/crates/common/src/integrations/adserver_mock.rs index b84625e5..729bc4f1 100644 --- a/crates/common/src/integrations/adserver_mock.rs +++ b/crates/common/src/integrations/adserver_mock.rs @@ -85,6 +85,40 @@ impl AdServerMockProvider { Self { config } } + /// Build the mediation endpoint URL, appending Permutive segments as a query + /// string parameter when present in the auction request context. + /// + /// For example, if segments `[10000001, 10000003]` are present, the URL + /// becomes `https://…/adserver/mediate?permutive=10000001,10000003`. + fn build_endpoint_url(&self, request: &AuctionRequest) -> String { + if let Some(segments_val) = request.context.get("permutive_segments") { + if let Some(segments) = segments_val.as_array() { + let csv: String = segments + .iter() + .filter_map(|v| v.as_u64().or_else(|| v.as_f64().map(|f| f as u64))) + .map(|n| n.to_string()) + .collect::>() + .join(","); + + if !csv.is_empty() { + // Append as query parameter, respecting existing query strings + let sep = if self.config.endpoint.contains('?') { + "&" + } else { + "?" + }; + let url = format!("{}{}permutive={}", self.config.endpoint, sep, csv); + log::info!( + "AdServer Mock: appending {} Permutive segments to mediation URL", + segments.len() + ); + return url; + } + } + } + self.config.endpoint.clone() + } + /// Build mediation request from auction request and bidder responses. /// /// Handles both: @@ -256,8 +290,11 @@ impl AuctionProvider for AdServerMockProvider { log::debug!("AdServer Mock: mediation request: {:?}", mediation_req); + // Build endpoint URL with optional Permutive segments query string + let endpoint_url = self.build_endpoint_url(request); + // Create HTTP POST request - let mut req = Request::new(Method::POST, &self.config.endpoint); + let mut req = Request::new(Method::POST, &endpoint_url); // Set Host header with port to ensure mocktioneer generates correct iframe URLs if let Ok(url) = url::Url::parse(&self.config.endpoint) { @@ -713,4 +750,82 @@ mod tests { "Bid without price field should have None price" ); } + + #[test] + fn test_build_endpoint_url_with_permutive_segments() { + let config = AdServerMockConfig { + enabled: true, + endpoint: "http://localhost:6767/adserver/mediate".to_string(), + timeout_ms: 500, + price_floor: None, + }; + let provider = AdServerMockProvider::new(config); + + let mut request = create_test_auction_request(); + request.context.insert( + "permutive_segments".to_string(), + json!([10000001, 10000003, 10000008]), + ); + + let url = provider.build_endpoint_url(&request); + assert_eq!( + url, + "http://localhost:6767/adserver/mediate?permutive=10000001,10000003,10000008" + ); + } + + #[test] + fn test_build_endpoint_url_without_segments() { + let config = AdServerMockConfig { + enabled: true, + endpoint: "http://localhost:6767/adserver/mediate".to_string(), + timeout_ms: 500, + price_floor: None, + }; + let provider = AdServerMockProvider::new(config); + + let request = create_test_auction_request(); + let url = provider.build_endpoint_url(&request); + assert_eq!(url, "http://localhost:6767/adserver/mediate"); + } + + #[test] + fn test_build_endpoint_url_with_empty_segments() { + let config = AdServerMockConfig::default(); + let provider = AdServerMockProvider::new(config); + + let mut request = create_test_auction_request(); + request + .context + .insert("permutive_segments".to_string(), json!([])); + + let url = provider.build_endpoint_url(&request); + // Empty segments array should NOT append query param + assert!( + !url.contains("permutive="), + "Empty segments should not add query param" + ); + } + + #[test] + fn test_build_endpoint_url_preserves_existing_query_params() { + let config = AdServerMockConfig { + enabled: true, + endpoint: "http://localhost:6767/adserver/mediate?debug=true".to_string(), + timeout_ms: 500, + price_floor: None, + }; + let provider = AdServerMockProvider::new(config); + + let mut request = create_test_auction_request(); + request + .context + .insert("permutive_segments".to_string(), json!([123, 456])); + + let url = provider.build_endpoint_url(&request); + assert_eq!( + url, + "http://localhost:6767/adserver/mediate?debug=true&permutive=123,456" + ); + } } diff --git a/crates/js/lib/src/core/request.ts b/crates/js/lib/src/core/request.ts index a84af082..0dbb6244 100644 --- a/crates/js/lib/src/core/request.ts +++ b/crates/js/lib/src/core/request.ts @@ -6,6 +6,42 @@ import type { RequestAdsCallback, RequestAdsOptions } from './types'; // getHighestCpmBids is provided by the Prebid extension (shim) to mirror Prebid's API +/** + * Read Permutive segment IDs from localStorage. + * + * Permutive stores event data in the `permutive-app` key. Each event entry in + * `eventPublication.eventUpload` is a tuple of [eventKey, eventObject]. We + * iterate in reverse (most recent first) looking for any event whose + * `properties.segments` is a non-empty array. + * + * Returns an array of segment ID numbers, or an empty array if unavailable. + */ +function getPermutiveSegments(): number[] { + try { + const raw = localStorage.getItem('permutive-app'); + if (!raw) return []; + + const data = JSON.parse(raw); + const uploads: unknown[] = data?.eventPublication?.eventUpload; + if (!Array.isArray(uploads) || uploads.length === 0) return []; + + // Iterate most-recent-first to get the freshest segments + for (let i = uploads.length - 1; i >= 0; i--) { + const entry = uploads[i]; + if (!Array.isArray(entry) || entry.length < 2) continue; + + const segments = entry[1]?.event?.properties?.segments; + if (Array.isArray(segments) && segments.length > 0) { + log.debug('getPermutiveSegments: found segments', { count: segments.length }); + return segments.filter((s: unknown) => typeof s === 'number') as number[]; + } + } + } catch { + log.debug('getPermutiveSegments: failed to read from localStorage'); + } + return []; +} + // Entry point matching Prebid's requestBids signature; uses unified /auction endpoint. export function requestAds( callbackOrOpts?: RequestAdsCallback | RequestAdsOptions, @@ -24,8 +60,13 @@ export function requestAds( log.info('requestAds: called', { hasCallback: typeof callback === 'function' }); try { const adUnits = getAllUnits(); - const payload = { adUnits, config: {} }; - log.debug('requestAds: payload', { units: adUnits.length }); + const permutiveSegments = getPermutiveSegments(); + const config: Record = {}; + if (permutiveSegments.length > 0) { + config.permutive_segments = permutiveSegments; + } + const payload = { adUnits, config }; + log.debug('requestAds: payload', { units: adUnits.length, permutiveSegments: permutiveSegments.length }); // Use unified auction endpoint void requestAdsUnified(payload); From 801b51ddc3784d4cd04de0f0e55fbf346fb0638d Mon Sep 17 00:00:00 2001 From: Christian Date: Mon, 9 Feb 2026 13:17:40 -0600 Subject: [PATCH 2/3] refactors --- crates/common/src/auction/formats.rs | 19 ++-- .../common/src/integrations/adserver_mock.rs | 15 +-- crates/js/lib/src/core/context.ts | 41 ++++++++ crates/js/lib/src/core/request.ts | 47 +-------- .../lib/src/integrations/permutive/index.ts | 10 ++ .../src/integrations/permutive/segments.ts | 57 +++++++++++ crates/js/lib/test/core/context.test.ts | 48 ++++++++++ .../integrations/permutive/segments.test.ts | 95 +++++++++++++++++++ 8 files changed, 275 insertions(+), 57 deletions(-) create mode 100644 crates/js/lib/src/core/context.ts create mode 100644 crates/js/lib/src/integrations/permutive/segments.ts create mode 100644 crates/js/lib/test/core/context.test.ts create mode 100644 crates/js/lib/test/integrations/permutive/segments.test.ts diff --git a/crates/common/src/auction/formats.rs b/crates/common/src/auction/formats.rs index abb9c480..3815cbd6 100644 --- a/crates/common/src/auction/formats.rs +++ b/crates/common/src/auction/formats.rs @@ -30,7 +30,6 @@ use super::types::{ #[serde(rename_all = "camelCase")] pub struct AdRequest { pub ad_units: Vec, - #[allow(dead_code)] pub config: Option, } @@ -135,16 +134,22 @@ pub fn convert_tsjs_to_auction_request( geo: Some(geo), }); - // Extract optional Permutive segments from the request config + // Forward all config entries from the JS request into the context map. + // Each integration's context provider contributes its own keys (e.g. + // permutive_segments, lockr_ids, …) — we pass them all through so + // auction providers can read whatever they need. let mut context = HashMap::new(); if let Some(ref config) = body.config { - if let Some(segments) = config.get("permutive_segments") { - if segments.is_array() { + if let Some(obj) = config.as_object() { + for (key, value) in obj { + context.insert(key.clone(), value.clone()); + } + if !context.is_empty() { log::info!( - "Auction request includes {} Permutive segments", - segments.as_array().map_or(0, Vec::len) + "Auction request context: {} entries ({})", + context.len(), + context.keys().cloned().collect::>().join(", ") ); - context.insert("permutive_segments".to_string(), segments.clone()); } } } diff --git a/crates/common/src/integrations/adserver_mock.rs b/crates/common/src/integrations/adserver_mock.rs index 729bc4f1..a088afb7 100644 --- a/crates/common/src/integrations/adserver_mock.rs +++ b/crates/common/src/integrations/adserver_mock.rs @@ -95,8 +95,11 @@ impl AdServerMockProvider { if let Some(segments) = segments_val.as_array() { let csv: String = segments .iter() - .filter_map(|v| v.as_u64().or_else(|| v.as_f64().map(|f| f as u64))) - .map(|n| n.to_string()) + .filter_map(|v| { + v.as_str() + .map(String::from) + .or_else(|| v.as_u64().map(|n| n.to_string())) + }) .collect::>() .join(","); @@ -764,13 +767,13 @@ mod tests { let mut request = create_test_auction_request(); request.context.insert( "permutive_segments".to_string(), - json!([10000001, 10000003, 10000008]), + json!(["10000001", "10000003", "adv", "bhgp"]), ); let url = provider.build_endpoint_url(&request); assert_eq!( url, - "http://localhost:6767/adserver/mediate?permutive=10000001,10000003,10000008" + "http://localhost:6767/adserver/mediate?permutive=10000001,10000003,adv,bhgp" ); } @@ -820,12 +823,12 @@ mod tests { let mut request = create_test_auction_request(); request .context - .insert("permutive_segments".to_string(), json!([123, 456])); + .insert("permutive_segments".to_string(), json!(["123", "adv"])); let url = provider.build_endpoint_url(&request); assert_eq!( url, - "http://localhost:6767/adserver/mediate?debug=true&permutive=123,456" + "http://localhost:6767/adserver/mediate?debug=true&permutive=123,adv" ); } } diff --git a/crates/js/lib/src/core/context.ts b/crates/js/lib/src/core/context.ts new file mode 100644 index 00000000..49e9c012 --- /dev/null +++ b/crates/js/lib/src/core/context.ts @@ -0,0 +1,41 @@ +// Context provider registry: lets integrations contribute data to auction requests +// without core needing integration-specific knowledge. +import { log } from './log'; + +/** + * A context provider returns key-value pairs to merge into the auction + * request's `config` payload, or `undefined` to contribute nothing. + */ +export type ContextProvider = () => Record | undefined; + +const providers: ContextProvider[] = []; + +/** + * Register a context provider that will be called before every auction request. + * Integrations call this at import time to inject their data (e.g. segments, + * identifiers) into the auction payload without core needing to know about them. + */ +export function registerContextProvider(provider: ContextProvider): void { + providers.push(provider); + log.debug('context: registered provider', { total: providers.length }); +} + +/** + * Collect context from all registered providers. Called by core's `requestAds` + * to build the `config` object sent to `/auction`. + * + * Each provider's returned keys are merged (later providers win on collision). + * Providers that throw or return `undefined` are silently skipped. + */ +export function collectContext(): Record { + const context: Record = {}; + for (const provider of providers) { + try { + const data = provider(); + if (data) Object.assign(context, data); + } catch { + log.debug('context: provider threw, skipping'); + } + } + return context; +} diff --git a/crates/js/lib/src/core/request.ts b/crates/js/lib/src/core/request.ts index 0dbb6244..1d5e4e08 100644 --- a/crates/js/lib/src/core/request.ts +++ b/crates/js/lib/src/core/request.ts @@ -1,47 +1,10 @@ // Request orchestration for tsjs: unified auction endpoint with iframe-based creative rendering. import { log } from './log'; +import { collectContext } from './context'; import { getAllUnits, firstSize } from './registry'; import { createAdIframe, findSlot, buildCreativeDocument } from './render'; import type { RequestAdsCallback, RequestAdsOptions } from './types'; -// getHighestCpmBids is provided by the Prebid extension (shim) to mirror Prebid's API - -/** - * Read Permutive segment IDs from localStorage. - * - * Permutive stores event data in the `permutive-app` key. Each event entry in - * `eventPublication.eventUpload` is a tuple of [eventKey, eventObject]. We - * iterate in reverse (most recent first) looking for any event whose - * `properties.segments` is a non-empty array. - * - * Returns an array of segment ID numbers, or an empty array if unavailable. - */ -function getPermutiveSegments(): number[] { - try { - const raw = localStorage.getItem('permutive-app'); - if (!raw) return []; - - const data = JSON.parse(raw); - const uploads: unknown[] = data?.eventPublication?.eventUpload; - if (!Array.isArray(uploads) || uploads.length === 0) return []; - - // Iterate most-recent-first to get the freshest segments - for (let i = uploads.length - 1; i >= 0; i--) { - const entry = uploads[i]; - if (!Array.isArray(entry) || entry.length < 2) continue; - - const segments = entry[1]?.event?.properties?.segments; - if (Array.isArray(segments) && segments.length > 0) { - log.debug('getPermutiveSegments: found segments', { count: segments.length }); - return segments.filter((s: unknown) => typeof s === 'number') as number[]; - } - } - } catch { - log.debug('getPermutiveSegments: failed to read from localStorage'); - } - return []; -} - // Entry point matching Prebid's requestBids signature; uses unified /auction endpoint. export function requestAds( callbackOrOpts?: RequestAdsCallback | RequestAdsOptions, @@ -60,13 +23,9 @@ export function requestAds( log.info('requestAds: called', { hasCallback: typeof callback === 'function' }); try { const adUnits = getAllUnits(); - const permutiveSegments = getPermutiveSegments(); - const config: Record = {}; - if (permutiveSegments.length > 0) { - config.permutive_segments = permutiveSegments; - } + const config = collectContext(); const payload = { adUnits, config }; - log.debug('requestAds: payload', { units: adUnits.length, permutiveSegments: permutiveSegments.length }); + log.debug('requestAds: payload', { units: adUnits.length, contextKeys: Object.keys(config) }); // Use unified auction endpoint void requestAdsUnified(payload); diff --git a/crates/js/lib/src/integrations/permutive/index.ts b/crates/js/lib/src/integrations/permutive/index.ts index 84ce148a..e3f56269 100644 --- a/crates/js/lib/src/integrations/permutive/index.ts +++ b/crates/js/lib/src/integrations/permutive/index.ts @@ -1,6 +1,8 @@ import { log } from '../../core/log'; +import { registerContextProvider } from '../../core/context'; import { installPermutiveGuard } from './script_guard'; +import { getPermutiveSegments } from './segments'; declare const permutive: { config: { @@ -100,5 +102,13 @@ function waitForPermutiveSDK(callback: () => void, maxAttempts = 50) { if (typeof window !== 'undefined') { installPermutiveGuard(); + // Register a context provider so Permutive segments are included in auction + // requests. Core calls collectContext() before every /auction POST — this + // keeps all Permutive localStorage knowledge inside this integration. + registerContextProvider(() => { + const segments = getPermutiveSegments(); + return segments.length > 0 ? { permutive_segments: segments } : undefined; + }); + waitForPermutiveSDK(() => installPermutiveShim()); } diff --git a/crates/js/lib/src/integrations/permutive/segments.ts b/crates/js/lib/src/integrations/permutive/segments.ts new file mode 100644 index 00000000..a24d8e6c --- /dev/null +++ b/crates/js/lib/src/integrations/permutive/segments.ts @@ -0,0 +1,57 @@ +// Permutive segment extraction from localStorage. +// This logic is owned by the Permutive integration, keeping core free of +// integration-specific data-reading code. +import { log } from '../../core/log'; + +/** + * Read Permutive segment IDs from localStorage. + * + * Permutive stores cohort data in the `permutive-app` key. We check two + * locations (most reliable first): + * + * 1. `core.cohorts.all` — full cohort membership (numeric IDs + activation keys). + * 2. `eventPublication.eventUpload` — transient event data; we iterate + * most-recent-first looking for any event whose `properties.segments` is a + * non-empty array. + * + * Returns an array of segment ID strings, or an empty array if unavailable. + */ +export function getPermutiveSegments(): string[] { + try { + const raw = localStorage.getItem('permutive-app'); + if (!raw) return []; + + const data = JSON.parse(raw); + + // Primary: core.cohorts.all (full cohort membership — numeric IDs + activation keys) + const all = data?.core?.cohorts?.all; + if (Array.isArray(all) && all.length > 0) { + log.debug('getPermutiveSegments: found segments in core.cohorts.all', { count: all.length }); + return all + .filter((s: unknown) => typeof s === 'string' || typeof s === 'number') + .map(String); + } + + // Fallback: eventUpload entries (transient event data) + const uploads: unknown[] = data?.eventPublication?.eventUpload; + if (Array.isArray(uploads)) { + for (let i = uploads.length - 1; i >= 0; i--) { + const entry = uploads[i]; + if (!Array.isArray(entry) || entry.length < 2) continue; + + const segments = entry[1]?.event?.properties?.segments; + if (Array.isArray(segments) && segments.length > 0) { + log.debug('getPermutiveSegments: found segments in eventUpload', { + count: segments.length, + }); + return segments + .filter((s: unknown) => typeof s === 'string' || typeof s === 'number') + .map(String); + } + } + } + } catch { + log.debug('getPermutiveSegments: failed to read from localStorage'); + } + return []; +} diff --git a/crates/js/lib/test/core/context.test.ts b/crates/js/lib/test/core/context.test.ts new file mode 100644 index 00000000..950d9e01 --- /dev/null +++ b/crates/js/lib/test/core/context.test.ts @@ -0,0 +1,48 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest'; + +describe('context provider registry', () => { + beforeEach(async () => { + await vi.resetModules(); + }); + + it('returns empty context when no providers registered', async () => { + const { collectContext } = await import('../../src/core/context'); + expect(collectContext()).toEqual({}); + }); + + it('collects data from a single provider', async () => { + const { registerContextProvider, collectContext } = await import('../../src/core/context'); + registerContextProvider(() => ({ foo: 'bar' })); + expect(collectContext()).toEqual({ foo: 'bar' }); + }); + + it('merges data from multiple providers', async () => { + const { registerContextProvider, collectContext } = await import('../../src/core/context'); + registerContextProvider(() => ({ a: 1 })); + registerContextProvider(() => ({ b: 2 })); + expect(collectContext()).toEqual({ a: 1, b: 2 }); + }); + + it('later providers overwrite earlier ones on key collision', async () => { + const { registerContextProvider, collectContext } = await import('../../src/core/context'); + registerContextProvider(() => ({ key: 'first' })); + registerContextProvider(() => ({ key: 'second' })); + expect(collectContext()).toEqual({ key: 'second' }); + }); + + it('skips providers that return undefined', async () => { + const { registerContextProvider, collectContext } = await import('../../src/core/context'); + registerContextProvider(() => undefined); + registerContextProvider(() => ({ kept: true })); + expect(collectContext()).toEqual({ kept: true }); + }); + + it('skips providers that throw', async () => { + const { registerContextProvider, collectContext } = await import('../../src/core/context'); + registerContextProvider(() => { + throw new Error('boom'); + }); + registerContextProvider(() => ({ survived: true })); + expect(collectContext()).toEqual({ survived: true }); + }); +}); diff --git a/crates/js/lib/test/integrations/permutive/segments.test.ts b/crates/js/lib/test/integrations/permutive/segments.test.ts new file mode 100644 index 00000000..c1723541 --- /dev/null +++ b/crates/js/lib/test/integrations/permutive/segments.test.ts @@ -0,0 +1,95 @@ +import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'; + +describe('getPermutiveSegments', () => { + let getPermutiveSegments: () => string[]; + + beforeEach(async () => { + await vi.resetModules(); + localStorage.clear(); + const mod = await import('../../../src/integrations/permutive/segments'); + getPermutiveSegments = mod.getPermutiveSegments; + }); + + afterEach(() => { + localStorage.clear(); + }); + + it('returns empty array when no permutive-app in localStorage', () => { + expect(getPermutiveSegments()).toEqual([]); + }); + + it('returns empty array when permutive-app is invalid JSON', () => { + localStorage.setItem('permutive-app', 'not-json'); + expect(getPermutiveSegments()).toEqual([]); + }); + + it('reads segments from core.cohorts.all (primary path)', () => { + localStorage.setItem( + 'permutive-app', + JSON.stringify({ + core: { cohorts: { all: ['10000001', '10000003', 'adv', 'bhgp'] } }, + }), + ); + expect(getPermutiveSegments()).toEqual(['10000001', '10000003', 'adv', 'bhgp']); + }); + + it('converts numeric cohort IDs to strings', () => { + localStorage.setItem( + 'permutive-app', + JSON.stringify({ + core: { cohorts: { all: [123, 456] } }, + }), + ); + expect(getPermutiveSegments()).toEqual(['123', '456']); + }); + + it('falls back to eventUpload when cohorts.all is missing', () => { + localStorage.setItem( + 'permutive-app', + JSON.stringify({ + eventPublication: { + eventUpload: [ + ['key1', { event: { properties: { segments: ['seg1', 'seg2'] } } }], + ], + }, + }), + ); + expect(getPermutiveSegments()).toEqual(['seg1', 'seg2']); + }); + + it('reads most recent eventUpload entry first', () => { + localStorage.setItem( + 'permutive-app', + JSON.stringify({ + eventPublication: { + eventUpload: [ + ['old', { event: { properties: { segments: ['old1'] } } }], + ['new', { event: { properties: { segments: ['new1', 'new2'] } } }], + ], + }, + }), + ); + // Should return the last (most recent) entry + expect(getPermutiveSegments()).toEqual(['new1', 'new2']); + }); + + it('returns empty array when cohorts.all is empty and no eventUpload', () => { + localStorage.setItem( + 'permutive-app', + JSON.stringify({ + core: { cohorts: { all: [] } }, + }), + ); + expect(getPermutiveSegments()).toEqual([]); + }); + + it('filters out non-string non-number values', () => { + localStorage.setItem( + 'permutive-app', + JSON.stringify({ + core: { cohorts: { all: ['valid', 123, null, undefined, true, { obj: 1 }] } }, + }), + ); + expect(getPermutiveSegments()).toEqual(['valid', '123']); + }); +}); From 9d56491454d19c8b63119a37e87a42839bfa9448 Mon Sep 17 00:00:00 2001 From: Christian Date: Wed, 11 Feb 2026 09:42:24 -0600 Subject: [PATCH 3/3] typescript format --- .../lib/src/integrations/permutive/segments.ts | 4 +--- .../test/integrations/permutive/segments.test.ts | 16 +++++++--------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/crates/js/lib/src/integrations/permutive/segments.ts b/crates/js/lib/src/integrations/permutive/segments.ts index a24d8e6c..be404f00 100644 --- a/crates/js/lib/src/integrations/permutive/segments.ts +++ b/crates/js/lib/src/integrations/permutive/segments.ts @@ -27,9 +27,7 @@ export function getPermutiveSegments(): string[] { const all = data?.core?.cohorts?.all; if (Array.isArray(all) && all.length > 0) { log.debug('getPermutiveSegments: found segments in core.cohorts.all', { count: all.length }); - return all - .filter((s: unknown) => typeof s === 'string' || typeof s === 'number') - .map(String); + return all.filter((s: unknown) => typeof s === 'string' || typeof s === 'number').map(String); } // Fallback: eventUpload entries (transient event data) diff --git a/crates/js/lib/test/integrations/permutive/segments.test.ts b/crates/js/lib/test/integrations/permutive/segments.test.ts index c1723541..47acfb9c 100644 --- a/crates/js/lib/test/integrations/permutive/segments.test.ts +++ b/crates/js/lib/test/integrations/permutive/segments.test.ts @@ -28,7 +28,7 @@ describe('getPermutiveSegments', () => { 'permutive-app', JSON.stringify({ core: { cohorts: { all: ['10000001', '10000003', 'adv', 'bhgp'] } }, - }), + }) ); expect(getPermutiveSegments()).toEqual(['10000001', '10000003', 'adv', 'bhgp']); }); @@ -38,7 +38,7 @@ describe('getPermutiveSegments', () => { 'permutive-app', JSON.stringify({ core: { cohorts: { all: [123, 456] } }, - }), + }) ); expect(getPermutiveSegments()).toEqual(['123', '456']); }); @@ -48,11 +48,9 @@ describe('getPermutiveSegments', () => { 'permutive-app', JSON.stringify({ eventPublication: { - eventUpload: [ - ['key1', { event: { properties: { segments: ['seg1', 'seg2'] } } }], - ], + eventUpload: [['key1', { event: { properties: { segments: ['seg1', 'seg2'] } } }]], }, - }), + }) ); expect(getPermutiveSegments()).toEqual(['seg1', 'seg2']); }); @@ -67,7 +65,7 @@ describe('getPermutiveSegments', () => { ['new', { event: { properties: { segments: ['new1', 'new2'] } } }], ], }, - }), + }) ); // Should return the last (most recent) entry expect(getPermutiveSegments()).toEqual(['new1', 'new2']); @@ -78,7 +76,7 @@ describe('getPermutiveSegments', () => { 'permutive-app', JSON.stringify({ core: { cohorts: { all: [] } }, - }), + }) ); expect(getPermutiveSegments()).toEqual([]); }); @@ -88,7 +86,7 @@ describe('getPermutiveSegments', () => { 'permutive-app', JSON.stringify({ core: { cohorts: { all: ['valid', 123, null, undefined, true, { obj: 1 }] } }, - }), + }) ); expect(getPermutiveSegments()).toEqual(['valid', '123']); });