@wireai/activation 0.4.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +57 -0
- package/dist/analytics/index.d.mts +15 -2
- package/dist/analytics/index.d.ts +15 -2
- package/dist/analytics/index.js +181 -13
- package/dist/analytics/index.js.map +1 -1
- package/dist/analytics/index.mjs +179 -14
- package/dist/analytics/index.mjs.map +1 -1
- package/dist/{eventQueue-CA1d8Fmn.d.mts → currentSession-BJBB7i4-.d.mts} +143 -15
- package/dist/{eventQueue-CrNB9gzH.d.ts → currentSession-CxnP7gAa.d.ts} +143 -15
- package/dist/index.d.mts +37 -37
- package/dist/index.d.ts +37 -37
- package/dist/index.js +158 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +149 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/WireOnboarding.tsx +4 -3
- package/src/analytics/analyticsFacade.ts +74 -11
- package/src/analytics/contextEnvelope.ts +13 -7
- package/src/analytics/currentSession.ts +35 -0
- package/src/analytics/index.ts +3 -0
- package/src/context/userContext.ts +210 -0
- package/src/device/appVersion.ts +103 -0
- package/src/device/deviceContext.ts +17 -5
- package/src/identity/userIdentity.ts +5 -0
- package/src/index.ts +23 -0
- package/src/session-analytics/reportSessionStart.ts +7 -0
- package/src/session-analytics/useLifecycleEvents.ts +4 -2
- package/src/session-analytics/useSessionStart.ts +2 -1
- package/src/types.ts +6 -5
|
@@ -11,9 +11,12 @@ import { Platform } from 'react-native';
|
|
|
11
11
|
* adding a single dependency to the kit or changing a host app's App Privacy / Data Safety
|
|
12
12
|
* declarations.
|
|
13
13
|
*
|
|
14
|
-
* HARD RULE (why this file
|
|
14
|
+
* HARD RULE (why this file adds no dependency):
|
|
15
15
|
* The kit stays dependency-free. Everything here comes from `Platform`, `Dimensions`,
|
|
16
|
-
* `I18nManager`, and the standard `Intl` global
|
|
16
|
+
* `I18nManager`, and the standard `Intl` global — plus a best-effort `appVersion` read via
|
|
17
|
+
* `detectAppVersion()`, which itself adds NO dependency (it reaches for `expo-constants` /
|
|
18
|
+
* `expo-application` through a guarded, variable-specifier require that a host without them
|
|
19
|
+
* simply never resolves — see device/appVersion.ts). There are NO advertising IDs, NO
|
|
17
20
|
* `getUniqueId`/IDFA/GAID/fingerprinting APIs, and nothing that would require a new
|
|
18
21
|
* privacy-label entry. A host can adopt this without touching its store declarations.
|
|
19
22
|
*
|
|
@@ -55,9 +58,11 @@ type DeviceContext = {
|
|
|
55
58
|
/** IANA time zone (e.g. "Europe/Berlin"), from `Intl` when available. */
|
|
56
59
|
timeZone?: string;
|
|
57
60
|
/**
|
|
58
|
-
* Host app version (e.g. "1.4.2).
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
+
* Host app version (e.g. "1.4.2"). BEST-EFFORT auto-detected here via `detectAppVersion()`
|
|
62
|
+
* (reads `expo-constants` / `expo-application` when present; adds no dependency — see
|
|
63
|
+
* device/appVersion.ts). An explicit host-injected `config.appVersion` always WINS: the merge
|
|
64
|
+
* sites (`WireOnboarding`, the session-analytics hooks, the context envelope) overwrite this
|
|
65
|
+
* with the host value when one is supplied. Omitted when neither source yields a version.
|
|
61
66
|
*/
|
|
62
67
|
appVersion?: string;
|
|
63
68
|
};
|
|
@@ -179,11 +184,12 @@ type WireOnboardingConfig = {
|
|
|
179
184
|
*/
|
|
180
185
|
metadata?: Record<string, unknown>;
|
|
181
186
|
/**
|
|
182
|
-
* Host app version string (e.g. "1.4.2").
|
|
183
|
-
*
|
|
184
|
-
* (`
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
+
* Host app version string (e.g. "1.4.2"). OPTIONAL — when omitted, the kit makes a best-effort
|
|
188
|
+
* auto-detection from `expo-constants` (`Constants.expoConfig?.version` / `nativeAppVersion`) or
|
|
189
|
+
* `expo-application` (`nativeApplicationVersion`) WITHOUT adding a dependency (a host that lacks
|
|
190
|
+
* those modules just gets no version — see device/appVersion.ts). Pass this to override the
|
|
191
|
+
* auto-detected value (it always wins). Forwarded to the backend on the session metadata and on
|
|
192
|
+
* client events (as `device.appVersion`) so analytics can segment the funnel by app version.
|
|
187
193
|
*/
|
|
188
194
|
appVersion?: string;
|
|
189
195
|
};
|
|
@@ -451,7 +457,7 @@ type ContextEnvelope = {
|
|
|
451
457
|
device: DeviceContext;
|
|
452
458
|
/** Correlation id for this app-open / flow (caller-supplied). */
|
|
453
459
|
sessionId?: string;
|
|
454
|
-
/**
|
|
460
|
+
/** App version, e.g. "1.4.2" (mirrors `device.appVersion`; host-injected, else auto-detected). */
|
|
455
461
|
appVersion?: string;
|
|
456
462
|
/** Host native build number, e.g. "412" (from `expo-constants` `nativeBuildVersion`). */
|
|
457
463
|
appBuild?: string;
|
|
@@ -466,9 +472,10 @@ type ContextEnvelopeInput = {
|
|
|
466
472
|
networkType?: string;
|
|
467
473
|
};
|
|
468
474
|
/**
|
|
469
|
-
* Build a fresh context envelope. Reuses `collectDeviceContext()` for the device block
|
|
470
|
-
*
|
|
471
|
-
*
|
|
475
|
+
* Build a fresh context envelope. Reuses `collectDeviceContext()` for the device block (which
|
|
476
|
+
* already carries a best-effort auto-detected `appVersion`) and layers the host-injected scalars
|
|
477
|
+
* on top. An explicit `input.appVersion` overrides the auto-detected `device.appVersion`, and the
|
|
478
|
+
* outer `appVersion` scalar mirrors whichever version is effective.
|
|
472
479
|
*
|
|
473
480
|
* Returns a NEW object on every call (no shared mutable reference), so a caller can hold or mutate
|
|
474
481
|
* the result without leaking into the next envelope. Never throws — `collectDeviceContext` is
|
|
@@ -547,4 +554,125 @@ type EventQueue = {
|
|
|
547
554
|
*/
|
|
548
555
|
declare const createEventQueue: (options: EventQueueOptions) => EventQueue;
|
|
549
556
|
|
|
550
|
-
|
|
557
|
+
/**
|
|
558
|
+
* The single, extensible user-context object. A host passes it ONCE (at analytics init) and may
|
|
559
|
+
* update it post-mount (e.g. attach `userId`/`userEmail` at login) via `setUserContext(partial)`.
|
|
560
|
+
* Every field is optional; missing fields are omitted from the wire payload.
|
|
561
|
+
*/
|
|
562
|
+
interface WireUserContext {
|
|
563
|
+
/**
|
|
564
|
+
* Host app version, e.g. "1.4.2". EXPLICIT — wins over the #42 auto-detected `device.appVersion`.
|
|
565
|
+
* Lands in `user_context.app_version`. Omitted when neither this nor auto-detect yields a version.
|
|
566
|
+
*/
|
|
567
|
+
appVersion?: string;
|
|
568
|
+
/**
|
|
569
|
+
* A stable, non-PII device id the host owns. Lands in `user_context.device_key` (NOT `session_id`),
|
|
570
|
+
* where the server groups a device's sessions. Host-supplied; the kit never mints or reads one.
|
|
571
|
+
*/
|
|
572
|
+
deviceKey?: string;
|
|
573
|
+
/**
|
|
574
|
+
* The host's OPAQUE PSEUDONYMOUS user id (their internal id — NOT an email/name/phone). Sanitized +
|
|
575
|
+
* capped (see `sanitizeUserId`) and placed on the event's top-level `user_id`. NEVER the bucket.
|
|
576
|
+
*/
|
|
577
|
+
userId?: string;
|
|
578
|
+
/**
|
|
579
|
+
* OPT-IN PII. The user's email, its OWN field (`user_context.user_email`) — NEVER merged into
|
|
580
|
+
* `userId`. The kit NEVER auto-collects this; a host passes it only WITH the user's consent (EU
|
|
581
|
+
* users: treat as personal data). For a non-reversible form, set {@link hashEmail} `true` (the kit
|
|
582
|
+
* folds it with a dependency-free hash and stamps `user_context.user_email_hashed: true`), OR
|
|
583
|
+
* pre-hash host-side with a cryptographic digest and pass that here with `hashEmail` falsy.
|
|
584
|
+
*/
|
|
585
|
+
userEmail?: string;
|
|
586
|
+
/**
|
|
587
|
+
* When `true`, {@link userEmail} is folded with the kit's dependency-free {@link hashEmailFnv1a}
|
|
588
|
+
* before it leaves the device, and `user_context.user_email_hashed` is set `true`. NOTE: FNV-1a is
|
|
589
|
+
* a lightweight NON-cryptographic fold (obfuscation, not a secure digest). For a cryptographic
|
|
590
|
+
* hash, compute it host-side (e.g. SHA-256 via `expo-crypto`) and pass the digest as `userEmail`
|
|
591
|
+
* with `hashEmail` falsy. Default: raw email is sent as-is (opt-in already gated it upstream).
|
|
592
|
+
*/
|
|
593
|
+
hashEmail?: boolean;
|
|
594
|
+
/**
|
|
595
|
+
* Arbitrary host context (signup method, referral, plan tier…). Each value is coerced to a scalar
|
|
596
|
+
* (`string | number | boolean`; non-scalars and non-finite numbers are DROPPED) and NAMESPACED
|
|
597
|
+
* under a `custom.` key prefix in `user_context` (e.g. `user_context["custom.referral"]`) so it can
|
|
598
|
+
* never collide with a reserved key. No raw PII — use {@link userEmail} for email.
|
|
599
|
+
*/
|
|
600
|
+
extra?: Record<string, string | number | boolean>;
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* The wire-shaped result of {@link resolveUserContext}. `userContext` is the non-PII/opt-in-PII
|
|
604
|
+
* bucket stamped onto the event; `userId` is the top-level opaque id; `appVersion`/`deviceKey` are
|
|
605
|
+
* echoed for callers that also place them elsewhere (e.g. `device.appVersion`). Absent fields are
|
|
606
|
+
* omitted so a caller can spread this without sending empties.
|
|
607
|
+
*/
|
|
608
|
+
interface ResolvedUserContext {
|
|
609
|
+
/** The opaque, sanitized user id → the event's top-level `user_id`. Omitted when unset/blank. */
|
|
610
|
+
userId?: string;
|
|
611
|
+
/** The stable device id → `user_context.device_key`. Omitted when unset. */
|
|
612
|
+
deviceKey?: string;
|
|
613
|
+
/** The effective app version (explicit > auto-detected) → `user_context.app_version`. */
|
|
614
|
+
appVersion?: string;
|
|
615
|
+
/** The `user_context` bucket (device_key, app_version, user_email[+ _hashed], custom.*). */
|
|
616
|
+
userContext?: Record<string, string | number | boolean>;
|
|
617
|
+
}
|
|
618
|
+
/** Reserved `user_context` keys the kit itself writes; host `extra` is namespaced away from these. */
|
|
619
|
+
declare const RESERVED_USER_CONTEXT_KEYS: readonly ["device_key", "app_version", "app_build", "network_type", "session_count", "returning", "platform", "user_email", "user_email_hashed"];
|
|
620
|
+
/** The prefix applied to every host `extra` key so it can never collide with a reserved key. */
|
|
621
|
+
declare const EXTRA_KEY_PREFIX: "custom.";
|
|
622
|
+
/** A finite scalar the wire accepts. Non-finite numbers (NaN/Infinity) are NOT scalars here. */
|
|
623
|
+
declare const isWireScalar: (value: unknown) => value is string | number | boolean;
|
|
624
|
+
/**
|
|
625
|
+
* Fold an email to a stable, dependency-free 32-bit FNV-1a hex token (lowercased + trimmed first so
|
|
626
|
+
* the same address always folds identically). This is OBFUSCATION, not a cryptographic digest — it
|
|
627
|
+
* is not collision-resistant. For a real hash, pre-hash host-side and pass the digest as `userEmail`.
|
|
628
|
+
*/
|
|
629
|
+
declare const hashEmailFnv1a: (email: string) => string;
|
|
630
|
+
/**
|
|
631
|
+
* Coerce a host `extra` map into the namespaced, scalar-only bucket shape. Every kept value is
|
|
632
|
+
* placed under `custom.<key>`; non-scalar values (objects, arrays, null, functions, NaN/Infinity)
|
|
633
|
+
* are DROPPED. Returns an object (possibly empty).
|
|
634
|
+
*/
|
|
635
|
+
declare const namespaceExtra: (extra: Record<string, unknown> | undefined) => Record<string, string | number | boolean>;
|
|
636
|
+
/** Options for {@link resolveUserContext}. */
|
|
637
|
+
interface ResolveUserContextOptions {
|
|
638
|
+
/**
|
|
639
|
+
* The kit's best-effort auto-detected app version (#42; from `detectAppVersion()`/the device
|
|
640
|
+
* snapshot). Used ONLY when the explicit `WireUserContext.appVersion` is absent — explicit wins.
|
|
641
|
+
*/
|
|
642
|
+
autoAppVersion?: string;
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Merge a {@link WireUserContext} into the wire shape with the precedence rule (explicit field >
|
|
646
|
+
* auto-detected). Pure, never throws. Missing fields are omitted so the result can be spread onto an
|
|
647
|
+
* event without sending empties.
|
|
648
|
+
*/
|
|
649
|
+
declare const resolveUserContext: (ctx?: WireUserContext, opts?: ResolveUserContextOptions) => ResolvedUserContext;
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* currentSession — a tiny module-level registry of the CURRENT per-open `session_id`.
|
|
653
|
+
*
|
|
654
|
+
* WHY it exists (kills the phantom-session): the per-open emitters (`reportSessionStart` and the
|
|
655
|
+
* `useSessionStart` / `useLifecycleEvents` hooks) mint a fresh `session_id` for each app-open and
|
|
656
|
+
* post `app.session_started` with it — so the SERVER knows that id. But other client paths
|
|
657
|
+
* (`identify`, host `app_event`s through the analytics façade) used to reference a DIFFERENT id
|
|
658
|
+
* (a frozen per-instance id), which the server had never seen, so it back-filled a synthetic
|
|
659
|
+
* `session_started` — inflating session counts (the Morrow/Myelino "phantom-session" bug).
|
|
660
|
+
*
|
|
661
|
+
* This registry is the single seam that lets those paths reuse the LIVE per-open session id the
|
|
662
|
+
* server already ingested. `reportSessionStart` writes the current id here on every open; the façade
|
|
663
|
+
* reads it so `identify`/app-events correlate to the real session instead of minting a phantom.
|
|
664
|
+
*
|
|
665
|
+
* DEPENDENCY-FREE + PROCESS-LOCAL: a plain module variable. It is intentionally NOT persisted — it
|
|
666
|
+
* tracks the CURRENT process's open, and a fresh open always overwrites it. No cross-launch state.
|
|
667
|
+
*/
|
|
668
|
+
/**
|
|
669
|
+
* Record the current per-open `session_id`. Called by `reportSessionStart` when it emits an
|
|
670
|
+
* app-open. A blank / non-string id is ignored (the previous id stays current). Idempotent.
|
|
671
|
+
*/
|
|
672
|
+
declare const setCurrentSessionId: (id: string | undefined) => void;
|
|
673
|
+
/** The current per-open `session_id`, or `undefined` when no app-open has been registered yet. */
|
|
674
|
+
declare const getCurrentSessionId: () => string | undefined;
|
|
675
|
+
/** Test-only: forget the current session id so a unit test starts from a clean registry. */
|
|
676
|
+
declare const resetCurrentSessionId: () => void;
|
|
677
|
+
|
|
678
|
+
export { type AnalyticsEvent as A, collectDeviceContext as B, type ClientEvent as C, type DeviceContext as D, type EventQueueOptions as E, hashEmailFnv1a as F, isWireScalar as G, namespaceExtra as H, resolveUserContext as I, type OnboardingResult as O, RESERVED_USER_CONTEXT_KEYS as R, type StepValidator as S, type WireUserContext as W, type ClientEventTarget as a, type ClientEventType as b, type ContextEnvelope as c, type ContextEnvelopeInput as d, type EnvelopeSource as e, type EventQueue as f, WIRE_ONBOARDING_EVENTS as g, type WireOnboardingEventName as h, buildContextEnvelope as i, createEventQueue as j, getCurrentSessionId as k, reportClientEvents as l, makeSessionId as m, resetCurrentSessionId as n, type WireOnboardingProps as o, type WireOnboardingConfig as p, type OnboardingEvent as q, reportClientEvent as r, setCurrentSessionId as s, toAnalyticsEvent as t, type OnboardingCopy as u, type DeviceFormFactor as v, EXTRA_KEY_PREFIX as w, type OnboardingProgress as x, type ResolveUserContextOptions as y, type ResolvedUserContext as z };
|
|
@@ -11,9 +11,12 @@ import { Platform } from 'react-native';
|
|
|
11
11
|
* adding a single dependency to the kit or changing a host app's App Privacy / Data Safety
|
|
12
12
|
* declarations.
|
|
13
13
|
*
|
|
14
|
-
* HARD RULE (why this file
|
|
14
|
+
* HARD RULE (why this file adds no dependency):
|
|
15
15
|
* The kit stays dependency-free. Everything here comes from `Platform`, `Dimensions`,
|
|
16
|
-
* `I18nManager`, and the standard `Intl` global
|
|
16
|
+
* `I18nManager`, and the standard `Intl` global — plus a best-effort `appVersion` read via
|
|
17
|
+
* `detectAppVersion()`, which itself adds NO dependency (it reaches for `expo-constants` /
|
|
18
|
+
* `expo-application` through a guarded, variable-specifier require that a host without them
|
|
19
|
+
* simply never resolves — see device/appVersion.ts). There are NO advertising IDs, NO
|
|
17
20
|
* `getUniqueId`/IDFA/GAID/fingerprinting APIs, and nothing that would require a new
|
|
18
21
|
* privacy-label entry. A host can adopt this without touching its store declarations.
|
|
19
22
|
*
|
|
@@ -55,9 +58,11 @@ type DeviceContext = {
|
|
|
55
58
|
/** IANA time zone (e.g. "Europe/Berlin"), from `Intl` when available. */
|
|
56
59
|
timeZone?: string;
|
|
57
60
|
/**
|
|
58
|
-
* Host app version (e.g. "1.4.2).
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
+
* Host app version (e.g. "1.4.2"). BEST-EFFORT auto-detected here via `detectAppVersion()`
|
|
62
|
+
* (reads `expo-constants` / `expo-application` when present; adds no dependency — see
|
|
63
|
+
* device/appVersion.ts). An explicit host-injected `config.appVersion` always WINS: the merge
|
|
64
|
+
* sites (`WireOnboarding`, the session-analytics hooks, the context envelope) overwrite this
|
|
65
|
+
* with the host value when one is supplied. Omitted when neither source yields a version.
|
|
61
66
|
*/
|
|
62
67
|
appVersion?: string;
|
|
63
68
|
};
|
|
@@ -179,11 +184,12 @@ type WireOnboardingConfig = {
|
|
|
179
184
|
*/
|
|
180
185
|
metadata?: Record<string, unknown>;
|
|
181
186
|
/**
|
|
182
|
-
* Host app version string (e.g. "1.4.2").
|
|
183
|
-
*
|
|
184
|
-
* (`
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
+
* Host app version string (e.g. "1.4.2"). OPTIONAL — when omitted, the kit makes a best-effort
|
|
188
|
+
* auto-detection from `expo-constants` (`Constants.expoConfig?.version` / `nativeAppVersion`) or
|
|
189
|
+
* `expo-application` (`nativeApplicationVersion`) WITHOUT adding a dependency (a host that lacks
|
|
190
|
+
* those modules just gets no version — see device/appVersion.ts). Pass this to override the
|
|
191
|
+
* auto-detected value (it always wins). Forwarded to the backend on the session metadata and on
|
|
192
|
+
* client events (as `device.appVersion`) so analytics can segment the funnel by app version.
|
|
187
193
|
*/
|
|
188
194
|
appVersion?: string;
|
|
189
195
|
};
|
|
@@ -451,7 +457,7 @@ type ContextEnvelope = {
|
|
|
451
457
|
device: DeviceContext;
|
|
452
458
|
/** Correlation id for this app-open / flow (caller-supplied). */
|
|
453
459
|
sessionId?: string;
|
|
454
|
-
/**
|
|
460
|
+
/** App version, e.g. "1.4.2" (mirrors `device.appVersion`; host-injected, else auto-detected). */
|
|
455
461
|
appVersion?: string;
|
|
456
462
|
/** Host native build number, e.g. "412" (from `expo-constants` `nativeBuildVersion`). */
|
|
457
463
|
appBuild?: string;
|
|
@@ -466,9 +472,10 @@ type ContextEnvelopeInput = {
|
|
|
466
472
|
networkType?: string;
|
|
467
473
|
};
|
|
468
474
|
/**
|
|
469
|
-
* Build a fresh context envelope. Reuses `collectDeviceContext()` for the device block
|
|
470
|
-
*
|
|
471
|
-
*
|
|
475
|
+
* Build a fresh context envelope. Reuses `collectDeviceContext()` for the device block (which
|
|
476
|
+
* already carries a best-effort auto-detected `appVersion`) and layers the host-injected scalars
|
|
477
|
+
* on top. An explicit `input.appVersion` overrides the auto-detected `device.appVersion`, and the
|
|
478
|
+
* outer `appVersion` scalar mirrors whichever version is effective.
|
|
472
479
|
*
|
|
473
480
|
* Returns a NEW object on every call (no shared mutable reference), so a caller can hold or mutate
|
|
474
481
|
* the result without leaking into the next envelope. Never throws — `collectDeviceContext` is
|
|
@@ -547,4 +554,125 @@ type EventQueue = {
|
|
|
547
554
|
*/
|
|
548
555
|
declare const createEventQueue: (options: EventQueueOptions) => EventQueue;
|
|
549
556
|
|
|
550
|
-
|
|
557
|
+
/**
|
|
558
|
+
* The single, extensible user-context object. A host passes it ONCE (at analytics init) and may
|
|
559
|
+
* update it post-mount (e.g. attach `userId`/`userEmail` at login) via `setUserContext(partial)`.
|
|
560
|
+
* Every field is optional; missing fields are omitted from the wire payload.
|
|
561
|
+
*/
|
|
562
|
+
interface WireUserContext {
|
|
563
|
+
/**
|
|
564
|
+
* Host app version, e.g. "1.4.2". EXPLICIT — wins over the #42 auto-detected `device.appVersion`.
|
|
565
|
+
* Lands in `user_context.app_version`. Omitted when neither this nor auto-detect yields a version.
|
|
566
|
+
*/
|
|
567
|
+
appVersion?: string;
|
|
568
|
+
/**
|
|
569
|
+
* A stable, non-PII device id the host owns. Lands in `user_context.device_key` (NOT `session_id`),
|
|
570
|
+
* where the server groups a device's sessions. Host-supplied; the kit never mints or reads one.
|
|
571
|
+
*/
|
|
572
|
+
deviceKey?: string;
|
|
573
|
+
/**
|
|
574
|
+
* The host's OPAQUE PSEUDONYMOUS user id (their internal id — NOT an email/name/phone). Sanitized +
|
|
575
|
+
* capped (see `sanitizeUserId`) and placed on the event's top-level `user_id`. NEVER the bucket.
|
|
576
|
+
*/
|
|
577
|
+
userId?: string;
|
|
578
|
+
/**
|
|
579
|
+
* OPT-IN PII. The user's email, its OWN field (`user_context.user_email`) — NEVER merged into
|
|
580
|
+
* `userId`. The kit NEVER auto-collects this; a host passes it only WITH the user's consent (EU
|
|
581
|
+
* users: treat as personal data). For a non-reversible form, set {@link hashEmail} `true` (the kit
|
|
582
|
+
* folds it with a dependency-free hash and stamps `user_context.user_email_hashed: true`), OR
|
|
583
|
+
* pre-hash host-side with a cryptographic digest and pass that here with `hashEmail` falsy.
|
|
584
|
+
*/
|
|
585
|
+
userEmail?: string;
|
|
586
|
+
/**
|
|
587
|
+
* When `true`, {@link userEmail} is folded with the kit's dependency-free {@link hashEmailFnv1a}
|
|
588
|
+
* before it leaves the device, and `user_context.user_email_hashed` is set `true`. NOTE: FNV-1a is
|
|
589
|
+
* a lightweight NON-cryptographic fold (obfuscation, not a secure digest). For a cryptographic
|
|
590
|
+
* hash, compute it host-side (e.g. SHA-256 via `expo-crypto`) and pass the digest as `userEmail`
|
|
591
|
+
* with `hashEmail` falsy. Default: raw email is sent as-is (opt-in already gated it upstream).
|
|
592
|
+
*/
|
|
593
|
+
hashEmail?: boolean;
|
|
594
|
+
/**
|
|
595
|
+
* Arbitrary host context (signup method, referral, plan tier…). Each value is coerced to a scalar
|
|
596
|
+
* (`string | number | boolean`; non-scalars and non-finite numbers are DROPPED) and NAMESPACED
|
|
597
|
+
* under a `custom.` key prefix in `user_context` (e.g. `user_context["custom.referral"]`) so it can
|
|
598
|
+
* never collide with a reserved key. No raw PII — use {@link userEmail} for email.
|
|
599
|
+
*/
|
|
600
|
+
extra?: Record<string, string | number | boolean>;
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* The wire-shaped result of {@link resolveUserContext}. `userContext` is the non-PII/opt-in-PII
|
|
604
|
+
* bucket stamped onto the event; `userId` is the top-level opaque id; `appVersion`/`deviceKey` are
|
|
605
|
+
* echoed for callers that also place them elsewhere (e.g. `device.appVersion`). Absent fields are
|
|
606
|
+
* omitted so a caller can spread this without sending empties.
|
|
607
|
+
*/
|
|
608
|
+
interface ResolvedUserContext {
|
|
609
|
+
/** The opaque, sanitized user id → the event's top-level `user_id`. Omitted when unset/blank. */
|
|
610
|
+
userId?: string;
|
|
611
|
+
/** The stable device id → `user_context.device_key`. Omitted when unset. */
|
|
612
|
+
deviceKey?: string;
|
|
613
|
+
/** The effective app version (explicit > auto-detected) → `user_context.app_version`. */
|
|
614
|
+
appVersion?: string;
|
|
615
|
+
/** The `user_context` bucket (device_key, app_version, user_email[+ _hashed], custom.*). */
|
|
616
|
+
userContext?: Record<string, string | number | boolean>;
|
|
617
|
+
}
|
|
618
|
+
/** Reserved `user_context` keys the kit itself writes; host `extra` is namespaced away from these. */
|
|
619
|
+
declare const RESERVED_USER_CONTEXT_KEYS: readonly ["device_key", "app_version", "app_build", "network_type", "session_count", "returning", "platform", "user_email", "user_email_hashed"];
|
|
620
|
+
/** The prefix applied to every host `extra` key so it can never collide with a reserved key. */
|
|
621
|
+
declare const EXTRA_KEY_PREFIX: "custom.";
|
|
622
|
+
/** A finite scalar the wire accepts. Non-finite numbers (NaN/Infinity) are NOT scalars here. */
|
|
623
|
+
declare const isWireScalar: (value: unknown) => value is string | number | boolean;
|
|
624
|
+
/**
|
|
625
|
+
* Fold an email to a stable, dependency-free 32-bit FNV-1a hex token (lowercased + trimmed first so
|
|
626
|
+
* the same address always folds identically). This is OBFUSCATION, not a cryptographic digest — it
|
|
627
|
+
* is not collision-resistant. For a real hash, pre-hash host-side and pass the digest as `userEmail`.
|
|
628
|
+
*/
|
|
629
|
+
declare const hashEmailFnv1a: (email: string) => string;
|
|
630
|
+
/**
|
|
631
|
+
* Coerce a host `extra` map into the namespaced, scalar-only bucket shape. Every kept value is
|
|
632
|
+
* placed under `custom.<key>`; non-scalar values (objects, arrays, null, functions, NaN/Infinity)
|
|
633
|
+
* are DROPPED. Returns an object (possibly empty).
|
|
634
|
+
*/
|
|
635
|
+
declare const namespaceExtra: (extra: Record<string, unknown> | undefined) => Record<string, string | number | boolean>;
|
|
636
|
+
/** Options for {@link resolveUserContext}. */
|
|
637
|
+
interface ResolveUserContextOptions {
|
|
638
|
+
/**
|
|
639
|
+
* The kit's best-effort auto-detected app version (#42; from `detectAppVersion()`/the device
|
|
640
|
+
* snapshot). Used ONLY when the explicit `WireUserContext.appVersion` is absent — explicit wins.
|
|
641
|
+
*/
|
|
642
|
+
autoAppVersion?: string;
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Merge a {@link WireUserContext} into the wire shape with the precedence rule (explicit field >
|
|
646
|
+
* auto-detected). Pure, never throws. Missing fields are omitted so the result can be spread onto an
|
|
647
|
+
* event without sending empties.
|
|
648
|
+
*/
|
|
649
|
+
declare const resolveUserContext: (ctx?: WireUserContext, opts?: ResolveUserContextOptions) => ResolvedUserContext;
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* currentSession — a tiny module-level registry of the CURRENT per-open `session_id`.
|
|
653
|
+
*
|
|
654
|
+
* WHY it exists (kills the phantom-session): the per-open emitters (`reportSessionStart` and the
|
|
655
|
+
* `useSessionStart` / `useLifecycleEvents` hooks) mint a fresh `session_id` for each app-open and
|
|
656
|
+
* post `app.session_started` with it — so the SERVER knows that id. But other client paths
|
|
657
|
+
* (`identify`, host `app_event`s through the analytics façade) used to reference a DIFFERENT id
|
|
658
|
+
* (a frozen per-instance id), which the server had never seen, so it back-filled a synthetic
|
|
659
|
+
* `session_started` — inflating session counts (the Morrow/Myelino "phantom-session" bug).
|
|
660
|
+
*
|
|
661
|
+
* This registry is the single seam that lets those paths reuse the LIVE per-open session id the
|
|
662
|
+
* server already ingested. `reportSessionStart` writes the current id here on every open; the façade
|
|
663
|
+
* reads it so `identify`/app-events correlate to the real session instead of minting a phantom.
|
|
664
|
+
*
|
|
665
|
+
* DEPENDENCY-FREE + PROCESS-LOCAL: a plain module variable. It is intentionally NOT persisted — it
|
|
666
|
+
* tracks the CURRENT process's open, and a fresh open always overwrites it. No cross-launch state.
|
|
667
|
+
*/
|
|
668
|
+
/**
|
|
669
|
+
* Record the current per-open `session_id`. Called by `reportSessionStart` when it emits an
|
|
670
|
+
* app-open. A blank / non-string id is ignored (the previous id stays current). Idempotent.
|
|
671
|
+
*/
|
|
672
|
+
declare const setCurrentSessionId: (id: string | undefined) => void;
|
|
673
|
+
/** The current per-open `session_id`, or `undefined` when no app-open has been registered yet. */
|
|
674
|
+
declare const getCurrentSessionId: () => string | undefined;
|
|
675
|
+
/** Test-only: forget the current session id so a unit test starts from a clean registry. */
|
|
676
|
+
declare const resetCurrentSessionId: () => void;
|
|
677
|
+
|
|
678
|
+
export { type AnalyticsEvent as A, collectDeviceContext as B, type ClientEvent as C, type DeviceContext as D, type EventQueueOptions as E, hashEmailFnv1a as F, isWireScalar as G, namespaceExtra as H, resolveUserContext as I, type OnboardingResult as O, RESERVED_USER_CONTEXT_KEYS as R, type StepValidator as S, type WireUserContext as W, type ClientEventTarget as a, type ClientEventType as b, type ContextEnvelope as c, type ContextEnvelopeInput as d, type EnvelopeSource as e, type EventQueue as f, WIRE_ONBOARDING_EVENTS as g, type WireOnboardingEventName as h, buildContextEnvelope as i, createEventQueue as j, getCurrentSessionId as k, reportClientEvents as l, makeSessionId as m, resetCurrentSessionId as n, type WireOnboardingProps as o, type WireOnboardingConfig as p, type OnboardingEvent as q, reportClientEvent as r, setCurrentSessionId as s, toAnalyticsEvent as t, type OnboardingCopy as u, type DeviceFormFactor as v, EXTRA_KEY_PREFIX as w, type OnboardingProgress as x, type ResolveUserContextOptions as y, type ResolvedUserContext as z };
|
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 {
|
|
4
|
-
export { A as AnalyticsEvent, b as ClientEventType,
|
|
3
|
+
import { o as WireOnboardingProps, p as WireOnboardingConfig, O as OnboardingResult, S as StepValidator, q as OnboardingEvent, u as OnboardingCopy, a as ClientEventTarget, D as DeviceContext, C as ClientEvent, e as EnvelopeSource } from './currentSession-BJBB7i4-.mjs';
|
|
4
|
+
export { A as AnalyticsEvent, b as ClientEventType, v as DeviceFormFactor, w as EXTRA_KEY_PREFIX, x as OnboardingProgress, R as RESERVED_USER_CONTEXT_KEYS, y as ResolveUserContextOptions, z as ResolvedUserContext, g as WIRE_ONBOARDING_EVENTS, h as WireOnboardingEventName, W as WireUserContext, B as collectDeviceContext, k as getCurrentSessionId, F as hashEmailFnv1a, G as isWireScalar, m as makeSessionId, H as namespaceExtra, r as reportClientEvent, l as reportClientEvents, n as resetCurrentSessionId, I as resolveUserContext, s as setCurrentSessionId, t as toAnalyticsEvent } from './currentSession-BJBB7i4-.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';
|
|
@@ -826,6 +826,40 @@ declare const attributionMetadata: (a: OnboardingAttribution) => {
|
|
|
826
826
|
attribution: Record<string, unknown>;
|
|
827
827
|
};
|
|
828
828
|
|
|
829
|
+
/**
|
|
830
|
+
* appVersion — best-effort, DEPENDENCY-FREE auto-detection of the host app's version string.
|
|
831
|
+
*
|
|
832
|
+
* WHY this exists: analytics segments the funnel `by_app_version`, but that breakdown is only
|
|
833
|
+
* populated when a `device.appVersion` rides the event. `config.appVersion` (see types.ts) has
|
|
834
|
+
* always been the way to supply it — but it is easy for a host to forget, and then the release
|
|
835
|
+
* breakdown is silently empty. This module fills that gap: when the host does NOT pass a version,
|
|
836
|
+
* the kit makes a best-effort read of the app version the host already ships in its Expo config,
|
|
837
|
+
* so the breakdown works out of the box. An explicit `config.appVersion` always WINS over this.
|
|
838
|
+
*
|
|
839
|
+
* WHY it adds NO dependency (the kit's hard rule): `expo-constants` / `expo-application` are read
|
|
840
|
+
* through a GUARDED, VARIABLE-specifier `require`. Passing a variable (not a string literal) keeps
|
|
841
|
+
* Metro/esbuild from statically resolving the module, so a host that does NOT have it installed
|
|
842
|
+
* (e.g. bare React Native) never fails to bundle — the require simply throws at runtime and is
|
|
843
|
+
* swallowed. Nothing is added to `package.json`; nothing is forced on the host.
|
|
844
|
+
*
|
|
845
|
+
* PRIVACY: an app version string is not PII and identifies no user or device, so surfacing it
|
|
846
|
+
* changes no App Privacy / Data Safety declaration (same guarantee as the rest of deviceContext).
|
|
847
|
+
*
|
|
848
|
+
* NEVER THROWS: every read is guarded; a missing/odd value yields `undefined`, never an exception.
|
|
849
|
+
* Analytics must never be able to break onboarding.
|
|
850
|
+
*/
|
|
851
|
+
/** A `require`-like resolver. Injectable in tests; production uses the guarded runtime require. */
|
|
852
|
+
type OptionalRequire = (moduleName: string) => unknown;
|
|
853
|
+
/**
|
|
854
|
+
* Detect the host app version, preferring `expo-constants` (`expoConfig.version`, then
|
|
855
|
+
* `nativeAppVersion`) and finally `expo-application` (`nativeApplicationVersion`). Returns the
|
|
856
|
+
* first real string, or `undefined` when none of those are available. Pure and never throws.
|
|
857
|
+
*
|
|
858
|
+
* `requireModule` is injectable so tests can exercise the "found" path without the native modules;
|
|
859
|
+
* production defaults to the guarded runtime require above.
|
|
860
|
+
*/
|
|
861
|
+
declare const detectAppVersion: (requireModule?: OptionalRequire) => string | undefined;
|
|
862
|
+
|
|
829
863
|
/** Max accepted user-id length. Longer strings are truncated (never rejected). Keep in sync
|
|
830
864
|
* with the server's `USER_ID_MAX_LENGTH` (analytics/events.py). */
|
|
831
865
|
declare const USER_ID_MAX_LENGTH = 128;
|
|
@@ -872,40 +906,6 @@ type IdentifyOnboardingOptions = {
|
|
|
872
906
|
*/
|
|
873
907
|
declare const identifyOnboarding: (opts: IdentifyOnboardingOptions) => Promise<boolean>;
|
|
874
908
|
|
|
875
|
-
/**
|
|
876
|
-
* reportSessionStart — the ONE standard emitter for "the user opened the app again".
|
|
877
|
-
*
|
|
878
|
-
* Every app-open posts a single `app.session_started` event to the Wire analytics backend
|
|
879
|
-
* (`POST {serverUrl}/v1/events`). That event does two jobs at once, with ZERO server changes:
|
|
880
|
-
*
|
|
881
|
-
* 1. SESSION MAPPING. It carries the host's opaque `userId` + a stable `deviceKey`, so the
|
|
882
|
-
* backend can group a user's (or a pre-auth device's) opens over time — "when did this
|
|
883
|
-
* user last use the app, and how many times". The device_key links the pre-auth opens to
|
|
884
|
-
* the user once `userId` arrives (here or via `identifyOnboarding`).
|
|
885
|
-
* 2. QUESTIONNAIRE / RETENTION FIRING. It flows into the SAME event stream the server's
|
|
886
|
-
* decision engines read, so a questionnaire trigger `{event:"app.session_started", min_count:N}`
|
|
887
|
-
* (matched within a session) or a questionnaire `min_sessions:N` (distinct device sessions)
|
|
888
|
-
* fires on the user's Nth open with no new server endpoint.
|
|
889
|
-
*
|
|
890
|
-
* WIRE CONTRACT (server: analytics/events.py + routers/onboarding.py `POST /v1/events`):
|
|
891
|
-
* The event is stored as `event_type='app_event'`, `question_key='app.session_started'` — the
|
|
892
|
-
* generic app.* namespace the reviews wave shipped. The server's `_event_name(ev)` returns the
|
|
893
|
-
* `question_key` for an `app_event`, which is what a trigger's `event` string matches. We do NOT
|
|
894
|
-
* send `event_type:'app.session_started'` — that name is not in the server's EVENT_TYPES and
|
|
895
|
-
* would be rejected. `device_key` rides in the non-PII `user_context` bucket, where the server's
|
|
896
|
-
* `_event_device_key(ev)` reads it to group a device's sessions. `app_id` + `environment` are
|
|
897
|
-
* filled server-side from the resolving key (never sent here).
|
|
898
|
-
*
|
|
899
|
-
* SESSION ID DISTINCTION (important): the `session_id` on this event is a PER-OPEN id (a fresh
|
|
900
|
-
* `makeSessionId()` each app-open) — it is NOT the onboarding A2A `contextId`. Onboarding runs
|
|
901
|
-
* ONCE (first launch) and owns its own context id; session-start fires on EVERY open, so it needs
|
|
902
|
-
* its own per-open id. Grouping over time is done by `device_key`/`user_id`, not by session_id.
|
|
903
|
-
*
|
|
904
|
-
* Fire-and-forget: like every analytics path in the kit, this never throws into the UI, never
|
|
905
|
-
* awaits, and swallows a missing target / bad URL / missing fetch / network error. Analytics must
|
|
906
|
-
* never be able to break the app.
|
|
907
|
-
*/
|
|
908
|
-
|
|
909
909
|
/** The canonical event name for an app-open. A trigger keys off this exact string. */
|
|
910
910
|
declare const SESSION_STARTED_EVENT: "app.session_started";
|
|
911
911
|
/** Options for {@link reportSessionStart}. Everything except `target` is optional so a pre-auth
|
|
@@ -1132,4 +1132,4 @@ interface UseLifecycleEventsOptions {
|
|
|
1132
1132
|
*/
|
|
1133
1133
|
declare const useLifecycleEvents: (config: LifecycleConfig | undefined, options?: UseLifecycleEventsOptions) => void;
|
|
1134
1134
|
|
|
1135
|
-
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 };
|
|
1135
|
+
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, detectAppVersion, 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 {
|
|
4
|
-
export { A as AnalyticsEvent, b as ClientEventType,
|
|
3
|
+
import { o as WireOnboardingProps, p as WireOnboardingConfig, O as OnboardingResult, S as StepValidator, q as OnboardingEvent, u as OnboardingCopy, a as ClientEventTarget, D as DeviceContext, C as ClientEvent, e as EnvelopeSource } from './currentSession-CxnP7gAa.js';
|
|
4
|
+
export { A as AnalyticsEvent, b as ClientEventType, v as DeviceFormFactor, w as EXTRA_KEY_PREFIX, x as OnboardingProgress, R as RESERVED_USER_CONTEXT_KEYS, y as ResolveUserContextOptions, z as ResolvedUserContext, g as WIRE_ONBOARDING_EVENTS, h as WireOnboardingEventName, W as WireUserContext, B as collectDeviceContext, k as getCurrentSessionId, F as hashEmailFnv1a, G as isWireScalar, m as makeSessionId, H as namespaceExtra, r as reportClientEvent, l as reportClientEvents, n as resetCurrentSessionId, I as resolveUserContext, s as setCurrentSessionId, t as toAnalyticsEvent } from './currentSession-CxnP7gAa.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';
|
|
@@ -826,6 +826,40 @@ declare const attributionMetadata: (a: OnboardingAttribution) => {
|
|
|
826
826
|
attribution: Record<string, unknown>;
|
|
827
827
|
};
|
|
828
828
|
|
|
829
|
+
/**
|
|
830
|
+
* appVersion — best-effort, DEPENDENCY-FREE auto-detection of the host app's version string.
|
|
831
|
+
*
|
|
832
|
+
* WHY this exists: analytics segments the funnel `by_app_version`, but that breakdown is only
|
|
833
|
+
* populated when a `device.appVersion` rides the event. `config.appVersion` (see types.ts) has
|
|
834
|
+
* always been the way to supply it — but it is easy for a host to forget, and then the release
|
|
835
|
+
* breakdown is silently empty. This module fills that gap: when the host does NOT pass a version,
|
|
836
|
+
* the kit makes a best-effort read of the app version the host already ships in its Expo config,
|
|
837
|
+
* so the breakdown works out of the box. An explicit `config.appVersion` always WINS over this.
|
|
838
|
+
*
|
|
839
|
+
* WHY it adds NO dependency (the kit's hard rule): `expo-constants` / `expo-application` are read
|
|
840
|
+
* through a GUARDED, VARIABLE-specifier `require`. Passing a variable (not a string literal) keeps
|
|
841
|
+
* Metro/esbuild from statically resolving the module, so a host that does NOT have it installed
|
|
842
|
+
* (e.g. bare React Native) never fails to bundle — the require simply throws at runtime and is
|
|
843
|
+
* swallowed. Nothing is added to `package.json`; nothing is forced on the host.
|
|
844
|
+
*
|
|
845
|
+
* PRIVACY: an app version string is not PII and identifies no user or device, so surfacing it
|
|
846
|
+
* changes no App Privacy / Data Safety declaration (same guarantee as the rest of deviceContext).
|
|
847
|
+
*
|
|
848
|
+
* NEVER THROWS: every read is guarded; a missing/odd value yields `undefined`, never an exception.
|
|
849
|
+
* Analytics must never be able to break onboarding.
|
|
850
|
+
*/
|
|
851
|
+
/** A `require`-like resolver. Injectable in tests; production uses the guarded runtime require. */
|
|
852
|
+
type OptionalRequire = (moduleName: string) => unknown;
|
|
853
|
+
/**
|
|
854
|
+
* Detect the host app version, preferring `expo-constants` (`expoConfig.version`, then
|
|
855
|
+
* `nativeAppVersion`) and finally `expo-application` (`nativeApplicationVersion`). Returns the
|
|
856
|
+
* first real string, or `undefined` when none of those are available. Pure and never throws.
|
|
857
|
+
*
|
|
858
|
+
* `requireModule` is injectable so tests can exercise the "found" path without the native modules;
|
|
859
|
+
* production defaults to the guarded runtime require above.
|
|
860
|
+
*/
|
|
861
|
+
declare const detectAppVersion: (requireModule?: OptionalRequire) => string | undefined;
|
|
862
|
+
|
|
829
863
|
/** Max accepted user-id length. Longer strings are truncated (never rejected). Keep in sync
|
|
830
864
|
* with the server's `USER_ID_MAX_LENGTH` (analytics/events.py). */
|
|
831
865
|
declare const USER_ID_MAX_LENGTH = 128;
|
|
@@ -872,40 +906,6 @@ type IdentifyOnboardingOptions = {
|
|
|
872
906
|
*/
|
|
873
907
|
declare const identifyOnboarding: (opts: IdentifyOnboardingOptions) => Promise<boolean>;
|
|
874
908
|
|
|
875
|
-
/**
|
|
876
|
-
* reportSessionStart — the ONE standard emitter for "the user opened the app again".
|
|
877
|
-
*
|
|
878
|
-
* Every app-open posts a single `app.session_started` event to the Wire analytics backend
|
|
879
|
-
* (`POST {serverUrl}/v1/events`). That event does two jobs at once, with ZERO server changes:
|
|
880
|
-
*
|
|
881
|
-
* 1. SESSION MAPPING. It carries the host's opaque `userId` + a stable `deviceKey`, so the
|
|
882
|
-
* backend can group a user's (or a pre-auth device's) opens over time — "when did this
|
|
883
|
-
* user last use the app, and how many times". The device_key links the pre-auth opens to
|
|
884
|
-
* the user once `userId` arrives (here or via `identifyOnboarding`).
|
|
885
|
-
* 2. QUESTIONNAIRE / RETENTION FIRING. It flows into the SAME event stream the server's
|
|
886
|
-
* decision engines read, so a questionnaire trigger `{event:"app.session_started", min_count:N}`
|
|
887
|
-
* (matched within a session) or a questionnaire `min_sessions:N` (distinct device sessions)
|
|
888
|
-
* fires on the user's Nth open with no new server endpoint.
|
|
889
|
-
*
|
|
890
|
-
* WIRE CONTRACT (server: analytics/events.py + routers/onboarding.py `POST /v1/events`):
|
|
891
|
-
* The event is stored as `event_type='app_event'`, `question_key='app.session_started'` — the
|
|
892
|
-
* generic app.* namespace the reviews wave shipped. The server's `_event_name(ev)` returns the
|
|
893
|
-
* `question_key` for an `app_event`, which is what a trigger's `event` string matches. We do NOT
|
|
894
|
-
* send `event_type:'app.session_started'` — that name is not in the server's EVENT_TYPES and
|
|
895
|
-
* would be rejected. `device_key` rides in the non-PII `user_context` bucket, where the server's
|
|
896
|
-
* `_event_device_key(ev)` reads it to group a device's sessions. `app_id` + `environment` are
|
|
897
|
-
* filled server-side from the resolving key (never sent here).
|
|
898
|
-
*
|
|
899
|
-
* SESSION ID DISTINCTION (important): the `session_id` on this event is a PER-OPEN id (a fresh
|
|
900
|
-
* `makeSessionId()` each app-open) — it is NOT the onboarding A2A `contextId`. Onboarding runs
|
|
901
|
-
* ONCE (first launch) and owns its own context id; session-start fires on EVERY open, so it needs
|
|
902
|
-
* its own per-open id. Grouping over time is done by `device_key`/`user_id`, not by session_id.
|
|
903
|
-
*
|
|
904
|
-
* Fire-and-forget: like every analytics path in the kit, this never throws into the UI, never
|
|
905
|
-
* awaits, and swallows a missing target / bad URL / missing fetch / network error. Analytics must
|
|
906
|
-
* never be able to break the app.
|
|
907
|
-
*/
|
|
908
|
-
|
|
909
909
|
/** The canonical event name for an app-open. A trigger keys off this exact string. */
|
|
910
910
|
declare const SESSION_STARTED_EVENT: "app.session_started";
|
|
911
911
|
/** Options for {@link reportSessionStart}. Everything except `target` is optional so a pre-auth
|
|
@@ -1132,4 +1132,4 @@ interface UseLifecycleEventsOptions {
|
|
|
1132
1132
|
*/
|
|
1133
1133
|
declare const useLifecycleEvents: (config: LifecycleConfig | undefined, options?: UseLifecycleEventsOptions) => void;
|
|
1134
1134
|
|
|
1135
|
-
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 };
|
|
1135
|
+
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, detectAppVersion, 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 };
|