@wireai/activation 0.1.1 → 0.3.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.
@@ -1,12 +1,13 @@
1
1
  export { R as ReportAppEventOptions, r as reportAppEvent } from '../transport-BeO_Brcu.mjs';
2
- export { A as AnalyticsEvent, C as ClientEvent, a as ClientEventTarget, b as ClientEventType, W as WIRE_ONBOARDING_EVENTS, c as WireOnboardingEventName, m as makeSessionId, r as reportClientEvent, d as reportClientEvents, t as toAnalyticsEvent } from '../analyticsEvent-B8v3BZjM.mjs';
2
+ import { E as EventQueueOptions } from '../eventQueue-CA1d8Fmn.mjs';
3
+ export { A as AnalyticsEvent, C as ClientEvent, a as ClientEventTarget, b as ClientEventType, c as ContextEnvelope, d as ContextEnvelopeInput, e as EnvelopeSource, f as EventQueue, W as WIRE_ONBOARDING_EVENTS, g as WireOnboardingEventName, h as buildContextEnvelope, i as createEventQueue, m as makeSessionId, r as reportClientEvent, j as reportClientEvents, t as toAnalyticsEvent } from '../eventQueue-CA1d8Fmn.mjs';
3
4
  import '../types-A6pTxIZV.mjs';
4
5
  import '../types-BKfpdZzX.mjs';
5
6
  import '../types-GL_hQ0TN.mjs';
6
7
  import '../types-CMuOexw0.mjs';
7
- import 'react-native';
8
8
  import 'react';
9
9
  import 'wireai-rn';
10
+ import 'react-native';
10
11
 
11
12
  /** A single route inside a React-Navigation-shaped state (structural — no `@react-navigation`). */
12
13
  interface NavigationRouteLike {
@@ -89,4 +90,70 @@ interface NavigationRefLike {
89
90
  */
90
91
  declare const useScreenTracking: (navigationRef: NavigationRefLike | undefined, options?: ScreenTrackerOptions) => void;
91
92
 
92
- export { type NavigationRefLike, type NavigationRouteLike, type NavigationStateLike, type ScreenTracker, type ScreenTrackerOptions, createScreenTracker, getActiveRouteName, screenTrackingHandler, useScreenTracking };
93
+ /** Arbitrary non-PII event properties. Serialized to the event's `meta` (a JSON string) on the wire. */
94
+ type AnalyticsProps = Record<string, unknown>;
95
+ /**
96
+ * Tenant transport + context inputs for {@link createAnalytics}. `serverUrl`/`apiKey` are the
97
+ * SAME creds as onboarding (never a second key). The rest feed the context envelope + the queue's
98
+ * offline persistence — all optional.
99
+ */
100
+ type CreateAnalyticsConfig = {
101
+ /** Base server URL (same as `WireOnboardingConfig.serverUrl`); `/v1/events` is appended. */
102
+ serverUrl: string;
103
+ /** Tenant API key; sent as `Authorization: Bearer`. */
104
+ apiKey: string;
105
+ /**
106
+ * Correlation id shared by every event from this instance (and the `identify` event). Defaults
107
+ * to a fresh `makeSessionId()` at creation so all events agree on one id per analytics instance.
108
+ */
109
+ sessionId?: string;
110
+ /** Tenant/app id used to namespace the queue's default storage key (`wireai:evtq:<appId>`). */
111
+ appId?: string;
112
+ /**
113
+ * Host persistence (AsyncStorage-compatible subset) for offline-first durability. When omitted,
114
+ * the queue runs in the documented in-memory mode (survives re-renders, not app kills).
115
+ */
116
+ storage?: EventQueueOptions["storage"];
117
+ /** Host app version, e.g. "1.4.2" (host-injected; stamped onto every event's context). */
118
+ appVersion?: string;
119
+ /** Host native build number, e.g. "412" (host-injected). */
120
+ appBuild?: string;
121
+ /** Host connectivity signal, e.g. "wifi" | "cellular" — read fresh per event via the provider. */
122
+ networkType?: string;
123
+ };
124
+ /** Optional queue tuning knobs, forwarded verbatim to {@link createEventQueue}. */
125
+ type AnalyticsOptions = Partial<Pick<EventQueueOptions, "maxSize" | "batchSize" | "baseBackoffMs" | "maxBackoffMs" | "maxRetries">>;
126
+ /**
127
+ * The developer-facing analytics surface. `track`/`screen`/`identify` are the Segment/PostHog-shaped
128
+ * API; `flush`/`notifyOnline`/`size` expose the underlying queue so a host can drive reconnect
129
+ * draining (load-bearing for offline-first) and inspect the pending buffer.
130
+ */
131
+ type Analytics = {
132
+ /** Record an in-app event: `event_type='app_event'`, `question_key=<event>`, `props`→`meta`. */
133
+ track(event: string, props?: AnalyticsProps): void;
134
+ /** Record a screen view: `event_type='app_event'`, `question_key='screen'`, `meta={ screen, ...props }`. */
135
+ screen(name: string, props?: AnalyticsProps): void;
136
+ /** Bind the host's opaque user id (per-session, in-memory) and emit an `identify` event. */
137
+ identify(userId: string, traits?: AnalyticsProps): void;
138
+ /** Attempt an immediate drain of the pending buffer. Fire-and-forget. */
139
+ flush(): void;
140
+ /** Host reconnect signal: reset backoff and drain now. Fire-and-forget. */
141
+ notifyOnline(): void;
142
+ /** Current pending (in-memory) count. */
143
+ size(): number;
144
+ };
145
+ /**
146
+ * Create a bound analytics instance. Seeds one correlation `sessionId`, builds an offline-first
147
+ * queue over the tenant transport, and passes a fresh-per-event context envelope PROVIDER so the
148
+ * connectivity type is read at enqueue time. The bound user id starts unset (see `identify`).
149
+ */
150
+ declare const createAnalytics: (config: CreateAnalyticsConfig, options?: AnalyticsOptions) => Analytics;
151
+
152
+ /**
153
+ * Build a per-mount analytics instance. `config`/`options` are read once at first render (the
154
+ * instance is stable for the component's lifetime, held in a ref). Returns the {@link Analytics}
155
+ * surface so the component can `track` / `screen` / `identify` and drive `notifyOnline` on reconnect.
156
+ */
157
+ declare const useAnalytics: (config: CreateAnalyticsConfig, options?: AnalyticsOptions) => Analytics;
158
+
159
+ export { type Analytics, type AnalyticsOptions, type AnalyticsProps, type CreateAnalyticsConfig, EventQueueOptions, type NavigationRefLike, type NavigationRouteLike, type NavigationStateLike, type ScreenTracker, type ScreenTrackerOptions, createAnalytics, createScreenTracker, getActiveRouteName, screenTrackingHandler, useAnalytics, useScreenTracking };
@@ -1,12 +1,13 @@
1
1
  export { R as ReportAppEventOptions, r as reportAppEvent } from '../transport-DLpd1v5_.js';
2
- export { A as AnalyticsEvent, C as ClientEvent, a as ClientEventTarget, b as ClientEventType, W as WIRE_ONBOARDING_EVENTS, c as WireOnboardingEventName, m as makeSessionId, r as reportClientEvent, d as reportClientEvents, t as toAnalyticsEvent } from '../analyticsEvent-DvjB92kK.js';
2
+ import { E as EventQueueOptions } from '../eventQueue-CrNB9gzH.js';
3
+ export { A as AnalyticsEvent, C as ClientEvent, a as ClientEventTarget, b as ClientEventType, c as ContextEnvelope, d as ContextEnvelopeInput, e as EnvelopeSource, f as EventQueue, W as WIRE_ONBOARDING_EVENTS, g as WireOnboardingEventName, h as buildContextEnvelope, i as createEventQueue, m as makeSessionId, r as reportClientEvent, j as reportClientEvents, t as toAnalyticsEvent } from '../eventQueue-CrNB9gzH.js';
3
4
  import '../types-BhpXJGlg.js';
4
5
  import '../types-BKfpdZzX.js';
5
6
  import '../types-GL_hQ0TN.js';
6
7
  import '../types-CMuOexw0.js';
7
- import 'react-native';
8
8
  import 'react';
9
9
  import 'wireai-rn';
10
+ import 'react-native';
10
11
 
11
12
  /** A single route inside a React-Navigation-shaped state (structural — no `@react-navigation`). */
12
13
  interface NavigationRouteLike {
@@ -89,4 +90,70 @@ interface NavigationRefLike {
89
90
  */
90
91
  declare const useScreenTracking: (navigationRef: NavigationRefLike | undefined, options?: ScreenTrackerOptions) => void;
91
92
 
92
- export { type NavigationRefLike, type NavigationRouteLike, type NavigationStateLike, type ScreenTracker, type ScreenTrackerOptions, createScreenTracker, getActiveRouteName, screenTrackingHandler, useScreenTracking };
93
+ /** Arbitrary non-PII event properties. Serialized to the event's `meta` (a JSON string) on the wire. */
94
+ type AnalyticsProps = Record<string, unknown>;
95
+ /**
96
+ * Tenant transport + context inputs for {@link createAnalytics}. `serverUrl`/`apiKey` are the
97
+ * SAME creds as onboarding (never a second key). The rest feed the context envelope + the queue's
98
+ * offline persistence — all optional.
99
+ */
100
+ type CreateAnalyticsConfig = {
101
+ /** Base server URL (same as `WireOnboardingConfig.serverUrl`); `/v1/events` is appended. */
102
+ serverUrl: string;
103
+ /** Tenant API key; sent as `Authorization: Bearer`. */
104
+ apiKey: string;
105
+ /**
106
+ * Correlation id shared by every event from this instance (and the `identify` event). Defaults
107
+ * to a fresh `makeSessionId()` at creation so all events agree on one id per analytics instance.
108
+ */
109
+ sessionId?: string;
110
+ /** Tenant/app id used to namespace the queue's default storage key (`wireai:evtq:<appId>`). */
111
+ appId?: string;
112
+ /**
113
+ * Host persistence (AsyncStorage-compatible subset) for offline-first durability. When omitted,
114
+ * the queue runs in the documented in-memory mode (survives re-renders, not app kills).
115
+ */
116
+ storage?: EventQueueOptions["storage"];
117
+ /** Host app version, e.g. "1.4.2" (host-injected; stamped onto every event's context). */
118
+ appVersion?: string;
119
+ /** Host native build number, e.g. "412" (host-injected). */
120
+ appBuild?: string;
121
+ /** Host connectivity signal, e.g. "wifi" | "cellular" — read fresh per event via the provider. */
122
+ networkType?: string;
123
+ };
124
+ /** Optional queue tuning knobs, forwarded verbatim to {@link createEventQueue}. */
125
+ type AnalyticsOptions = Partial<Pick<EventQueueOptions, "maxSize" | "batchSize" | "baseBackoffMs" | "maxBackoffMs" | "maxRetries">>;
126
+ /**
127
+ * The developer-facing analytics surface. `track`/`screen`/`identify` are the Segment/PostHog-shaped
128
+ * API; `flush`/`notifyOnline`/`size` expose the underlying queue so a host can drive reconnect
129
+ * draining (load-bearing for offline-first) and inspect the pending buffer.
130
+ */
131
+ type Analytics = {
132
+ /** Record an in-app event: `event_type='app_event'`, `question_key=<event>`, `props`→`meta`. */
133
+ track(event: string, props?: AnalyticsProps): void;
134
+ /** Record a screen view: `event_type='app_event'`, `question_key='screen'`, `meta={ screen, ...props }`. */
135
+ screen(name: string, props?: AnalyticsProps): void;
136
+ /** Bind the host's opaque user id (per-session, in-memory) and emit an `identify` event. */
137
+ identify(userId: string, traits?: AnalyticsProps): void;
138
+ /** Attempt an immediate drain of the pending buffer. Fire-and-forget. */
139
+ flush(): void;
140
+ /** Host reconnect signal: reset backoff and drain now. Fire-and-forget. */
141
+ notifyOnline(): void;
142
+ /** Current pending (in-memory) count. */
143
+ size(): number;
144
+ };
145
+ /**
146
+ * Create a bound analytics instance. Seeds one correlation `sessionId`, builds an offline-first
147
+ * queue over the tenant transport, and passes a fresh-per-event context envelope PROVIDER so the
148
+ * connectivity type is read at enqueue time. The bound user id starts unset (see `identify`).
149
+ */
150
+ declare const createAnalytics: (config: CreateAnalyticsConfig, options?: AnalyticsOptions) => Analytics;
151
+
152
+ /**
153
+ * Build a per-mount analytics instance. `config`/`options` are read once at first render (the
154
+ * instance is stable for the component's lifetime, held in a ref). Returns the {@link Analytics}
155
+ * surface so the component can `track` / `screen` / `identify` and drive `notifyOnline` on reconnect.
156
+ */
157
+ declare const useAnalytics: (config: CreateAnalyticsConfig, options?: AnalyticsOptions) => Analytics;
158
+
159
+ export { type Analytics, type AnalyticsOptions, type AnalyticsProps, type CreateAnalyticsConfig, EventQueueOptions, type NavigationRefLike, type NavigationRouteLike, type NavigationStateLike, type ScreenTracker, type ScreenTrackerOptions, createAnalytics, createScreenTracker, getActiveRouteName, screenTrackingHandler, useAnalytics, useScreenTracking };
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var react = require('react');
4
+ var reactNative = require('react-native');
4
5
 
5
6
  // src/reviews/transport.ts
6
7
  var reportAppEvent = (target, name, options = {}) => {
@@ -135,8 +136,374 @@ var toAnalyticsEvent = (event) => {
135
136
  }
136
137
  }
137
138
  };
139
+ var deriveFormFactor = (iosIdiom, width, height) => {
140
+ if (iosIdiom === "pad") return "tablet";
141
+ if (iosIdiom === "phone") return "phone";
142
+ if (typeof width === "number" && typeof height === "number") {
143
+ return Math.min(width, height) >= 600 ? "tablet" : "phone";
144
+ }
145
+ return void 0;
146
+ };
147
+ var collectDeviceContext = () => {
148
+ var _a;
149
+ const ctx = { platform: reactNative.Platform.OS };
150
+ try {
151
+ const version = reactNative.Platform.Version;
152
+ if (version !== void 0 && version !== null && String(version)) {
153
+ ctx.osVersion = String(version);
154
+ }
155
+ } catch {
156
+ }
157
+ let constants = {};
158
+ try {
159
+ constants = (_a = reactNative.Platform.constants) != null ? _a : {};
160
+ } catch {
161
+ constants = {};
162
+ }
163
+ let iosIdiom;
164
+ try {
165
+ if (reactNative.Platform.OS === "android") {
166
+ const brand = constants.Brand;
167
+ const model = constants.Model;
168
+ const release = constants.Release;
169
+ if (typeof brand === "string" && brand) ctx.brand = brand;
170
+ if (typeof model === "string" && model) ctx.model = model;
171
+ if (release !== void 0 && release !== null && String(release)) {
172
+ ctx.osVersion = String(release);
173
+ }
174
+ } else if (reactNative.Platform.OS === "ios") {
175
+ const osVersion = constants.osVersion;
176
+ const idiom = constants.interfaceIdiom;
177
+ if (osVersion !== void 0 && osVersion !== null && String(osVersion)) {
178
+ ctx.osVersion = String(osVersion);
179
+ }
180
+ if (typeof idiom === "string" && idiom) {
181
+ ctx.interfaceIdiom = idiom;
182
+ iosIdiom = idiom;
183
+ }
184
+ }
185
+ } catch {
186
+ }
187
+ try {
188
+ const screen = reactNative.Dimensions.get("screen");
189
+ if (screen) {
190
+ if (typeof screen.width === "number") ctx.screenWidth = screen.width;
191
+ if (typeof screen.height === "number") ctx.screenHeight = screen.height;
192
+ if (typeof screen.scale === "number") ctx.screenScale = screen.scale;
193
+ const formFactor = deriveFormFactor(iosIdiom, screen.width, screen.height);
194
+ if (formFactor) ctx.formFactor = formFactor;
195
+ }
196
+ } catch {
197
+ }
198
+ try {
199
+ ctx.isRTL = reactNative.I18nManager.isRTL;
200
+ } catch {
201
+ }
202
+ try {
203
+ const resolved = Intl.DateTimeFormat().resolvedOptions();
204
+ if (resolved.locale) ctx.locale = resolved.locale;
205
+ if (resolved.timeZone) ctx.timeZone = resolved.timeZone;
206
+ } catch {
207
+ }
208
+ return ctx;
209
+ };
210
+
211
+ // src/analytics/contextEnvelope.ts
212
+ var buildContextEnvelope = (input = {}) => {
213
+ const device = { ...collectDeviceContext() };
214
+ if (input.appVersion && !device.appVersion) device.appVersion = input.appVersion;
215
+ const envelope = { device };
216
+ if (input.sessionId) envelope.sessionId = input.sessionId;
217
+ if (input.appVersion) envelope.appVersion = input.appVersion;
218
+ if (input.appBuild) envelope.appBuild = input.appBuild;
219
+ if (input.networkType) envelope.networkType = input.networkType;
220
+ return envelope;
221
+ };
222
+
223
+ // src/analytics/eventQueue.ts
224
+ var DEFAULTS = {
225
+ maxSize: 200,
226
+ batchSize: 20,
227
+ baseBackoffMs: 1e3,
228
+ maxBackoffMs: 3e4,
229
+ maxRetries: 6
230
+ };
231
+ var READ_TIMEOUT_MS = 1500;
232
+ var withTimeout = (p, ms) => {
233
+ let timer;
234
+ const timeout = new Promise((resolve) => {
235
+ timer = setTimeout(() => resolve(void 0), ms);
236
+ });
237
+ return Promise.race([p, timeout]).finally(() => clearTimeout(timer));
238
+ };
239
+ var unrefTimer = (timer) => {
240
+ const t = timer;
241
+ if (typeof t.unref === "function") t.unref();
242
+ };
243
+ var parsePersisted = (raw) => {
244
+ if (!raw) return [];
245
+ try {
246
+ const parsed = JSON.parse(raw);
247
+ if (!Array.isArray(parsed)) return [];
248
+ const items = [];
249
+ for (const entry of parsed) {
250
+ if (entry && typeof entry === "object" && typeof entry.id === "number" && entry.event && typeof entry.event === "object") {
251
+ items.push(entry);
252
+ }
253
+ }
254
+ return items;
255
+ } catch {
256
+ return [];
257
+ }
258
+ };
259
+ var createEventQueue = (options) => {
260
+ var _a, _b, _c, _d, _e, _f, _g;
261
+ const target = options.target;
262
+ const storage = options.storage;
263
+ const key = (_b = options.storageKey) != null ? _b : `wireai:evtq:${(_a = options.appId) != null ? _a : "default"}`;
264
+ const maxSize = (_c = options.maxSize) != null ? _c : DEFAULTS.maxSize;
265
+ const batchSize = (_d = options.batchSize) != null ? _d : DEFAULTS.batchSize;
266
+ const baseBackoffMs = (_e = options.baseBackoffMs) != null ? _e : DEFAULTS.baseBackoffMs;
267
+ const maxBackoffMs = (_f = options.maxBackoffMs) != null ? _f : DEFAULTS.maxBackoffMs;
268
+ const maxRetries = (_g = options.maxRetries) != null ? _g : DEFAULTS.maxRetries;
269
+ let pending = [];
270
+ let nextId = 0;
271
+ let flushing = false;
272
+ let attempt = 0;
273
+ let retryTimer;
274
+ const resolveEnvelope = () => {
275
+ try {
276
+ return typeof options.envelope === "function" ? options.envelope() : options.envelope;
277
+ } catch {
278
+ return void 0;
279
+ }
280
+ };
281
+ const stamp = (event) => {
282
+ var _a2;
283
+ const env = resolveEnvelope();
284
+ const stamped = { ...event };
285
+ if (!env) return stamped;
286
+ if (!stamped.device && env.device) stamped.device = env.device;
287
+ if (!stamped.session_id && env.sessionId) stamped.session_id = env.sessionId;
288
+ const uc = { ...(_a2 = stamped.user_context) != null ? _a2 : {} };
289
+ if (env.appVersion && uc.app_version === void 0) uc.app_version = env.appVersion;
290
+ if (env.appBuild && uc.app_build === void 0) uc.app_build = env.appBuild;
291
+ if (env.networkType && uc.network_type === void 0) uc.network_type = env.networkType;
292
+ if (Object.keys(uc).length > 0) stamped.user_context = uc;
293
+ return stamped;
294
+ };
295
+ const persist = () => {
296
+ if (!storage) return;
297
+ try {
298
+ if (pending.length === 0) {
299
+ void storage.removeItem(key).catch(() => {
300
+ });
301
+ return;
302
+ }
303
+ const payload = pending.map((item) => ({ id: item.id, event: item.event }));
304
+ void storage.setItem(key, JSON.stringify(payload)).catch(() => {
305
+ });
306
+ } catch {
307
+ }
308
+ };
309
+ const enforceSizeCap = () => {
310
+ if (pending.length > maxSize) pending.splice(0, pending.length - maxSize);
311
+ };
312
+ const safeSig = (event) => {
313
+ try {
314
+ return JSON.stringify(event);
315
+ } catch {
316
+ return `__nosig_${nextId}_${Math.random()}`;
317
+ }
318
+ };
319
+ const loadPromise = (async () => {
320
+ if (!storage) return;
321
+ try {
322
+ const persistedItems = parsePersisted(await withTimeout(storage.getItem(key), READ_TIMEOUT_MS));
323
+ if (persistedItems.length === 0) return;
324
+ const events = [...persistedItems.map((p) => p.event), ...pending.map((p) => p.event)];
325
+ pending = [];
326
+ nextId = 0;
327
+ const seen = /* @__PURE__ */ new Set();
328
+ for (const event of events) {
329
+ const sig = safeSig(event);
330
+ if (seen.has(sig)) continue;
331
+ seen.add(sig);
332
+ pending.push({ id: nextId++, event, sig });
333
+ }
334
+ enforceSizeCap();
335
+ persist();
336
+ } catch {
337
+ }
338
+ })();
339
+ const postBatch = async (events) => {
340
+ if (!(target == null ? void 0 : target.serverUrl) || events.length === 0) return false;
341
+ try {
342
+ const url = `${target.serverUrl.replace(/\/$/, "")}/v1/events`;
343
+ const headers = { "Content-Type": "application/json" };
344
+ if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
345
+ const res = await fetch(url, {
346
+ method: "POST",
347
+ headers,
348
+ body: JSON.stringify({ events })
349
+ });
350
+ return !!(res && res.ok);
351
+ } catch {
352
+ return false;
353
+ }
354
+ };
355
+ const clearRetry = () => {
356
+ if (retryTimer !== void 0) {
357
+ clearTimeout(retryTimer);
358
+ retryTimer = void 0;
359
+ }
360
+ };
361
+ const scheduleRetry = () => {
362
+ if (attempt >= maxRetries) return;
363
+ const delay = Math.min(baseBackoffMs * 2 ** attempt, maxBackoffMs);
364
+ attempt++;
365
+ clearRetry();
366
+ retryTimer = setTimeout(() => {
367
+ retryTimer = void 0;
368
+ void drain();
369
+ }, delay);
370
+ unrefTimer(retryTimer);
371
+ };
372
+ const drain = async () => {
373
+ try {
374
+ await loadPromise;
375
+ } catch {
376
+ }
377
+ if (flushing) return;
378
+ flushing = true;
379
+ try {
380
+ while (pending.length > 0) {
381
+ const batch = pending.slice(0, batchSize);
382
+ const ok = await postBatch(batch.map((item) => item.event));
383
+ if (!ok) {
384
+ scheduleRetry();
385
+ return;
386
+ }
387
+ const acked = new Set(batch.map((item) => item.id));
388
+ pending = pending.filter((item) => !acked.has(item.id));
389
+ persist();
390
+ attempt = 0;
391
+ clearRetry();
392
+ }
393
+ } finally {
394
+ flushing = false;
395
+ }
396
+ };
397
+ const flush = () => {
398
+ try {
399
+ void drain();
400
+ } catch {
401
+ }
402
+ };
403
+ const enqueue = (event) => {
404
+ try {
405
+ const stamped = stamp(event);
406
+ const sig = safeSig(stamped);
407
+ for (const item of pending) {
408
+ if (item.sig === sig) return;
409
+ }
410
+ pending.push({ id: nextId++, event: stamped, sig });
411
+ enforceSizeCap();
412
+ persist();
413
+ if (retryTimer === void 0) flush();
414
+ } catch {
415
+ }
416
+ };
417
+ const notifyOnline = () => {
418
+ attempt = 0;
419
+ clearRetry();
420
+ flush();
421
+ };
422
+ const size = () => pending.length;
423
+ return { enqueue, flush, notifyOnline, size };
424
+ };
425
+
426
+ // src/identity/userIdentity.ts
427
+ var USER_ID_MAX_LENGTH = 128;
428
+ var sanitizeUserId = (raw) => {
429
+ if (typeof raw !== "string") return void 0;
430
+ const trimmed = raw.trim();
431
+ if (!trimmed) return void 0;
432
+ return trimmed.length > USER_ID_MAX_LENGTH ? trimmed.slice(0, USER_ID_MAX_LENGTH) : trimmed;
433
+ };
434
+
435
+ // src/analytics/analyticsFacade.ts
436
+ var createAnalytics = (config, options = {}) => {
437
+ var _a;
438
+ const sessionId = (_a = config.sessionId) != null ? _a : makeSessionId();
439
+ const envelope = () => buildContextEnvelope({
440
+ sessionId,
441
+ appVersion: config.appVersion,
442
+ appBuild: config.appBuild,
443
+ networkType: config.networkType
444
+ });
445
+ const queue = createEventQueue({
446
+ target: { serverUrl: config.serverUrl, apiKey: config.apiKey },
447
+ storage: config.storage,
448
+ appId: config.appId,
449
+ envelope,
450
+ ...options
451
+ });
452
+ let boundUserId;
453
+ const track = (event, props) => {
454
+ if (!event) return;
455
+ const clientEvent = {
456
+ event_type: "app_event",
457
+ session_id: sessionId,
458
+ question_key: event
459
+ };
460
+ if (props && Object.keys(props).length > 0) clientEvent.meta = JSON.stringify(props);
461
+ if (boundUserId) clientEvent.user_id = boundUserId;
462
+ queue.enqueue(clientEvent);
463
+ };
464
+ const screen = (name, props) => {
465
+ if (!name) return;
466
+ const meta = { screen: name, ...props != null ? props : {} };
467
+ const clientEvent = {
468
+ event_type: "app_event",
469
+ session_id: sessionId,
470
+ question_key: "screen",
471
+ meta: JSON.stringify(meta)
472
+ };
473
+ if (boundUserId) clientEvent.user_id = boundUserId;
474
+ queue.enqueue(clientEvent);
475
+ };
476
+ const identify = (userId, traits) => {
477
+ const clean = sanitizeUserId(userId);
478
+ if (!clean) return;
479
+ boundUserId = clean;
480
+ const clientEvent = {
481
+ event_type: "identify",
482
+ session_id: sessionId,
483
+ user_id: clean
484
+ };
485
+ if (traits && Object.keys(traits).length > 0) clientEvent.meta = JSON.stringify(traits);
486
+ queue.enqueue(clientEvent);
487
+ };
488
+ return {
489
+ track,
490
+ screen,
491
+ identify,
492
+ flush: queue.flush,
493
+ notifyOnline: queue.notifyOnline,
494
+ size: queue.size
495
+ };
496
+ };
497
+ var useAnalytics = (config, options = {}) => {
498
+ const ref = react.useRef(void 0);
499
+ if (!ref.current) ref.current = createAnalytics(config, options);
500
+ return ref.current;
501
+ };
138
502
 
139
503
  exports.WIRE_ONBOARDING_EVENTS = WIRE_ONBOARDING_EVENTS;
504
+ exports.buildContextEnvelope = buildContextEnvelope;
505
+ exports.createAnalytics = createAnalytics;
506
+ exports.createEventQueue = createEventQueue;
140
507
  exports.createScreenTracker = createScreenTracker;
141
508
  exports.getActiveRouteName = getActiveRouteName;
142
509
  exports.makeSessionId = makeSessionId;
@@ -145,6 +512,7 @@ exports.reportClientEvent = reportClientEvent;
145
512
  exports.reportClientEvents = reportClientEvents;
146
513
  exports.screenTrackingHandler = screenTrackingHandler;
147
514
  exports.toAnalyticsEvent = toAnalyticsEvent;
515
+ exports.useAnalytics = useAnalytics;
148
516
  exports.useScreenTracking = useScreenTracking;
149
517
  //# sourceMappingURL=index.js.map
150
518
  //# sourceMappingURL=index.js.map