@wireai/activation 0.1.1 → 0.2.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,9 +1,9 @@
1
- import { Platform } from 'react-native';
2
1
  import * as React from 'react';
3
2
  import * as wireai_rn from 'wireai-rn';
4
3
  import { Message } from 'wireai-rn';
5
4
  import { O as OnboardingTheme } from './types-BKfpdZzX.js';
6
5
  import { b as WireOnboardingStorage } from './types-CMuOexw0.js';
6
+ import { Platform } from 'react-native';
7
7
 
8
8
  /**
9
9
  * deviceContext — collect a small, privacy-label-neutral snapshot of the device so
@@ -416,4 +416,131 @@ type AnalyticsEvent = {
416
416
  */
417
417
  declare const toAnalyticsEvent: (event: OnboardingEvent) => AnalyticsEvent;
418
418
 
419
- export { type AnalyticsEvent as A, type ClientEvent as C, type DeviceContext as D, type OnboardingResult as O, type StepValidator as S, WIRE_ONBOARDING_EVENTS as W, type ClientEventTarget as a, type ClientEventType as b, type WireOnboardingEventName as c, reportClientEvents as d, type WireOnboardingProps as e, type WireOnboardingConfig as f, type OnboardingEvent as g, type OnboardingCopy as h, type DeviceFormFactor as i, type OnboardingProgress as j, collectDeviceContext as k, makeSessionId as m, reportClientEvent as r, toAnalyticsEvent as t };
419
+ /**
420
+ * contextEnvelope — a small, PRIVACY-NEUTRAL context bundle stamped onto every outgoing
421
+ * analytics event, giving the Wire dashboard the Sentry/Firebase-parity segmentation fields
422
+ * (device model, OS + version, screen, locale, timezone, form factor) plus a few host-injected
423
+ * scalars (session correlation id, app version + native build number, connectivity type).
424
+ *
425
+ * WHY a separate builder (not just `collectDeviceContext`): the envelope COMPOSES the existing
426
+ * device snapshot with the handful of extras a host can cheaply supply but the kit can't collect
427
+ * dependency-free (native build number, connectivity type). It never re-implements device
428
+ * collection — it reuses `collectDeviceContext()` verbatim (see device/deviceContext.ts).
429
+ *
430
+ * HARD PRIVACY RULE (why this file, like deviceContext.ts, adds nothing new):
431
+ * NEVER GPS / location, NEVER an advertising id (IDFA / GAID), NEVER a device fingerprint.
432
+ * Location is derived SERVER-SIDE from IP-geo only — nothing here carries a coordinate or an
433
+ * ad id, so a host adopting this changes no App Privacy / Data Safety declaration. There is a
434
+ * test (contextEnvelope.test.ts) that asserts the ABSENCE of any such field.
435
+ *
436
+ * DEPENDENCY-FREE: the only import is the kit's own `collectDeviceContext`. `networkType` and
437
+ * `appBuild` are HOST-INJECTED — there is no dependency-free RN core signal for either, so the
438
+ * envelope simply omits them when the host does not pass them (no forced peer dependency).
439
+ */
440
+
441
+ /**
442
+ * The context stamped onto every event. `device` is always present (from
443
+ * `collectDeviceContext`); every scalar is optional and OMITTED when the host does not supply it.
444
+ */
445
+ type ContextEnvelope = {
446
+ /** The privacy-neutral device snapshot (reused from `collectDeviceContext`). */
447
+ device: DeviceContext;
448
+ /** Correlation id for this app-open / flow (caller-supplied). */
449
+ sessionId?: string;
450
+ /** Host app version, e.g. "1.4.2" (mirrors `device.appVersion`; host-injected). */
451
+ appVersion?: string;
452
+ /** Host native build number, e.g. "412" (from `expo-constants` `nativeBuildVersion`). */
453
+ appBuild?: string;
454
+ /** Host connectivity signal, e.g. "wifi" | "cellular" (from `@react-native-community/netinfo`). */
455
+ networkType?: string;
456
+ };
457
+ /** Host-injected inputs for {@link buildContextEnvelope}. All optional; each is omitted when absent. */
458
+ type ContextEnvelopeInput = {
459
+ sessionId?: string;
460
+ appVersion?: string;
461
+ appBuild?: string;
462
+ networkType?: string;
463
+ };
464
+ /**
465
+ * Build a fresh context envelope. Reuses `collectDeviceContext()` for the device block and layers
466
+ * the host-injected scalars on top. `appVersion` is additionally merged onto `device.appVersion`
467
+ * when the device block lacks it (mirroring how `useSessionStart` back-fills the host version).
468
+ *
469
+ * Returns a NEW object on every call (no shared mutable reference), so a caller can hold or mutate
470
+ * the result without leaking into the next envelope. Never throws — `collectDeviceContext` is
471
+ * itself guarded, and the rest is plain assignment.
472
+ */
473
+ declare const buildContextEnvelope: (input?: ContextEnvelopeInput) => ContextEnvelope;
474
+
475
+ /**
476
+ * eventQueue — the OFFLINE-FIRST, persistent transport buffer for client analytics events.
477
+ *
478
+ * The existing `reportClientEvents` is a blind fire-and-forget POST: it returns `void`, has no
479
+ * success signal, and drops events when the network is down. The analytics data story depends on
480
+ * NEVER losing an event offline, so this queue adds the missing durability layer on top of the
481
+ * same `POST {serverUrl}/v1/events` contract:
482
+ *
483
+ * • Persists pending events to the host-injected `WireOnboardingStorage` (survives app kills).
484
+ * • Batches them into one request body `{ events: [...] }`.
485
+ * • Owns its OWN awaited `fetch` that reads `res.ok` — the only way to drive retry + dequeue,
486
+ * since `reportClientEvents` cannot ack. A 2xx dequeues the batch; a non-ok / rejected / thrown
487
+ * response keeps it and schedules an exponential backoff retry.
488
+ * • Flushes on `enqueue`, on an explicit `flush()`, and on `notifyOnline()` (host reconnect).
489
+ * • Caps the buffer (drop-OLDEST under pressure) and de-dups identical pending events.
490
+ * • Stamps the current context envelope (device + host scalars) onto every event before send.
491
+ *
492
+ * FIRE-AND-FORGET (load-bearing): `enqueue` returns immediately and NEVER throws into the UI. A
493
+ * missing `fetch`, a hung/broken storage, a rejecting network, or a JSON error is swallowed and
494
+ * degrades gracefully — analytics must never be able to break the app. In-memory fallback covers
495
+ * the no-storage case (survives re-renders, not app kills).
496
+ *
497
+ * DEPENDENCY-FREE: no network-detection or persistence library. Connectivity is host-driven via
498
+ * `notifyOnline()`; persistence is the host-injected AsyncStorage-compatible subset.
499
+ */
500
+
501
+ /** Envelope source: a fixed envelope or a provider evaluated at enqueue time (fresh network type). */
502
+ type EnvelopeSource = ContextEnvelope | (() => ContextEnvelope | undefined);
503
+ /** Options for {@link createEventQueue}. Only `target` is conceptually required to actually send. */
504
+ type EventQueueOptions = {
505
+ /** Where to POST — the tenant transport (`serverUrl` + `apiKey`), same as `WireOnboardingConfig`. */
506
+ target: ClientEventTarget | undefined;
507
+ /**
508
+ * Host persistence (AsyncStorage-compatible subset). When omitted, the queue runs in the
509
+ * documented DEGRADED in-memory mode — it survives re-renders but not an app kill.
510
+ */
511
+ storage?: WireOnboardingStorage;
512
+ /** Tenant/app id used to namespace the default storage key (`wireai:evtq:<appId>`). */
513
+ appId?: string;
514
+ /** Explicit storage key override (wins over the `appId`-derived default). */
515
+ storageKey?: string;
516
+ /** The context envelope stamped onto every event before send (device + host scalars). */
517
+ envelope?: EnvelopeSource;
518
+ /** Max pending events; enqueuing past this DROPS THE OLDEST first (default 200). */
519
+ maxSize?: number;
520
+ /** Events per POST batch (default 20). */
521
+ batchSize?: number;
522
+ /** First retry delay in ms; doubles each failed attempt (default 1000). */
523
+ baseBackoffMs?: number;
524
+ /** Backoff ceiling in ms (default 30000). */
525
+ maxBackoffMs?: number;
526
+ /** Max AUTOMATIC backoff retries before pausing (default 6); `notifyOnline()`/`flush()` re-arm it. */
527
+ maxRetries?: number;
528
+ };
529
+ /** The queue's public surface. `enqueue` is fire-and-forget (returns immediately, never throws). */
530
+ type EventQueue = {
531
+ /** Buffer one event (envelope-stamped), persist, and schedule a flush. Never throws. */
532
+ enqueue(event: ClientEvent): void;
533
+ /** Attempt an immediate drain of the pending buffer. Fire-and-forget. */
534
+ flush(): void;
535
+ /** Host reconnect signal: reset backoff and drain immediately. Fire-and-forget. */
536
+ notifyOnline(): void;
537
+ /** Current pending (in-memory) count. */
538
+ size(): number;
539
+ };
540
+ /**
541
+ * Create an offline-first event queue. Loads any persisted backlog on creation so a
542
+ * killed-and-relaunched app resumes where it left off. Returns the {@link EventQueue} surface.
543
+ */
544
+ declare const createEventQueue: (options: EventQueueOptions) => EventQueue;
545
+
546
+ export { type AnalyticsEvent as A, type ClientEvent as C, type DeviceContext as D, type EnvelopeSource as E, type OnboardingResult as O, type StepValidator as S, WIRE_ONBOARDING_EVENTS as W, type ClientEventTarget as a, type ClientEventType as b, type ContextEnvelope as c, type ContextEnvelopeInput as d, type EventQueue as e, type EventQueueOptions as f, type WireOnboardingEventName as g, buildContextEnvelope as h, createEventQueue as i, reportClientEvents as j, type WireOnboardingProps as k, type WireOnboardingConfig as l, makeSessionId as m, type OnboardingEvent as n, type OnboardingCopy as o, type DeviceFormFactor as p, type OnboardingProgress as q, reportClientEvent as r, collectDeviceContext as s, toAnalyticsEvent as t };
package/dist/index.d.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import React__default, { ReactNode } from 'react';
3
- import { e as WireOnboardingProps, f as WireOnboardingConfig, O as OnboardingResult, S as StepValidator, g as OnboardingEvent, h as OnboardingCopy, a as ClientEventTarget, D as DeviceContext } from './analyticsEvent-B8v3BZjM.mjs';
4
- export { A as AnalyticsEvent, C as ClientEvent, b as ClientEventType, i as DeviceFormFactor, j as OnboardingProgress, W as WIRE_ONBOARDING_EVENTS, c as WireOnboardingEventName, k as collectDeviceContext, m as makeSessionId, r as reportClientEvent, d as reportClientEvents, t as toAnalyticsEvent } from './analyticsEvent-B8v3BZjM.mjs';
3
+ import { k as WireOnboardingProps, l as WireOnboardingConfig, O as OnboardingResult, S as StepValidator, n as OnboardingEvent, o as OnboardingCopy, a as ClientEventTarget, D as DeviceContext, C as ClientEvent, E as EnvelopeSource } from './eventQueue-CxKi7Qd5.mjs';
4
+ export { A as AnalyticsEvent, b as ClientEventType, p as DeviceFormFactor, q as OnboardingProgress, W as WIRE_ONBOARDING_EVENTS, g as WireOnboardingEventName, s as collectDeviceContext, m as makeSessionId, r as reportClientEvent, j as reportClientEvents, t as toAnalyticsEvent } from './eventQueue-CxKi7Qd5.mjs';
5
5
  import { O as OnboardingTheme } from './types-BKfpdZzX.mjs';
6
6
  export { a as OnboardingButtonStyle, b as OnboardingColors, c as OnboardingFonts, d as OnboardingRadius, e as OnboardingSpacing } from './types-BKfpdZzX.mjs';
7
7
  export { C as CenteredModal, a as CenteredModalHandle, b as CenteredModalProps } from './CenteredModal-C3qQBHsA.mjs';
@@ -937,6 +937,15 @@ interface ReportSessionStartOptions {
937
937
  meta?: Record<string, unknown>;
938
938
  /** Set `false` to bypass the once-per-open guard (default on). See {@link resetSessionStartGuard}. */
939
939
  once?: boolean;
940
+ /**
941
+ * OPTIONAL transport sink. When provided, the built `app.session_started` event is routed HERE
942
+ * (e.g. the offline-first event queue's `enqueue`) INSTEAD of this emitter's own direct `fetch`,
943
+ * while KEEPING the once-per-open guard above. This is how the lifecycle wiring
944
+ * (`useLifecycleEvents` / `wireLifecycleEvents`) offline-buffers session-start WITHOUT adding a
945
+ * second session emitter: one emitter, one guard, now durable. Omit it for the direct-POST path.
946
+ * A throwing sink is swallowed — analytics must never surface into the UI.
947
+ */
948
+ sink?: (event: ClientEvent) => void;
940
949
  }
941
950
  /** Test-only: forget every emitted session id so a unit test starts from a clean guard. */
942
951
  declare const resetSessionStartGuard: () => void;
@@ -977,4 +986,149 @@ interface UseSessionStartOptions {
977
986
  */
978
987
  declare const useSessionStart: (config: SessionStartConfig | undefined, options?: UseSessionStartOptions) => void;
979
988
 
980
- export { AnimatedSparkle, BACKGROUND_SESSION_MS, type CachedFeatures, CardHandoff, type CardHandoffProps, type CardHandoffVariant, ChipSelectCard, ClientEventTarget, CompletionView, DEFAULT_FEATURES_TTL_MS, DemoOnboarding, type DemoOnboardingProps, DeviceContext, DoneBlock, ErrorBlock, type IdentifyOnboardingOptions, IllustrationProvider, type IllustrationRegistry, InterstitialCard, LoadingBlock, LoadingScreen, NumberStepperCard, type OnboardingAttribution, Button as OnboardingButton, OnboardingCopy, OnboardingEvent, type OnboardingFlagOptions, OnboardingFlow, OnboardingResult, OnboardingScaffold, OnboardingTheme, OnboardingThemeProvider, type ReportSessionStartOptions, type ResolveFeaturesOptions, SESSION_STARTED_EVENT, SelectionCard, type SessionStartConfig, StatusCard, StepProgress, StepValidator, TextInputCard, type ThemeFromBrandInput, USER_ID_MAX_LENGTH, type UseSessionStartOptions, type WireConfigOverrides, WireFeatures, WireFeaturesConfig, WireFeaturesProvider, type WireFeaturesProviderProps, WireOnboarding, WireOnboardingConfig, WireOnboardingProps, WireOnboardingStorage, attributionMetadata, defaultIllustrations, defaultOnboardingTheme, defaultWireFeatures, deriveAnswers, featuresCacheKey, featuresEqual, fetchWireFeatures, identifyOnboarding, isFeaturesFresh, isOnboardingEnabled, mergeTheme, motionSpec, onboardingComponents, parseWireFeatures, readCachedFeatures, readProgress, reportSessionStart, resetSessionStartGuard, sanitizeUserId, themeFromBrand, useIllustration, useOnboardingTheme, useReducedMotion, useResolvedFeatures, useSessionStart, useWireFeatures, useWireFeaturesContext, wireConfigFromEnv, writeCachedFeatures };
989
+ /**
990
+ * lifecycle — the TOP-OF-FUNNEL app lifecycle events that sit ABOVE onboarding: `app.first_open`
991
+ * (once ever per install) and `app.session_started` (per app-open). They compose with, and never
992
+ * duplicate, the events the funnel already records.
993
+ *
994
+ * WHERE THIS FITS (one funnel, no double counting):
995
+ * • `app.first_open` → THIS module, once ever (persisted flag). The in-app "install" proxy.
996
+ * • `app.session_started` → the EXISTING {@link reportSessionStart} emitter (per app-open). This
997
+ * module REUSES it (routed through the offline queue), never re-emits.
998
+ * • onboarding started / completed / activated → ALREADY recorded: the server writes
999
+ * `session_started` + `completed` during the A2A flow, and the client reports `dropped` /
1000
+ * `client_fallback` / `identify` via `reportClientEvent`. This module does NOT touch them —
1001
+ * emitting them here would double-count. It only ADDS the two lifecycle events above them.
1002
+ *
1003
+ * WIRE CONTRACT (identical to `app.session_started`): both lifecycle events are stored as
1004
+ * `event_type='app_event'` with the name in `question_key` (the server's `_event_name` returns
1005
+ * `question_key` for an `app_event`, which a trigger matches). `app.first_open` uses the SAME
1006
+ * `app.*` namespace + the SAME event shape as `app.session_started` — no bespoke `event_type`,
1007
+ * no invented contract. `device_key` rides in the non-PII `user_context` bucket; `app_id` +
1008
+ * `environment` are filled server-side.
1009
+ *
1010
+ * TWO FIRST-CLASS ENTRY POINTS (mirrors reportSessionStart vs useSessionStart):
1011
+ * • `reportFirstOpen(...)` / `wireLifecycleEvents(...)` — pure, React-free, for a host that owns
1012
+ * its own app-open path.
1013
+ * • `useLifecycleEvents(...)` — the batteries-included React hook (in `useLifecycleEvents.ts`).
1014
+ *
1015
+ * OFFLINE-FIRST: pass the Brief-01 event queue's `enqueue` as the `sink` and every lifecycle event
1016
+ * is buffered + persisted + retried instead of a blind fetch. Without a sink they degrade to a
1017
+ * direct fire-and-forget POST.
1018
+ *
1019
+ * PRIVACY (hard rule, same as the rest of the kit): NO GPS/location, NO advertising id. Nothing
1020
+ * here carries a coordinate or an ad id — a host adopting it changes no store privacy declaration.
1021
+ *
1022
+ * FIRE-AND-FORGET: like every analytics path in the kit, nothing here throws into the UI, awaits
1023
+ * in the caller, or hangs the app — storage reads race a short timeout, writes swallow errors.
1024
+ */
1025
+
1026
+ /** The canonical event name for the first-ever app open. Same `app.*` namespace as
1027
+ * {@link SESSION_STARTED_EVENT}; a trigger keys off this exact string. */
1028
+ declare const FIRST_OPEN_EVENT: "app.first_open";
1029
+ /** Storage key for the once-ever first-open flag, e.g. `wireai:first_open:myelino`. Mirrors the
1030
+ * `wireai:<concern>:<appId>` namespacing of {@link sessionStorageKey}. */
1031
+ declare const firstOpenStorageKey: (appId: string) => string;
1032
+ /** Test-only: forget the first-open latch so a unit test starts from a clean process state. */
1033
+ declare const resetFirstOpenLatch: () => void;
1034
+ /** Shared inputs for a lifecycle event. Everything is optional except a transport (`target` for the
1035
+ * direct-POST fallback, or a `sink`). A pre-auth open (no user yet) is a valid device-only event. */
1036
+ interface LifecycleEventInput {
1037
+ /** Where to POST if no `sink` is wired (the tenant transport, same as `WireOnboardingConfig`). */
1038
+ target?: ClientEventTarget;
1039
+ /** Preferred transport: route the built event HERE (the offline queue's `enqueue`). */
1040
+ sink?: (event: ClientEvent) => void;
1041
+ /** The per-open session id. Defaults to a fresh `makeSessionId()`. */
1042
+ sessionId?: string;
1043
+ /** The host's OPAQUE pseudonymous user id (NOT PII). Sanitized + capped; omitted pre-auth. */
1044
+ userId?: string;
1045
+ /** A stable, non-PII device id the host owns. Rides in `user_context.device_key`. */
1046
+ deviceKey?: string;
1047
+ /** The host's local open-counter value. Drives `returning` + "Nth session". */
1048
+ sessionCount?: number;
1049
+ /** Host app version (e.g. "1.4.2"), if cheaply available. */
1050
+ appVersion?: string;
1051
+ /** Platform string (e.g. "ios"), if cheaply available. */
1052
+ platform?: string;
1053
+ /** An optional richer device snapshot (from `collectDeviceContext()`); the hook fills this. */
1054
+ device?: DeviceContext;
1055
+ /** Small non-PII extras, stored as a JSON string in the event `meta`. */
1056
+ meta?: Record<string, unknown>;
1057
+ }
1058
+ /** Options for {@link reportFirstOpen}. Adds the once-ever persistence inputs on top of the shared
1059
+ * lifecycle inputs. Without `storage` it degrades to the in-memory latch (once per process). */
1060
+ interface ReportFirstOpenOptions extends LifecycleEventInput {
1061
+ /** Host persistence (AsyncStorage subset). The once-ever flag lives here — it is what survives an
1062
+ * app kill. Omit it for the documented degraded (in-memory, once-per-process) mode. */
1063
+ storage?: WireOnboardingStorage;
1064
+ /** Tenant/app id — namespaces the persisted flag (`wireai:first_open:<appId>`). */
1065
+ appId?: string;
1066
+ }
1067
+ /**
1068
+ * Emit `app.first_open` EXACTLY ONCE EVER per install. Fire-and-forget; returns immediately.
1069
+ *
1070
+ * • With `storage`: reads the persisted flag (`wireai:first_open:<appId>`). Absent → emit, then
1071
+ * write the flag (survives app kills, so a second launch is a no-op). Present → no-op.
1072
+ * • Race guard: an in-memory latch is set SYNCHRONOUSLY before the async read, so two
1073
+ * near-simultaneous calls fire at most once.
1074
+ * • Without `storage`: degraded mode — fires once per PROCESS via the latch only (documented).
1075
+ */
1076
+ declare const reportFirstOpen: (opts: ReportFirstOpenOptions) => void;
1077
+ /** Options for {@link wireLifecycleEvents}: the shared lifecycle inputs + first-open persistence. */
1078
+ interface WireLifecycleOptions extends ReportFirstOpenOptions {
1079
+ }
1080
+ /**
1081
+ * Wire BOTH lifecycle events in one call for a host that owns its own app-open path (the non-hook
1082
+ * counterpart to {@link useLifecycleEvents}). Fires `app.first_open` (once ever) and one
1083
+ * `app.session_started` for THIS open through the EXISTING {@link reportSessionStart} emitter (so
1084
+ * the once-per-open guard still applies — pass the same `sessionId` and it never double-fires).
1085
+ * Route both through the same `sink` (the offline queue) to buffer them. Fire-and-forget.
1086
+ */
1087
+ declare const wireLifecycleEvents: (opts: WireLifecycleOptions) => void;
1088
+
1089
+ /** Tenant transport + host persistence for the lifecycle wiring. Same creds as `WireOnboardingConfig`. */
1090
+ interface LifecycleConfig {
1091
+ /** Base server URL (same as `WireOnboardingConfig.serverUrl`); `/v1/events` is appended. */
1092
+ serverUrl?: string;
1093
+ /** Tenant API key; sent as `Authorization: Bearer`. */
1094
+ apiKey?: string;
1095
+ /** Host app version (e.g. "1.4.2"), forwarded for release segmentation. Optional. */
1096
+ appVersion?: string;
1097
+ /** Tenant/app id — namespaces the first-open flag AND the hook's internal queue storage key. */
1098
+ appId?: string;
1099
+ /** Host storage (AsyncStorage subset). Enables the persisted once-ever first-open flag AND the
1100
+ * offline durability of the hook's internal queue. Omit it and both degrade to in-memory. */
1101
+ storage?: WireOnboardingStorage;
1102
+ }
1103
+ /** Per-open identity + wiring the host supplies. All optional: a pre-auth open is device-only. */
1104
+ interface UseLifecycleEventsOptions {
1105
+ /** The host's opaque pseudonymous user id (NOT PII). Omitted before the user authenticates. */
1106
+ userId?: string;
1107
+ /** The host's local open-counter value for this open. Drives `returning` + "Nth session". */
1108
+ sessionCount?: number;
1109
+ /** A stable, non-PII device id the host owns. Groups this device's sessions server-side. */
1110
+ deviceKey?: string;
1111
+ /** Small non-PII extras forwarded on the event `meta`. */
1112
+ meta?: Record<string, unknown>;
1113
+ /** Set `false` to disable firing (e.g. behind a consent gate). Default enabled. */
1114
+ enabled?: boolean;
1115
+ /**
1116
+ * Explicit transport sink (e.g. an existing shared `EventQueue.enqueue`). When provided, BOTH
1117
+ * lifecycle events route here and the hook does NOT create its own queue — pass this to share ONE
1118
+ * offline queue across the kit's analytics (screen tracking + lifecycle).
1119
+ */
1120
+ sink?: (event: ClientEvent) => void;
1121
+ /**
1122
+ * Context envelope (or provider) for the hook's internally-created queue. Ignored when `sink` is
1123
+ * supplied (the host's queue owns envelope stamping).
1124
+ */
1125
+ envelope?: EnvelopeSource;
1126
+ }
1127
+ /**
1128
+ * Fire `app.first_open` (once ever) + `app.session_started` (per open), offline-buffered. Returns
1129
+ * nothing — a side-effecting hook. Safe to call with inline options (read through a ref, so
1130
+ * changing `userId`/`sessionCount` never re-fires a session).
1131
+ */
1132
+ declare const useLifecycleEvents: (config: LifecycleConfig | undefined, options?: UseLifecycleEventsOptions) => void;
1133
+
1134
+ export { AnimatedSparkle, BACKGROUND_SESSION_MS, type CachedFeatures, CardHandoff, type CardHandoffProps, type CardHandoffVariant, ChipSelectCard, ClientEvent, ClientEventTarget, CompletionView, DEFAULT_FEATURES_TTL_MS, DemoOnboarding, type DemoOnboardingProps, DeviceContext, DoneBlock, ErrorBlock, FIRST_OPEN_EVENT, type IdentifyOnboardingOptions, IllustrationProvider, type IllustrationRegistry, InterstitialCard, type LifecycleConfig, type LifecycleEventInput, LoadingBlock, LoadingScreen, NumberStepperCard, type OnboardingAttribution, Button as OnboardingButton, OnboardingCopy, OnboardingEvent, type OnboardingFlagOptions, OnboardingFlow, OnboardingResult, OnboardingScaffold, OnboardingTheme, OnboardingThemeProvider, type ReportFirstOpenOptions, type ReportSessionStartOptions, type ResolveFeaturesOptions, SESSION_STARTED_EVENT, SelectionCard, type SessionStartConfig, StatusCard, StepProgress, StepValidator, TextInputCard, type ThemeFromBrandInput, USER_ID_MAX_LENGTH, type UseLifecycleEventsOptions, type UseSessionStartOptions, type WireConfigOverrides, WireFeatures, WireFeaturesConfig, WireFeaturesProvider, type WireFeaturesProviderProps, type WireLifecycleOptions, WireOnboarding, WireOnboardingConfig, WireOnboardingProps, WireOnboardingStorage, attributionMetadata, defaultIllustrations, defaultOnboardingTheme, defaultWireFeatures, deriveAnswers, featuresCacheKey, featuresEqual, fetchWireFeatures, firstOpenStorageKey, identifyOnboarding, isFeaturesFresh, isOnboardingEnabled, mergeTheme, motionSpec, onboardingComponents, parseWireFeatures, readCachedFeatures, readProgress, reportFirstOpen, reportSessionStart, resetFirstOpenLatch, resetSessionStartGuard, sanitizeUserId, themeFromBrand, useIllustration, useLifecycleEvents, useOnboardingTheme, useReducedMotion, useResolvedFeatures, useSessionStart, useWireFeatures, useWireFeaturesContext, wireConfigFromEnv, wireLifecycleEvents, writeCachedFeatures };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import React__default, { ReactNode } from 'react';
3
- import { e as WireOnboardingProps, f as WireOnboardingConfig, O as OnboardingResult, S as StepValidator, g as OnboardingEvent, h as OnboardingCopy, a as ClientEventTarget, D as DeviceContext } from './analyticsEvent-DvjB92kK.js';
4
- export { A as AnalyticsEvent, C as ClientEvent, b as ClientEventType, i as DeviceFormFactor, j as OnboardingProgress, W as WIRE_ONBOARDING_EVENTS, c as WireOnboardingEventName, k as collectDeviceContext, m as makeSessionId, r as reportClientEvent, d as reportClientEvents, t as toAnalyticsEvent } from './analyticsEvent-DvjB92kK.js';
3
+ import { k as WireOnboardingProps, l as WireOnboardingConfig, O as OnboardingResult, S as StepValidator, n as OnboardingEvent, o as OnboardingCopy, a as ClientEventTarget, D as DeviceContext, C as ClientEvent, E as EnvelopeSource } from './eventQueue-rV1dtJJR.js';
4
+ export { A as AnalyticsEvent, b as ClientEventType, p as DeviceFormFactor, q as OnboardingProgress, W as WIRE_ONBOARDING_EVENTS, g as WireOnboardingEventName, s as collectDeviceContext, m as makeSessionId, r as reportClientEvent, j as reportClientEvents, t as toAnalyticsEvent } from './eventQueue-rV1dtJJR.js';
5
5
  import { O as OnboardingTheme } from './types-BKfpdZzX.js';
6
6
  export { a as OnboardingButtonStyle, b as OnboardingColors, c as OnboardingFonts, d as OnboardingRadius, e as OnboardingSpacing } from './types-BKfpdZzX.js';
7
7
  export { C as CenteredModal, a as CenteredModalHandle, b as CenteredModalProps } from './CenteredModal-Cdgns6--.js';
@@ -937,6 +937,15 @@ interface ReportSessionStartOptions {
937
937
  meta?: Record<string, unknown>;
938
938
  /** Set `false` to bypass the once-per-open guard (default on). See {@link resetSessionStartGuard}. */
939
939
  once?: boolean;
940
+ /**
941
+ * OPTIONAL transport sink. When provided, the built `app.session_started` event is routed HERE
942
+ * (e.g. the offline-first event queue's `enqueue`) INSTEAD of this emitter's own direct `fetch`,
943
+ * while KEEPING the once-per-open guard above. This is how the lifecycle wiring
944
+ * (`useLifecycleEvents` / `wireLifecycleEvents`) offline-buffers session-start WITHOUT adding a
945
+ * second session emitter: one emitter, one guard, now durable. Omit it for the direct-POST path.
946
+ * A throwing sink is swallowed — analytics must never surface into the UI.
947
+ */
948
+ sink?: (event: ClientEvent) => void;
940
949
  }
941
950
  /** Test-only: forget every emitted session id so a unit test starts from a clean guard. */
942
951
  declare const resetSessionStartGuard: () => void;
@@ -977,4 +986,149 @@ interface UseSessionStartOptions {
977
986
  */
978
987
  declare const useSessionStart: (config: SessionStartConfig | undefined, options?: UseSessionStartOptions) => void;
979
988
 
980
- export { AnimatedSparkle, BACKGROUND_SESSION_MS, type CachedFeatures, CardHandoff, type CardHandoffProps, type CardHandoffVariant, ChipSelectCard, ClientEventTarget, CompletionView, DEFAULT_FEATURES_TTL_MS, DemoOnboarding, type DemoOnboardingProps, DeviceContext, DoneBlock, ErrorBlock, type IdentifyOnboardingOptions, IllustrationProvider, type IllustrationRegistry, InterstitialCard, LoadingBlock, LoadingScreen, NumberStepperCard, type OnboardingAttribution, Button as OnboardingButton, OnboardingCopy, OnboardingEvent, type OnboardingFlagOptions, OnboardingFlow, OnboardingResult, OnboardingScaffold, OnboardingTheme, OnboardingThemeProvider, type ReportSessionStartOptions, type ResolveFeaturesOptions, SESSION_STARTED_EVENT, SelectionCard, type SessionStartConfig, StatusCard, StepProgress, StepValidator, TextInputCard, type ThemeFromBrandInput, USER_ID_MAX_LENGTH, type UseSessionStartOptions, type WireConfigOverrides, WireFeatures, WireFeaturesConfig, WireFeaturesProvider, type WireFeaturesProviderProps, WireOnboarding, WireOnboardingConfig, WireOnboardingProps, WireOnboardingStorage, attributionMetadata, defaultIllustrations, defaultOnboardingTheme, defaultWireFeatures, deriveAnswers, featuresCacheKey, featuresEqual, fetchWireFeatures, identifyOnboarding, isFeaturesFresh, isOnboardingEnabled, mergeTheme, motionSpec, onboardingComponents, parseWireFeatures, readCachedFeatures, readProgress, reportSessionStart, resetSessionStartGuard, sanitizeUserId, themeFromBrand, useIllustration, useOnboardingTheme, useReducedMotion, useResolvedFeatures, useSessionStart, useWireFeatures, useWireFeaturesContext, wireConfigFromEnv, writeCachedFeatures };
989
+ /**
990
+ * lifecycle — the TOP-OF-FUNNEL app lifecycle events that sit ABOVE onboarding: `app.first_open`
991
+ * (once ever per install) and `app.session_started` (per app-open). They compose with, and never
992
+ * duplicate, the events the funnel already records.
993
+ *
994
+ * WHERE THIS FITS (one funnel, no double counting):
995
+ * • `app.first_open` → THIS module, once ever (persisted flag). The in-app "install" proxy.
996
+ * • `app.session_started` → the EXISTING {@link reportSessionStart} emitter (per app-open). This
997
+ * module REUSES it (routed through the offline queue), never re-emits.
998
+ * • onboarding started / completed / activated → ALREADY recorded: the server writes
999
+ * `session_started` + `completed` during the A2A flow, and the client reports `dropped` /
1000
+ * `client_fallback` / `identify` via `reportClientEvent`. This module does NOT touch them —
1001
+ * emitting them here would double-count. It only ADDS the two lifecycle events above them.
1002
+ *
1003
+ * WIRE CONTRACT (identical to `app.session_started`): both lifecycle events are stored as
1004
+ * `event_type='app_event'` with the name in `question_key` (the server's `_event_name` returns
1005
+ * `question_key` for an `app_event`, which a trigger matches). `app.first_open` uses the SAME
1006
+ * `app.*` namespace + the SAME event shape as `app.session_started` — no bespoke `event_type`,
1007
+ * no invented contract. `device_key` rides in the non-PII `user_context` bucket; `app_id` +
1008
+ * `environment` are filled server-side.
1009
+ *
1010
+ * TWO FIRST-CLASS ENTRY POINTS (mirrors reportSessionStart vs useSessionStart):
1011
+ * • `reportFirstOpen(...)` / `wireLifecycleEvents(...)` — pure, React-free, for a host that owns
1012
+ * its own app-open path.
1013
+ * • `useLifecycleEvents(...)` — the batteries-included React hook (in `useLifecycleEvents.ts`).
1014
+ *
1015
+ * OFFLINE-FIRST: pass the Brief-01 event queue's `enqueue` as the `sink` and every lifecycle event
1016
+ * is buffered + persisted + retried instead of a blind fetch. Without a sink they degrade to a
1017
+ * direct fire-and-forget POST.
1018
+ *
1019
+ * PRIVACY (hard rule, same as the rest of the kit): NO GPS/location, NO advertising id. Nothing
1020
+ * here carries a coordinate or an ad id — a host adopting it changes no store privacy declaration.
1021
+ *
1022
+ * FIRE-AND-FORGET: like every analytics path in the kit, nothing here throws into the UI, awaits
1023
+ * in the caller, or hangs the app — storage reads race a short timeout, writes swallow errors.
1024
+ */
1025
+
1026
+ /** The canonical event name for the first-ever app open. Same `app.*` namespace as
1027
+ * {@link SESSION_STARTED_EVENT}; a trigger keys off this exact string. */
1028
+ declare const FIRST_OPEN_EVENT: "app.first_open";
1029
+ /** Storage key for the once-ever first-open flag, e.g. `wireai:first_open:myelino`. Mirrors the
1030
+ * `wireai:<concern>:<appId>` namespacing of {@link sessionStorageKey}. */
1031
+ declare const firstOpenStorageKey: (appId: string) => string;
1032
+ /** Test-only: forget the first-open latch so a unit test starts from a clean process state. */
1033
+ declare const resetFirstOpenLatch: () => void;
1034
+ /** Shared inputs for a lifecycle event. Everything is optional except a transport (`target` for the
1035
+ * direct-POST fallback, or a `sink`). A pre-auth open (no user yet) is a valid device-only event. */
1036
+ interface LifecycleEventInput {
1037
+ /** Where to POST if no `sink` is wired (the tenant transport, same as `WireOnboardingConfig`). */
1038
+ target?: ClientEventTarget;
1039
+ /** Preferred transport: route the built event HERE (the offline queue's `enqueue`). */
1040
+ sink?: (event: ClientEvent) => void;
1041
+ /** The per-open session id. Defaults to a fresh `makeSessionId()`. */
1042
+ sessionId?: string;
1043
+ /** The host's OPAQUE pseudonymous user id (NOT PII). Sanitized + capped; omitted pre-auth. */
1044
+ userId?: string;
1045
+ /** A stable, non-PII device id the host owns. Rides in `user_context.device_key`. */
1046
+ deviceKey?: string;
1047
+ /** The host's local open-counter value. Drives `returning` + "Nth session". */
1048
+ sessionCount?: number;
1049
+ /** Host app version (e.g. "1.4.2"), if cheaply available. */
1050
+ appVersion?: string;
1051
+ /** Platform string (e.g. "ios"), if cheaply available. */
1052
+ platform?: string;
1053
+ /** An optional richer device snapshot (from `collectDeviceContext()`); the hook fills this. */
1054
+ device?: DeviceContext;
1055
+ /** Small non-PII extras, stored as a JSON string in the event `meta`. */
1056
+ meta?: Record<string, unknown>;
1057
+ }
1058
+ /** Options for {@link reportFirstOpen}. Adds the once-ever persistence inputs on top of the shared
1059
+ * lifecycle inputs. Without `storage` it degrades to the in-memory latch (once per process). */
1060
+ interface ReportFirstOpenOptions extends LifecycleEventInput {
1061
+ /** Host persistence (AsyncStorage subset). The once-ever flag lives here — it is what survives an
1062
+ * app kill. Omit it for the documented degraded (in-memory, once-per-process) mode. */
1063
+ storage?: WireOnboardingStorage;
1064
+ /** Tenant/app id — namespaces the persisted flag (`wireai:first_open:<appId>`). */
1065
+ appId?: string;
1066
+ }
1067
+ /**
1068
+ * Emit `app.first_open` EXACTLY ONCE EVER per install. Fire-and-forget; returns immediately.
1069
+ *
1070
+ * • With `storage`: reads the persisted flag (`wireai:first_open:<appId>`). Absent → emit, then
1071
+ * write the flag (survives app kills, so a second launch is a no-op). Present → no-op.
1072
+ * • Race guard: an in-memory latch is set SYNCHRONOUSLY before the async read, so two
1073
+ * near-simultaneous calls fire at most once.
1074
+ * • Without `storage`: degraded mode — fires once per PROCESS via the latch only (documented).
1075
+ */
1076
+ declare const reportFirstOpen: (opts: ReportFirstOpenOptions) => void;
1077
+ /** Options for {@link wireLifecycleEvents}: the shared lifecycle inputs + first-open persistence. */
1078
+ interface WireLifecycleOptions extends ReportFirstOpenOptions {
1079
+ }
1080
+ /**
1081
+ * Wire BOTH lifecycle events in one call for a host that owns its own app-open path (the non-hook
1082
+ * counterpart to {@link useLifecycleEvents}). Fires `app.first_open` (once ever) and one
1083
+ * `app.session_started` for THIS open through the EXISTING {@link reportSessionStart} emitter (so
1084
+ * the once-per-open guard still applies — pass the same `sessionId` and it never double-fires).
1085
+ * Route both through the same `sink` (the offline queue) to buffer them. Fire-and-forget.
1086
+ */
1087
+ declare const wireLifecycleEvents: (opts: WireLifecycleOptions) => void;
1088
+
1089
+ /** Tenant transport + host persistence for the lifecycle wiring. Same creds as `WireOnboardingConfig`. */
1090
+ interface LifecycleConfig {
1091
+ /** Base server URL (same as `WireOnboardingConfig.serverUrl`); `/v1/events` is appended. */
1092
+ serverUrl?: string;
1093
+ /** Tenant API key; sent as `Authorization: Bearer`. */
1094
+ apiKey?: string;
1095
+ /** Host app version (e.g. "1.4.2"), forwarded for release segmentation. Optional. */
1096
+ appVersion?: string;
1097
+ /** Tenant/app id — namespaces the first-open flag AND the hook's internal queue storage key. */
1098
+ appId?: string;
1099
+ /** Host storage (AsyncStorage subset). Enables the persisted once-ever first-open flag AND the
1100
+ * offline durability of the hook's internal queue. Omit it and both degrade to in-memory. */
1101
+ storage?: WireOnboardingStorage;
1102
+ }
1103
+ /** Per-open identity + wiring the host supplies. All optional: a pre-auth open is device-only. */
1104
+ interface UseLifecycleEventsOptions {
1105
+ /** The host's opaque pseudonymous user id (NOT PII). Omitted before the user authenticates. */
1106
+ userId?: string;
1107
+ /** The host's local open-counter value for this open. Drives `returning` + "Nth session". */
1108
+ sessionCount?: number;
1109
+ /** A stable, non-PII device id the host owns. Groups this device's sessions server-side. */
1110
+ deviceKey?: string;
1111
+ /** Small non-PII extras forwarded on the event `meta`. */
1112
+ meta?: Record<string, unknown>;
1113
+ /** Set `false` to disable firing (e.g. behind a consent gate). Default enabled. */
1114
+ enabled?: boolean;
1115
+ /**
1116
+ * Explicit transport sink (e.g. an existing shared `EventQueue.enqueue`). When provided, BOTH
1117
+ * lifecycle events route here and the hook does NOT create its own queue — pass this to share ONE
1118
+ * offline queue across the kit's analytics (screen tracking + lifecycle).
1119
+ */
1120
+ sink?: (event: ClientEvent) => void;
1121
+ /**
1122
+ * Context envelope (or provider) for the hook's internally-created queue. Ignored when `sink` is
1123
+ * supplied (the host's queue owns envelope stamping).
1124
+ */
1125
+ envelope?: EnvelopeSource;
1126
+ }
1127
+ /**
1128
+ * Fire `app.first_open` (once ever) + `app.session_started` (per open), offline-buffered. Returns
1129
+ * nothing — a side-effecting hook. Safe to call with inline options (read through a ref, so
1130
+ * changing `userId`/`sessionCount` never re-fires a session).
1131
+ */
1132
+ declare const useLifecycleEvents: (config: LifecycleConfig | undefined, options?: UseLifecycleEventsOptions) => void;
1133
+
1134
+ export { AnimatedSparkle, BACKGROUND_SESSION_MS, type CachedFeatures, CardHandoff, type CardHandoffProps, type CardHandoffVariant, ChipSelectCard, ClientEvent, ClientEventTarget, CompletionView, DEFAULT_FEATURES_TTL_MS, DemoOnboarding, type DemoOnboardingProps, DeviceContext, DoneBlock, ErrorBlock, FIRST_OPEN_EVENT, type IdentifyOnboardingOptions, IllustrationProvider, type IllustrationRegistry, InterstitialCard, type LifecycleConfig, type LifecycleEventInput, LoadingBlock, LoadingScreen, NumberStepperCard, type OnboardingAttribution, Button as OnboardingButton, OnboardingCopy, OnboardingEvent, type OnboardingFlagOptions, OnboardingFlow, OnboardingResult, OnboardingScaffold, OnboardingTheme, OnboardingThemeProvider, type ReportFirstOpenOptions, type ReportSessionStartOptions, type ResolveFeaturesOptions, SESSION_STARTED_EVENT, SelectionCard, type SessionStartConfig, StatusCard, StepProgress, StepValidator, TextInputCard, type ThemeFromBrandInput, USER_ID_MAX_LENGTH, type UseLifecycleEventsOptions, type UseSessionStartOptions, type WireConfigOverrides, WireFeatures, WireFeaturesConfig, WireFeaturesProvider, type WireFeaturesProviderProps, type WireLifecycleOptions, WireOnboarding, WireOnboardingConfig, WireOnboardingProps, WireOnboardingStorage, attributionMetadata, defaultIllustrations, defaultOnboardingTheme, defaultWireFeatures, deriveAnswers, featuresCacheKey, featuresEqual, fetchWireFeatures, firstOpenStorageKey, identifyOnboarding, isFeaturesFresh, isOnboardingEnabled, mergeTheme, motionSpec, onboardingComponents, parseWireFeatures, readCachedFeatures, readProgress, reportFirstOpen, reportSessionStart, resetFirstOpenLatch, resetSessionStartGuard, sanitizeUserId, themeFromBrand, useIllustration, useLifecycleEvents, useOnboardingTheme, useReducedMotion, useResolvedFeatures, useSessionStart, useWireFeatures, useWireFeaturesContext, wireConfigFromEnv, wireLifecycleEvents, writeCachedFeatures };