@wireai/activation 0.2.0 → 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,5 +1,6 @@
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, c as ContextEnvelope, d as ContextEnvelopeInput, E as EnvelopeSource, e as EventQueue, f as EventQueueOptions, 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-CxKi7Qd5.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';
@@ -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,5 +1,6 @@
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, c as ContextEnvelope, d as ContextEnvelopeInput, E as EnvelopeSource, e as EventQueue, f as EventQueueOptions, 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-rV1dtJJR.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';
@@ -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 };
@@ -423,8 +423,86 @@ var createEventQueue = (options) => {
423
423
  return { enqueue, flush, notifyOnline, size };
424
424
  };
425
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
+ };
502
+
426
503
  exports.WIRE_ONBOARDING_EVENTS = WIRE_ONBOARDING_EVENTS;
427
504
  exports.buildContextEnvelope = buildContextEnvelope;
505
+ exports.createAnalytics = createAnalytics;
428
506
  exports.createEventQueue = createEventQueue;
429
507
  exports.createScreenTracker = createScreenTracker;
430
508
  exports.getActiveRouteName = getActiveRouteName;
@@ -434,6 +512,7 @@ exports.reportClientEvent = reportClientEvent;
434
512
  exports.reportClientEvents = reportClientEvents;
435
513
  exports.screenTrackingHandler = screenTrackingHandler;
436
514
  exports.toAnalyticsEvent = toAnalyticsEvent;
515
+ exports.useAnalytics = useAnalytics;
437
516
  exports.useScreenTracking = useScreenTracking;
438
517
  //# sourceMappingURL=index.js.map
439
518
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/reviews/transport.ts","../../src/analytics/screenTracking.ts","../../src/analytics/useScreenTracking.ts","../../src/analytics/reportClientEvent.ts","../../src/analytics/analyticsEvent.ts","../../src/device/deviceContext.ts","../../src/analytics/contextEnvelope.ts","../../src/analytics/eventQueue.ts"],"names":["useRef","useEffect","Platform","Dimensions","I18nManager","_a"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * transport.ts — kit → Wire server requests for the review module, all fire-and-forget\n * (analytics/reviews must never break the app). Mirrors analytics/reportClientEvent: a\n * thin fetch wrapper, Bearer tenant key, swallow every error.\n *\n * • submitReview → POST {serverUrl}/v1/reviews (the 1-4 feedback body)\n * • reportAppEvent → POST {serverUrl}/v1/events (generic app.* namespace)\n *\n * `reportAppEvent` is the strategic extension: it lets a host report arbitrary in-app\n * events through the SAME transport (stored server-side as event_type='app_event',\n * question_key=<name>), which is what the backend review-firing rules evaluate on — and\n * it seeds the broader app-analytics stream. Keep payloads minimal + non-PII.\n */\nimport type { ReviewSubmission, ReviewTarget } from \"./types\";\n\n/**\n * POST a review (the 1-4 feedback path). Fire-and-forget: a missing target, a build error,\n * a missing `fetch`, or a network failure is swallowed and the call returns immediately.\n */\nexport const submitReview = (\n target: ReviewTarget | undefined,\n review: ReviewSubmission,\n): void => {\n if (!target?.serverUrl) return;\n try {\n const url = `${target.serverUrl.replace(/\\/$/, \"\")}/v1/reviews`;\n const headers: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;\n void fetch(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(review),\n }).catch(() => {\n /* best-effort, swallow */\n });\n } catch {\n /* URL/JSON/missing-fetch — swallow */\n }\n};\n\n/** Options for a reported app event. `deviceKey` groups a device's sessions server-side. */\nexport interface ReportAppEventOptions {\n /** The onboarding/session id to correlate with, when known. */\n sessionId?: string;\n /** A stable, non-PII device id — the review-decision endpoint reads it for min-sessions. */\n deviceKey?: string;\n /** Small non-PII extras. */\n meta?: Record<string, unknown>;\n}\n\n/**\n * Report a generic in-app event through the existing events transport. Stored server-side\n * as `event_type='app_event'`, `question_key=<name>`. Fire-and-forget. Keep `name` a short\n * stable identifier and `meta` small + non-PII.\n *\n * reportAppEvent(target, \"content_share\", { sessionId, deviceKey });\n */\nexport const reportAppEvent = (\n target: ReviewTarget | undefined,\n name: string,\n options: ReportAppEventOptions = {},\n): void => {\n if (!target?.serverUrl || !name) return;\n try {\n const url = `${target.serverUrl.replace(/\\/$/, \"\")}/v1/events`;\n const headers: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;\n const event: Record<string, unknown> = {\n event_type: \"app_event\",\n question_key: name,\n };\n if (options.sessionId) event.session_id = options.sessionId;\n // device_key rides in the non-PII user_context bucket the server sanitizes; the\n // review-decision endpoint reads it to group a device's sessions.\n if (options.deviceKey) event.user_context = { device_key: options.deviceKey };\n if (options.meta && Object.keys(options.meta).length > 0) {\n event.meta = JSON.stringify(options.meta);\n }\n void fetch(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify({ events: [event] }),\n }).catch(() => {\n /* best-effort, swallow */\n });\n } catch {\n /* swallow */\n }\n};\n","/**\n * screenTracking — an OPT-IN, dependency-free automatic screen-view helper.\n *\n * Given the host app's navigation STATE (or a nav ref, via the useScreenTracking hook), this\n * emits exactly ONE `screen` app-event per REAL screen change through the kit's existing\n * `reportAppEvent` transport (`POST {serverUrl}/v1/events`, `event_type='app_event'`,\n * `question_key='screen'`, `meta={ screen }`). Param-only changes and re-renders are de-duped\n * away because we resolve and compare the route NAME only — never the params.\n *\n * Dependency-free core: this file imports NO navigation library. React-Navigation / expo-router\n * are host concerns; the host supplies plain state objects and refs, typed structurally here\n * (`NavigationStateLike`). It is also React-free — the optional React glue lives in\n * `useScreenTracking.ts`. `reportAppEvent` is imported from the pure `../reviews/transport`\n * module (NOT the `../reviews` barrel, which would drag the review UI into an analytics-only\n * bundle and defeat tree-shaking).\n *\n * Privacy: only the route NAME ever leaves the device. Route params, query strings, and any\n * user data are never read into the event. Fire-and-forget — this never throws into the UI.\n */\nimport { reportAppEvent } from \"../reviews/transport\";\n\n/** A single route inside a React-Navigation-shaped state (structural — no `@react-navigation`). */\nexport interface NavigationRouteLike {\n name: string;\n /** A nested navigator's own state, when this route hosts one. */\n state?: NavigationStateLike;\n /** Route params are intentionally left `unknown` — this helper never reads them. */\n params?: unknown;\n}\n\n/** A React-Navigation-shaped navigator state (structural type; no library import). */\nexport interface NavigationStateLike {\n /** Index of the active route within `routes`. */\n index?: number;\n routes?: NavigationRouteLike[];\n}\n\n/** Options for {@link createScreenTracker}. */\nexport interface ScreenTrackerOptions {\n /**\n * Where to POST. `{ serverUrl, apiKey }` — same shape the kit's review/analytics config\n * exposes. When omitted, the tracker still de-dups and fires `onScreen`, but sends nothing.\n */\n target?: { serverUrl: string; apiKey: string };\n /** The onboarding/session id to correlate screen views with, when known. */\n sessionId?: string;\n /** A stable, non-PII device id — groups a device's sessions server-side. */\n deviceKey?: string;\n /** Called on every REAL screen change (after de-dup), before the network emit. */\n onScreen?: (screen: string) => void;\n /**\n * Per-screen filter. Return `false` to skip the NETWORK emit for a screen (last-screen memory\n * is still advanced + `onScreen` still fires) — e.g. to keep a sensitive route out of analytics.\n */\n shouldTrack?: (screen: string) => boolean;\n}\n\n/** The screen tracker returned by {@link createScreenTracker}. */\nexport interface ScreenTracker {\n /** Report the active screen. Ignores `undefined`/empty and de-dups repeats of the last screen. */\n track: (screen: string | undefined) => void;\n /** Clear the last-screen memory (e.g. on logout) so the next `track` always emits. */\n reset: () => void;\n}\n\n/**\n * Walk a React-Navigation-shaped state to the DEEPEST active route and return its NAME (never\n * its params). Recurses `routes[index]` while a nested `.state` exists. Returns `undefined` for\n * a missing/empty/malformed state — the caller treats that as \"nothing to report\".\n */\nexport const getActiveRouteName = (\n state: NavigationStateLike | undefined,\n): string | undefined => {\n let current: NavigationStateLike | undefined = state;\n let name: string | undefined;\n // Bounded by the finite nesting depth of a real navigator tree.\n while (current && Array.isArray(current.routes) && current.routes.length > 0) {\n const index = typeof current.index === \"number\" ? current.index : 0;\n const route = current.routes[index];\n if (!route) break;\n name = route.name;\n current = route.state;\n }\n return name;\n};\n\n/**\n * Build a stateful screen tracker. `track` de-dups against the last reported screen so only a\n * REAL change emits; `reset` clears that memory. Fire-and-forget throughout — a missing `target`\n * skips the network but keeps the de-dup + `onScreen` behaviour intact.\n */\nexport const createScreenTracker = (options: ScreenTrackerOptions = {}): ScreenTracker => {\n let lastScreen: string | undefined;\n return {\n track: (screen: string | undefined): void => {\n if (!screen || screen === lastScreen) return;\n lastScreen = screen;\n options.onScreen?.(screen);\n if (options.shouldTrack && !options.shouldTrack(screen)) return;\n reportAppEvent(options.target, \"screen\", {\n sessionId: options.sessionId,\n deviceKey: options.deviceKey,\n meta: { screen },\n });\n },\n reset: (): void => {\n lastScreen = undefined;\n },\n };\n};\n\n/**\n * Adapt a tracker into a React-Navigation `onStateChange` handler — the one-place wiring:\n *\n * <NavigationContainer onStateChange={screenTrackingHandler(tracker)}>\n *\n * It resolves the deepest active route name and hands it to `tracker.track` (which de-dups).\n */\nexport const screenTrackingHandler =\n (tracker: ScreenTracker) =>\n (state: NavigationStateLike | undefined): void =>\n tracker.track(getActiveRouteName(state));\n","/**\n * useScreenTracking — a THIN optional React hook over the pure screen-tracking core.\n *\n * It builds one tracker for the component's lifetime and subscribes to the host's navigation\n * ref: it reports the current route on mount and on every `state` event, and unsubscribes on\n * unmount. React is a REQUIRED peer of the kit, so importing it here is allowed; the hook adds\n * NO navigation-library dependency — the ref is typed structurally (`NavigationRefLike`).\n *\n * const navigationRef = useNavigationContainerRef(); // host's @react-navigation ref\n * useScreenTracking(navigationRef, { target, sessionId });\n * // ...<NavigationContainer ref={navigationRef}>\n */\nimport { useEffect, useRef } from \"react\";\n\nimport { createScreenTracker, type ScreenTracker, type ScreenTrackerOptions } from \"./screenTracking\";\n\n/**\n * The structural slice of a React-Navigation container ref this hook needs — no\n * `@react-navigation` import. `getCurrentRoute` yields the active route; `addListener(\"state\", …)`\n * fires on every navigation state change and returns its own unsubscribe.\n */\nexport interface NavigationRefLike {\n getCurrentRoute?: () => { name?: string } | undefined;\n addListener?: (type: \"state\", callback: () => void) => () => void;\n}\n\n/**\n * Subscribe screen tracking to a host navigation ref. Safe to call with a not-yet-ready ref\n * (the effect no-ops until `addListener` exists). Returns nothing — it wires side effects only.\n */\nexport const useScreenTracking = (\n navigationRef: NavigationRefLike | undefined,\n options: ScreenTrackerOptions = {},\n): void => {\n // One tracker per mount; kept in a ref so re-renders never rebuild the de-dup memory.\n const trackerRef = useRef<ScreenTracker | undefined>(undefined);\n if (!trackerRef.current) trackerRef.current = createScreenTracker(options);\n\n useEffect(() => {\n const tracker = trackerRef.current;\n if (!tracker || !navigationRef?.addListener) return;\n const report = (): void => tracker.track(navigationRef.getCurrentRoute?.()?.name);\n report(); // initial screen on mount\n const unsubscribe = navigationRef.addListener(\"state\", report);\n return unsubscribe;\n // Re-subscribe only when the ref identity changes; option changes are read live off the closure.\n }, [navigationRef]);\n};\n","/**\n * reportClientEvent — forward DEVICE-ONLY onboarding events to the Wire AI analytics\n * backend (`POST {serverUrl}/v1/events`), completing the funnel for events the server\n * can't observe on its own.\n *\n * The backend already records the server-observable funnel during the A2A flow\n * (`session_started`, `screen_shown`, `answer_submitted`, `completed`, `llm_fallback`,\n * and even `screen_skipped` — it derives that from the kit's skip sentinel). The one\n * event no server request can capture is `dropped`: the user closing the app / unmounting\n * the flow without finishing. That's what this reporter is for.\n *\n * Contract (server: routers/onboarding.py → analytics/events.py):\n * POST {serverUrl}/v1/events\n * Authorization: Bearer {apiKey}\n * { \"events\": [ { event_type, session_id, screen_index?, component?, question_key?,\n * latency_ms?, meta?, device?, user_context? } ] }\n * The server fills `app_id` + `environment` from the resolving key (never send app_id),\n * and silently skips malformed events — one bad payload never fails the batch.\n *\n * ⚠️ Correlation: `session_id` MUST equal the A2A `contextId` the server uses to key the\n * server-side events, or the funnel report (which groups by `session_id`) treats this as a\n * phantom session. See `makeSessionId` + WireOnboarding for how the kit seeds it.\n *\n * Fire-and-forget: this never throws into the UI and never awaits — analytics must never\n * be able to break onboarding.\n */\nimport type { DeviceContext } from \"../device/deviceContext\";\n\n/** Event types a CLIENT may report. The rest of the funnel is server-side; sending those\n * here would double-count. `screen_skipped` is included for completeness, but the kit does\n * NOT emit it — the backend already derives it from the skip sentinel (see OnboardingFlow).\n * `client_fallback` is emitted by the kit when the AI flow degrades to the static fallback,\n * so the dashboard's fallback-rate counts the whole-flow case (distinct from the server's\n * per-turn `llm_fallback`). The server back-fills a `session_started` for it if unseen.\n * This is the SINGLE fallback signal — hosts must NOT also report their own.\n * `identify` binds the host's opaque `user_id` to this `session_id` (late binding — the user\n * registered during/after onboarding). It carries no funnel weight; the server maps the\n * session to the user and back-fills a `session_started` if it never saw the session. */\nexport type ClientEventType = \"screen_skipped\" | \"dropped\" | \"client_fallback\" | \"identify\";\n\n/** One client-reported event. Mirrors the server's `OnboardingEvent` (client-settable fields). */\nexport type ClientEvent = {\n event_type: ClientEventType;\n /** Must match the server-side A2A contextId for this onboarding (see makeSessionId). */\n session_id: string;\n /** 0-based index of the screen the event refers to (matches server `screen_shown`). */\n screen_index?: number;\n component?: string;\n question_key?: string;\n latency_ms?: number;\n /** JSON-stringified extras; the server stores it verbatim. */\n meta?: string;\n /**\n * Privacy-label-neutral device snapshot (platform / form factor / locale / host appVersion).\n * Sent as an object; the server sanitizes + persists it and derives a coarse country. Old\n * servers ignore this unknown field — fully backward compatible. See device/deviceContext.ts.\n */\n device?: DeviceContext;\n /**\n * Host-injected, non-PII context (signup method, referral, plan, hashed user id). Old servers\n * ignore it. MUST NOT contain PII like raw emails — see the README `userContext` section.\n */\n user_context?: Record<string, string | number | boolean>;\n /**\n * The host's OPAQUE PSEUDONYMOUS user id (their internal id, NOT an email/name). Required on\n * `identify`, optional (rides along) on other events. Trimmed + capped at 128 chars host-side.\n * Lets the backend reconcile onboarding sessions to real users. Old servers ignore it.\n */\n user_id?: string;\n};\n\n/** Where to POST. Derived from `WireOnboardingConfig` (`serverUrl` + `apiKey`). */\nexport type ClientEventTarget = {\n /** Base server URL (same as `WireOnboardingConfig.serverUrl`); `/v1/events` is appended. */\n serverUrl: string;\n /** Tenant API key; sent as `Authorization: Bearer`. */\n apiKey: string;\n};\n\n/**\n * A unique-per-onboarding session id. Used both as the client event `session_id` AND as the\n * seed the kit forwards to the backend so the SERVER adopts it as the A2A `contextId` — making\n * client and server agree (see WireOnboarding + the SDK-correlation note in the kit docs).\n * No crypto dependency: timestamp + random is collision-safe for a single device's onboarding.\n */\nexport const makeSessionId = (): string =>\n `wire_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;\n\n/**\n * POST one or more client events, fire-and-forget. A missing/invalid target, a build error,\n * a missing `fetch`, or a network failure is swallowed — the call returns immediately and the\n * request (if any) runs in the background.\n */\nexport const reportClientEvents = (\n target: ClientEventTarget | undefined,\n events: ClientEvent[],\n): void => {\n if (!target?.serverUrl || events.length === 0) return;\n try {\n const url = `${target.serverUrl.replace(/\\/$/, \"\")}/v1/events`;\n const headers: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;\n void fetch(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify({ events }),\n }).catch(() => {\n // Network/transport error — analytics is best-effort, swallow.\n });\n } catch {\n // URL construction, JSON serialization, or a missing fetch — swallow.\n }\n};\n\n/** Convenience single-event wrapper around {@link reportClientEvents}. */\nexport const reportClientEvent = (\n target: ClientEventTarget | undefined,\n event: ClientEvent,\n): void => reportClientEvents(target, [event]);\n","/**\n * Canonical analytics names for the onboarding funnel. The kit already emits a typed\n * `OnboardingEvent` (`started | turn | error | retry | fallback`) — but one app logged them as\n * `onboarding_*` and another as `AI_ONBOARDING_*`, so the same funnel reads differently per app.\n * This maps the kit event to ONE canonical `wire_onboarding_*` name + params, and the app logs\n * it through whatever transport it already has (Firebase, Amplitude, console). The app still\n * owns the logger; only the NAMES are standardized.\n *\n * <WireOnboarding\n * onEvent={(e) => { const a = toAnalyticsEvent(e); logEvent(a.name, a.params); }}\n * onComplete={(r) => { logEvent(WIRE_ONBOARDING_EVENTS.completed, { answers: Object.keys(r.answers).length }); persist(r); }}\n * />\n *\n * `completed` has no kit `OnboardingEvent` (the kit signals completion via `onComplete`, not\n * `onEvent`) — the app logs it explicitly on `onComplete` using the constant below, so the\n * funnel name stays canonical.\n */\nimport type { OnboardingEvent } from \"../types\";\n\nexport const WIRE_ONBOARDING_EVENTS = {\n started: \"wire_onboarding_started\",\n /** A persisted session was restored after an app kill (fires instead of `started`). */\n resumed: \"wire_onboarding_resumed\",\n turn: \"wire_onboarding_turn\",\n error: \"wire_onboarding_error\",\n retry: \"wire_onboarding_retry\",\n fallback: \"wire_onboarding_fallback\",\n /** Logged by the host on `onComplete` (no matching kit `OnboardingEvent`). */\n completed: \"wire_onboarding_completed\",\n} as const;\n\nexport type WireOnboardingEventName =\n (typeof WIRE_ONBOARDING_EVENTS)[keyof typeof WIRE_ONBOARDING_EVENTS];\n\nexport type AnalyticsEvent = {\n name: WireOnboardingEventName;\n params?: Record<string, unknown>;\n};\n\n/**\n * Map a kit `OnboardingEvent` to its canonical `{ name, params }`. Exhaustive over the union\n * (the `never` default makes a new event type a compile error here — intentional).\n */\nexport const toAnalyticsEvent = (event: OnboardingEvent): AnalyticsEvent => {\n switch (event.type) {\n case \"started\":\n return { name: WIRE_ONBOARDING_EVENTS.started };\n case \"resumed\":\n return { name: WIRE_ONBOARDING_EVENTS.resumed };\n case \"turn\":\n return {\n name: WIRE_ONBOARDING_EVENTS.turn,\n params: { step: event.step, component: event.component },\n };\n case \"error\":\n return { name: WIRE_ONBOARDING_EVENTS.error, params: { reason: event.reason } };\n case \"retry\":\n return {\n name: WIRE_ONBOARDING_EVENTS.retry,\n params: { reason: event.reason, attempt: event.attempt },\n };\n case \"fallback\":\n return { name: WIRE_ONBOARDING_EVENTS.fallback, params: { reason: event.reason } };\n default: {\n const _exhaustive: never = event;\n return _exhaustive;\n }\n }\n};\n","/**\n * deviceContext — collect a small, privacy-label-neutral snapshot of the device so\n * onboarding analytics can segment the funnel (platform / form factor / locale) WITHOUT\n * adding a single dependency to the kit or changing a host app's App Privacy / Data Safety\n * declarations.\n *\n * HARD RULE (why this file has no imports beyond React Native built-ins):\n * The kit stays dependency-free. Everything here comes from `Platform`, `Dimensions`,\n * `I18nManager`, and the standard `Intl` global. There are NO advertising IDs, NO\n * `getUniqueId`/IDFA/GAID/fingerprinting APIs, and nothing that would require a new\n * privacy-label entry. A host can adopt this without touching its store declarations.\n *\n * DEFENSIVE BY DESIGN: `collectDeviceContext()` never throws. Every read is guarded and\n * a missing/unavailable field is simply omitted (Hermes may ship without full `Intl`,\n * `Platform.constants` differs per OS and RN version, etc.). Analytics must never be able\n * to break onboarding.\n */\nimport { Dimensions, I18nManager, Platform } from \"react-native\";\n\n/** Coarse device class. iOS uses the reported interface idiom; else a screen-size heuristic. */\nexport type DeviceFormFactor = \"phone\" | \"tablet\";\n\n/**\n * A privacy-label-neutral device snapshot. ALL fields except `platform` are optional and are\n * omitted when unavailable. Nothing here identifies a user or device uniquely.\n */\nexport type DeviceContext = {\n /** `Platform.OS` — \"ios\" | \"android\" | \"windows\" | \"macos\" | \"web\". Always present. */\n platform: typeof Platform.OS;\n /** OS version string (iOS `osVersion`/`Platform.Version`, Android `Release`). */\n osVersion?: string;\n /** Android device brand (e.g. \"samsung\"). Android only. */\n brand?: string;\n /** Android device model (e.g. \"SM-G991B\"). Android only. */\n model?: string;\n /** iOS interface idiom (\"phone\" | \"pad\" | …), when reported. iOS only. */\n interfaceIdiom?: string;\n /** Derived device class. */\n formFactor?: DeviceFormFactor;\n /** `Dimensions.get('screen')` width in dp. */\n screenWidth?: number;\n /** `Dimensions.get('screen')` height in dp. */\n screenHeight?: number;\n /** Screen pixel density (`scale`). */\n screenScale?: number;\n /** Right-to-left layout (`I18nManager.isRTL`). */\n isRTL?: boolean;\n /** Resolved locale (e.g. \"en-US\"), from `Intl` when available. */\n locale?: string;\n /** IANA time zone (e.g. \"Europe/Berlin\"), from `Intl` when available. */\n timeZone?: string;\n /**\n * Host app version (e.g. \"1.4.2). HOST-INJECTED — NOT collected here. `WireOnboarding`\n * merges `config.appVersion` into the snapshot; `collectDeviceContext()` never sets it.\n * Hosts typically pass it from `expo-constants` (the kit itself adds no dependency).\n */\n appVersion?: string;\n};\n\n/** iOS idiom wins; otherwise the shortest side in dp (>= 600 → tablet) picks the class. */\nconst deriveFormFactor = (\n iosIdiom: string | undefined,\n width: number | undefined,\n height: number | undefined,\n): DeviceFormFactor | undefined => {\n if (iosIdiom === \"pad\") return \"tablet\";\n if (iosIdiom === \"phone\") return \"phone\";\n if (typeof width === \"number\" && typeof height === \"number\") {\n return Math.min(width, height) >= 600 ? \"tablet\" : \"phone\";\n }\n return undefined;\n};\n\n/**\n * Collect the device snapshot. Pure, synchronous, and never throws — call it once per\n * onboarding session. Missing fields are omitted rather than sent as null/undefined so the\n * payload (and the server's stored dict) stays compact.\n */\nexport const collectDeviceContext = (): DeviceContext => {\n const ctx: DeviceContext = { platform: Platform.OS };\n\n // OS version (fallback; per-OS constants below may refine it).\n try {\n const version = Platform.Version;\n if (version !== undefined && version !== null && String(version)) {\n ctx.osVersion = String(version);\n }\n } catch {\n // ignore\n }\n\n // `Platform.constants` shape differs by OS and RN version — read every field defensively.\n let constants: Record<string, unknown> = {};\n try {\n constants = (Platform.constants ?? {}) as Record<string, unknown>;\n } catch {\n constants = {};\n }\n\n let iosIdiom: string | undefined;\n try {\n if (Platform.OS === \"android\") {\n const brand = constants.Brand;\n const model = constants.Model;\n const release = constants.Release;\n if (typeof brand === \"string\" && brand) ctx.brand = brand;\n if (typeof model === \"string\" && model) ctx.model = model;\n if (release !== undefined && release !== null && String(release)) {\n ctx.osVersion = String(release);\n }\n } else if (Platform.OS === \"ios\") {\n const osVersion = constants.osVersion;\n const idiom = constants.interfaceIdiom;\n if (osVersion !== undefined && osVersion !== null && String(osVersion)) {\n ctx.osVersion = String(osVersion);\n }\n if (typeof idiom === \"string\" && idiom) {\n ctx.interfaceIdiom = idiom;\n iosIdiom = idiom;\n }\n }\n } catch {\n // ignore per-OS constant reads\n }\n\n // Screen dimensions + derived form factor.\n try {\n const screen = Dimensions.get(\"screen\");\n if (screen) {\n if (typeof screen.width === \"number\") ctx.screenWidth = screen.width;\n if (typeof screen.height === \"number\") ctx.screenHeight = screen.height;\n if (typeof screen.scale === \"number\") ctx.screenScale = screen.scale;\n const formFactor = deriveFormFactor(iosIdiom, screen.width, screen.height);\n if (formFactor) ctx.formFactor = formFactor;\n }\n } catch {\n // ignore\n }\n\n // Layout direction.\n try {\n ctx.isRTL = I18nManager.isRTL;\n } catch {\n // ignore\n }\n\n // Locale + time zone via the standard Intl global. Hermes may ship without full Intl,\n // so referencing it can throw ReferenceError — the try/catch covers that too.\n try {\n const resolved = Intl.DateTimeFormat().resolvedOptions();\n if (resolved.locale) ctx.locale = resolved.locale;\n if (resolved.timeZone) ctx.timeZone = resolved.timeZone;\n } catch {\n // Intl unavailable — omit locale/timeZone.\n }\n\n return ctx;\n};\n","/**\n * contextEnvelope — a small, PRIVACY-NEUTRAL context bundle stamped onto every outgoing\n * analytics event, giving the Wire dashboard the Sentry/Firebase-parity segmentation fields\n * (device model, OS + version, screen, locale, timezone, form factor) plus a few host-injected\n * scalars (session correlation id, app version + native build number, connectivity type).\n *\n * WHY a separate builder (not just `collectDeviceContext`): the envelope COMPOSES the existing\n * device snapshot with the handful of extras a host can cheaply supply but the kit can't collect\n * dependency-free (native build number, connectivity type). It never re-implements device\n * collection — it reuses `collectDeviceContext()` verbatim (see device/deviceContext.ts).\n *\n * HARD PRIVACY RULE (why this file, like deviceContext.ts, adds nothing new):\n * NEVER GPS / location, NEVER an advertising id (IDFA / GAID), NEVER a device fingerprint.\n * Location is derived SERVER-SIDE from IP-geo only — nothing here carries a coordinate or an\n * ad id, so a host adopting this changes no App Privacy / Data Safety declaration. There is a\n * test (contextEnvelope.test.ts) that asserts the ABSENCE of any such field.\n *\n * DEPENDENCY-FREE: the only import is the kit's own `collectDeviceContext`. `networkType` and\n * `appBuild` are HOST-INJECTED — there is no dependency-free RN core signal for either, so the\n * envelope simply omits them when the host does not pass them (no forced peer dependency).\n */\nimport { collectDeviceContext, type DeviceContext } from \"../device/deviceContext\";\n\n/**\n * The context stamped onto every event. `device` is always present (from\n * `collectDeviceContext`); every scalar is optional and OMITTED when the host does not supply it.\n */\nexport type ContextEnvelope = {\n /** The privacy-neutral device snapshot (reused from `collectDeviceContext`). */\n device: DeviceContext;\n /** Correlation id for this app-open / flow (caller-supplied). */\n sessionId?: string;\n /** Host app version, e.g. \"1.4.2\" (mirrors `device.appVersion`; host-injected). */\n appVersion?: string;\n /** Host native build number, e.g. \"412\" (from `expo-constants` `nativeBuildVersion`). */\n appBuild?: string;\n /** Host connectivity signal, e.g. \"wifi\" | \"cellular\" (from `@react-native-community/netinfo`). */\n networkType?: string;\n};\n\n/** Host-injected inputs for {@link buildContextEnvelope}. All optional; each is omitted when absent. */\nexport type ContextEnvelopeInput = {\n sessionId?: string;\n appVersion?: string;\n appBuild?: string;\n networkType?: string;\n};\n\n/**\n * Build a fresh context envelope. Reuses `collectDeviceContext()` for the device block and layers\n * the host-injected scalars on top. `appVersion` is additionally merged onto `device.appVersion`\n * when the device block lacks it (mirroring how `useSessionStart` back-fills the host version).\n *\n * Returns a NEW object on every call (no shared mutable reference), so a caller can hold or mutate\n * the result without leaking into the next envelope. Never throws — `collectDeviceContext` is\n * itself guarded, and the rest is plain assignment.\n */\nexport const buildContextEnvelope = (input: ContextEnvelopeInput = {}): ContextEnvelope => {\n // Fresh copy so the returned envelope never aliases a cached device snapshot.\n const device: DeviceContext = { ...collectDeviceContext() };\n\n // Mirror useSessionStart: fill the host app version onto the device block when it lacks one.\n if (input.appVersion && !device.appVersion) device.appVersion = input.appVersion;\n\n const envelope: ContextEnvelope = { device };\n if (input.sessionId) envelope.sessionId = input.sessionId;\n if (input.appVersion) envelope.appVersion = input.appVersion;\n if (input.appBuild) envelope.appBuild = input.appBuild;\n if (input.networkType) envelope.networkType = input.networkType;\n\n return envelope;\n};\n","/**\n * eventQueue — the OFFLINE-FIRST, persistent transport buffer for client analytics events.\n *\n * The existing `reportClientEvents` is a blind fire-and-forget POST: it returns `void`, has no\n * success signal, and drops events when the network is down. The analytics data story depends on\n * NEVER losing an event offline, so this queue adds the missing durability layer on top of the\n * same `POST {serverUrl}/v1/events` contract:\n *\n * • Persists pending events to the host-injected `WireOnboardingStorage` (survives app kills).\n * • Batches them into one request body `{ events: [...] }`.\n * • Owns its OWN awaited `fetch` that reads `res.ok` — the only way to drive retry + dequeue,\n * since `reportClientEvents` cannot ack. A 2xx dequeues the batch; a non-ok / rejected / thrown\n * response keeps it and schedules an exponential backoff retry.\n * • Flushes on `enqueue`, on an explicit `flush()`, and on `notifyOnline()` (host reconnect).\n * • Caps the buffer (drop-OLDEST under pressure) and de-dups identical pending events.\n * • Stamps the current context envelope (device + host scalars) onto every event before send.\n *\n * FIRE-AND-FORGET (load-bearing): `enqueue` returns immediately and NEVER throws into the UI. A\n * missing `fetch`, a hung/broken storage, a rejecting network, or a JSON error is swallowed and\n * degrades gracefully — analytics must never be able to break the app. In-memory fallback covers\n * the no-storage case (survives re-renders, not app kills).\n *\n * DEPENDENCY-FREE: no network-detection or persistence library. Connectivity is host-driven via\n * `notifyOnline()`; persistence is the host-injected AsyncStorage-compatible subset.\n */\nimport type { ContextEnvelope } from \"./contextEnvelope\";\nimport type { ClientEvent, ClientEventTarget } from \"./reportClientEvent\";\nimport type { WireOnboardingStorage } from \"../session/persistedSession\";\n\n/** Envelope source: a fixed envelope or a provider evaluated at enqueue time (fresh network type). */\nexport type EnvelopeSource = ContextEnvelope | (() => ContextEnvelope | undefined);\n\n/** Options for {@link createEventQueue}. Only `target` is conceptually required to actually send. */\nexport type EventQueueOptions = {\n /** Where to POST — the tenant transport (`serverUrl` + `apiKey`), same as `WireOnboardingConfig`. */\n target: ClientEventTarget | undefined;\n /**\n * Host persistence (AsyncStorage-compatible subset). When omitted, the queue runs in the\n * documented DEGRADED in-memory mode — it survives re-renders but not an app kill.\n */\n storage?: WireOnboardingStorage;\n /** Tenant/app id used to namespace the default storage key (`wireai:evtq:<appId>`). */\n appId?: string;\n /** Explicit storage key override (wins over the `appId`-derived default). */\n storageKey?: string;\n /** The context envelope stamped onto every event before send (device + host scalars). */\n envelope?: EnvelopeSource;\n /** Max pending events; enqueuing past this DROPS THE OLDEST first (default 200). */\n maxSize?: number;\n /** Events per POST batch (default 20). */\n batchSize?: number;\n /** First retry delay in ms; doubles each failed attempt (default 1000). */\n baseBackoffMs?: number;\n /** Backoff ceiling in ms (default 30000). */\n maxBackoffMs?: number;\n /** Max AUTOMATIC backoff retries before pausing (default 6); `notifyOnline()`/`flush()` re-arm it. */\n maxRetries?: number;\n};\n\n/** The queue's public surface. `enqueue` is fire-and-forget (returns immediately, never throws). */\nexport type EventQueue = {\n /** Buffer one event (envelope-stamped), persist, and schedule a flush. Never throws. */\n enqueue(event: ClientEvent): void;\n /** Attempt an immediate drain of the pending buffer. Fire-and-forget. */\n flush(): void;\n /** Host reconnect signal: reset backoff and drain immediately. Fire-and-forget. */\n notifyOnline(): void;\n /** Current pending (in-memory) count. */\n size(): number;\n};\n\nconst DEFAULTS = {\n maxSize: 200,\n batchSize: 20,\n baseBackoffMs: 1000,\n maxBackoffMs: 30000,\n maxRetries: 6,\n} as const;\n\n/** Ceiling on the persisted-backlog read — a hung adapter degrades to an empty start, never a stall. */\nconst READ_TIMEOUT_MS = 1500;\n\n/** Internal buffered item. `id` is a local monotonic handle for deterministic dequeue-after-ack;\n * it is NEVER sent to the server. `sig` is the de-dup signature (serialized stamped event). */\ntype QueuedItem = { id: number; event: ClientEvent; sig: string };\n\n/** Persisted shape — the local id + the (already envelope-stamped) event. `sig` is recomputed on load. */\ntype PersistedItem = { id: number; event: ClientEvent };\n\nconst withTimeout = <T>(p: Promise<T>, ms: number): Promise<T | undefined> => {\n let timer: ReturnType<typeof setTimeout>;\n const timeout = new Promise<undefined>((resolve) => {\n timer = setTimeout(() => resolve(undefined), ms);\n });\n return Promise.race([p, timeout]).finally(() => clearTimeout(timer));\n};\n\n/** Detach a timer from the event loop where the runtime supports it (Node test process / some RNs). */\nconst unrefTimer = (timer: ReturnType<typeof setTimeout>): void => {\n const t = timer as unknown as { unref?: () => void };\n if (typeof t.unref === \"function\") t.unref();\n};\n\nconst parsePersisted = (raw: string | null | undefined): PersistedItem[] => {\n if (!raw) return [];\n try {\n const parsed: unknown = JSON.parse(raw);\n if (!Array.isArray(parsed)) return [];\n const items: PersistedItem[] = [];\n for (const entry of parsed) {\n if (\n entry &&\n typeof entry === \"object\" &&\n typeof (entry as PersistedItem).id === \"number\" &&\n (entry as PersistedItem).event &&\n typeof (entry as PersistedItem).event === \"object\"\n ) {\n items.push(entry as PersistedItem);\n }\n }\n return items;\n } catch {\n // Corrupt backlog → start empty; the next persist overwrites it.\n return [];\n }\n};\n\n/**\n * Create an offline-first event queue. Loads any persisted backlog on creation so a\n * killed-and-relaunched app resumes where it left off. Returns the {@link EventQueue} surface.\n */\nexport const createEventQueue = (options: EventQueueOptions): EventQueue => {\n const target = options.target;\n const storage = options.storage;\n const key = options.storageKey ?? `wireai:evtq:${options.appId ?? \"default\"}`;\n const maxSize = options.maxSize ?? DEFAULTS.maxSize;\n const batchSize = options.batchSize ?? DEFAULTS.batchSize;\n const baseBackoffMs = options.baseBackoffMs ?? DEFAULTS.baseBackoffMs;\n const maxBackoffMs = options.maxBackoffMs ?? DEFAULTS.maxBackoffMs;\n const maxRetries = options.maxRetries ?? DEFAULTS.maxRetries;\n\n let pending: QueuedItem[] = [];\n let nextId = 0;\n let flushing = false;\n let attempt = 0;\n let retryTimer: ReturnType<typeof setTimeout> | undefined;\n\n const resolveEnvelope = (): ContextEnvelope | undefined => {\n try {\n return typeof options.envelope === \"function\" ? options.envelope() : options.envelope;\n } catch {\n return undefined;\n }\n };\n\n // Stamp the current envelope onto a COPY of the event (never mutate the caller's object):\n // device → event.device (when the event has none)\n // sessionId → event.session_id (when the event has none)\n // appVersion / appBuild / networkType → event.user_context (the non-PII bucket the server\n // sanitizes), never overwriting a key the caller already set.\n const stamp = (event: ClientEvent): ClientEvent => {\n const env = resolveEnvelope();\n const stamped: ClientEvent = { ...event };\n if (!env) return stamped;\n if (!stamped.device && env.device) stamped.device = env.device;\n if (!stamped.session_id && env.sessionId) stamped.session_id = env.sessionId;\n const uc: Record<string, string | number | boolean> = { ...(stamped.user_context ?? {}) };\n if (env.appVersion && uc.app_version === undefined) uc.app_version = env.appVersion;\n if (env.appBuild && uc.app_build === undefined) uc.app_build = env.appBuild;\n if (env.networkType && uc.network_type === undefined) uc.network_type = env.networkType;\n if (Object.keys(uc).length > 0) stamped.user_context = uc;\n return stamped;\n };\n\n const persist = (): void => {\n if (!storage) return;\n try {\n if (pending.length === 0) {\n void storage.removeItem(key).catch(() => {});\n return;\n }\n const payload: PersistedItem[] = pending.map((item) => ({ id: item.id, event: item.event }));\n void storage.setItem(key, JSON.stringify(payload)).catch(() => {});\n } catch {\n // Best-effort: a failed write just means the backlog is not durable this launch.\n }\n };\n\n const enforceSizeCap = (): void => {\n // Drop the OLDEST first so the newest events are never the ones lost under pressure.\n if (pending.length > maxSize) pending.splice(0, pending.length - maxSize);\n };\n\n const safeSig = (event: ClientEvent): string => {\n try {\n return JSON.stringify(event);\n } catch {\n // Non-serializable event → give it a unique signature so it is never wrongly de-duped.\n return `__nosig_${nextId}_${Math.random()}`;\n }\n };\n\n // Load any persisted backlog. Anything enqueued before this settles stays in memory; we merge\n // persisted (older) ahead of it and reassign monotonic ids so dequeue-after-ack is deterministic.\n const loadPromise: Promise<void> = (async () => {\n if (!storage) return;\n try {\n const persistedItems = parsePersisted(await withTimeout(storage.getItem(key), READ_TIMEOUT_MS));\n if (persistedItems.length === 0) return;\n const events = [...persistedItems.map((p) => p.event), ...pending.map((p) => p.event)];\n pending = [];\n nextId = 0;\n const seen = new Set<string>();\n for (const event of events) {\n const sig = safeSig(event);\n if (seen.has(sig)) continue; // collapse duplicates carried across the merge\n seen.add(sig);\n pending.push({ id: nextId++, event, sig });\n }\n enforceSizeCap();\n persist();\n } catch {\n // Unreadable backlog → start empty; nothing enqueued in-memory is lost.\n }\n })();\n\n // The queue's OWN awaited POST. Reads `res.ok` to drive retry/dequeue. NEVER throws — a missing\n // fetch, a rejecting network, or a JSON error resolves to `false` (batch stays, retry schedules).\n const postBatch = async (events: ClientEvent[]): Promise<boolean> => {\n if (!target?.serverUrl || events.length === 0) return false;\n try {\n const url = `${target.serverUrl.replace(/\\/$/, \"\")}/v1/events`;\n const headers: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;\n const res = await fetch(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify({ events }),\n });\n return !!(res && (res as { ok?: boolean }).ok);\n } catch {\n return false;\n }\n };\n\n const clearRetry = (): void => {\n if (retryTimer !== undefined) {\n clearTimeout(retryTimer);\n retryTimer = undefined;\n }\n };\n\n const scheduleRetry = (): void => {\n // Bounded automatic retry. Past the cap the backlog simply waits for the next\n // `notifyOnline()` / `flush()` (both re-arm attempt), so events are paused, never dropped.\n if (attempt >= maxRetries) return;\n const delay = Math.min(baseBackoffMs * 2 ** attempt, maxBackoffMs);\n attempt++;\n clearRetry();\n retryTimer = setTimeout(() => {\n retryTimer = undefined;\n void drain();\n }, delay);\n unrefTimer(retryTimer);\n };\n\n const drain = async (): Promise<void> => {\n try {\n await loadPromise;\n } catch {\n // load already swallows; guard the await defensively.\n }\n if (flushing) return;\n flushing = true;\n try {\n while (pending.length > 0) {\n const batch = pending.slice(0, batchSize);\n const ok = await postBatch(batch.map((item) => item.event));\n if (!ok) {\n scheduleRetry();\n return;\n }\n // Dequeue exactly the acked batch by id (pending may have grown while in flight).\n const acked = new Set(batch.map((item) => item.id));\n pending = pending.filter((item) => !acked.has(item.id));\n persist();\n attempt = 0;\n clearRetry();\n }\n } finally {\n flushing = false;\n }\n };\n\n const flush = (): void => {\n try {\n void drain();\n } catch {\n // drain never throws synchronously, but guard the kick anyway.\n }\n };\n\n const enqueue = (event: ClientEvent): void => {\n try {\n const stamped = stamp(event);\n const sig = safeSig(stamped);\n // Collapse a redundant re-enqueue of an identical pending event.\n for (const item of pending) {\n if (item.sig === sig) return;\n }\n pending.push({ id: nextId++, event: stamped, sig });\n enforceSizeCap();\n persist();\n // Only kick a drain when no retry is already pending — avoids hammering fetch while offline.\n if (retryTimer === undefined) flush();\n } catch {\n // Fire-and-forget: nothing in enqueue may surface to the UI.\n }\n };\n\n const notifyOnline = (): void => {\n // Host reconnected: reset the backoff and drain now.\n attempt = 0;\n clearRetry();\n flush();\n };\n\n const size = (): number => pending.length;\n\n return { enqueue, flush, notifyOnline, size };\n};\n"]}
1
+ {"version":3,"sources":["../../src/reviews/transport.ts","../../src/analytics/screenTracking.ts","../../src/analytics/useScreenTracking.ts","../../src/analytics/reportClientEvent.ts","../../src/analytics/analyticsEvent.ts","../../src/device/deviceContext.ts","../../src/analytics/contextEnvelope.ts","../../src/analytics/eventQueue.ts","../../src/identity/userIdentity.ts","../../src/analytics/analyticsFacade.ts","../../src/analytics/useAnalytics.ts"],"names":["useRef","useEffect","Platform","Dimensions","I18nManager","_a"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * transport.ts — kit → Wire server requests for the review module, all fire-and-forget\n * (analytics/reviews must never break the app). Mirrors analytics/reportClientEvent: a\n * thin fetch wrapper, Bearer tenant key, swallow every error.\n *\n * • submitReview → POST {serverUrl}/v1/reviews (the 1-4 feedback body)\n * • reportAppEvent → POST {serverUrl}/v1/events (generic app.* namespace)\n *\n * `reportAppEvent` is the strategic extension: it lets a host report arbitrary in-app\n * events through the SAME transport (stored server-side as event_type='app_event',\n * question_key=<name>), which is what the backend review-firing rules evaluate on — and\n * it seeds the broader app-analytics stream. Keep payloads minimal + non-PII.\n */\nimport type { ReviewSubmission, ReviewTarget } from \"./types\";\n\n/**\n * POST a review (the 1-4 feedback path). Fire-and-forget: a missing target, a build error,\n * a missing `fetch`, or a network failure is swallowed and the call returns immediately.\n */\nexport const submitReview = (\n target: ReviewTarget | undefined,\n review: ReviewSubmission,\n): void => {\n if (!target?.serverUrl) return;\n try {\n const url = `${target.serverUrl.replace(/\\/$/, \"\")}/v1/reviews`;\n const headers: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;\n void fetch(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(review),\n }).catch(() => {\n /* best-effort, swallow */\n });\n } catch {\n /* URL/JSON/missing-fetch — swallow */\n }\n};\n\n/** Options for a reported app event. `deviceKey` groups a device's sessions server-side. */\nexport interface ReportAppEventOptions {\n /** The onboarding/session id to correlate with, when known. */\n sessionId?: string;\n /** A stable, non-PII device id — the review-decision endpoint reads it for min-sessions. */\n deviceKey?: string;\n /** Small non-PII extras. */\n meta?: Record<string, unknown>;\n}\n\n/**\n * Report a generic in-app event through the existing events transport. Stored server-side\n * as `event_type='app_event'`, `question_key=<name>`. Fire-and-forget. Keep `name` a short\n * stable identifier and `meta` small + non-PII.\n *\n * reportAppEvent(target, \"content_share\", { sessionId, deviceKey });\n */\nexport const reportAppEvent = (\n target: ReviewTarget | undefined,\n name: string,\n options: ReportAppEventOptions = {},\n): void => {\n if (!target?.serverUrl || !name) return;\n try {\n const url = `${target.serverUrl.replace(/\\/$/, \"\")}/v1/events`;\n const headers: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;\n const event: Record<string, unknown> = {\n event_type: \"app_event\",\n question_key: name,\n };\n if (options.sessionId) event.session_id = options.sessionId;\n // device_key rides in the non-PII user_context bucket the server sanitizes; the\n // review-decision endpoint reads it to group a device's sessions.\n if (options.deviceKey) event.user_context = { device_key: options.deviceKey };\n if (options.meta && Object.keys(options.meta).length > 0) {\n event.meta = JSON.stringify(options.meta);\n }\n void fetch(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify({ events: [event] }),\n }).catch(() => {\n /* best-effort, swallow */\n });\n } catch {\n /* swallow */\n }\n};\n","/**\n * screenTracking — an OPT-IN, dependency-free automatic screen-view helper.\n *\n * Given the host app's navigation STATE (or a nav ref, via the useScreenTracking hook), this\n * emits exactly ONE `screen` app-event per REAL screen change through the kit's existing\n * `reportAppEvent` transport (`POST {serverUrl}/v1/events`, `event_type='app_event'`,\n * `question_key='screen'`, `meta={ screen }`). Param-only changes and re-renders are de-duped\n * away because we resolve and compare the route NAME only — never the params.\n *\n * Dependency-free core: this file imports NO navigation library. React-Navigation / expo-router\n * are host concerns; the host supplies plain state objects and refs, typed structurally here\n * (`NavigationStateLike`). It is also React-free — the optional React glue lives in\n * `useScreenTracking.ts`. `reportAppEvent` is imported from the pure `../reviews/transport`\n * module (NOT the `../reviews` barrel, which would drag the review UI into an analytics-only\n * bundle and defeat tree-shaking).\n *\n * Privacy: only the route NAME ever leaves the device. Route params, query strings, and any\n * user data are never read into the event. Fire-and-forget — this never throws into the UI.\n */\nimport { reportAppEvent } from \"../reviews/transport\";\n\n/** A single route inside a React-Navigation-shaped state (structural — no `@react-navigation`). */\nexport interface NavigationRouteLike {\n name: string;\n /** A nested navigator's own state, when this route hosts one. */\n state?: NavigationStateLike;\n /** Route params are intentionally left `unknown` — this helper never reads them. */\n params?: unknown;\n}\n\n/** A React-Navigation-shaped navigator state (structural type; no library import). */\nexport interface NavigationStateLike {\n /** Index of the active route within `routes`. */\n index?: number;\n routes?: NavigationRouteLike[];\n}\n\n/** Options for {@link createScreenTracker}. */\nexport interface ScreenTrackerOptions {\n /**\n * Where to POST. `{ serverUrl, apiKey }` — same shape the kit's review/analytics config\n * exposes. When omitted, the tracker still de-dups and fires `onScreen`, but sends nothing.\n */\n target?: { serverUrl: string; apiKey: string };\n /** The onboarding/session id to correlate screen views with, when known. */\n sessionId?: string;\n /** A stable, non-PII device id — groups a device's sessions server-side. */\n deviceKey?: string;\n /** Called on every REAL screen change (after de-dup), before the network emit. */\n onScreen?: (screen: string) => void;\n /**\n * Per-screen filter. Return `false` to skip the NETWORK emit for a screen (last-screen memory\n * is still advanced + `onScreen` still fires) — e.g. to keep a sensitive route out of analytics.\n */\n shouldTrack?: (screen: string) => boolean;\n}\n\n/** The screen tracker returned by {@link createScreenTracker}. */\nexport interface ScreenTracker {\n /** Report the active screen. Ignores `undefined`/empty and de-dups repeats of the last screen. */\n track: (screen: string | undefined) => void;\n /** Clear the last-screen memory (e.g. on logout) so the next `track` always emits. */\n reset: () => void;\n}\n\n/**\n * Walk a React-Navigation-shaped state to the DEEPEST active route and return its NAME (never\n * its params). Recurses `routes[index]` while a nested `.state` exists. Returns `undefined` for\n * a missing/empty/malformed state — the caller treats that as \"nothing to report\".\n */\nexport const getActiveRouteName = (\n state: NavigationStateLike | undefined,\n): string | undefined => {\n let current: NavigationStateLike | undefined = state;\n let name: string | undefined;\n // Bounded by the finite nesting depth of a real navigator tree.\n while (current && Array.isArray(current.routes) && current.routes.length > 0) {\n const index = typeof current.index === \"number\" ? current.index : 0;\n const route = current.routes[index];\n if (!route) break;\n name = route.name;\n current = route.state;\n }\n return name;\n};\n\n/**\n * Build a stateful screen tracker. `track` de-dups against the last reported screen so only a\n * REAL change emits; `reset` clears that memory. Fire-and-forget throughout — a missing `target`\n * skips the network but keeps the de-dup + `onScreen` behaviour intact.\n */\nexport const createScreenTracker = (options: ScreenTrackerOptions = {}): ScreenTracker => {\n let lastScreen: string | undefined;\n return {\n track: (screen: string | undefined): void => {\n if (!screen || screen === lastScreen) return;\n lastScreen = screen;\n options.onScreen?.(screen);\n if (options.shouldTrack && !options.shouldTrack(screen)) return;\n reportAppEvent(options.target, \"screen\", {\n sessionId: options.sessionId,\n deviceKey: options.deviceKey,\n meta: { screen },\n });\n },\n reset: (): void => {\n lastScreen = undefined;\n },\n };\n};\n\n/**\n * Adapt a tracker into a React-Navigation `onStateChange` handler — the one-place wiring:\n *\n * <NavigationContainer onStateChange={screenTrackingHandler(tracker)}>\n *\n * It resolves the deepest active route name and hands it to `tracker.track` (which de-dups).\n */\nexport const screenTrackingHandler =\n (tracker: ScreenTracker) =>\n (state: NavigationStateLike | undefined): void =>\n tracker.track(getActiveRouteName(state));\n","/**\n * useScreenTracking — a THIN optional React hook over the pure screen-tracking core.\n *\n * It builds one tracker for the component's lifetime and subscribes to the host's navigation\n * ref: it reports the current route on mount and on every `state` event, and unsubscribes on\n * unmount. React is a REQUIRED peer of the kit, so importing it here is allowed; the hook adds\n * NO navigation-library dependency — the ref is typed structurally (`NavigationRefLike`).\n *\n * const navigationRef = useNavigationContainerRef(); // host's @react-navigation ref\n * useScreenTracking(navigationRef, { target, sessionId });\n * // ...<NavigationContainer ref={navigationRef}>\n */\nimport { useEffect, useRef } from \"react\";\n\nimport { createScreenTracker, type ScreenTracker, type ScreenTrackerOptions } from \"./screenTracking\";\n\n/**\n * The structural slice of a React-Navigation container ref this hook needs — no\n * `@react-navigation` import. `getCurrentRoute` yields the active route; `addListener(\"state\", …)`\n * fires on every navigation state change and returns its own unsubscribe.\n */\nexport interface NavigationRefLike {\n getCurrentRoute?: () => { name?: string } | undefined;\n addListener?: (type: \"state\", callback: () => void) => () => void;\n}\n\n/**\n * Subscribe screen tracking to a host navigation ref. Safe to call with a not-yet-ready ref\n * (the effect no-ops until `addListener` exists). Returns nothing — it wires side effects only.\n */\nexport const useScreenTracking = (\n navigationRef: NavigationRefLike | undefined,\n options: ScreenTrackerOptions = {},\n): void => {\n // One tracker per mount; kept in a ref so re-renders never rebuild the de-dup memory.\n const trackerRef = useRef<ScreenTracker | undefined>(undefined);\n if (!trackerRef.current) trackerRef.current = createScreenTracker(options);\n\n useEffect(() => {\n const tracker = trackerRef.current;\n if (!tracker || !navigationRef?.addListener) return;\n const report = (): void => tracker.track(navigationRef.getCurrentRoute?.()?.name);\n report(); // initial screen on mount\n const unsubscribe = navigationRef.addListener(\"state\", report);\n return unsubscribe;\n // Re-subscribe only when the ref identity changes; option changes are read live off the closure.\n }, [navigationRef]);\n};\n","/**\n * reportClientEvent — forward DEVICE-ONLY onboarding events to the Wire AI analytics\n * backend (`POST {serverUrl}/v1/events`), completing the funnel for events the server\n * can't observe on its own.\n *\n * The backend already records the server-observable funnel during the A2A flow\n * (`session_started`, `screen_shown`, `answer_submitted`, `completed`, `llm_fallback`,\n * and even `screen_skipped` — it derives that from the kit's skip sentinel). The one\n * event no server request can capture is `dropped`: the user closing the app / unmounting\n * the flow without finishing. That's what this reporter is for.\n *\n * Contract (server: routers/onboarding.py → analytics/events.py):\n * POST {serverUrl}/v1/events\n * Authorization: Bearer {apiKey}\n * { \"events\": [ { event_type, session_id, screen_index?, component?, question_key?,\n * latency_ms?, meta?, device?, user_context? } ] }\n * The server fills `app_id` + `environment` from the resolving key (never send app_id),\n * and silently skips malformed events — one bad payload never fails the batch.\n *\n * ⚠️ Correlation: `session_id` MUST equal the A2A `contextId` the server uses to key the\n * server-side events, or the funnel report (which groups by `session_id`) treats this as a\n * phantom session. See `makeSessionId` + WireOnboarding for how the kit seeds it.\n *\n * Fire-and-forget: this never throws into the UI and never awaits — analytics must never\n * be able to break onboarding.\n */\nimport type { DeviceContext } from \"../device/deviceContext\";\n\n/** Event types a CLIENT may report. The rest of the funnel is server-side; sending those\n * here would double-count. `screen_skipped` is included for completeness, but the kit does\n * NOT emit it — the backend already derives it from the skip sentinel (see OnboardingFlow).\n * `client_fallback` is emitted by the kit when the AI flow degrades to the static fallback,\n * so the dashboard's fallback-rate counts the whole-flow case (distinct from the server's\n * per-turn `llm_fallback`). The server back-fills a `session_started` for it if unseen.\n * This is the SINGLE fallback signal — hosts must NOT also report their own.\n * `identify` binds the host's opaque `user_id` to this `session_id` (late binding — the user\n * registered during/after onboarding). It carries no funnel weight; the server maps the\n * session to the user and back-fills a `session_started` if it never saw the session.\n * `app_event` is the host's own in-app event (the `createAnalytics` façade's `track`/`screen`\n * route through the offline queue as first-class `ClientEvent`s). The server already ingests\n * it — `reportAppEvent` (reviews/transport) has fired `event_type='app_event'` all along; this\n * widening just lets the same shape flow through the durable queue instead of a blind POST. */\nexport type ClientEventType =\n | \"screen_skipped\"\n | \"dropped\"\n | \"client_fallback\"\n | \"identify\"\n | \"app_event\";\n\n/** One client-reported event. Mirrors the server's `OnboardingEvent` (client-settable fields). */\nexport type ClientEvent = {\n event_type: ClientEventType;\n /** Must match the server-side A2A contextId for this onboarding (see makeSessionId). */\n session_id: string;\n /** 0-based index of the screen the event refers to (matches server `screen_shown`). */\n screen_index?: number;\n component?: string;\n question_key?: string;\n latency_ms?: number;\n /** JSON-stringified extras; the server stores it verbatim. */\n meta?: string;\n /**\n * Privacy-label-neutral device snapshot (platform / form factor / locale / host appVersion).\n * Sent as an object; the server sanitizes + persists it and derives a coarse country. Old\n * servers ignore this unknown field — fully backward compatible. See device/deviceContext.ts.\n */\n device?: DeviceContext;\n /**\n * Host-injected, non-PII context (signup method, referral, plan, hashed user id). Old servers\n * ignore it. MUST NOT contain PII like raw emails — see the README `userContext` section.\n */\n user_context?: Record<string, string | number | boolean>;\n /**\n * The host's OPAQUE PSEUDONYMOUS user id (their internal id, NOT an email/name). Required on\n * `identify`, optional (rides along) on other events. Trimmed + capped at 128 chars host-side.\n * Lets the backend reconcile onboarding sessions to real users. Old servers ignore it.\n */\n user_id?: string;\n};\n\n/** Where to POST. Derived from `WireOnboardingConfig` (`serverUrl` + `apiKey`). */\nexport type ClientEventTarget = {\n /** Base server URL (same as `WireOnboardingConfig.serverUrl`); `/v1/events` is appended. */\n serverUrl: string;\n /** Tenant API key; sent as `Authorization: Bearer`. */\n apiKey: string;\n};\n\n/**\n * A unique-per-onboarding session id. Used both as the client event `session_id` AND as the\n * seed the kit forwards to the backend so the SERVER adopts it as the A2A `contextId` — making\n * client and server agree (see WireOnboarding + the SDK-correlation note in the kit docs).\n * No crypto dependency: timestamp + random is collision-safe for a single device's onboarding.\n */\nexport const makeSessionId = (): string =>\n `wire_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;\n\n/**\n * POST one or more client events, fire-and-forget. A missing/invalid target, a build error,\n * a missing `fetch`, or a network failure is swallowed — the call returns immediately and the\n * request (if any) runs in the background.\n */\nexport const reportClientEvents = (\n target: ClientEventTarget | undefined,\n events: ClientEvent[],\n): void => {\n if (!target?.serverUrl || events.length === 0) return;\n try {\n const url = `${target.serverUrl.replace(/\\/$/, \"\")}/v1/events`;\n const headers: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;\n void fetch(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify({ events }),\n }).catch(() => {\n // Network/transport error — analytics is best-effort, swallow.\n });\n } catch {\n // URL construction, JSON serialization, or a missing fetch — swallow.\n }\n};\n\n/** Convenience single-event wrapper around {@link reportClientEvents}. */\nexport const reportClientEvent = (\n target: ClientEventTarget | undefined,\n event: ClientEvent,\n): void => reportClientEvents(target, [event]);\n","/**\n * Canonical analytics names for the onboarding funnel. The kit already emits a typed\n * `OnboardingEvent` (`started | turn | error | retry | fallback`) — but one app logged them as\n * `onboarding_*` and another as `AI_ONBOARDING_*`, so the same funnel reads differently per app.\n * This maps the kit event to ONE canonical `wire_onboarding_*` name + params, and the app logs\n * it through whatever transport it already has (Firebase, Amplitude, console). The app still\n * owns the logger; only the NAMES are standardized.\n *\n * <WireOnboarding\n * onEvent={(e) => { const a = toAnalyticsEvent(e); logEvent(a.name, a.params); }}\n * onComplete={(r) => { logEvent(WIRE_ONBOARDING_EVENTS.completed, { answers: Object.keys(r.answers).length }); persist(r); }}\n * />\n *\n * `completed` has no kit `OnboardingEvent` (the kit signals completion via `onComplete`, not\n * `onEvent`) — the app logs it explicitly on `onComplete` using the constant below, so the\n * funnel name stays canonical.\n */\nimport type { OnboardingEvent } from \"../types\";\n\nexport const WIRE_ONBOARDING_EVENTS = {\n started: \"wire_onboarding_started\",\n /** A persisted session was restored after an app kill (fires instead of `started`). */\n resumed: \"wire_onboarding_resumed\",\n turn: \"wire_onboarding_turn\",\n error: \"wire_onboarding_error\",\n retry: \"wire_onboarding_retry\",\n fallback: \"wire_onboarding_fallback\",\n /** Logged by the host on `onComplete` (no matching kit `OnboardingEvent`). */\n completed: \"wire_onboarding_completed\",\n} as const;\n\nexport type WireOnboardingEventName =\n (typeof WIRE_ONBOARDING_EVENTS)[keyof typeof WIRE_ONBOARDING_EVENTS];\n\nexport type AnalyticsEvent = {\n name: WireOnboardingEventName;\n params?: Record<string, unknown>;\n};\n\n/**\n * Map a kit `OnboardingEvent` to its canonical `{ name, params }`. Exhaustive over the union\n * (the `never` default makes a new event type a compile error here — intentional).\n */\nexport const toAnalyticsEvent = (event: OnboardingEvent): AnalyticsEvent => {\n switch (event.type) {\n case \"started\":\n return { name: WIRE_ONBOARDING_EVENTS.started };\n case \"resumed\":\n return { name: WIRE_ONBOARDING_EVENTS.resumed };\n case \"turn\":\n return {\n name: WIRE_ONBOARDING_EVENTS.turn,\n params: { step: event.step, component: event.component },\n };\n case \"error\":\n return { name: WIRE_ONBOARDING_EVENTS.error, params: { reason: event.reason } };\n case \"retry\":\n return {\n name: WIRE_ONBOARDING_EVENTS.retry,\n params: { reason: event.reason, attempt: event.attempt },\n };\n case \"fallback\":\n return { name: WIRE_ONBOARDING_EVENTS.fallback, params: { reason: event.reason } };\n default: {\n const _exhaustive: never = event;\n return _exhaustive;\n }\n }\n};\n","/**\n * deviceContext — collect a small, privacy-label-neutral snapshot of the device so\n * onboarding analytics can segment the funnel (platform / form factor / locale) WITHOUT\n * adding a single dependency to the kit or changing a host app's App Privacy / Data Safety\n * declarations.\n *\n * HARD RULE (why this file has no imports beyond React Native built-ins):\n * The kit stays dependency-free. Everything here comes from `Platform`, `Dimensions`,\n * `I18nManager`, and the standard `Intl` global. There are NO advertising IDs, NO\n * `getUniqueId`/IDFA/GAID/fingerprinting APIs, and nothing that would require a new\n * privacy-label entry. A host can adopt this without touching its store declarations.\n *\n * DEFENSIVE BY DESIGN: `collectDeviceContext()` never throws. Every read is guarded and\n * a missing/unavailable field is simply omitted (Hermes may ship without full `Intl`,\n * `Platform.constants` differs per OS and RN version, etc.). Analytics must never be able\n * to break onboarding.\n */\nimport { Dimensions, I18nManager, Platform } from \"react-native\";\n\n/** Coarse device class. iOS uses the reported interface idiom; else a screen-size heuristic. */\nexport type DeviceFormFactor = \"phone\" | \"tablet\";\n\n/**\n * A privacy-label-neutral device snapshot. ALL fields except `platform` are optional and are\n * omitted when unavailable. Nothing here identifies a user or device uniquely.\n */\nexport type DeviceContext = {\n /** `Platform.OS` — \"ios\" | \"android\" | \"windows\" | \"macos\" | \"web\". Always present. */\n platform: typeof Platform.OS;\n /** OS version string (iOS `osVersion`/`Platform.Version`, Android `Release`). */\n osVersion?: string;\n /** Android device brand (e.g. \"samsung\"). Android only. */\n brand?: string;\n /** Android device model (e.g. \"SM-G991B\"). Android only. */\n model?: string;\n /** iOS interface idiom (\"phone\" | \"pad\" | …), when reported. iOS only. */\n interfaceIdiom?: string;\n /** Derived device class. */\n formFactor?: DeviceFormFactor;\n /** `Dimensions.get('screen')` width in dp. */\n screenWidth?: number;\n /** `Dimensions.get('screen')` height in dp. */\n screenHeight?: number;\n /** Screen pixel density (`scale`). */\n screenScale?: number;\n /** Right-to-left layout (`I18nManager.isRTL`). */\n isRTL?: boolean;\n /** Resolved locale (e.g. \"en-US\"), from `Intl` when available. */\n locale?: string;\n /** IANA time zone (e.g. \"Europe/Berlin\"), from `Intl` when available. */\n timeZone?: string;\n /**\n * Host app version (e.g. \"1.4.2). HOST-INJECTED — NOT collected here. `WireOnboarding`\n * merges `config.appVersion` into the snapshot; `collectDeviceContext()` never sets it.\n * Hosts typically pass it from `expo-constants` (the kit itself adds no dependency).\n */\n appVersion?: string;\n};\n\n/** iOS idiom wins; otherwise the shortest side in dp (>= 600 → tablet) picks the class. */\nconst deriveFormFactor = (\n iosIdiom: string | undefined,\n width: number | undefined,\n height: number | undefined,\n): DeviceFormFactor | undefined => {\n if (iosIdiom === \"pad\") return \"tablet\";\n if (iosIdiom === \"phone\") return \"phone\";\n if (typeof width === \"number\" && typeof height === \"number\") {\n return Math.min(width, height) >= 600 ? \"tablet\" : \"phone\";\n }\n return undefined;\n};\n\n/**\n * Collect the device snapshot. Pure, synchronous, and never throws — call it once per\n * onboarding session. Missing fields are omitted rather than sent as null/undefined so the\n * payload (and the server's stored dict) stays compact.\n */\nexport const collectDeviceContext = (): DeviceContext => {\n const ctx: DeviceContext = { platform: Platform.OS };\n\n // OS version (fallback; per-OS constants below may refine it).\n try {\n const version = Platform.Version;\n if (version !== undefined && version !== null && String(version)) {\n ctx.osVersion = String(version);\n }\n } catch {\n // ignore\n }\n\n // `Platform.constants` shape differs by OS and RN version — read every field defensively.\n let constants: Record<string, unknown> = {};\n try {\n constants = (Platform.constants ?? {}) as Record<string, unknown>;\n } catch {\n constants = {};\n }\n\n let iosIdiom: string | undefined;\n try {\n if (Platform.OS === \"android\") {\n const brand = constants.Brand;\n const model = constants.Model;\n const release = constants.Release;\n if (typeof brand === \"string\" && brand) ctx.brand = brand;\n if (typeof model === \"string\" && model) ctx.model = model;\n if (release !== undefined && release !== null && String(release)) {\n ctx.osVersion = String(release);\n }\n } else if (Platform.OS === \"ios\") {\n const osVersion = constants.osVersion;\n const idiom = constants.interfaceIdiom;\n if (osVersion !== undefined && osVersion !== null && String(osVersion)) {\n ctx.osVersion = String(osVersion);\n }\n if (typeof idiom === \"string\" && idiom) {\n ctx.interfaceIdiom = idiom;\n iosIdiom = idiom;\n }\n }\n } catch {\n // ignore per-OS constant reads\n }\n\n // Screen dimensions + derived form factor.\n try {\n const screen = Dimensions.get(\"screen\");\n if (screen) {\n if (typeof screen.width === \"number\") ctx.screenWidth = screen.width;\n if (typeof screen.height === \"number\") ctx.screenHeight = screen.height;\n if (typeof screen.scale === \"number\") ctx.screenScale = screen.scale;\n const formFactor = deriveFormFactor(iosIdiom, screen.width, screen.height);\n if (formFactor) ctx.formFactor = formFactor;\n }\n } catch {\n // ignore\n }\n\n // Layout direction.\n try {\n ctx.isRTL = I18nManager.isRTL;\n } catch {\n // ignore\n }\n\n // Locale + time zone via the standard Intl global. Hermes may ship without full Intl,\n // so referencing it can throw ReferenceError — the try/catch covers that too.\n try {\n const resolved = Intl.DateTimeFormat().resolvedOptions();\n if (resolved.locale) ctx.locale = resolved.locale;\n if (resolved.timeZone) ctx.timeZone = resolved.timeZone;\n } catch {\n // Intl unavailable — omit locale/timeZone.\n }\n\n return ctx;\n};\n","/**\n * contextEnvelope — a small, PRIVACY-NEUTRAL context bundle stamped onto every outgoing\n * analytics event, giving the Wire dashboard the Sentry/Firebase-parity segmentation fields\n * (device model, OS + version, screen, locale, timezone, form factor) plus a few host-injected\n * scalars (session correlation id, app version + native build number, connectivity type).\n *\n * WHY a separate builder (not just `collectDeviceContext`): the envelope COMPOSES the existing\n * device snapshot with the handful of extras a host can cheaply supply but the kit can't collect\n * dependency-free (native build number, connectivity type). It never re-implements device\n * collection — it reuses `collectDeviceContext()` verbatim (see device/deviceContext.ts).\n *\n * HARD PRIVACY RULE (why this file, like deviceContext.ts, adds nothing new):\n * NEVER GPS / location, NEVER an advertising id (IDFA / GAID), NEVER a device fingerprint.\n * Location is derived SERVER-SIDE from IP-geo only — nothing here carries a coordinate or an\n * ad id, so a host adopting this changes no App Privacy / Data Safety declaration. There is a\n * test (contextEnvelope.test.ts) that asserts the ABSENCE of any such field.\n *\n * DEPENDENCY-FREE: the only import is the kit's own `collectDeviceContext`. `networkType` and\n * `appBuild` are HOST-INJECTED — there is no dependency-free RN core signal for either, so the\n * envelope simply omits them when the host does not pass them (no forced peer dependency).\n */\nimport { collectDeviceContext, type DeviceContext } from \"../device/deviceContext\";\n\n/**\n * The context stamped onto every event. `device` is always present (from\n * `collectDeviceContext`); every scalar is optional and OMITTED when the host does not supply it.\n */\nexport type ContextEnvelope = {\n /** The privacy-neutral device snapshot (reused from `collectDeviceContext`). */\n device: DeviceContext;\n /** Correlation id for this app-open / flow (caller-supplied). */\n sessionId?: string;\n /** Host app version, e.g. \"1.4.2\" (mirrors `device.appVersion`; host-injected). */\n appVersion?: string;\n /** Host native build number, e.g. \"412\" (from `expo-constants` `nativeBuildVersion`). */\n appBuild?: string;\n /** Host connectivity signal, e.g. \"wifi\" | \"cellular\" (from `@react-native-community/netinfo`). */\n networkType?: string;\n};\n\n/** Host-injected inputs for {@link buildContextEnvelope}. All optional; each is omitted when absent. */\nexport type ContextEnvelopeInput = {\n sessionId?: string;\n appVersion?: string;\n appBuild?: string;\n networkType?: string;\n};\n\n/**\n * Build a fresh context envelope. Reuses `collectDeviceContext()` for the device block and layers\n * the host-injected scalars on top. `appVersion` is additionally merged onto `device.appVersion`\n * when the device block lacks it (mirroring how `useSessionStart` back-fills the host version).\n *\n * Returns a NEW object on every call (no shared mutable reference), so a caller can hold or mutate\n * the result without leaking into the next envelope. Never throws — `collectDeviceContext` is\n * itself guarded, and the rest is plain assignment.\n */\nexport const buildContextEnvelope = (input: ContextEnvelopeInput = {}): ContextEnvelope => {\n // Fresh copy so the returned envelope never aliases a cached device snapshot.\n const device: DeviceContext = { ...collectDeviceContext() };\n\n // Mirror useSessionStart: fill the host app version onto the device block when it lacks one.\n if (input.appVersion && !device.appVersion) device.appVersion = input.appVersion;\n\n const envelope: ContextEnvelope = { device };\n if (input.sessionId) envelope.sessionId = input.sessionId;\n if (input.appVersion) envelope.appVersion = input.appVersion;\n if (input.appBuild) envelope.appBuild = input.appBuild;\n if (input.networkType) envelope.networkType = input.networkType;\n\n return envelope;\n};\n","/**\n * eventQueue — the OFFLINE-FIRST, persistent transport buffer for client analytics events.\n *\n * The existing `reportClientEvents` is a blind fire-and-forget POST: it returns `void`, has no\n * success signal, and drops events when the network is down. The analytics data story depends on\n * NEVER losing an event offline, so this queue adds the missing durability layer on top of the\n * same `POST {serverUrl}/v1/events` contract:\n *\n * • Persists pending events to the host-injected `WireOnboardingStorage` (survives app kills).\n * • Batches them into one request body `{ events: [...] }`.\n * • Owns its OWN awaited `fetch` that reads `res.ok` — the only way to drive retry + dequeue,\n * since `reportClientEvents` cannot ack. A 2xx dequeues the batch; a non-ok / rejected / thrown\n * response keeps it and schedules an exponential backoff retry.\n * • Flushes on `enqueue`, on an explicit `flush()`, and on `notifyOnline()` (host reconnect).\n * • Caps the buffer (drop-OLDEST under pressure) and de-dups identical pending events.\n * • Stamps the current context envelope (device + host scalars) onto every event before send.\n *\n * FIRE-AND-FORGET (load-bearing): `enqueue` returns immediately and NEVER throws into the UI. A\n * missing `fetch`, a hung/broken storage, a rejecting network, or a JSON error is swallowed and\n * degrades gracefully — analytics must never be able to break the app. In-memory fallback covers\n * the no-storage case (survives re-renders, not app kills).\n *\n * DEPENDENCY-FREE: no network-detection or persistence library. Connectivity is host-driven via\n * `notifyOnline()`; persistence is the host-injected AsyncStorage-compatible subset.\n */\nimport type { ContextEnvelope } from \"./contextEnvelope\";\nimport type { ClientEvent, ClientEventTarget } from \"./reportClientEvent\";\nimport type { WireOnboardingStorage } from \"../session/persistedSession\";\n\n/** Envelope source: a fixed envelope or a provider evaluated at enqueue time (fresh network type). */\nexport type EnvelopeSource = ContextEnvelope | (() => ContextEnvelope | undefined);\n\n/** Options for {@link createEventQueue}. Only `target` is conceptually required to actually send. */\nexport type EventQueueOptions = {\n /** Where to POST — the tenant transport (`serverUrl` + `apiKey`), same as `WireOnboardingConfig`. */\n target: ClientEventTarget | undefined;\n /**\n * Host persistence (AsyncStorage-compatible subset). When omitted, the queue runs in the\n * documented DEGRADED in-memory mode — it survives re-renders but not an app kill.\n */\n storage?: WireOnboardingStorage;\n /** Tenant/app id used to namespace the default storage key (`wireai:evtq:<appId>`). */\n appId?: string;\n /** Explicit storage key override (wins over the `appId`-derived default). */\n storageKey?: string;\n /** The context envelope stamped onto every event before send (device + host scalars). */\n envelope?: EnvelopeSource;\n /** Max pending events; enqueuing past this DROPS THE OLDEST first (default 200). */\n maxSize?: number;\n /** Events per POST batch (default 20). */\n batchSize?: number;\n /** First retry delay in ms; doubles each failed attempt (default 1000). */\n baseBackoffMs?: number;\n /** Backoff ceiling in ms (default 30000). */\n maxBackoffMs?: number;\n /** Max AUTOMATIC backoff retries before pausing (default 6); `notifyOnline()`/`flush()` re-arm it. */\n maxRetries?: number;\n};\n\n/** The queue's public surface. `enqueue` is fire-and-forget (returns immediately, never throws). */\nexport type EventQueue = {\n /** Buffer one event (envelope-stamped), persist, and schedule a flush. Never throws. */\n enqueue(event: ClientEvent): void;\n /** Attempt an immediate drain of the pending buffer. Fire-and-forget. */\n flush(): void;\n /** Host reconnect signal: reset backoff and drain immediately. Fire-and-forget. */\n notifyOnline(): void;\n /** Current pending (in-memory) count. */\n size(): number;\n};\n\nconst DEFAULTS = {\n maxSize: 200,\n batchSize: 20,\n baseBackoffMs: 1000,\n maxBackoffMs: 30000,\n maxRetries: 6,\n} as const;\n\n/** Ceiling on the persisted-backlog read — a hung adapter degrades to an empty start, never a stall. */\nconst READ_TIMEOUT_MS = 1500;\n\n/** Internal buffered item. `id` is a local monotonic handle for deterministic dequeue-after-ack;\n * it is NEVER sent to the server. `sig` is the de-dup signature (serialized stamped event). */\ntype QueuedItem = { id: number; event: ClientEvent; sig: string };\n\n/** Persisted shape — the local id + the (already envelope-stamped) event. `sig` is recomputed on load. */\ntype PersistedItem = { id: number; event: ClientEvent };\n\nconst withTimeout = <T>(p: Promise<T>, ms: number): Promise<T | undefined> => {\n let timer: ReturnType<typeof setTimeout>;\n const timeout = new Promise<undefined>((resolve) => {\n timer = setTimeout(() => resolve(undefined), ms);\n });\n return Promise.race([p, timeout]).finally(() => clearTimeout(timer));\n};\n\n/** Detach a timer from the event loop where the runtime supports it (Node test process / some RNs). */\nconst unrefTimer = (timer: ReturnType<typeof setTimeout>): void => {\n const t = timer as unknown as { unref?: () => void };\n if (typeof t.unref === \"function\") t.unref();\n};\n\nconst parsePersisted = (raw: string | null | undefined): PersistedItem[] => {\n if (!raw) return [];\n try {\n const parsed: unknown = JSON.parse(raw);\n if (!Array.isArray(parsed)) return [];\n const items: PersistedItem[] = [];\n for (const entry of parsed) {\n if (\n entry &&\n typeof entry === \"object\" &&\n typeof (entry as PersistedItem).id === \"number\" &&\n (entry as PersistedItem).event &&\n typeof (entry as PersistedItem).event === \"object\"\n ) {\n items.push(entry as PersistedItem);\n }\n }\n return items;\n } catch {\n // Corrupt backlog → start empty; the next persist overwrites it.\n return [];\n }\n};\n\n/**\n * Create an offline-first event queue. Loads any persisted backlog on creation so a\n * killed-and-relaunched app resumes where it left off. Returns the {@link EventQueue} surface.\n */\nexport const createEventQueue = (options: EventQueueOptions): EventQueue => {\n const target = options.target;\n const storage = options.storage;\n const key = options.storageKey ?? `wireai:evtq:${options.appId ?? \"default\"}`;\n const maxSize = options.maxSize ?? DEFAULTS.maxSize;\n const batchSize = options.batchSize ?? DEFAULTS.batchSize;\n const baseBackoffMs = options.baseBackoffMs ?? DEFAULTS.baseBackoffMs;\n const maxBackoffMs = options.maxBackoffMs ?? DEFAULTS.maxBackoffMs;\n const maxRetries = options.maxRetries ?? DEFAULTS.maxRetries;\n\n let pending: QueuedItem[] = [];\n let nextId = 0;\n let flushing = false;\n let attempt = 0;\n let retryTimer: ReturnType<typeof setTimeout> | undefined;\n\n const resolveEnvelope = (): ContextEnvelope | undefined => {\n try {\n return typeof options.envelope === \"function\" ? options.envelope() : options.envelope;\n } catch {\n return undefined;\n }\n };\n\n // Stamp the current envelope onto a COPY of the event (never mutate the caller's object):\n // device → event.device (when the event has none)\n // sessionId → event.session_id (when the event has none)\n // appVersion / appBuild / networkType → event.user_context (the non-PII bucket the server\n // sanitizes), never overwriting a key the caller already set.\n const stamp = (event: ClientEvent): ClientEvent => {\n const env = resolveEnvelope();\n const stamped: ClientEvent = { ...event };\n if (!env) return stamped;\n if (!stamped.device && env.device) stamped.device = env.device;\n if (!stamped.session_id && env.sessionId) stamped.session_id = env.sessionId;\n const uc: Record<string, string | number | boolean> = { ...(stamped.user_context ?? {}) };\n if (env.appVersion && uc.app_version === undefined) uc.app_version = env.appVersion;\n if (env.appBuild && uc.app_build === undefined) uc.app_build = env.appBuild;\n if (env.networkType && uc.network_type === undefined) uc.network_type = env.networkType;\n if (Object.keys(uc).length > 0) stamped.user_context = uc;\n return stamped;\n };\n\n const persist = (): void => {\n if (!storage) return;\n try {\n if (pending.length === 0) {\n void storage.removeItem(key).catch(() => {});\n return;\n }\n const payload: PersistedItem[] = pending.map((item) => ({ id: item.id, event: item.event }));\n void storage.setItem(key, JSON.stringify(payload)).catch(() => {});\n } catch {\n // Best-effort: a failed write just means the backlog is not durable this launch.\n }\n };\n\n const enforceSizeCap = (): void => {\n // Drop the OLDEST first so the newest events are never the ones lost under pressure.\n if (pending.length > maxSize) pending.splice(0, pending.length - maxSize);\n };\n\n const safeSig = (event: ClientEvent): string => {\n try {\n return JSON.stringify(event);\n } catch {\n // Non-serializable event → give it a unique signature so it is never wrongly de-duped.\n return `__nosig_${nextId}_${Math.random()}`;\n }\n };\n\n // Load any persisted backlog. Anything enqueued before this settles stays in memory; we merge\n // persisted (older) ahead of it and reassign monotonic ids so dequeue-after-ack is deterministic.\n const loadPromise: Promise<void> = (async () => {\n if (!storage) return;\n try {\n const persistedItems = parsePersisted(await withTimeout(storage.getItem(key), READ_TIMEOUT_MS));\n if (persistedItems.length === 0) return;\n const events = [...persistedItems.map((p) => p.event), ...pending.map((p) => p.event)];\n pending = [];\n nextId = 0;\n const seen = new Set<string>();\n for (const event of events) {\n const sig = safeSig(event);\n if (seen.has(sig)) continue; // collapse duplicates carried across the merge\n seen.add(sig);\n pending.push({ id: nextId++, event, sig });\n }\n enforceSizeCap();\n persist();\n } catch {\n // Unreadable backlog → start empty; nothing enqueued in-memory is lost.\n }\n })();\n\n // The queue's OWN awaited POST. Reads `res.ok` to drive retry/dequeue. NEVER throws — a missing\n // fetch, a rejecting network, or a JSON error resolves to `false` (batch stays, retry schedules).\n const postBatch = async (events: ClientEvent[]): Promise<boolean> => {\n if (!target?.serverUrl || events.length === 0) return false;\n try {\n const url = `${target.serverUrl.replace(/\\/$/, \"\")}/v1/events`;\n const headers: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;\n const res = await fetch(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify({ events }),\n });\n return !!(res && (res as { ok?: boolean }).ok);\n } catch {\n return false;\n }\n };\n\n const clearRetry = (): void => {\n if (retryTimer !== undefined) {\n clearTimeout(retryTimer);\n retryTimer = undefined;\n }\n };\n\n const scheduleRetry = (): void => {\n // Bounded automatic retry. Past the cap the backlog simply waits for the next\n // `notifyOnline()` / `flush()` (both re-arm attempt), so events are paused, never dropped.\n if (attempt >= maxRetries) return;\n const delay = Math.min(baseBackoffMs * 2 ** attempt, maxBackoffMs);\n attempt++;\n clearRetry();\n retryTimer = setTimeout(() => {\n retryTimer = undefined;\n void drain();\n }, delay);\n unrefTimer(retryTimer);\n };\n\n const drain = async (): Promise<void> => {\n try {\n await loadPromise;\n } catch {\n // load already swallows; guard the await defensively.\n }\n if (flushing) return;\n flushing = true;\n try {\n while (pending.length > 0) {\n const batch = pending.slice(0, batchSize);\n const ok = await postBatch(batch.map((item) => item.event));\n if (!ok) {\n scheduleRetry();\n return;\n }\n // Dequeue exactly the acked batch by id (pending may have grown while in flight).\n const acked = new Set(batch.map((item) => item.id));\n pending = pending.filter((item) => !acked.has(item.id));\n persist();\n attempt = 0;\n clearRetry();\n }\n } finally {\n flushing = false;\n }\n };\n\n const flush = (): void => {\n try {\n void drain();\n } catch {\n // drain never throws synchronously, but guard the kick anyway.\n }\n };\n\n const enqueue = (event: ClientEvent): void => {\n try {\n const stamped = stamp(event);\n const sig = safeSig(stamped);\n // Collapse a redundant re-enqueue of an identical pending event.\n for (const item of pending) {\n if (item.sig === sig) return;\n }\n pending.push({ id: nextId++, event: stamped, sig });\n enforceSizeCap();\n persist();\n // Only kick a drain when no retry is already pending — avoids hammering fetch while offline.\n if (retryTimer === undefined) flush();\n } catch {\n // Fire-and-forget: nothing in enqueue may surface to the UI.\n }\n };\n\n const notifyOnline = (): void => {\n // Host reconnected: reset the backoff and drain now.\n attempt = 0;\n clearRetry();\n flush();\n };\n\n const size = (): number => pending.length;\n\n return { enqueue, flush, notifyOnline, size };\n};\n","/**\n * User identity — bind an onboarding session to the HOST's own user id so the funnel can\n * be reconciled to real users later (console sessions ↔ your user table / GA4 users).\n *\n * The id is an OPAQUE PSEUDONYMOUS STRING the host owns — its internal user id, NOT an\n * email/name/phone. Same host-injection philosophy as `storage` and `userContext`: the kit\n * mints nothing and reads nothing; the host passes what it wants. Dependency-free.\n *\n * Three binding moments (see WireOnboarding + the README \"User identity\" section):\n * 1. MOUNT — pass `userId` and it rides the A2A session-start metadata; the server\n * binds it when it creates the session.\n * 2. MID-SESSION— the user registers DURING onboarding: change the `userId` prop and the\n * kit emits an `identify` client event that attaches the id to the live\n * session (late binding — the key scenario).\n * 3. POST-FLOW — the user registers AFTER onboarding: call `identifyOnboarding(...)` with\n * the same host storage (it recovers the persisted contextId, before completion\n * clears it) or, more reliably, the `contextId` you captured from the\n * `started`/`resumed` `onEvent` (both events carry a `contextId` field).\n *\n * ⚠️ NO PII. Pass an opaque id (or a hash), never a raw email/name/phone. The id is capped at\n * {@link USER_ID_MAX_LENGTH} chars (longer ids are truncated, not rejected).\n */\nimport { reportClientEvent } from \"../analytics/reportClientEvent\";\nimport {\n peekPersistedSession,\n sessionStorageKey,\n type WireOnboardingStorage,\n} from \"../session/persistedSession\";\n\n/** Max accepted user-id length. Longer strings are truncated (never rejected). Keep in sync\n * with the server's `USER_ID_MAX_LENGTH` (analytics/events.py). */\nexport const USER_ID_MAX_LENGTH = 128;\n\n/**\n * Normalize a host-supplied user id: trim, drop empty, and cap at {@link USER_ID_MAX_LENGTH}.\n * Returns `undefined` for a missing/blank/non-string value so callers can `if (id)`-gate.\n * PII is a host concern — this only bounds length, it does not (and cannot) detect an email.\n */\nexport const sanitizeUserId = (raw: unknown): string | undefined => {\n if (typeof raw !== \"string\") return undefined;\n const trimmed = raw.trim();\n if (!trimmed) return undefined;\n return trimmed.length > USER_ID_MAX_LENGTH ? trimmed.slice(0, USER_ID_MAX_LENGTH) : trimmed;\n};\n\n/** Options for {@link identifyOnboarding}. */\nexport type IdentifyOnboardingOptions = {\n /** Tenant transport, same shape as `WireOnboardingConfig` (only these two fields are used). */\n config: { serverUrl: string; apiKey: string };\n /** The host's opaque user id to bind. Trimmed + capped; NO PII. */\n userId: string;\n /**\n * The onboarding session id to bind to (the A2A contextId) — the `contextId` field carried on\n * the `started`/`resumed` `onEvent`. Pass this when you captured it there. Required after the\n * flow COMPLETED, since completion clears the persisted session. Wins over the storage lookup.\n */\n contextId?: string;\n /**\n * The SAME host storage you passed to `<WireOnboarding storage={…} />`. When `contextId` is\n * omitted, the helper reads the persisted contextId from it (works while the session is still\n * persisted — i.e. dropped or mid-flow, before completion clears it).\n */\n storage?: WireOnboardingStorage;\n /** App id, to derive the default storage key `wireai:session:<appId>` when reading from storage. */\n appId?: string;\n /** Storage key override — pass the same `persistKey` you gave `<WireOnboarding>`, if any. */\n persistKey?: string;\n};\n\n/**\n * Attach a host user id to an onboarding session AFTER the fact (post-registration), by sending\n * an `identify` client event to `/v1/events`. Resolves the contextId from an explicit\n * `contextId` or, failing that, from the persisted session in the host `storage`.\n *\n * Fire-and-forget under the hood (never throws, never blocks onboarding). Resolves to `true`\n * when an identify event was dispatched, `false` when it couldn't (no user id, no server url,\n * or no resolvable contextId).\n */\nexport const identifyOnboarding = async (\n opts: IdentifyOnboardingOptions,\n): Promise<boolean> => {\n const userId = sanitizeUserId(opts.userId);\n if (!userId || !opts.config?.serverUrl) return false;\n\n let contextId = opts.contextId?.trim() || undefined;\n if (!contextId && opts.storage) {\n const key = opts.persistKey ?? (opts.appId ? sessionStorageKey(opts.appId) : undefined);\n if (key) {\n const stored = await peekPersistedSession(opts.storage, key);\n contextId = stored?.id;\n }\n }\n if (!contextId) return false;\n\n reportClientEvent(\n { serverUrl: opts.config.serverUrl, apiKey: opts.config.apiKey },\n { event_type: \"identify\", session_id: contextId, user_id: userId },\n );\n return true;\n};\n","/**\n * analyticsFacade — a Segment/PostHog-shaped developer API (`track` / `screen` / `identify`)\n * over the kit's OWN offline-first event queue. One package, one key, one line:\n *\n * const analytics = createAnalytics({ serverUrl, apiKey, storage });\n * analytics.track(\"content_share\", { source: \"feed\" });\n * analytics.screen(\"Home\", { tab: \"explore\" });\n * analytics.identify(\"u_123\", { plan: \"pro\" });\n *\n * WHY a façade: the primitives already exist (`createEventQueue` for durable offline-first\n * transport, `buildContextEnvelope` for the non-PII device context, the `identify` client-event\n * contract for user binding), but a host had to wire them together by hand. This composes them\n * into the familiar analytics-SDK surface so a consumer gets the ergonomics with NO second\n * install and NO second key — the same `{ serverUrl, apiKey }` creds as onboarding.\n *\n * ROUTING: every call builds a `ClientEvent` and `enqueue`s it. The queue stamps the context\n * envelope (device + host scalars), persists offline, batches, retries, and dequeues on ack —\n * so `track`/`screen`/`identify` inherit offline-first durability for free. `screen` routes\n * through the SAME queue rather than the direct `reportAppEvent` POST (a deliberate upgrade:\n * screen views survive being offline too).\n *\n * DEPENDENCY-FREE + TREE-SHAKEABLE: this module imports ONLY the queue, the envelope builder,\n * the client-event types + session-id seed, and the identity sanitizer. It reaches NO\n * onboarding / showcase / review UI, so an analytics-only consumer bundles none of it. React is\n * absent here on purpose — the optional React glue is the thin `useAnalytics` hook.\n *\n * FIRE-AND-FORGET: no method throws into the UI or blocks — the queue already guarantees that.\n */\nimport { buildContextEnvelope, type ContextEnvelope } from \"./contextEnvelope\";\nimport { createEventQueue, type EventQueue, type EventQueueOptions } from \"./eventQueue\";\nimport { makeSessionId, type ClientEvent } from \"./reportClientEvent\";\nimport { sanitizeUserId } from \"../identity/userIdentity\";\n\n/** Arbitrary non-PII event properties. Serialized to the event's `meta` (a JSON string) on the wire. */\nexport type AnalyticsProps = Record<string, unknown>;\n\n/**\n * Tenant transport + context inputs for {@link createAnalytics}. `serverUrl`/`apiKey` are the\n * SAME creds as onboarding (never a second key). The rest feed the context envelope + the queue's\n * offline persistence — all optional.\n */\nexport type CreateAnalyticsConfig = {\n /** Base server URL (same as `WireOnboardingConfig.serverUrl`); `/v1/events` is appended. */\n serverUrl: string;\n /** Tenant API key; sent as `Authorization: Bearer`. */\n apiKey: string;\n /**\n * Correlation id shared by every event from this instance (and the `identify` event). Defaults\n * to a fresh `makeSessionId()` at creation so all events agree on one id per analytics instance.\n */\n sessionId?: string;\n /** Tenant/app id used to namespace the queue's default storage key (`wireai:evtq:<appId>`). */\n appId?: string;\n /**\n * Host persistence (AsyncStorage-compatible subset) for offline-first durability. When omitted,\n * the queue runs in the documented in-memory mode (survives re-renders, not app kills).\n */\n storage?: EventQueueOptions[\"storage\"];\n /** Host app version, e.g. \"1.4.2\" (host-injected; stamped onto every event's context). */\n appVersion?: string;\n /** Host native build number, e.g. \"412\" (host-injected). */\n appBuild?: string;\n /** Host connectivity signal, e.g. \"wifi\" | \"cellular\" — read fresh per event via the provider. */\n networkType?: string;\n};\n\n/** Optional queue tuning knobs, forwarded verbatim to {@link createEventQueue}. */\nexport type AnalyticsOptions = Partial<\n Pick<EventQueueOptions, \"maxSize\" | \"batchSize\" | \"baseBackoffMs\" | \"maxBackoffMs\" | \"maxRetries\">\n>;\n\n/**\n * The developer-facing analytics surface. `track`/`screen`/`identify` are the Segment/PostHog-shaped\n * API; `flush`/`notifyOnline`/`size` expose the underlying queue so a host can drive reconnect\n * draining (load-bearing for offline-first) and inspect the pending buffer.\n */\nexport type Analytics = {\n /** Record an in-app event: `event_type='app_event'`, `question_key=<event>`, `props`→`meta`. */\n track(event: string, props?: AnalyticsProps): void;\n /** Record a screen view: `event_type='app_event'`, `question_key='screen'`, `meta={ screen, ...props }`. */\n screen(name: string, props?: AnalyticsProps): void;\n /** Bind the host's opaque user id (per-session, in-memory) and emit an `identify` event. */\n identify(userId: string, traits?: AnalyticsProps): void;\n /** Attempt an immediate drain of the pending buffer. Fire-and-forget. */\n flush(): void;\n /** Host reconnect signal: reset backoff and drain now. Fire-and-forget. */\n notifyOnline(): void;\n /** Current pending (in-memory) count. */\n size(): number;\n};\n\n/**\n * Create a bound analytics instance. Seeds one correlation `sessionId`, builds an offline-first\n * queue over the tenant transport, and passes a fresh-per-event context envelope PROVIDER so the\n * connectivity type is read at enqueue time. The bound user id starts unset (see `identify`).\n */\nexport const createAnalytics = (\n config: CreateAnalyticsConfig,\n options: AnalyticsOptions = {},\n): Analytics => {\n const sessionId = config.sessionId ?? makeSessionId();\n\n // A provider (not a fixed value) so `networkType` is evaluated fresh on every enqueue.\n const envelope = (): ContextEnvelope =>\n buildContextEnvelope({\n sessionId,\n appVersion: config.appVersion,\n appBuild: config.appBuild,\n networkType: config.networkType,\n });\n\n const queue: EventQueue = createEventQueue({\n target: { serverUrl: config.serverUrl, apiKey: config.apiKey },\n storage: config.storage,\n appId: config.appId,\n envelope,\n ...options,\n });\n\n // Per-session, in-memory user binding. Not persisted across launches (that is a follow-up).\n let boundUserId: string | undefined;\n\n const track = (event: string, props?: AnalyticsProps): void => {\n if (!event) return;\n const clientEvent: ClientEvent = {\n event_type: \"app_event\",\n session_id: sessionId,\n question_key: event,\n };\n if (props && Object.keys(props).length > 0) clientEvent.meta = JSON.stringify(props);\n if (boundUserId) clientEvent.user_id = boundUserId;\n queue.enqueue(clientEvent);\n };\n\n const screen = (name: string, props?: AnalyticsProps): void => {\n if (!name) return;\n // Reuse the K6 screen event shape (question_key='screen'); route through the queue for offline-first.\n const meta = { screen: name, ...(props ?? {}) };\n const clientEvent: ClientEvent = {\n event_type: \"app_event\",\n session_id: sessionId,\n question_key: \"screen\",\n meta: JSON.stringify(meta),\n };\n if (boundUserId) clientEvent.user_id = boundUserId;\n queue.enqueue(clientEvent);\n };\n\n const identify = (userId: string, traits?: AnalyticsProps): void => {\n const clean = sanitizeUserId(userId);\n // Blank / non-string → no binding, no event (sanitizeUserId returns undefined). >128 → truncated.\n if (!clean) return;\n boundUserId = clean;\n const clientEvent: ClientEvent = {\n event_type: \"identify\",\n session_id: sessionId,\n user_id: clean,\n };\n if (traits && Object.keys(traits).length > 0) clientEvent.meta = JSON.stringify(traits);\n queue.enqueue(clientEvent);\n };\n\n return {\n track,\n screen,\n identify,\n flush: queue.flush,\n notifyOnline: queue.notifyOnline,\n size: queue.size,\n };\n};\n","/**\n * useAnalytics — a THIN optional React hook over the pure {@link createAnalytics} factory.\n *\n * It builds ONE analytics instance for the component's lifetime and returns it, so re-renders\n * never rebuild the queue or lose the in-memory user binding. React is a REQUIRED peer of the\n * kit, so importing it here is allowed; the hook adds NO other dependency. Mirrors the existing\n * `createScreenTracker` / `useScreenTracking` split — the factory stays React-free, this is the\n * glue.\n *\n * const analytics = useAnalytics({ serverUrl, apiKey, storage, appId });\n * analytics.track(\"content_share\", { source: \"feed\" });\n * // ...on reconnect: analytics.notifyOnline();\n */\nimport { useRef } from \"react\";\n\nimport {\n createAnalytics,\n type Analytics,\n type AnalyticsOptions,\n type CreateAnalyticsConfig,\n} from \"./analyticsFacade\";\n\n/**\n * Build a per-mount analytics instance. `config`/`options` are read once at first render (the\n * instance is stable for the component's lifetime, held in a ref). Returns the {@link Analytics}\n * surface so the component can `track` / `screen` / `identify` and drive `notifyOnline` on reconnect.\n */\nexport const useAnalytics = (\n config: CreateAnalyticsConfig,\n options: AnalyticsOptions = {},\n): Analytics => {\n // One instance per mount; kept in a ref so re-renders never rebuild the queue or drop the binding.\n const ref = useRef<Analytics | undefined>(undefined);\n if (!ref.current) ref.current = createAnalytics(config, options);\n return ref.current;\n};\n"]}