@layers/client 0.1.0-alpha.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.
package/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # @layers/client
2
+
3
+ JavaScript client SDK for Layers Analytics. Suitable for web or Node environments.
4
+
5
+ ## Install
6
+
7
+ ```
8
+ npm install @layers/client
9
+ # or
10
+ pnpm add @layers/client
11
+ ```
12
+
13
+ ## Quick start
14
+
15
+ ```ts
16
+ import { LayersClient } from '@layers/client';
17
+
18
+ const client = new LayersClient({
19
+ apiKey: 'your-api-key',
20
+ appId: 'your-app-id',
21
+ environment: 'production',
22
+ enableDebug: true
23
+ });
24
+
25
+ await client.init();
26
+ await client.track('app_open', { source: 'home' });
27
+ ```
28
+
29
+ ## Links
30
+
31
+ - Repo: https://github.com/layersai/sdks
32
+ - Issues: https://github.com/layersai/sdks/issues
33
+
34
+ ## License
35
+
36
+ ISC
@@ -0,0 +1,370 @@
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
+ constructor(config: LayersConfig);
317
+ private generateSessionId;
318
+ private initializeDeviceInfo;
319
+ init(): Promise<void>;
320
+ private fetchRemoteConfig;
321
+ private extractRemoteConfigSettings;
322
+ private setupNetworkListener;
323
+ setOnlineState(isOnline: boolean): void;
324
+ private hydrateQueue;
325
+ private persistQueue;
326
+ track(eventName: string, properties?: EventData): Promise<void>;
327
+ private shouldProcessEvent;
328
+ private passesSampling;
329
+ private resolveAppUserId;
330
+ screen(screenName: string, properties?: EventData): Promise<void>;
331
+ setUserProperties(properties: UserProperties): Promise<void>;
332
+ isAnalyticsEnabled(): boolean;
333
+ isAdvertisingEnabled(): boolean;
334
+ setConsent(consent: ConsentOptions): Promise<void>;
335
+ private enqueue;
336
+ private scheduleQueueFlush;
337
+ private processQueue;
338
+ private dropExpiredQueueItems;
339
+ private getBackoffDelay;
340
+ private resetRateLimitState;
341
+ private getEventRateCounter;
342
+ private normalizeRateLimitRule;
343
+ private passesRateLimit;
344
+ private canConsumeRateLimit;
345
+ private canConsumeWindow;
346
+ private recordRateLimit;
347
+ private incrementWindow;
348
+ private getWindowInfo;
349
+ private makeRequest;
350
+ private isRetryableStatus;
351
+ private generateEventId;
352
+ private generateBatchId;
353
+ getRemoteConfig(): RemoteConfig | null;
354
+ getRemoteConfigVersion(): string | undefined;
355
+ getConsentState(): Required<ConsentOptions>;
356
+ setAppUserId(appUserId: string | undefined): void;
357
+ /** @deprecated Use setAppUserId instead */
358
+ setUserId(userId: string): void;
359
+ getConfig(): LayersConfig;
360
+ getAppUserId(): string | undefined;
361
+ /** @deprecated Use getAppUserId instead */
362
+ getUserId(): string | undefined;
363
+ getSessionId(): string;
364
+ flush(): Promise<void>;
365
+ setDeviceInfo(deviceInfo: Partial<BaseEvent>): void;
366
+ startNewSession(): void;
367
+ }
368
+ //#endregion
369
+ 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 };
370
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/api-types.ts","../src/index.ts"],"sourcesContent":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,586 @@
1
+ //#region src/index.ts
2
+ const SDK_VERSION = "0.0.1";
3
+ const DEFAULT_BASE_URL = "http://c9badd27.layers.com";
4
+ const SYSTEM_EVENTS = new Set(["consent_updated", "att_status_changed"]);
5
+ const DEFAULT_CONSENT = {
6
+ advertising: true,
7
+ analytics: true
8
+ };
9
+ const DEFAULT_QUEUE_CONFIG = {
10
+ flushIntervalMs: 1e4,
11
+ maxQueueSize: 200,
12
+ maxItemAgeMs: 5 * 6e4,
13
+ requestTimeoutMs: 1e4,
14
+ maxRetries: 5,
15
+ baseRetryDelayMs: 1e3,
16
+ maxRetryDelayMs: 3e4
17
+ };
18
+ function now() {
19
+ return Date.now();
20
+ }
21
+ function generateId(prefix) {
22
+ return `${prefix}_${Math.random().toString(36).slice(2)}_${now()}`;
23
+ }
24
+ var LayersClient = class {
25
+ config;
26
+ remoteConfig = null;
27
+ remoteConfigMeta = {};
28
+ eventQueue = [];
29
+ isOnline = true;
30
+ consentState = { ...DEFAULT_CONSENT };
31
+ sessionId;
32
+ deviceInfo = {};
33
+ queueTimer = null;
34
+ queueConfig;
35
+ queueStorage;
36
+ rateLimitState = {
37
+ global: {},
38
+ perEvent: /* @__PURE__ */ new Map()
39
+ };
40
+ constructor(config) {
41
+ this.queueConfig = {
42
+ ...DEFAULT_QUEUE_CONFIG,
43
+ ...config.queueOptions || {}
44
+ };
45
+ this.queueStorage = config.queueStorage ?? createDefaultQueueStorage(config.appId);
46
+ const resolvedAppUserId = config.appUserId;
47
+ this.config = {
48
+ ...config,
49
+ baseUrl: (config.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, ""),
50
+ appUserId: resolvedAppUserId
51
+ };
52
+ this.sessionId = this.generateSessionId();
53
+ this.resetRateLimitState();
54
+ this.initializeDeviceInfo();
55
+ }
56
+ generateSessionId() {
57
+ return generateId("sess");
58
+ }
59
+ initializeDeviceInfo() {
60
+ this.deviceInfo = {
61
+ platform: "react-native",
62
+ os_version: "unknown",
63
+ app_version: "unknown",
64
+ locale: "en-US",
65
+ device_model: "unknown"
66
+ };
67
+ }
68
+ async init() {
69
+ await this.hydrateQueue();
70
+ await this.fetchRemoteConfig();
71
+ this.setupNetworkListener();
72
+ this.scheduleQueueFlush(this.eventQueue.length > 0);
73
+ }
74
+ async fetchRemoteConfig(force = false) {
75
+ const expired = !this.remoteConfigMeta.expiresAt || this.remoteConfigMeta.expiresAt < now();
76
+ if (!force && this.remoteConfig && !expired) return;
77
+ try {
78
+ const response = await this.makeRequest("/config", { method: "GET" });
79
+ if (!response?.config) return;
80
+ this.remoteConfig = response.config;
81
+ const ttlMs = (response.cache_ttl ?? 300) * 1e3;
82
+ this.remoteConfigMeta = {
83
+ version: response.version,
84
+ expiresAt: now() + ttlMs,
85
+ ...this.extractRemoteConfigSettings(response)
86
+ };
87
+ this.resetRateLimitState();
88
+ } catch (error) {
89
+ if (this.config.enableDebug) console.warn("Failed to fetch remote config:", error);
90
+ }
91
+ }
92
+ extractRemoteConfigSettings(envelope) {
93
+ const meta = {};
94
+ const config = envelope.config ?? {};
95
+ if (config.privacy) {
96
+ if (typeof config.privacy.analytics_enabled === "boolean") meta.analyticsEnabled = config.privacy.analytics_enabled;
97
+ if (typeof config.privacy.advertising_enabled === "boolean") meta.advertisingEnabled = config.privacy.advertising_enabled;
98
+ if (config.privacy.killswitches) {
99
+ const killswitches = new Set(config.privacy.killswitches);
100
+ if (killswitches.has("analytics")) meta.analyticsEnabled = false;
101
+ if (killswitches.has("advertising")) meta.advertisingEnabled = false;
102
+ }
103
+ }
104
+ if (config.events) {
105
+ if (Array.isArray(config.events.allowlist)) meta.eventAllowlist = new Set(config.events.allowlist);
106
+ if (Array.isArray(config.events.denylist)) meta.eventDenylist = new Set(config.events.denylist);
107
+ const samplingEntries = {};
108
+ if (config.events.sampling) for (const [eventName, value] of Object.entries(config.events.sampling)) {
109
+ const numeric = Number(value);
110
+ if (Number.isFinite(numeric)) samplingEntries[eventName] = Math.max(0, Math.min(1, numeric));
111
+ }
112
+ if (typeof config.events.sampling_rate === "number") samplingEntries["*"] = Math.max(0, Math.min(1, config.events.sampling_rate));
113
+ if (Object.keys(samplingEntries).length > 0) meta.samplingRates = samplingEntries;
114
+ const rateLimitConfig = config.events.rate_limit;
115
+ if (rateLimitConfig) {
116
+ const globalRule = this.normalizeRateLimitRule(rateLimitConfig);
117
+ let perEventRules;
118
+ if (rateLimitConfig.per_event) {
119
+ const entries = Object.entries(rateLimitConfig.per_event);
120
+ if (entries.length > 0) {
121
+ perEventRules = /* @__PURE__ */ new Map();
122
+ for (const [eventName, ruleConfig] of entries) {
123
+ const rule = this.normalizeRateLimitRule(ruleConfig);
124
+ if (rule) perEventRules.set(eventName, rule);
125
+ }
126
+ if (perEventRules.size === 0) perEventRules = void 0;
127
+ }
128
+ }
129
+ const rateLimits = {};
130
+ if (globalRule) rateLimits.global = globalRule;
131
+ if (perEventRules) rateLimits.perEvent = perEventRules;
132
+ if (rateLimits.global || rateLimits.perEvent) meta.rateLimits = rateLimits;
133
+ }
134
+ }
135
+ return meta;
136
+ }
137
+ setupNetworkListener() {
138
+ if (typeof window !== "undefined" && typeof window.addEventListener === "function") {
139
+ window.addEventListener("online", () => {
140
+ this.setOnlineState(true);
141
+ });
142
+ window.addEventListener("offline", () => {
143
+ this.setOnlineState(false);
144
+ });
145
+ }
146
+ }
147
+ setOnlineState(isOnline) {
148
+ if (this.isOnline === isOnline) return;
149
+ this.isOnline = isOnline;
150
+ if (isOnline) this.scheduleQueueFlush(true);
151
+ }
152
+ async hydrateQueue() {
153
+ if (!this.queueStorage) return;
154
+ try {
155
+ const snapshot = await this.queueStorage.load();
156
+ if (!snapshot || !Array.isArray(snapshot.items) || snapshot.items.length === 0) {
157
+ if (snapshot) await this.queueStorage.clear();
158
+ return;
159
+ }
160
+ const nowTs = now();
161
+ const maxAge = this.queueConfig.maxItemAgeMs;
162
+ const hydrated = [];
163
+ for (const item of snapshot.items) if (item && typeof item.endpoint === "string") {
164
+ const queuedAt = typeof item.queuedAt === "number" ? item.queuedAt : nowTs;
165
+ if (nowTs - queuedAt <= maxAge) hydrated.push({
166
+ endpoint: item.endpoint,
167
+ data: item.data,
168
+ attempts: typeof item.attempts === "number" ? item.attempts : 0,
169
+ queuedAt,
170
+ nextAttemptAt: typeof item.nextAttemptAt === "number" ? item.nextAttemptAt : nowTs,
171
+ requestId: typeof item.requestId === "string" ? item.requestId : generateId("req"),
172
+ retryable: item.retryable !== false
173
+ });
174
+ }
175
+ if (hydrated.length === 0) {
176
+ await this.queueStorage.clear();
177
+ this.eventQueue = [];
178
+ return;
179
+ }
180
+ const limited = hydrated.slice(0, this.queueConfig.maxQueueSize);
181
+ const trimmed = limited.length !== hydrated.length;
182
+ this.eventQueue = limited;
183
+ if (trimmed || limited.length !== snapshot.items.length) await this.persistQueue();
184
+ } catch (error) {
185
+ if (this.config.enableDebug) console.warn("Failed to hydrate queue", error);
186
+ }
187
+ }
188
+ async persistQueue() {
189
+ if (!this.queueStorage) return;
190
+ const snapshot = {
191
+ version: 1,
192
+ items: this.eventQueue.map((item) => ({
193
+ endpoint: item.endpoint,
194
+ data: item.data,
195
+ attempts: item.attempts,
196
+ queuedAt: item.queuedAt,
197
+ nextAttemptAt: item.nextAttemptAt,
198
+ requestId: item.requestId,
199
+ retryable: item.retryable
200
+ }))
201
+ };
202
+ try {
203
+ if (snapshot.items.length === 0) await this.queueStorage.clear();
204
+ else await this.queueStorage.save(snapshot);
205
+ } catch (error) {
206
+ if (this.config.enableDebug) console.warn("Failed to persist queue", error);
207
+ }
208
+ }
209
+ async track(eventName, properties) {
210
+ if (!this.shouldProcessEvent(eventName)) {
211
+ if (this.config.enableDebug) console.log(`Event '${eventName}' dropped due to consent or config gating`);
212
+ return;
213
+ }
214
+ if (!this.passesSampling(eventName)) {
215
+ if (this.config.enableDebug) console.log(`Event '${eventName}' sampled out`);
216
+ return;
217
+ }
218
+ const batchPayload = {
219
+ events: [{
220
+ event: eventName,
221
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
222
+ event_id: this.generateEventId(),
223
+ app_id: this.config.appId,
224
+ session_id: this.sessionId,
225
+ environment: this.config.environment,
226
+ platform: this.deviceInfo.platform,
227
+ os_version: this.deviceInfo.os_version,
228
+ app_version: this.deviceInfo.app_version,
229
+ device_model: this.deviceInfo.device_model,
230
+ locale: this.deviceInfo.locale,
231
+ ...this.deviceInfo,
232
+ properties: properties || {},
233
+ ...this.resolveAppUserId() && { app_user_id: this.resolveAppUserId() }
234
+ }],
235
+ batch_id: this.generateBatchId(),
236
+ sent_at: (/* @__PURE__ */ new Date()).toISOString()
237
+ };
238
+ await this.enqueue("/events", batchPayload);
239
+ }
240
+ shouldProcessEvent(eventName) {
241
+ if (SYSTEM_EVENTS.has(eventName)) return true;
242
+ if (!this.isAnalyticsEnabled()) return false;
243
+ if (this.remoteConfigMeta.eventAllowlist && !this.remoteConfigMeta.eventAllowlist.has(eventName)) return false;
244
+ if (this.remoteConfigMeta.eventDenylist && this.remoteConfigMeta.eventDenylist.has(eventName)) return false;
245
+ if (!this.passesRateLimit(eventName)) return false;
246
+ return true;
247
+ }
248
+ passesSampling(eventName) {
249
+ const sampling = this.remoteConfigMeta.samplingRates;
250
+ if (!sampling) return true;
251
+ const rate = sampling[eventName] ?? sampling["*"];
252
+ if (typeof rate !== "number") return true;
253
+ return Math.random() <= Math.max(0, Math.min(1, rate));
254
+ }
255
+ resolveAppUserId() {
256
+ return this.config.appUserId;
257
+ }
258
+ async screen(screenName, properties) {
259
+ await this.track("screen_view", {
260
+ screen_name: screenName,
261
+ ...properties
262
+ });
263
+ }
264
+ async setUserProperties(properties) {
265
+ if (!this.isAnalyticsEnabled()) return;
266
+ const payload = {
267
+ app_id: this.config.appId,
268
+ properties,
269
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
270
+ ...this.resolveAppUserId() && { app_user_id: this.resolveAppUserId() }
271
+ };
272
+ await this.enqueue("/users/properties", payload);
273
+ }
274
+ isAnalyticsEnabled() {
275
+ if (this.remoteConfigMeta.analyticsEnabled === false) return false;
276
+ return this.consentState.analytics !== false;
277
+ }
278
+ isAdvertisingEnabled() {
279
+ if (this.remoteConfigMeta.advertisingEnabled === false) return false;
280
+ return this.consentState.advertising !== false;
281
+ }
282
+ async setConsent(consent) {
283
+ this.consentState = {
284
+ advertising: typeof consent.advertising === "boolean" ? consent.advertising : this.consentState.advertising,
285
+ analytics: typeof consent.analytics === "boolean" ? consent.analytics : this.consentState.analytics
286
+ };
287
+ const payload = {
288
+ app_id: this.config.appId,
289
+ consent: { ...this.consentState },
290
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
291
+ ...this.resolveAppUserId() && { app_user_id: this.resolveAppUserId() }
292
+ };
293
+ await this.enqueue("/consent", payload);
294
+ }
295
+ async enqueue(endpoint, data, options) {
296
+ const retryable = options?.retryable !== false;
297
+ if (this.eventQueue.length >= this.queueConfig.maxQueueSize) this.eventQueue.shift();
298
+ this.eventQueue.push({
299
+ endpoint,
300
+ data,
301
+ attempts: 0,
302
+ queuedAt: now(),
303
+ nextAttemptAt: now(),
304
+ requestId: generateId("req"),
305
+ retryable
306
+ });
307
+ if (this.isOnline) this.scheduleQueueFlush(true);
308
+ else this.scheduleQueueFlush();
309
+ this.persistQueue();
310
+ }
311
+ scheduleQueueFlush(immediate = false) {
312
+ if (this.queueTimer) {
313
+ clearTimeout(this.queueTimer);
314
+ this.queueTimer = null;
315
+ }
316
+ const delay = immediate ? 0 : this.queueConfig.flushIntervalMs;
317
+ this.queueTimer = setTimeout(() => {
318
+ this.queueTimer = null;
319
+ this.processQueue();
320
+ }, delay);
321
+ }
322
+ async processQueue() {
323
+ if (!this.isOnline || this.eventQueue.length === 0) {
324
+ this.scheduleQueueFlush();
325
+ return;
326
+ }
327
+ this.dropExpiredQueueItems();
328
+ const pending = [];
329
+ for (const item of this.eventQueue) if (item.nextAttemptAt > now()) pending.push(item);
330
+ else {
331
+ let shouldRequeue = false;
332
+ try {
333
+ await this.makeRequest(item.endpoint, {
334
+ method: "POST",
335
+ body: JSON.stringify(item.data)
336
+ }, item.requestId);
337
+ } catch (err) {
338
+ if (!item.retryable) {
339
+ if (this.config.enableDebug) console.warn("Dropping non-retryable item", {
340
+ endpoint: item.endpoint,
341
+ error: err
342
+ });
343
+ } else {
344
+ item.attempts += 1;
345
+ if (item.attempts > this.queueConfig.maxRetries) {
346
+ if (this.config.enableDebug) console.warn("Dropping item after max retries", {
347
+ endpoint: item.endpoint,
348
+ error: err
349
+ });
350
+ } else {
351
+ item.nextAttemptAt = now() + this.getBackoffDelay(item.attempts);
352
+ shouldRequeue = true;
353
+ if (this.config.enableDebug) console.warn("Retry scheduled", {
354
+ endpoint: item.endpoint,
355
+ nextAttemptAt: item.nextAttemptAt,
356
+ error: err
357
+ });
358
+ }
359
+ }
360
+ }
361
+ if (shouldRequeue) pending.push(item);
362
+ }
363
+ this.eventQueue = pending;
364
+ await this.persistQueue();
365
+ this.scheduleQueueFlush(this.eventQueue.length > 0);
366
+ }
367
+ dropExpiredQueueItems() {
368
+ const cutoff = now() - this.queueConfig.maxItemAgeMs;
369
+ if (this.eventQueue.length === 0) return;
370
+ const retained = this.eventQueue.filter((item) => item.queuedAt >= cutoff);
371
+ if (this.config.enableDebug && retained.length !== this.eventQueue.length) console.warn("Dropped stale queue items", { dropped: this.eventQueue.length - retained.length });
372
+ this.eventQueue = retained;
373
+ this.persistQueue();
374
+ }
375
+ getBackoffDelay(attempt) {
376
+ const jitter = Math.random() * 250;
377
+ const delay = this.queueConfig.baseRetryDelayMs * 2 ** (attempt - 1);
378
+ return Math.min(delay + jitter, this.queueConfig.maxRetryDelayMs);
379
+ }
380
+ resetRateLimitState() {
381
+ this.rateLimitState = {
382
+ global: {},
383
+ perEvent: /* @__PURE__ */ new Map()
384
+ };
385
+ }
386
+ getEventRateCounter(eventName) {
387
+ let counter = this.rateLimitState.perEvent.get(eventName);
388
+ if (!counter) {
389
+ counter = {};
390
+ this.rateLimitState.perEvent.set(eventName, counter);
391
+ }
392
+ return counter;
393
+ }
394
+ normalizeRateLimitRule(input) {
395
+ if (!input) return;
396
+ const rule = {};
397
+ if (typeof input.per_minute === "number" && input.per_minute > 0) rule.perMinute = Math.floor(input.per_minute);
398
+ if (typeof input.per_hour === "number" && input.per_hour > 0) rule.perHour = Math.floor(input.per_hour);
399
+ return Object.keys(rule).length > 0 ? rule : void 0;
400
+ }
401
+ passesRateLimit(eventName) {
402
+ if (SYSTEM_EVENTS.has(eventName)) return true;
403
+ const { rateLimits } = this.remoteConfigMeta;
404
+ if (!rateLimits) return true;
405
+ const timestamp = now();
406
+ const perEventRules = rateLimits.perEvent;
407
+ const eventRule = perEventRules?.get(eventName) ?? perEventRules?.get("*");
408
+ if (eventRule && !this.canConsumeRateLimit(eventRule, this.getEventRateCounter(eventName), timestamp)) {
409
+ if (this.config.enableDebug) console.warn("Rate limit reached for event", { event: eventName });
410
+ return false;
411
+ }
412
+ if (rateLimits.global && !this.canConsumeRateLimit(rateLimits.global, this.rateLimitState.global, timestamp)) {
413
+ if (this.config.enableDebug) console.warn("Global rate limit reached", { event: eventName });
414
+ return false;
415
+ }
416
+ if (eventRule) this.recordRateLimit(eventRule, this.getEventRateCounter(eventName), timestamp);
417
+ if (rateLimits.global) this.recordRateLimit(rateLimits.global, this.rateLimitState.global, timestamp);
418
+ return true;
419
+ }
420
+ canConsumeRateLimit(rule, counter, timestamp) {
421
+ return this.canConsumeWindow(rule.perMinute, counter, "perMinute", 6e4, timestamp) && this.canConsumeWindow(rule.perHour, counter, "perHour", 36e5, timestamp);
422
+ }
423
+ canConsumeWindow(limit, counter, key, windowMs, timestamp) {
424
+ if (limit === void 0) return true;
425
+ const { count } = this.getWindowInfo(counter, key, windowMs, timestamp);
426
+ return count + 1 <= limit;
427
+ }
428
+ recordRateLimit(rule, counter, timestamp) {
429
+ this.incrementWindow(rule.perMinute, counter, "perMinute", 6e4, timestamp);
430
+ this.incrementWindow(rule.perHour, counter, "perHour", 36e5, timestamp);
431
+ }
432
+ incrementWindow(limit, counter, key, windowMs, timestamp) {
433
+ if (limit === void 0) return;
434
+ const windowStart = Math.floor(timestamp / windowMs) * windowMs;
435
+ const existing = counter[key];
436
+ if (!existing || existing.windowStart !== windowStart) counter[key] = {
437
+ windowStart,
438
+ count: 1
439
+ };
440
+ else existing.count += 1;
441
+ }
442
+ getWindowInfo(counter, key, windowMs, timestamp) {
443
+ const windowStart = Math.floor(timestamp / windowMs) * windowMs;
444
+ const existing = counter[key];
445
+ if (!existing || existing.windowStart !== windowStart) return {
446
+ windowStart,
447
+ count: 0
448
+ };
449
+ return existing;
450
+ }
451
+ async makeRequest(endpoint, options, requestId = generateId("req")) {
452
+ const url = `${this.config.baseUrl}${endpoint}`;
453
+ const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
454
+ const headers = {
455
+ "Content-Type": "application/json",
456
+ "X-Api-Key": this.config.apiKey,
457
+ "X-App-Id": this.config.appId,
458
+ "X-Environment": this.config.environment,
459
+ "X-SDK-Version": SDK_VERSION,
460
+ "X-Request-Id": requestId,
461
+ ...options.headers
462
+ };
463
+ if (this.sessionId) headers["X-Session-Id"] = this.sessionId;
464
+ let timeoutHandle;
465
+ if (controller) {
466
+ options = {
467
+ ...options,
468
+ signal: controller.signal
469
+ };
470
+ timeoutHandle = setTimeout(() => {
471
+ controller.abort();
472
+ }, this.queueConfig.requestTimeoutMs);
473
+ }
474
+ try {
475
+ const response = await fetch(url, {
476
+ ...options,
477
+ headers
478
+ });
479
+ if (!response.ok) {
480
+ if (!this.isRetryableStatus(response.status)) throw Object.assign(/* @__PURE__ */ new Error(`HTTP ${response.status}: ${response.statusText}`), { status: response.status });
481
+ throw Object.assign(/* @__PURE__ */ new Error(`Retryable HTTP ${response.status}`), { status: response.status });
482
+ }
483
+ if (response.status === 204) return;
484
+ const text = await response.text();
485
+ if (!text) return;
486
+ return JSON.parse(text);
487
+ } finally {
488
+ if (timeoutHandle) clearTimeout(timeoutHandle);
489
+ }
490
+ }
491
+ isRetryableStatus(status) {
492
+ if (status === 429 || status >= 500 && status < 600) return true;
493
+ return false;
494
+ }
495
+ generateEventId() {
496
+ return generateId("evt");
497
+ }
498
+ generateBatchId() {
499
+ return generateId("batch");
500
+ }
501
+ getRemoteConfig() {
502
+ return this.remoteConfig;
503
+ }
504
+ getRemoteConfigVersion() {
505
+ return this.remoteConfigMeta.version;
506
+ }
507
+ getConsentState() {
508
+ return { ...this.consentState };
509
+ }
510
+ setAppUserId(appUserId) {
511
+ this.config.appUserId = appUserId;
512
+ }
513
+ /** @deprecated Use setAppUserId instead */
514
+ setUserId(userId) {
515
+ this.setAppUserId(userId);
516
+ }
517
+ getConfig() {
518
+ return { ...this.config };
519
+ }
520
+ getAppUserId() {
521
+ return this.resolveAppUserId();
522
+ }
523
+ /** @deprecated Use getAppUserId instead */
524
+ getUserId() {
525
+ return this.resolveAppUserId();
526
+ }
527
+ getSessionId() {
528
+ return this.sessionId;
529
+ }
530
+ async flush() {
531
+ await this.processQueue();
532
+ }
533
+ setDeviceInfo(deviceInfo) {
534
+ this.deviceInfo = {
535
+ ...this.deviceInfo,
536
+ ...Object.fromEntries(Object.entries(deviceInfo).filter(([, value]) => value !== void 0 && value !== null))
537
+ };
538
+ }
539
+ startNewSession() {
540
+ this.sessionId = this.generateSessionId();
541
+ }
542
+ };
543
+ function createDefaultQueueStorage(appId) {
544
+ if (typeof window === "undefined") return null;
545
+ try {
546
+ if (!window.localStorage) return null;
547
+ } catch {
548
+ return null;
549
+ }
550
+ const storageKey = `layers_queue_${appId}`;
551
+ return createLocalStorageQueueStorage(storageKey);
552
+ }
553
+ function createLocalStorageQueueStorage(storageKey) {
554
+ return {
555
+ async load() {
556
+ try {
557
+ const raw = window.localStorage.getItem(storageKey);
558
+ if (!raw) return null;
559
+ const parsed = JSON.parse(raw);
560
+ if (!parsed || typeof parsed !== "object") return null;
561
+ return parsed;
562
+ } catch (error) {
563
+ console.warn("Failed to load queue from localStorage", error);
564
+ return null;
565
+ }
566
+ },
567
+ async save(snapshot) {
568
+ try {
569
+ window.localStorage.setItem(storageKey, JSON.stringify(snapshot));
570
+ } catch (error) {
571
+ console.warn("Failed to persist queue to localStorage", error);
572
+ }
573
+ },
574
+ async clear() {
575
+ try {
576
+ window.localStorage.removeItem(storageKey);
577
+ } catch (error) {
578
+ console.warn("Failed to clear queue storage", error);
579
+ }
580
+ }
581
+ };
582
+ }
583
+
584
+ //#endregion
585
+ export { LayersClient };
586
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
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"],"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\nconst SDK_VERSION = '0.0.1';\nconst DEFAULT_BASE_URL = 'http://c9badd27.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 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.config.enableDebug) {\n console.warn('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.config.enableDebug) {\n console.warn('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.config.enableDebug) {\n console.warn('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.config.enableDebug) {\n console.log(`Event '${eventName}' dropped due to consent or config gating`);\n }\n return;\n }\n\n if (!this.passesSampling(eventName)) {\n if (this.config.enableDebug) {\n console.log(`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 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.config.enableDebug) {\n console.warn('Dropping non-retryable item', { endpoint: item.endpoint, error: err });\n }\n } else {\n item.attempts += 1;\n if (item.attempts > this.queueConfig.maxRetries) {\n if (this.config.enableDebug) {\n console.warn('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.config.enableDebug) {\n console.warn('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.config.enableDebug && retained.length !== this.eventQueue.length) {\n console.warn('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.config.enableDebug) {\n console.warn('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.config.enableDebug) {\n console.warn('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 const response = await fetch(url, {\n ...options,\n headers\n });\n\n if (!response.ok) {\n if (!this.isRetryableStatus(response.status)) {\n throw Object.assign(new Error(`HTTP ${response.status}: ${response.statusText}`), {\n status: response.status\n });\n }\n throw Object.assign(new Error(`Retryable HTTP ${response.status}`), {\n status: response.status\n });\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 return JSON.parse(text) as T;\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 }\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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"}
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@layers/client",
3
+ "version": "0.1.0-alpha.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "description": "Javascript Client SDK for Layers Analytics",
8
+ "license": "ISC",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/layersai/sdks.git",
12
+ "directory": "packages/client"
13
+ },
14
+ "author": "Layers Team",
15
+ "homepage": "https://github.com/layersai/sdks#readme",
16
+ "bugs": {
17
+ "url": "https://github.com/layersai/sdks/issues"
18
+ },
19
+ "type": "module",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "default": "./dist/index.js"
24
+ }
25
+ },
26
+ "files": [
27
+ "dist"
28
+ ],
29
+ "keywords": [],
30
+ "types": "./dist/index.d.ts",
31
+ "sideEffects": false,
32
+ "scripts": {
33
+ "build": "tsdown src/index.ts --format esm --dts"
34
+ }
35
+ }