@layers/client 0.1.1-alpha.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"src-SyPoXjEy.js","names":["params: URLSearchParams","clickIdParam: string | undefined","clickIdValue: string | undefined","utms: Partial<Pick<AttributionData, (typeof UTM_PARAMS)[number]>>","data: AttributionData","props: Record<string, string>","context: DeviceContext","SDK_VERSION: string"],"sources":["../src/attribution.ts","../src/index.ts"],"sourcesContent":["const CLICK_ID_PARAMS = [\n 'fbclid',\n 'gclid',\n 'gbraid',\n 'wbraid',\n 'ttclid',\n 'msclkid',\n 'rclid'\n] as const;\nconst UTM_PARAMS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term'] as const;\nconst STORAGE_KEY = 'layers_attribution';\nconst TTL_MS = 30 * 24 * 60 * 60 * 1000; // 30 days\n\nexport interface AttributionData {\n click_id_param?: string;\n click_id_value?: string;\n utm_source?: string;\n utm_medium?: string;\n utm_campaign?: string;\n utm_content?: string;\n utm_term?: string;\n referrer_url?: string;\n captured_at: number;\n}\n\n/**\n * Capture attribution signals from the current page URL and referrer.\n * Persists to localStorage with a 30-day TTL. Click IDs take priority:\n * if a new click ID is present, the entire record is overwritten.\n */\nexport function captureAttribution(): void {\n if (typeof window === 'undefined' || typeof localStorage === 'undefined') return;\n\n let params: URLSearchParams;\n try {\n params = new URLSearchParams(window.location.search);\n } catch {\n return;\n }\n\n // Check for click IDs first — any click ID overwrites the stored record\n let clickIdParam: string | undefined;\n let clickIdValue: string | undefined;\n for (const param of CLICK_ID_PARAMS) {\n const value = params.get(param);\n if (value) {\n clickIdParam = param;\n clickIdValue = value;\n break; // first match wins\n }\n }\n\n // Collect UTM params\n const utms: Partial<Pick<AttributionData, (typeof UTM_PARAMS)[number]>> = {};\n let hasUtm = false;\n for (const param of UTM_PARAMS) {\n const value = params.get(param);\n if (value) {\n utms[param] = value;\n hasUtm = true;\n }\n }\n\n const referrer =\n typeof document !== 'undefined' && document.referrer ? document.referrer : undefined;\n\n // Nothing to capture\n if (!clickIdParam && !hasUtm && !referrer) return;\n\n const existing = getAttribution();\n\n // If a new click ID is present, overwrite entirely\n if (clickIdParam) {\n const data: AttributionData = {\n click_id_param: clickIdParam,\n ...(clickIdValue != null && { click_id_value: clickIdValue }),\n ...utms,\n ...(referrer != null && { referrer_url: referrer }),\n captured_at: Date.now()\n };\n writeAttribution(data);\n return;\n }\n\n // If UTMs are present, overwrite (fresh campaign visit)\n if (hasUtm) {\n const data: AttributionData = {\n // Preserve existing click ID if no new one\n ...(existing?.click_id_param != null && { click_id_param: existing.click_id_param }),\n ...(existing?.click_id_value != null && { click_id_value: existing.click_id_value }),\n ...utms,\n ...(referrer != null && { referrer_url: referrer }),\n captured_at: Date.now()\n };\n writeAttribution(data);\n return;\n }\n\n // Only referrer and no existing attribution — store it\n if (!existing) {\n const data: AttributionData = {\n ...(referrer != null && { referrer_url: referrer }),\n captured_at: Date.now()\n };\n writeAttribution(data);\n }\n}\n\n/**\n * Read stored attribution data, returning null if missing or expired.\n */\nexport function getAttribution(): AttributionData | null {\n if (typeof localStorage === 'undefined') return null;\n\n try {\n const raw = localStorage.getItem(STORAGE_KEY);\n if (!raw) return null;\n\n const data: AttributionData = JSON.parse(raw);\n if (Date.now() - data.captured_at > TTL_MS) {\n localStorage.removeItem(STORAGE_KEY);\n return null;\n }\n return data;\n } catch {\n return null;\n }\n}\n\n/**\n * Return a flat property bag suitable for merging into event properties.\n * Keys are prefixed with `$attribution_` to avoid collisions.\n */\nexport function getAttributionProperties(): Record<string, string> {\n const data = getAttribution();\n if (!data) return {};\n\n const props: Record<string, string> = {};\n\n if (data.click_id_param) props['$attribution_click_id_param'] = data.click_id_param;\n if (data.click_id_value) props['$attribution_click_id_value'] = data.click_id_value;\n if (data.utm_source) props['$attribution_utm_source'] = data.utm_source;\n if (data.utm_medium) props['$attribution_utm_medium'] = data.utm_medium;\n if (data.utm_campaign) props['$attribution_utm_campaign'] = data.utm_campaign;\n if (data.utm_content) props['$attribution_utm_content'] = data.utm_content;\n if (data.utm_term) props['$attribution_utm_term'] = data.utm_term;\n if (data.referrer_url) props['$attribution_referrer_url'] = data.referrer_url;\n\n return props;\n}\n\nfunction writeAttribution(data: AttributionData): void {\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(data));\n } catch {\n // localStorage full or unavailable — silently ignore\n }\n}\n","// @layers/client — Web browser SDK.\n// Thin wrapper over @layers/core-wasm. Owns only:\n// - localStorage persistence\n// - window online/offline detection\n// - navigator device info\n// - visibilitychange / beforeunload flush\nimport {\n FetchHttpClient,\n LayersCore,\n LayersError,\n createDefaultPersistence\n} from '@layers/core-wasm';\nimport type {\n ConsentState,\n DeviceContext,\n Environment,\n EventProperties,\n UserProperties\n} from '@layers/core-wasm';\n\nimport { captureAttribution, getAttributionProperties } from './attribution.js';\n\nexport type {\n ConsentState,\n DeviceContext,\n Environment,\n EventProperties,\n UserProperties\n} from '@layers/core-wasm';\n\nexport { LayersError } from '@layers/core-wasm';\n\nexport * from './api-types.js';\nexport type { AttributionData } from './attribution.js';\nexport { getAttribution, getAttributionProperties } from './attribution.js';\n\nexport interface LayersConfig {\n /** API key used to authenticate requests to the Layers ingest endpoint. */\n apiKey: string;\n /** Unique application identifier issued by the Layers dashboard. */\n appId: string;\n /** Deployment environment label. @default \"production\" */\n environment: Environment;\n /** Optional user identifier to associate events with from the start. */\n appUserId?: string;\n /** Enable verbose debug logging to the console. @default false */\n enableDebug?: boolean;\n /** Base URL for the Layers ingest API. @default \"https://in.layers.com\" */\n baseUrl?: string;\n /** How often the event queue is flushed, in milliseconds. @default 30000 */\n flushIntervalMs?: number;\n /** Number of queued events that triggers an automatic flush. @default 10 */\n flushThreshold?: number;\n /** Maximum number of events to hold in the queue before dropping. @default 1000 */\n maxQueueSize?: number;\n}\n\nexport type ErrorListener = (error: Error) => void;\n\nexport class LayersClient {\n private core: LayersCore;\n private appUserId: string | undefined;\n private isOnline = true;\n private readonly enableDebug: boolean;\n private readonly baseUrl: string;\n\n // Stored listener references for cleanup on shutdown\n private onlineListener: (() => void) | null = null;\n private offlineListener: (() => void) | null = null;\n private visibilityListener: (() => void) | null = null;\n private beforeUnloadListener: (() => void) | null = null;\n\n // Error listeners\n private readonly errorListeners: Set<ErrorListener> = new Set();\n\n constructor(config: LayersConfig) {\n this.enableDebug = config.enableDebug ?? false;\n this.baseUrl = (config.baseUrl ?? 'https://in.layers.com').replace(/\\/$/, '');\n this.appUserId = config.appUserId;\n\n const persistence = createDefaultPersistence(config.appId);\n const httpClient = new FetchHttpClient();\n\n this.core = LayersCore.init({\n config: {\n apiKey: config.apiKey,\n appId: config.appId,\n environment: config.environment,\n ...(config.baseUrl != null && { baseUrl: config.baseUrl }),\n ...(config.enableDebug != null && { enableDebug: config.enableDebug }),\n ...(config.flushIntervalMs != null && { flushIntervalMs: config.flushIntervalMs }),\n ...(config.flushThreshold != null && { flushThreshold: config.flushThreshold }),\n ...(config.maxQueueSize != null && { maxQueueSize: config.maxQueueSize }),\n sdkVersion: `client/${SDK_VERSION}`\n },\n httpClient,\n persistence\n });\n\n if (this.appUserId) {\n this.core.identify(this.appUserId);\n }\n }\n\n /** Initialize the client: detects device info, attaches lifecycle listeners, and fetches remote config. */\n async init(): Promise<void> {\n this.initializeDeviceInfo();\n this.setupNetworkListener();\n this.setupLifecycleListeners();\n captureAttribution();\n await this.core.fetchRemoteConfig().catch(() => {\n // Remote config fetch is best-effort\n });\n }\n\n /**\n * Record a custom analytics event with an optional property bag.\n *\n * Events are batched and flushed automatically when the queue reaches\n * `flushThreshold` or the periodic flush timer fires.\n */\n track(eventName: string, properties?: EventProperties): void {\n if (this.enableDebug) {\n console.log(\n `[Layers] track(\"${eventName}\", ${Object.keys(properties ?? {}).length} properties)`\n );\n }\n try {\n const merged = { ...getAttributionProperties(), ...properties };\n this.core.track(eventName, merged, this.appUserId);\n } catch (e) {\n this.emitError(e);\n }\n }\n\n /**\n * Record a screen view event with an optional property bag.\n *\n * Events are batched and flushed automatically when the queue reaches\n * `flushThreshold` or the periodic flush timer fires.\n */\n screen(screenName: string, properties?: EventProperties): void {\n if (this.enableDebug) {\n console.log(\n `[Layers] screen(\"${screenName}\", ${Object.keys(properties ?? {}).length} properties)`\n );\n }\n try {\n const merged = { ...getAttributionProperties(), ...properties };\n this.core.screen(screenName, merged, this.appUserId);\n } catch (e) {\n this.emitError(e);\n }\n }\n\n /** Set or update user-level properties that persist across sessions. */\n setUserProperties(properties: UserProperties): void {\n this.core.setUserProperties(properties);\n }\n\n /** Update the user's consent state for analytics and advertising data collection. */\n setConsent(consent: ConsentState): void {\n this.core.setConsent(consent);\n }\n\n /** Associate all subsequent events with the given user ID, or clear it with `undefined`. */\n setAppUserId(appUserId: string | undefined): void {\n if (this.enableDebug) {\n console.log(`[Layers] setAppUserId(${appUserId ? `\"${appUserId}\"` : 'undefined'})`);\n }\n this.appUserId = appUserId;\n if (appUserId) {\n this.core.identify(appUserId);\n } else {\n this.core.identify('');\n }\n }\n\n /** @deprecated Use setAppUserId instead */\n setUserId(userId: string): void {\n this.setAppUserId(userId);\n }\n\n /** Return the current app user ID, or `undefined` if not set. */\n getAppUserId(): string | undefined {\n return this.appUserId;\n }\n\n /** @deprecated Use getAppUserId instead */\n getUserId(): string | undefined {\n return this.appUserId;\n }\n\n /** Return the current anonymous session ID. */\n getSessionId(): string {\n return this.core.getSessionId();\n }\n\n /** Return the current consent state for analytics and advertising. */\n getConsentState(): ConsentState {\n return this.core.getConsentState();\n }\n\n /** Override device-level context fields (platform, OS, locale, etc.). */\n setDeviceInfo(deviceInfo: DeviceContext): void {\n this.core.setDeviceContext(deviceInfo);\n }\n\n /** Flush all queued events to the server. Falls back to synchronous persistence on failure. */\n async flush(): Promise<void> {\n try {\n await this.core.flushAsync();\n } catch (e) {\n this.emitError(e);\n this.core.flush();\n }\n }\n\n /** Immediately shut down the client, removing all event listeners. Queued events are persisted but not flushed. */\n shutdown(): void {\n this.cleanupListeners();\n this.core.shutdown();\n }\n\n /**\n * Async shutdown: flushes remaining events before shutting down.\n * @param timeoutMs Maximum time to wait for flush (default 3000ms).\n */\n async shutdownAsync(timeoutMs = 3000): Promise<void> {\n this.cleanupListeners();\n try {\n await Promise.race([\n this.core.flushAsync(),\n new Promise<void>((resolve) => setTimeout(resolve, timeoutMs))\n ]);\n } catch {\n // Best effort — proceed with shutdown\n }\n this.core.shutdown();\n }\n\n private cleanupListeners(): void {\n if (typeof window !== 'undefined') {\n if (this.onlineListener) {\n window.removeEventListener('online', this.onlineListener);\n this.onlineListener = null;\n }\n if (this.offlineListener) {\n window.removeEventListener('offline', this.offlineListener);\n this.offlineListener = null;\n }\n if (this.beforeUnloadListener) {\n window.removeEventListener('beforeunload', this.beforeUnloadListener);\n this.beforeUnloadListener = null;\n }\n }\n if (typeof document !== 'undefined' && this.visibilityListener) {\n document.removeEventListener('visibilitychange', this.visibilityListener);\n this.visibilityListener = null;\n }\n }\n\n /** End the current session and start a new one with a fresh session ID. */\n startNewSession(): void {\n this.core.startNewSession();\n }\n\n /**\n * Register an error listener. Errors from track/screen/flush\n * that would otherwise be silently dropped are forwarded here.\n */\n on(event: 'error', listener: ErrorListener): this {\n if (event === 'error') this.errorListeners.add(listener);\n return this;\n }\n\n /**\n * Remove a previously registered error listener.\n */\n off(event: 'error', listener: ErrorListener): this {\n if (event === 'error') this.errorListeners.delete(listener);\n return this;\n }\n\n private emitError(error: unknown): void {\n const err = error instanceof Error ? error : new Error(String(error));\n for (const listener of this.errorListeners) {\n try {\n listener(err);\n } catch {}\n }\n if (this.enableDebug && this.errorListeners.size === 0) {\n console.warn('[Layers]', err.message);\n }\n }\n\n // --- Platform-specific: browser device info ---\n\n private initializeDeviceInfo(): void {\n const context: DeviceContext = {\n platform: 'web',\n osVersion: this.detectOS(),\n appVersion: SDK_VERSION,\n deviceModel: this.detectDeviceModel(),\n locale: this.detectLocale(),\n screenSize: this.detectScreenSize()\n };\n this.core.setDeviceContext(context);\n }\n\n private detectOS(): string {\n if (typeof navigator === 'undefined') return 'unknown';\n const ua = navigator.userAgent;\n if (ua.includes('Windows')) return 'Windows';\n if (ua.includes('Mac OS')) return 'macOS';\n if (ua.includes('Linux')) return 'Linux';\n if (ua.includes('Android')) return 'Android';\n if (ua.includes('iPhone') || ua.includes('iPad')) return 'iOS';\n return 'unknown';\n }\n\n private detectDeviceModel(): string {\n if (typeof navigator === 'undefined') return 'unknown';\n if ('userAgentData' in navigator) {\n const uaData = (navigator as { userAgentData?: { platform?: string } }).userAgentData;\n if (uaData?.platform) return uaData.platform;\n }\n return navigator.platform ?? 'unknown';\n }\n\n private detectLocale(): string {\n if (typeof navigator === 'undefined') return 'en-US';\n return navigator.language ?? 'en-US';\n }\n\n private detectScreenSize(): string {\n if (typeof window === 'undefined' || typeof screen === 'undefined') return 'unknown';\n return `${screen.width}x${screen.height}`;\n }\n\n // --- Platform-specific: network listener ---\n\n private setupNetworkListener(): void {\n if (typeof window === 'undefined') return;\n\n this.isOnline = navigator?.onLine ?? true;\n\n this.onlineListener = () => {\n this.isOnline = true;\n void this.core.flushAsync().catch(() => {});\n };\n this.offlineListener = () => {\n this.isOnline = false;\n };\n\n window.addEventListener('online', this.onlineListener);\n window.addEventListener('offline', this.offlineListener);\n }\n\n // --- Platform-specific: lifecycle listeners ---\n\n private getBeaconUrl(): string {\n return `${this.baseUrl}/events`;\n }\n\n private setupLifecycleListeners(): void {\n if (typeof window === 'undefined' || typeof document === 'undefined') return;\n\n // Flush on page hide / visibility change.\n // Use sendBeacon for best-effort network delivery before the page is hidden.\n // Falls back to synchronous localStorage persistence if sendBeacon fails.\n this.visibilityListener = () => {\n if (document.visibilityState === 'hidden') {\n if (\n typeof navigator !== 'undefined' &&\n navigator.sendBeacon &&\n this.core.queueDepth() > 0\n ) {\n try {\n const batch = this.core.createBeaconPayload();\n if (batch) {\n const sent = navigator.sendBeacon(this.getBeaconUrl(), batch);\n if (sent) {\n this.core.clearBeaconEvents();\n return;\n }\n // sendBeacon failed — requeue events before falling back to persistence\n this.core.requeueBeaconEvents();\n this.core.flush();\n return;\n }\n } catch {\n // sendBeacon threw — requeue any drained events before sync flush\n this.core.requeueBeaconEvents();\n }\n }\n this.core.flush();\n }\n };\n document.addEventListener('visibilitychange', this.visibilityListener);\n\n // Flush on beforeunload — synchronous persistence as last resort\n this.beforeUnloadListener = () => {\n this.core.flush();\n };\n window.addEventListener('beforeunload', this.beforeUnloadListener);\n }\n}\n\n// SDK version injected at build time by tsdown define\ndeclare const __LAYERS_CLIENT_VERSION__: string;\nconst SDK_VERSION: string =\n typeof __LAYERS_CLIENT_VERSION__ !== 'undefined' ? __LAYERS_CLIENT_VERSION__ : '0.1.1-alpha.1';\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "name": "@layers/client",
3
- "version": "0.1.1-alpha.1",
3
+ "version": "1.0.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
7
- "description": "Javascript Client SDK for Layers Analytics",
8
- "license": "ISC",
7
+ "description": "Layers Analytics Web SDK thin wrapper over Rust core via WASM",
8
+ "license": "MIT",
9
9
  "repository": {
10
10
  "type": "git",
11
- "url": "https://github.com/layersai/sdks.git",
11
+ "url": "https://github.com/layers/sdks.git",
12
12
  "directory": "packages/client"
13
13
  },
14
14
  "author": "Layers Team",
15
- "homepage": "https://github.com/layersai/sdks#readme",
15
+ "homepage": "https://github.com/layers/sdks#readme",
16
16
  "bugs": {
17
- "url": "https://github.com/layersai/sdks/issues"
17
+ "url": "https://github.com/layers/sdks/issues"
18
18
  },
19
19
  "type": "module",
20
20
  "main": "./dist/index.js",
@@ -23,15 +23,39 @@
23
23
  ".": {
24
24
  "types": "./dist/index.d.ts",
25
25
  "default": "./dist/index.js"
26
+ },
27
+ "./react": {
28
+ "types": "./dist/react.d.ts",
29
+ "default": "./dist/react.js"
26
30
  }
27
31
  },
28
32
  "files": [
29
33
  "dist"
30
34
  ],
31
- "keywords": [],
35
+ "dependencies": {
36
+ "@layers/core-wasm": "1.0.0"
37
+ },
38
+ "peerDependencies": {
39
+ "react": ">=18"
40
+ },
41
+ "peerDependenciesMeta": {
42
+ "react": {
43
+ "optional": true
44
+ }
45
+ },
46
+ "devDependencies": {
47
+ "@types/react": "^18 || ^19"
48
+ },
49
+ "keywords": [
50
+ "layers",
51
+ "analytics",
52
+ "sdk",
53
+ "web"
54
+ ],
32
55
  "types": "./dist/index.d.ts",
33
56
  "sideEffects": false,
34
57
  "scripts": {
35
- "build": "tsdown src/index.ts --format esm --dts"
58
+ "build": "tsdown src/index.ts src/react.tsx --format esm --dts",
59
+ "test": "vitest run"
36
60
  }
37
61
  }
@@ -1,372 +0,0 @@
1
- //#region src/api-types.d.ts
2
- interface BaseEvent {
3
- event: string;
4
- timestamp: string;
5
- event_id?: string;
6
- app_id: string;
7
- app_user_id?: string | undefined;
8
- session_id: string;
9
- environment: 'development' | 'staging' | 'production';
10
- platform: 'ios' | 'android' | 'react-native';
11
- os_version: string;
12
- app_version: string;
13
- build_number?: string;
14
- device_model: string;
15
- screen_size?: string;
16
- locale: string;
17
- install_id?: string;
18
- install_source?: string;
19
- campaign_hint?: string;
20
- referrer?: string;
21
- referrer_source?: string;
22
- utm_source?: string;
23
- utm_medium?: string;
24
- utm_campaign?: string;
25
- utm_content?: string;
26
- utm_term?: string;
27
- click_id_param?: string;
28
- idfa?: string;
29
- idfv?: string;
30
- att_status?: 'authorized' | 'denied' | 'restricted' | 'not_determined';
31
- country_code?: string;
32
- region?: string;
33
- city?: string;
34
- properties?: Record<string, any>;
35
- }
36
- interface AppInstallEvent extends BaseEvent {
37
- event: 'app_install';
38
- install_id: string;
39
- install_source?: string;
40
- campaign_hint?: string;
41
- }
42
- interface AppOpenEvent extends BaseEvent {
43
- event: 'app_open';
44
- session_id: string;
45
- referrer?: string;
46
- deeplink?: string;
47
- is_first_open?: boolean;
48
- }
49
- interface AppBackgroundEvent extends BaseEvent {
50
- event: 'app_background';
51
- session_id: string;
52
- duration_ms: number;
53
- }
54
- interface ScreenViewEvent extends BaseEvent {
55
- event: 'screen_view';
56
- screen_name: string;
57
- prev_screen?: string;
58
- }
59
- interface DeeplinkOpenEvent extends BaseEvent {
60
- event: 'deeplink_open';
61
- url: string;
62
- params: Record<string, string>;
63
- matched_universal_link: boolean;
64
- }
65
- interface PaywallShowEvent extends BaseEvent {
66
- event: 'paywall_show';
67
- paywall_id: string;
68
- placement: string;
69
- ab_test?: {
70
- id: string;
71
- variant: string;
72
- };
73
- }
74
- interface PaywallDismissEvent extends BaseEvent {
75
- event: 'paywall_dismiss';
76
- paywall_id: string;
77
- reason: string;
78
- }
79
- interface PurchaseAttemptEvent extends BaseEvent {
80
- event: 'purchase_attempt';
81
- product_id: string;
82
- price: number;
83
- currency: string;
84
- store: 'app_store' | 'play_store';
85
- }
86
- interface PurchaseSuccessEvent extends BaseEvent {
87
- event: 'purchase_success';
88
- product_id: string;
89
- price: number;
90
- currency: string;
91
- revenue: number;
92
- store: 'app_store' | 'play_store';
93
- transaction_id: string;
94
- receipt_data?: string;
95
- }
96
- interface PurchaseFailEvent extends BaseEvent {
97
- event: 'purchase_fail';
98
- product_id: string;
99
- error_code: string;
100
- error_domain: string;
101
- }
102
- interface TrialStartEvent extends BaseEvent {
103
- event: 'trial_start';
104
- product_id: string;
105
- store: 'app_store' | 'play_store';
106
- trial_period: string;
107
- }
108
- interface TrialConvertEvent extends BaseEvent {
109
- event: 'trial_convert';
110
- product_id: string;
111
- revenue: number;
112
- currency: string;
113
- store: 'app_store' | 'play_store';
114
- }
115
- interface SubscriptionStartEvent extends BaseEvent {
116
- event: 'subscription_start';
117
- product_id: string;
118
- period: string;
119
- subscription_id: string;
120
- }
121
- interface SubscriptionRenewEvent extends BaseEvent {
122
- event: 'subscription_renew';
123
- product_id: string;
124
- period: string;
125
- revenue: number;
126
- currency: string;
127
- subscription_id: string;
128
- }
129
- interface SubscriptionCancelEvent extends BaseEvent {
130
- event: 'subscription_cancel';
131
- product_id: string;
132
- reason: string;
133
- subscription_id: string;
134
- }
135
- interface RefundEvent extends BaseEvent {
136
- event: 'refund';
137
- product_id: string;
138
- amount: number;
139
- currency: string;
140
- transaction_id: string;
141
- }
142
- interface ContentOpenEvent extends BaseEvent {
143
- event: 'content_open';
144
- content_type: string;
145
- content_id: string;
146
- }
147
- interface BookmarkAddEvent extends BaseEvent {
148
- event: 'bookmark_add';
149
- content_id: string;
150
- }
151
- interface SearchEvent extends BaseEvent {
152
- event: 'search';
153
- query: string;
154
- results_count: number;
155
- }
156
- type LayersEvent = AppInstallEvent | AppOpenEvent | AppBackgroundEvent | ScreenViewEvent | DeeplinkOpenEvent | PaywallShowEvent | PaywallDismissEvent | PurchaseAttemptEvent | PurchaseSuccessEvent | PurchaseFailEvent | TrialStartEvent | TrialConvertEvent | SubscriptionStartEvent | SubscriptionRenewEvent | SubscriptionCancelEvent | RefundEvent | ContentOpenEvent | BookmarkAddEvent | SearchEvent;
157
- interface EventsBatchPayload {
158
- events: BaseEvent[];
159
- batch_id?: string;
160
- sent_at: string;
161
- }
162
- interface UserPropertiesPayload {
163
- app_user_id?: string | undefined;
164
- app_id: string;
165
- properties: Record<string, any>;
166
- timestamp: string;
167
- }
168
- interface ConsentPayload {
169
- app_user_id?: string | undefined;
170
- app_id: string;
171
- consent: {
172
- advertising?: boolean;
173
- analytics?: boolean;
174
- };
175
- att_status?: 'authorized' | 'denied' | 'restricted' | 'not_determined';
176
- timestamp: string;
177
- }
178
- interface RemoteConfigResponse {
179
- config: {
180
- att?: {
181
- strategy: 'immediate' | 'after_onboarding' | 'after_paywall_view' | 'manual';
182
- prompt_copy?: {
183
- title?: string;
184
- message?: string;
185
- };
186
- };
187
- skan?: {
188
- preset?: 'subscriptions' | 'engagement' | 'iap';
189
- rules?: Record<string, any>;
190
- lock_policy?: string;
191
- };
192
- events?: {
193
- allowlist?: string[];
194
- denylist?: string[];
195
- sampling?: Record<string, number>;
196
- sampling_rate?: number;
197
- rate_limit?: {
198
- per_minute?: number;
199
- per_hour?: number;
200
- per_event?: Record<string, {
201
- per_minute?: number;
202
- per_hour?: number;
203
- }>;
204
- };
205
- };
206
- connectors?: Record<string, {
207
- enabled?: boolean;
208
- app_id?: string;
209
- pixel_id?: string;
210
- test_mode?: boolean;
211
- }>;
212
- deeplinks?: {
213
- allowed_hosts?: string[];
214
- behavior?: string;
215
- };
216
- privacy?: {
217
- killswitches?: string[];
218
- analytics_enabled?: boolean;
219
- advertising_enabled?: boolean;
220
- };
221
- };
222
- version: string;
223
- cache_ttl: number;
224
- }
225
- interface SKANPostbackPayload {
226
- app_id: string;
227
- version: string;
228
- ad_network_id: string;
229
- campaign_id?: string;
230
- source_app_id?: string;
231
- conversion_value?: number;
232
- coarse_conversion_value?: string;
233
- lock_window?: boolean;
234
- postback_sequence_index?: number;
235
- did_win?: boolean;
236
- timestamp: string;
237
- }
238
- interface APIResponse<T = any> {
239
- success: boolean;
240
- data?: T;
241
- error?: {
242
- code: string;
243
- message: string;
244
- details?: any;
245
- };
246
- }
247
- interface EventsIngestResponse extends APIResponse {
248
- processed: number;
249
- failed: number;
250
- duplicate: number;
251
- }
252
- //#endregion
253
- //#region src/index.d.ts
254
- interface QueueOptions {
255
- flushIntervalMs?: number;
256
- maxQueueSize?: number;
257
- maxItemAgeMs?: number;
258
- requestTimeoutMs?: number;
259
- maxRetries?: number;
260
- baseRetryDelayMs?: number;
261
- maxRetryDelayMs?: number;
262
- }
263
- interface LayersConfig {
264
- apiKey: string;
265
- appId: string;
266
- environment: 'development' | 'staging' | 'production';
267
- appUserId?: string | undefined;
268
- enableDebug?: boolean;
269
- baseUrl?: string;
270
- queueOptions?: QueueOptions;
271
- queueStorage?: QueueStorage | null;
272
- }
273
- interface EventData {
274
- [key: string]: unknown;
275
- }
276
- interface UserProperties {
277
- [key: string]: unknown;
278
- }
279
- interface ConsentOptions {
280
- advertising?: boolean;
281
- analytics?: boolean;
282
- }
283
- type RemoteConfig = RemoteConfigResponse['config'];
284
- interface QueuedRequest {
285
- endpoint: string;
286
- data: unknown;
287
- attempts: number;
288
- queuedAt: number;
289
- nextAttemptAt: number;
290
- requestId: string;
291
- retryable: boolean;
292
- }
293
- interface StoredQueuedRequest extends QueuedRequest {}
294
- interface StoredQueueSnapshot {
295
- version: number;
296
- items: StoredQueuedRequest[];
297
- }
298
- interface QueueStorage {
299
- load(): Promise<StoredQueueSnapshot | null>;
300
- save(snapshot: StoredQueueSnapshot): Promise<void>;
301
- clear(): Promise<void>;
302
- }
303
- declare class LayersClient {
304
- private config;
305
- private remoteConfig;
306
- private remoteConfigMeta;
307
- private eventQueue;
308
- private isOnline;
309
- private consentState;
310
- private sessionId;
311
- private deviceInfo;
312
- private queueTimer;
313
- private queueConfig;
314
- private queueStorage;
315
- private rateLimitState;
316
- private logLevel;
317
- private shouldLog;
318
- constructor(config: LayersConfig);
319
- private generateSessionId;
320
- private initializeDeviceInfo;
321
- init(): Promise<void>;
322
- private fetchRemoteConfig;
323
- private extractRemoteConfigSettings;
324
- private setupNetworkListener;
325
- setOnlineState(isOnline: boolean): void;
326
- private hydrateQueue;
327
- private persistQueue;
328
- track(eventName: string, properties?: EventData): Promise<void>;
329
- private shouldProcessEvent;
330
- private passesSampling;
331
- private resolveAppUserId;
332
- screen(screenName: string, properties?: EventData): Promise<void>;
333
- setUserProperties(properties: UserProperties): Promise<void>;
334
- isAnalyticsEnabled(): boolean;
335
- isAdvertisingEnabled(): boolean;
336
- setConsent(consent: ConsentOptions): Promise<void>;
337
- private enqueue;
338
- private scheduleQueueFlush;
339
- private processQueue;
340
- private dropExpiredQueueItems;
341
- private getBackoffDelay;
342
- private resetRateLimitState;
343
- private getEventRateCounter;
344
- private normalizeRateLimitRule;
345
- private passesRateLimit;
346
- private canConsumeRateLimit;
347
- private canConsumeWindow;
348
- private recordRateLimit;
349
- private incrementWindow;
350
- private getWindowInfo;
351
- private makeRequest;
352
- private isRetryableStatus;
353
- private generateEventId;
354
- private generateBatchId;
355
- getRemoteConfig(): RemoteConfig | null;
356
- getRemoteConfigVersion(): string | undefined;
357
- getConsentState(): Required<ConsentOptions>;
358
- setAppUserId(appUserId: string | undefined): void;
359
- /** @deprecated Use setAppUserId instead */
360
- setUserId(userId: string): void;
361
- getConfig(): LayersConfig;
362
- getAppUserId(): string | undefined;
363
- /** @deprecated Use getAppUserId instead */
364
- getUserId(): string | undefined;
365
- getSessionId(): string;
366
- flush(): Promise<void>;
367
- setDeviceInfo(deviceInfo: Partial<BaseEvent>): void;
368
- startNewSession(): void;
369
- }
370
- //#endregion
371
- export { APIResponse, AppBackgroundEvent, AppInstallEvent, AppOpenEvent, BaseEvent, BookmarkAddEvent, ConsentOptions, ConsentPayload, ContentOpenEvent, DeeplinkOpenEvent, EventData, EventsBatchPayload, EventsIngestResponse, LayersClient, LayersConfig, LayersEvent, PaywallDismissEvent, PaywallShowEvent, PurchaseAttemptEvent, PurchaseFailEvent, PurchaseSuccessEvent, QueueOptions, QueueStorage, RefundEvent, RemoteConfig, RemoteConfigResponse, SKANPostbackPayload, ScreenViewEvent, SearchEvent, StoredQueueSnapshot, StoredQueuedRequest, SubscriptionCancelEvent, SubscriptionRenewEvent, SubscriptionStartEvent, TrialConvertEvent, TrialStartEvent, UserProperties, UserPropertiesPayload };
372
- //# sourceMappingURL=index-NMBS3y1V.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index-NMBS3y1V.d.ts","names":[],"sources":["../src/api-types.ts","../src/index.ts"],"sourcesContent":[],"mappings":"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"}
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","names":["DEFAULT_CONSENT: Required<ConsentOptions>","DEFAULT_QUEUE_CONFIG: QueueConfig","meta: RemoteConfigMeta","samplingEntries: Record<string, number>","perEventRules: Map<string, RateLimitRule> | undefined","rateLimits: { global?: RateLimitRule; perEvent?: Map<string, RateLimitRule> }","hydrated: QueuedRequest[]","snapshot: StoredQueueSnapshot","batchPayload: EventsBatchPayload","payload: UserPropertiesPayload","payload: ConsentPayload","pending: QueuedRequest[]","rule: RateLimitRule","headers: Record<string, string>","timeoutHandle: ReturnType<typeof setTimeout> | undefined","error"],"sources":["../src/index.ts"],"sourcesContent":["import type {\n BaseEvent,\n ConsentPayload,\n EventsBatchPayload,\n RemoteConfigResponse,\n UserPropertiesPayload\n} from './api-types.js';\n\nexport interface QueueOptions {\n flushIntervalMs?: number;\n maxQueueSize?: number;\n maxItemAgeMs?: number;\n requestTimeoutMs?: number;\n maxRetries?: number;\n baseRetryDelayMs?: number;\n maxRetryDelayMs?: number;\n}\n\nexport interface LayersConfig {\n apiKey: string;\n appId: string;\n environment: 'development' | 'staging' | 'production';\n appUserId?: string | undefined;\n enableDebug?: boolean;\n baseUrl?: string;\n queueOptions?: QueueOptions;\n queueStorage?: QueueStorage | null;\n}\n\nexport interface EventData {\n [key: string]: unknown;\n}\n\nexport interface UserProperties {\n [key: string]: unknown;\n}\n\nexport interface ConsentOptions {\n advertising?: boolean;\n analytics?: boolean;\n}\n\nexport type RemoteConfig = RemoteConfigResponse['config'];\n\nexport * from './api-types.js';\n\n// TODO: read this from package json, no need to manage version in the code\nconst SDK_VERSION = '0.0.0';\nconst DEFAULT_BASE_URL = 'https://in.layers.com';\nconst SYSTEM_EVENTS = new Set(['consent_updated', 'att_status_changed']);\nconst DEFAULT_CONSENT: Required<ConsentOptions> = {\n advertising: true,\n analytics: true\n};\n\ninterface QueueConfig {\n flushIntervalMs: number;\n maxQueueSize: number;\n maxItemAgeMs: number;\n requestTimeoutMs: number;\n maxRetries: number;\n baseRetryDelayMs: number;\n maxRetryDelayMs: number;\n}\n\ninterface QueuedRequest {\n endpoint: string;\n data: unknown;\n attempts: number;\n queuedAt: number;\n nextAttemptAt: number;\n requestId: string;\n retryable: boolean;\n}\n\nexport interface StoredQueuedRequest extends QueuedRequest {}\n\nexport interface StoredQueueSnapshot {\n version: number;\n items: StoredQueuedRequest[];\n}\n\nexport interface QueueStorage {\n load(): Promise<StoredQueueSnapshot | null>;\n save(snapshot: StoredQueueSnapshot): Promise<void>;\n clear(): Promise<void>;\n}\n\ntype RemoteConfigEnvelope = RemoteConfigResponse;\n\ninterface RateLimitRule {\n perMinute?: number;\n perHour?: number;\n}\n\ninterface RateWindowCounter {\n windowStart: number;\n count: number;\n}\n\ninterface RateLimitCounter {\n perMinute?: RateWindowCounter;\n perHour?: RateWindowCounter;\n}\n\ninterface RemoteConfigMeta {\n version?: string;\n etag?: string;\n expiresAt?: number;\n analyticsEnabled?: boolean;\n advertisingEnabled?: boolean;\n eventAllowlist?: Set<string>;\n eventDenylist?: Set<string>;\n samplingRates?: Record<string, number>;\n rateLimits?: {\n global?: RateLimitRule;\n perEvent?: Map<string, RateLimitRule>;\n };\n}\n\nconst DEFAULT_QUEUE_CONFIG: QueueConfig = {\n flushIntervalMs: 10_000,\n maxQueueSize: 200,\n maxItemAgeMs: 5 * 60_000,\n requestTimeoutMs: 10_000,\n maxRetries: 5,\n baseRetryDelayMs: 1_000,\n maxRetryDelayMs: 30_000\n};\n\nfunction now(): number {\n return Date.now();\n}\n\nfunction generateId(prefix: string): string {\n return `${prefix}_${Math.random().toString(36).slice(2)}_${now()}`;\n}\n\nexport class LayersClient {\n private config: LayersConfig;\n private remoteConfig: RemoteConfig | null = null;\n private remoteConfigMeta: RemoteConfigMeta = {};\n private eventQueue: QueuedRequest[] = [];\n private isOnline = true;\n private consentState: Required<ConsentOptions> = { ...DEFAULT_CONSENT };\n private sessionId: string;\n private deviceInfo: Partial<BaseEvent> = {};\n private queueTimer: ReturnType<typeof setTimeout> | null = null;\n private queueConfig: QueueConfig;\n private queueStorage: QueueStorage | null;\n private rateLimitState: {\n global: RateLimitCounter;\n perEvent: Map<string, RateLimitCounter>;\n } = {\n global: {},\n perEvent: new Map<string, RateLimitCounter>()\n };\n\n private logLevel(): string {\n // Check environment variable first\n if (typeof process !== 'undefined' && process.env?.LOG_LEVEL) {\n return process.env.LOG_LEVEL.toLowerCase();\n }\n // Fall back to config flag\n if (this.config.enableDebug) {\n return 'debug';\n }\n // Default to info\n return 'info';\n }\n\n private shouldLog(level: 'error' | 'warn' | 'info' | 'debug' | 'trace'): boolean {\n const currentLevel = this.logLevel();\n const levels = ['error', 'warn', 'info', 'debug', 'trace'];\n const currentIndex = levels.indexOf(currentLevel);\n const targetIndex = levels.indexOf(level);\n return currentIndex >= targetIndex;\n }\n\n constructor(config: LayersConfig) {\n this.queueConfig = { ...DEFAULT_QUEUE_CONFIG, ...(config.queueOptions || {}) };\n this.queueStorage = config.queueStorage ?? createDefaultQueueStorage(config.appId);\n\n const resolvedAppUserId = config.appUserId;\n\n this.config = {\n ...config,\n baseUrl: (config.baseUrl || DEFAULT_BASE_URL).replace(/\\/$/, ''),\n appUserId: resolvedAppUserId\n };\n this.sessionId = this.generateSessionId();\n this.resetRateLimitState();\n this.initializeDeviceInfo();\n }\n\n private generateSessionId(): string {\n return generateId('sess');\n }\n\n private initializeDeviceInfo(): void {\n this.deviceInfo = {\n platform: 'react-native' as const,\n os_version: 'unknown',\n app_version: 'unknown',\n locale: 'en-US',\n device_model: 'unknown'\n };\n }\n\n async init(): Promise<void> {\n await this.hydrateQueue();\n await this.fetchRemoteConfig();\n this.setupNetworkListener();\n this.scheduleQueueFlush(this.eventQueue.length > 0);\n }\n\n private async fetchRemoteConfig(force = false): Promise<void> {\n const expired = !this.remoteConfigMeta.expiresAt || this.remoteConfigMeta.expiresAt < now();\n if (!force && this.remoteConfig && !expired) {\n return;\n }\n\n try {\n const response = await this.makeRequest<RemoteConfigEnvelope>('/config', {\n method: 'GET'\n });\n\n if (!response?.config) {\n return;\n }\n\n this.remoteConfig = response.config;\n const ttlMs = (response.cache_ttl ?? 300) * 1000;\n this.remoteConfigMeta = {\n version: response.version,\n expiresAt: now() + ttlMs,\n ...this.extractRemoteConfigSettings(response)\n };\n this.resetRateLimitState();\n } catch (error) {\n if (this.shouldLog('debug')) {\n console.warn('[Layers] Failed to fetch remote config:', error);\n }\n }\n }\n\n private extractRemoteConfigSettings(envelope: RemoteConfigEnvelope): RemoteConfigMeta {\n const meta: RemoteConfigMeta = {};\n const config = envelope.config ?? {};\n\n if (config.privacy) {\n if (typeof config.privacy.analytics_enabled === 'boolean') {\n meta.analyticsEnabled = config.privacy.analytics_enabled;\n }\n if (typeof config.privacy.advertising_enabled === 'boolean') {\n meta.advertisingEnabled = config.privacy.advertising_enabled;\n }\n if (config.privacy.killswitches) {\n const killswitches = new Set(config.privacy.killswitches);\n if (killswitches.has('analytics')) {\n meta.analyticsEnabled = false;\n }\n if (killswitches.has('advertising')) {\n meta.advertisingEnabled = false;\n }\n }\n }\n\n if (config.events) {\n if (Array.isArray(config.events.allowlist)) {\n meta.eventAllowlist = new Set(config.events.allowlist);\n }\n if (Array.isArray(config.events.denylist)) {\n meta.eventDenylist = new Set(config.events.denylist);\n }\n\n const samplingEntries: Record<string, number> = {};\n if (config.events.sampling) {\n for (const [eventName, value] of Object.entries(config.events.sampling)) {\n const numeric = Number(value);\n if (Number.isFinite(numeric)) {\n samplingEntries[eventName] = Math.max(0, Math.min(1, numeric));\n }\n }\n }\n if (typeof config.events.sampling_rate === 'number') {\n samplingEntries['*'] = Math.max(0, Math.min(1, config.events.sampling_rate));\n }\n if (Object.keys(samplingEntries).length > 0) {\n meta.samplingRates = samplingEntries;\n }\n\n const rateLimitConfig = config.events.rate_limit;\n if (rateLimitConfig) {\n const globalRule = this.normalizeRateLimitRule(rateLimitConfig);\n let perEventRules: Map<string, RateLimitRule> | undefined;\n if (rateLimitConfig.per_event) {\n const entries = Object.entries(rateLimitConfig.per_event);\n if (entries.length > 0) {\n perEventRules = new Map<string, RateLimitRule>();\n for (const [eventName, ruleConfig] of entries) {\n const rule = this.normalizeRateLimitRule(ruleConfig);\n if (rule) {\n perEventRules.set(eventName, rule);\n }\n }\n if (perEventRules.size === 0) {\n perEventRules = undefined;\n }\n }\n }\n const rateLimits: { global?: RateLimitRule; perEvent?: Map<string, RateLimitRule> } = {};\n if (globalRule) {\n rateLimits.global = globalRule;\n }\n if (perEventRules) {\n rateLimits.perEvent = perEventRules;\n }\n if (rateLimits.global || rateLimits.perEvent) {\n meta.rateLimits = rateLimits;\n }\n }\n }\n\n return meta;\n }\n\n private setupNetworkListener(): void {\n if (typeof window !== 'undefined' && typeof window.addEventListener === 'function') {\n window.addEventListener('online', () => {\n this.setOnlineState(true);\n });\n\n window.addEventListener('offline', () => {\n this.setOnlineState(false);\n });\n }\n }\n\n setOnlineState(isOnline: boolean): void {\n if (this.isOnline === isOnline) {\n return;\n }\n\n this.isOnline = isOnline;\n\n if (isOnline) {\n this.scheduleQueueFlush(true);\n }\n }\n\n private async hydrateQueue(): Promise<void> {\n if (!this.queueStorage) {\n return;\n }\n\n try {\n const snapshot = await this.queueStorage.load();\n if (!snapshot || !Array.isArray(snapshot.items) || snapshot.items.length === 0) {\n if (snapshot) {\n await this.queueStorage.clear();\n }\n return;\n }\n\n const nowTs = now();\n const maxAge = this.queueConfig.maxItemAgeMs;\n const hydrated: QueuedRequest[] = [];\n\n for (const item of snapshot.items) {\n if (item && typeof item.endpoint === 'string') {\n const queuedAt = typeof item.queuedAt === 'number' ? item.queuedAt : nowTs;\n if (nowTs - queuedAt <= maxAge) {\n hydrated.push({\n endpoint: item.endpoint,\n data: item.data,\n attempts: typeof item.attempts === 'number' ? item.attempts : 0,\n queuedAt,\n nextAttemptAt: typeof item.nextAttemptAt === 'number' ? item.nextAttemptAt : nowTs,\n requestId: typeof item.requestId === 'string' ? item.requestId : generateId('req'),\n retryable: item.retryable !== false\n });\n }\n }\n }\n\n if (hydrated.length === 0) {\n await this.queueStorage.clear();\n this.eventQueue = [];\n return;\n }\n\n const limited = hydrated.slice(0, this.queueConfig.maxQueueSize);\n const trimmed = limited.length !== hydrated.length;\n\n this.eventQueue = limited;\n\n if (trimmed || limited.length !== snapshot.items.length) {\n await this.persistQueue();\n }\n } catch (error) {\n if (this.shouldLog('debug')) {\n console.warn('[Layers] Failed to hydrate queue', error);\n }\n }\n }\n\n private async persistQueue(): Promise<void> {\n if (!this.queueStorage) {\n return;\n }\n\n const snapshot: StoredQueueSnapshot = {\n version: 1,\n items: this.eventQueue.map((item) => ({\n endpoint: item.endpoint,\n data: item.data,\n attempts: item.attempts,\n queuedAt: item.queuedAt,\n nextAttemptAt: item.nextAttemptAt,\n requestId: item.requestId,\n retryable: item.retryable\n }))\n };\n\n try {\n if (snapshot.items.length === 0) {\n await this.queueStorage.clear();\n } else {\n await this.queueStorage.save(snapshot);\n }\n } catch (error) {\n if (this.shouldLog('debug')) {\n console.warn('[Layers] Failed to persist queue', error);\n }\n }\n }\n\n async track(eventName: string, properties?: EventData): Promise<void> {\n if (!this.shouldProcessEvent(eventName)) {\n if (this.shouldLog('debug')) {\n console.log(`[Layers] Event '${eventName}' dropped due to consent or config gating`);\n }\n return;\n }\n\n if (!this.passesSampling(eventName)) {\n if (this.shouldLog('debug')) {\n console.log(`[Layers] Event '${eventName}' sampled out`);\n }\n return;\n }\n\n const baseEvent: BaseEvent = {\n event: eventName,\n timestamp: new Date().toISOString(),\n event_id: this.generateEventId(),\n app_id: this.config.appId,\n session_id: this.sessionId,\n environment: this.config.environment,\n platform: this.deviceInfo.platform!,\n os_version: this.deviceInfo.os_version!,\n app_version: this.deviceInfo.app_version!,\n device_model: this.deviceInfo.device_model!,\n locale: this.deviceInfo.locale!,\n ...this.deviceInfo,\n properties: properties || {},\n ...(this.resolveAppUserId() && { app_user_id: this.resolveAppUserId() })\n };\n\n const batchPayload: EventsBatchPayload = {\n events: [baseEvent as unknown as BaseEvent],\n batch_id: this.generateBatchId(),\n sent_at: new Date().toISOString()\n };\n\n const appUserId = this.resolveAppUserId();\n console.log(\n `[Layers] Tracking event: ${eventName}${appUserId ? ` (user: ${appUserId})` : ' (no user)'}`\n );\n\n await this.enqueue('/events', batchPayload);\n }\n\n private shouldProcessEvent(eventName: string): boolean {\n if (SYSTEM_EVENTS.has(eventName)) {\n return true;\n }\n\n if (!this.isAnalyticsEnabled()) {\n return false;\n }\n\n if (\n this.remoteConfigMeta.eventAllowlist &&\n !this.remoteConfigMeta.eventAllowlist.has(eventName)\n ) {\n return false;\n }\n\n if (this.remoteConfigMeta.eventDenylist && this.remoteConfigMeta.eventDenylist.has(eventName)) {\n return false;\n }\n\n if (!this.passesRateLimit(eventName)) {\n return false;\n }\n\n return true;\n }\n\n private passesSampling(eventName: string): boolean {\n const sampling = this.remoteConfigMeta.samplingRates;\n if (!sampling) {\n return true;\n }\n\n const rate = sampling[eventName] ?? sampling['*'];\n if (typeof rate !== 'number') {\n return true;\n }\n\n return Math.random() <= Math.max(0, Math.min(1, rate));\n }\n\n private resolveAppUserId(): string | undefined {\n return this.config.appUserId;\n }\n\n async screen(screenName: string, properties?: EventData): Promise<void> {\n await this.track('screen_view', {\n screen_name: screenName,\n ...properties\n });\n }\n\n async setUserProperties(properties: UserProperties): Promise<void> {\n if (!this.isAnalyticsEnabled()) {\n return;\n }\n\n const payload: UserPropertiesPayload = {\n app_id: this.config.appId,\n properties,\n timestamp: new Date().toISOString(),\n ...(this.resolveAppUserId() && { app_user_id: this.resolveAppUserId() })\n };\n\n await this.enqueue('/users/properties', payload);\n }\n\n isAnalyticsEnabled(): boolean {\n if (this.remoteConfigMeta.analyticsEnabled === false) {\n return false;\n }\n return this.consentState.analytics !== false;\n }\n\n isAdvertisingEnabled(): boolean {\n if (this.remoteConfigMeta.advertisingEnabled === false) {\n return false;\n }\n return this.consentState.advertising !== false;\n }\n\n async setConsent(consent: ConsentOptions): Promise<void> {\n this.consentState = {\n advertising:\n typeof consent.advertising === 'boolean'\n ? consent.advertising\n : this.consentState.advertising,\n analytics:\n typeof consent.analytics === 'boolean' ? consent.analytics : this.consentState.analytics\n };\n\n const payload: ConsentPayload = {\n app_id: this.config.appId,\n consent: { ...this.consentState },\n timestamp: new Date().toISOString(),\n ...(this.resolveAppUserId() && { app_user_id: this.resolveAppUserId() })\n };\n\n await this.enqueue('/consent', payload);\n }\n\n private async enqueue(\n endpoint: string,\n data: unknown,\n options?: { retryable?: boolean }\n ): Promise<void> {\n const retryable = options?.retryable !== false;\n\n if (this.eventQueue.length >= this.queueConfig.maxQueueSize) {\n this.eventQueue.shift();\n }\n\n this.eventQueue.push({\n endpoint,\n data,\n attempts: 0,\n queuedAt: now(),\n nextAttemptAt: now(),\n requestId: generateId('req'),\n retryable\n });\n\n if (this.isOnline) {\n this.scheduleQueueFlush(true);\n } else {\n this.scheduleQueueFlush();\n }\n\n void this.persistQueue();\n }\n\n private scheduleQueueFlush(immediate = false): void {\n if (this.queueTimer) {\n clearTimeout(this.queueTimer);\n this.queueTimer = null;\n }\n\n const delay = immediate ? 0 : this.queueConfig.flushIntervalMs;\n this.queueTimer = setTimeout(() => {\n this.queueTimer = null;\n void this.processQueue();\n }, delay);\n }\n\n private async processQueue(): Promise<void> {\n if (!this.isOnline || this.eventQueue.length === 0) {\n this.scheduleQueueFlush();\n return;\n }\n\n this.dropExpiredQueueItems();\n\n const pending: QueuedRequest[] = [];\n\n for (const item of this.eventQueue) {\n if (item.nextAttemptAt > now()) {\n pending.push(item);\n } else {\n let shouldRequeue = false;\n try {\n await this.makeRequest(\n item.endpoint,\n {\n method: 'POST',\n body: JSON.stringify(item.data)\n },\n item.requestId\n );\n } catch (err) {\n if (!item.retryable) {\n if (this.shouldLog('debug')) {\n console.warn('[Layers] Dropping non-retryable item', {\n endpoint: item.endpoint,\n error: err\n });\n }\n } else {\n item.attempts += 1;\n if (item.attempts > this.queueConfig.maxRetries) {\n if (this.shouldLog('debug')) {\n console.warn('[Layers] Dropping item after max retries', {\n endpoint: item.endpoint,\n error: err\n });\n }\n } else {\n item.nextAttemptAt = now() + this.getBackoffDelay(item.attempts);\n shouldRequeue = true;\n if (this.shouldLog('debug')) {\n console.warn('[Layers] Retry scheduled', {\n endpoint: item.endpoint,\n nextAttemptAt: item.nextAttemptAt,\n error: err\n });\n }\n }\n }\n }\n\n if (shouldRequeue) {\n pending.push(item);\n }\n }\n }\n\n this.eventQueue = pending;\n await this.persistQueue();\n this.scheduleQueueFlush(this.eventQueue.length > 0);\n }\n\n private dropExpiredQueueItems(): void {\n const cutoff = now() - this.queueConfig.maxItemAgeMs;\n if (this.eventQueue.length === 0) {\n return;\n }\n\n const retained = this.eventQueue.filter((item) => item.queuedAt >= cutoff);\n if (this.shouldLog('debug') && retained.length !== this.eventQueue.length) {\n console.warn('[Layers] Dropped stale queue items', {\n dropped: this.eventQueue.length - retained.length\n });\n }\n this.eventQueue = retained;\n void this.persistQueue();\n }\n\n private getBackoffDelay(attempt: number): number {\n const jitter = Math.random() * 250;\n const delay = this.queueConfig.baseRetryDelayMs * 2 ** (attempt - 1);\n return Math.min(delay + jitter, this.queueConfig.maxRetryDelayMs);\n }\n\n private resetRateLimitState(): void {\n this.rateLimitState = {\n global: {},\n perEvent: new Map<string, RateLimitCounter>()\n };\n }\n\n private getEventRateCounter(eventName: string): RateLimitCounter {\n let counter = this.rateLimitState.perEvent.get(eventName);\n if (!counter) {\n counter = {};\n this.rateLimitState.perEvent.set(eventName, counter);\n }\n return counter;\n }\n\n private normalizeRateLimitRule(\n input: { per_minute?: number; per_hour?: number } | undefined\n ): RateLimitRule | undefined {\n if (!input) {\n return undefined;\n }\n const rule: RateLimitRule = {};\n if (typeof input.per_minute === 'number' && input.per_minute > 0) {\n rule.perMinute = Math.floor(input.per_minute);\n }\n if (typeof input.per_hour === 'number' && input.per_hour > 0) {\n rule.perHour = Math.floor(input.per_hour);\n }\n return Object.keys(rule).length > 0 ? rule : undefined;\n }\n\n private passesRateLimit(eventName: string): boolean {\n if (SYSTEM_EVENTS.has(eventName)) {\n return true;\n }\n const { rateLimits } = this.remoteConfigMeta;\n if (!rateLimits) {\n return true;\n }\n const timestamp = now();\n const perEventRules = rateLimits.perEvent;\n const eventRule = perEventRules?.get(eventName) ?? perEventRules?.get('*');\n if (\n eventRule &&\n !this.canConsumeRateLimit(eventRule, this.getEventRateCounter(eventName), timestamp)\n ) {\n if (this.shouldLog('debug')) {\n console.warn('[Layers] Rate limit reached for event', { event: eventName });\n }\n return false;\n }\n if (\n rateLimits.global &&\n !this.canConsumeRateLimit(rateLimits.global, this.rateLimitState.global, timestamp)\n ) {\n if (this.shouldLog('debug')) {\n console.warn('[Layers] Global rate limit reached', { event: eventName });\n }\n return false;\n }\n if (eventRule) {\n this.recordRateLimit(eventRule, this.getEventRateCounter(eventName), timestamp);\n }\n if (rateLimits.global) {\n this.recordRateLimit(rateLimits.global, this.rateLimitState.global, timestamp);\n }\n return true;\n }\n\n private canConsumeRateLimit(\n rule: RateLimitRule,\n counter: RateLimitCounter,\n timestamp: number\n ): boolean {\n return (\n this.canConsumeWindow(rule.perMinute, counter, 'perMinute', 60_000, timestamp) &&\n this.canConsumeWindow(rule.perHour, counter, 'perHour', 3_600_000, timestamp)\n );\n }\n\n private canConsumeWindow(\n limit: number | undefined,\n counter: RateLimitCounter,\n key: 'perMinute' | 'perHour',\n windowMs: number,\n timestamp: number\n ): boolean {\n if (limit === undefined) {\n return true;\n }\n const { count } = this.getWindowInfo(counter, key, windowMs, timestamp);\n return count + 1 <= limit;\n }\n\n private recordRateLimit(rule: RateLimitRule, counter: RateLimitCounter, timestamp: number): void {\n this.incrementWindow(rule.perMinute, counter, 'perMinute', 60_000, timestamp);\n this.incrementWindow(rule.perHour, counter, 'perHour', 3_600_000, timestamp);\n }\n\n private incrementWindow(\n limit: number | undefined,\n counter: RateLimitCounter,\n key: 'perMinute' | 'perHour',\n windowMs: number,\n timestamp: number\n ): void {\n if (limit === undefined) {\n return;\n }\n const windowStart = Math.floor(timestamp / windowMs) * windowMs;\n const existing = counter[key];\n if (!existing || existing.windowStart !== windowStart) {\n counter[key] = { windowStart, count: 1 };\n } else {\n existing.count += 1;\n }\n }\n\n private getWindowInfo(\n counter: RateLimitCounter,\n key: 'perMinute' | 'perHour',\n windowMs: number,\n timestamp: number\n ): RateWindowCounter {\n const windowStart = Math.floor(timestamp / windowMs) * windowMs;\n const existing = counter[key];\n if (!existing || existing.windowStart !== windowStart) {\n return { windowStart, count: 0 };\n }\n return existing;\n }\n\n private async makeRequest<T = unknown>(\n endpoint: string,\n options: RequestInit,\n requestId: string = generateId('req')\n ): Promise<T> {\n const url = `${this.config.baseUrl}${endpoint}`;\n const controller = typeof AbortController !== 'undefined' ? new AbortController() : undefined;\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'X-Api-Key': this.config.apiKey,\n 'X-App-Id': this.config.appId,\n 'X-Environment': this.config.environment,\n 'X-SDK-Version': SDK_VERSION,\n 'X-Request-Id': requestId,\n ...(options.headers as Record<string, string> | undefined)\n };\n\n if (this.sessionId) {\n headers['X-Session-Id'] = this.sessionId;\n }\n\n let timeoutHandle: ReturnType<typeof setTimeout> | undefined;\n if (controller) {\n options = { ...options, signal: controller.signal };\n timeoutHandle = setTimeout(() => {\n controller.abort();\n }, this.queueConfig.requestTimeoutMs);\n }\n\n try {\n if (this.shouldLog('trace')) {\n console.log(`[Layers] → ${options.method || 'GET'} ${url}`);\n if (options.method === 'POST' && options.body) {\n console.log(`[Layers] Request body:`, JSON.parse(options.body as string));\n }\n }\n\n const response = await fetch(url, {\n ...options,\n headers\n });\n\n if (this.shouldLog('trace')) {\n console.log(`[Layers] ← ${response.status} ${response.statusText}`);\n }\n\n if (!response.ok) {\n if (!this.isRetryableStatus(response.status)) {\n const error = Object.assign(\n new Error(`HTTP ${response.status}: ${response.statusText}`),\n {\n status: response.status\n }\n );\n if (this.shouldLog('debug')) {\n console.warn(`[Layers] Non-retryable error for ${url}:`, error);\n }\n throw error;\n }\n const error = Object.assign(new Error(`Retryable HTTP ${response.status}`), {\n status: response.status\n });\n if (this.shouldLog('debug')) {\n console.warn(`[Layers] Retryable error for ${url}:`, error);\n }\n throw error;\n }\n\n if (response.status === 204) {\n return undefined as T;\n }\n\n const text = await response.text();\n if (!text) {\n return undefined as T;\n }\n const parsed = JSON.parse(text) as T;\n if (this.shouldLog('trace')) {\n console.log(`[Layers] Response:`, parsed);\n }\n return parsed;\n } finally {\n if (timeoutHandle) {\n clearTimeout(timeoutHandle);\n }\n }\n }\n\n private isRetryableStatus(status: number): boolean {\n if (status === 429 || (status >= 500 && status < 600)) {\n return true;\n }\n return false;\n }\n\n private generateEventId(): string {\n return generateId('evt');\n }\n\n private generateBatchId(): string {\n return generateId('batch');\n }\n\n getRemoteConfig(): RemoteConfig | null {\n return this.remoteConfig;\n }\n\n getRemoteConfigVersion(): string | undefined {\n return this.remoteConfigMeta.version;\n }\n\n getConsentState(): Required<ConsentOptions> {\n return { ...this.consentState };\n }\n\n setAppUserId(appUserId: string | undefined): void {\n this.config.appUserId = appUserId;\n if (this.shouldLog('info')) {\n console.log(`[Layers] App user ID set: ${appUserId || '(cleared)'}`);\n }\n }\n\n /** @deprecated Use setAppUserId instead */\n setUserId(userId: string): void {\n this.setAppUserId(userId);\n }\n\n getConfig(): LayersConfig {\n return { ...this.config };\n }\n\n getAppUserId(): string | undefined {\n return this.resolveAppUserId();\n }\n\n /** @deprecated Use getAppUserId instead */\n getUserId(): string | undefined {\n return this.resolveAppUserId();\n }\n\n getSessionId(): string {\n return this.sessionId;\n }\n\n async flush(): Promise<void> {\n await this.processQueue();\n }\n\n setDeviceInfo(deviceInfo: Partial<BaseEvent>): void {\n this.deviceInfo = {\n ...this.deviceInfo,\n ...Object.fromEntries(\n Object.entries(deviceInfo).filter(([, value]) => value !== undefined && value !== null)\n )\n };\n }\n\n startNewSession(): void {\n this.sessionId = this.generateSessionId();\n }\n}\n\nfunction createDefaultQueueStorage(appId: string): QueueStorage | null {\n if (typeof window === 'undefined') {\n return null;\n }\n\n try {\n const storage = window.localStorage;\n if (!storage) {\n return null;\n }\n } catch {\n return null;\n }\n\n const storageKey = `layers_queue_${appId}`;\n return createLocalStorageQueueStorage(storageKey);\n}\n\nfunction createLocalStorageQueueStorage(storageKey: string): QueueStorage {\n return {\n async load() {\n try {\n const raw = window.localStorage.getItem(storageKey);\n if (!raw) {\n return null;\n }\n const parsed = JSON.parse(raw);\n if (!parsed || typeof parsed !== 'object') {\n return null;\n }\n return parsed as StoredQueueSnapshot;\n } catch (error) {\n console.warn('Failed to load queue from localStorage', error);\n return null;\n }\n },\n async save(snapshot: StoredQueueSnapshot) {\n try {\n window.localStorage.setItem(storageKey, JSON.stringify(snapshot));\n } catch (error) {\n console.warn('Failed to persist queue to localStorage', error);\n }\n },\n async clear() {\n try {\n window.localStorage.removeItem(storageKey);\n } catch (error) {\n console.warn('Failed to clear queue storage', error);\n }\n }\n };\n}\n"],"mappings":"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"}