@wireai/activation 0.10.0 → 0.12.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 +51 -0
- package/CHANGELOG.md +111 -1
- package/INTEGRATION_PROMPT.md +13 -1
- package/README.md +106 -4
- package/dist/analytics/index.d.mts +35 -6
- package/dist/analytics/index.d.ts +35 -6
- package/dist/analytics/index.js +222 -94
- package/dist/analytics/index.js.map +1 -1
- package/dist/analytics/index.mjs +214 -95
- package/dist/analytics/index.mjs.map +1 -1
- package/dist/{currentSession-D0Vq7_VE.d.ts → currentSession-D6RiVtc8.d.ts} +187 -29
- package/dist/{currentSession-DdDkprpM.d.mts → currentSession-DsSDHqor.d.mts} +187 -29
- package/dist/index.d.mts +236 -82
- package/dist/index.d.ts +236 -82
- package/dist/index.js +330 -60
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +314 -61
- package/dist/index.mjs.map +1 -1
- package/dist/questionnaire/index.d.mts +1 -1
- package/dist/questionnaire/index.d.ts +1 -1
- package/dist/questionnaire/index.js +59 -8
- package/dist/questionnaire/index.js.map +1 -1
- package/dist/questionnaire/index.mjs +59 -8
- package/dist/questionnaire/index.mjs.map +1 -1
- package/dist/reviews/index.d.mts +2 -2
- package/dist/reviews/index.d.ts +2 -2
- package/dist/reviews/index.js +97 -17
- package/dist/reviews/index.js.map +1 -1
- package/dist/reviews/index.mjs +97 -17
- package/dist/reviews/index.mjs.map +1 -1
- package/dist/{transport-Bzb-bcB2.d.mts → transport-CF_eHwzC.d.mts} +15 -16
- package/dist/{transport-B31G0Cib.d.ts → transport-DsRe4epC.d.ts} +15 -16
- package/llms.txt +9 -0
- package/package.json +1 -1
- package/src/activation/useWireActivation.ts +12 -1
- package/src/activation/wireActivation.ts +36 -24
- package/src/analytics/analyticsFacade.ts +113 -29
- package/src/analytics/currentSession.ts +83 -0
- package/src/analytics/eventQueue.ts +20 -11
- package/src/analytics/index.ts +29 -1
- package/src/analytics/reportClientEvent.ts +50 -2
- package/src/analytics/screenTracking.ts +6 -1
- package/src/analytics/useAnalytics.ts +22 -1
- package/src/context/deviceId.ts +109 -0
- package/src/context/userContext.ts +73 -0
- package/src/identity/userIdentity.ts +10 -0
- package/src/index.ts +50 -2
- package/src/questionnaire/runtime.ts +12 -2
- package/src/questionnaire/transport.ts +5 -1
- package/src/questionnaire/useQuestionnaireGate.ts +9 -7
- package/src/revenuecat/index.ts +55 -0
- package/src/revenuecat/purchaseEvents.ts +167 -0
- package/src/revenuecat/revenueCatBridge.ts +221 -0
- package/src/revenuecat/types.ts +95 -0
- package/src/reviews/decision.ts +8 -1
- package/src/reviews/runtime.ts +92 -1
- package/src/reviews/transport.ts +27 -10
- package/src/reviews/useReviewGate.ts +12 -7
- package/src/session-analytics/lifecycle.ts +15 -13
- package/src/session-analytics/reportSessionStart.ts +15 -4
- package/src/session-analytics/useLifecycleEvents.ts +68 -28
|
@@ -152,6 +152,16 @@ type ClientEvent = {
|
|
|
152
152
|
* Lets the backend reconcile onboarding sessions to real users. Old servers ignore it.
|
|
153
153
|
*/
|
|
154
154
|
user_id?: string;
|
|
155
|
+
/**
|
|
156
|
+
* Client-stamped epoch-ms timestamp of when the event was ENQUEUED on the device. Optional and
|
|
157
|
+
* ADDITIVE: the offline queue stamps it at enqueue time (see `createEventQueue`) so two otherwise
|
|
158
|
+
* byte-identical events fired seconds apart (a genuine repeat, e.g. the user taps "share" twice)
|
|
159
|
+
* are NOT collapsed by the queue's identical-JSON de-dup — while two truly simultaneous
|
|
160
|
+
* re-enqueues of the same instant (a redundant re-render) still share a `ts` and collapse. A raw
|
|
161
|
+
* `Date.now()`, never a wall-clock the server trusts (the server derives its own receive time);
|
|
162
|
+
* an old/strict server that does not model it simply ignores the unknown field.
|
|
163
|
+
*/
|
|
164
|
+
ts?: number;
|
|
155
165
|
};
|
|
156
166
|
/** Where to POST. Derived from `WireOnboardingConfig` (`serverUrl` + `apiKey`). */
|
|
157
167
|
type ClientEventTarget = {
|
|
@@ -676,6 +686,57 @@ declare const hashEmailFnv1a: (email: string) => string;
|
|
|
676
686
|
* are DROPPED. Returns an object (possibly empty).
|
|
677
687
|
*/
|
|
678
688
|
declare const namespaceExtra: (extra: Record<string, unknown> | undefined) => Record<string, string | number | boolean>;
|
|
689
|
+
/**
|
|
690
|
+
* The storage key the analytics façade persists the bound opaque `user_id` under (namespaced per
|
|
691
|
+
* `appId`, mirroring {@link deviceIdStorageKey}). Exported so a logout path can target it directly.
|
|
692
|
+
*/
|
|
693
|
+
declare const analyticsUserIdStorageKey: (appId?: string) => string;
|
|
694
|
+
/**
|
|
695
|
+
* Return a COPY of a {@link WireUserContext} with every USER-scoped (PII / pseudonymous) field
|
|
696
|
+
* removed — `userId`, `userEmail`, `hashEmail`, and `extra` — while KEEPING the non-PII device-scope
|
|
697
|
+
* fields (`appVersion`, `deviceKey`). This is the in-memory half of logout: after it, the same
|
|
698
|
+
* analytics instance keeps its stable `device_key` (which groups a DEVICE, not a user) but no longer
|
|
699
|
+
* stamps the previous user's id/email onto events. Pure; never mutates the input.
|
|
700
|
+
*/
|
|
701
|
+
declare const clearPiiFromContext: (ctx?: WireUserContext) => WireUserContext;
|
|
702
|
+
/** Options for {@link clearUserContext}. */
|
|
703
|
+
interface ClearUserContextOptions {
|
|
704
|
+
/** Host persistence (AsyncStorage subset) — the persisted bound `user_id` is removed from here. */
|
|
705
|
+
storage?: WireOnboardingStorage;
|
|
706
|
+
/** Tenant/app id — namespaces the persisted key (`wireai:analytics:userId:<appId>`). */
|
|
707
|
+
appId?: string;
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* LOGOUT primitive: purge the persisted, bound opaque `user_id` for an app so the NEXT user on a
|
|
711
|
+
* shared device is not silently attributed to the previous one. Removes the
|
|
712
|
+
* `wireai:analytics:userId:<appId>` key that the analytics façade persists and reuses across
|
|
713
|
+
* launches. Fire-and-forget: a missing storage or a failing adapter resolves quietly.
|
|
714
|
+
*
|
|
715
|
+
* COVERAGE. The stateful `createAnalytics(...)` instance also exposes {@link Analytics.reset}, which
|
|
716
|
+
* does this AND clears the in-memory binding + PII in one call — prefer it when you hold the
|
|
717
|
+
* instance. This standalone helper covers the `createWireActivation` / `wire` path (whose config is
|
|
718
|
+
* captured immutably, so it has no `reset`): call `clearUserContext({ storage, appId })` on logout,
|
|
719
|
+
* and RECREATE the `wire` / analytics instance without the user's `userContext` (userId/userEmail)
|
|
720
|
+
* so no further events carry the previous user's identity. The non-PII per-install `device_key`
|
|
721
|
+
* (`wireai:analytics:deviceKey:<appId>`) is intentionally left in place — it groups a device, not a
|
|
722
|
+
* person, and stays stable across users of the same install.
|
|
723
|
+
*/
|
|
724
|
+
declare const clearUserContext: (opts?: ClearUserContextOptions) => Promise<void>;
|
|
725
|
+
/**
|
|
726
|
+
* The `userContext` value to hand `<WireOnboarding userContext={...} />` so an onboarding session
|
|
727
|
+
* and the app's later events (purchases, actions, screens) share ONE join key.
|
|
728
|
+
*
|
|
729
|
+
* WHY it exists as a named function instead of an inline object literal: the wire key is
|
|
730
|
+
* `device_key`, the prop-facing name is `deviceKey`, and the analytics surfaces auto-mint the value
|
|
731
|
+
* for you. A host that hand-writes `userContext={{ deviceKey }}` produces a bucket the server's
|
|
732
|
+
* device lookup does not read, and the resulting funnel is silently EMPTY rather than wrong. This is
|
|
733
|
+
* the one place that spelling is decided.
|
|
734
|
+
*
|
|
735
|
+
* Pass the SAME `deviceKey` you gave `createAnalytics` / `createWireActivation`. `session_id` is not
|
|
736
|
+
* a join key across those two families: an onboarding session id is the A2A `contextId` and an
|
|
737
|
+
* app-event session id is the per-open id, so intersecting them returns nothing.
|
|
738
|
+
*/
|
|
739
|
+
declare const activationJoinContext: (deviceKey: string) => Record<string, string | number | boolean>;
|
|
679
740
|
/** Options for {@link resolveUserContext}. */
|
|
680
741
|
interface ResolveUserContextOptions {
|
|
681
742
|
/**
|
|
@@ -691,41 +752,120 @@ interface ResolveUserContextOptions {
|
|
|
691
752
|
*/
|
|
692
753
|
declare const resolveUserContext: (ctx?: WireUserContext, opts?: ResolveUserContextOptions) => ResolvedUserContext;
|
|
693
754
|
|
|
755
|
+
/** Max accepted user-id length. Longer strings are truncated (never rejected). Keep in sync
|
|
756
|
+
* with the server's `USER_ID_MAX_LENGTH` (analytics/events.py). */
|
|
757
|
+
declare const USER_ID_MAX_LENGTH = 128;
|
|
758
|
+
/**
|
|
759
|
+
* Normalize a host-supplied user id: trim, drop empty, and cap at {@link USER_ID_MAX_LENGTH}.
|
|
760
|
+
* Returns `undefined` for a missing/blank/non-string value so callers can `if (id)`-gate.
|
|
761
|
+
* PII is a host concern — this only bounds length, it does not (and cannot) detect an email.
|
|
762
|
+
*/
|
|
763
|
+
declare const sanitizeUserId: (raw: unknown) => string | undefined;
|
|
764
|
+
/**
|
|
765
|
+
* A permissive email-SHAPE test (`local@domain.tld`) — NOT an RFC validator. Its ONE job is to
|
|
766
|
+
* catch the common integration mistake of binding a RAW EMAIL as the opaque `user_id`: that leaks
|
|
767
|
+
* PII into the top-level id (which the server treats as an opaque key and may surface), when the
|
|
768
|
+
* email belongs in the opt-in `user_context.user_email` field instead. `identify()` uses this to
|
|
769
|
+
* refuse an email-shaped id (with a dev warning) unless the host opts in explicitly. Trims first.
|
|
770
|
+
*/
|
|
771
|
+
declare const looksLikeEmail: (value: unknown) => boolean;
|
|
772
|
+
/** Options for {@link identifyOnboarding}. */
|
|
773
|
+
type IdentifyOnboardingOptions = {
|
|
774
|
+
/** Tenant transport, same shape as `WireOnboardingConfig` (only these two fields are used). */
|
|
775
|
+
config: {
|
|
776
|
+
serverUrl: string;
|
|
777
|
+
apiKey: string;
|
|
778
|
+
};
|
|
779
|
+
/** The host's opaque user id to bind. Trimmed + capped; NO PII. */
|
|
780
|
+
userId: string;
|
|
781
|
+
/**
|
|
782
|
+
* The onboarding session id to bind to (the A2A contextId) — the `contextId` field carried on
|
|
783
|
+
* the `started`/`resumed` `onEvent`. Pass this when you captured it there. Required after the
|
|
784
|
+
* flow COMPLETED, since completion clears the persisted session. Wins over the storage lookup.
|
|
785
|
+
*/
|
|
786
|
+
contextId?: string;
|
|
787
|
+
/**
|
|
788
|
+
* The SAME host storage you passed to `<WireOnboarding storage={…} />`. When `contextId` is
|
|
789
|
+
* omitted, the helper reads the persisted contextId from it (works while the session is still
|
|
790
|
+
* persisted — i.e. dropped or mid-flow, before completion clears it).
|
|
791
|
+
*/
|
|
792
|
+
storage?: WireOnboardingStorage;
|
|
793
|
+
/** App id, to derive the default storage key `wireai:session:<appId>` when reading from storage. */
|
|
794
|
+
appId?: string;
|
|
795
|
+
/** Storage key override — pass the same `persistKey` you gave `<WireOnboarding>`, if any. */
|
|
796
|
+
persistKey?: string;
|
|
797
|
+
};
|
|
694
798
|
/**
|
|
695
|
-
*
|
|
799
|
+
* Attach a host user id to an onboarding session AFTER the fact (post-registration), by sending
|
|
800
|
+
* an `identify` client event to `/v1/events`. Resolves the contextId from an explicit
|
|
801
|
+
* `contextId` or, failing that, from the persisted session in the host `storage`.
|
|
696
802
|
*
|
|
697
|
-
*
|
|
698
|
-
*
|
|
699
|
-
*
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
803
|
+
* Fire-and-forget under the hood (never throws, never blocks onboarding). Resolves to `true`
|
|
804
|
+
* when an identify event was dispatched, `false` when it couldn't (no user id, no server url,
|
|
805
|
+
* or no resolvable contextId).
|
|
806
|
+
*/
|
|
807
|
+
declare const identifyOnboarding: (opts: IdentifyOnboardingOptions) => Promise<boolean>;
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* deviceId — mint a stable, NON-PII, per-install device id the kit owns when the host supplies
|
|
811
|
+
* none. This is the headline of "device fully automatic": the analytics façade auto-mints ONE id,
|
|
812
|
+
* persists it via the host's `storage` abstraction, and reuses it on every subsequent open — so
|
|
813
|
+
* `user_context.device_key` is ALWAYS present and the server's review/questionnaire gating +
|
|
814
|
+
* A/B stickiness (both key on `device_key`) work out of the box, with zero host wiring.
|
|
703
815
|
*
|
|
704
|
-
*
|
|
705
|
-
*
|
|
706
|
-
*
|
|
816
|
+
* WHY it is NOT PII and adds NO dependency (the kit's hard rules):
|
|
817
|
+
* The id is a random token generated from `Date.now()` + `Math.random()` — it carries NO hardware
|
|
818
|
+
* identifier, NO IDFA/GAID, NO fingerprint. It is a first-party per-install correlation key, the
|
|
819
|
+
* same privacy category as a first-party cookie: it groups a single install's sessions and cannot
|
|
820
|
+
* identify a person or be joined across apps. There is NO `uuid` (or any) dependency — a
|
|
821
|
+
* time+random scheme is sufficient because the id is minted ONCE and then persisted, so global
|
|
822
|
+
* uniqueness across the fleet is not required (a per-install collision is astronomically unlikely
|
|
823
|
+
* and inconsequential — worst case two installs share a bucket).
|
|
707
824
|
*
|
|
708
|
-
*
|
|
709
|
-
*
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
*
|
|
717
|
-
*
|
|
825
|
+
* A host that wants its OWN device id still wins: pass `WireUserContext.deviceKey` and the kit uses
|
|
826
|
+
* that verbatim and never mints/persists an auto id.
|
|
827
|
+
*/
|
|
828
|
+
/** Prefix so an auto-minted id is visibly the kit's (distinguishable from a host-supplied `deviceKey`). */
|
|
829
|
+
declare const AUTO_DEVICE_ID_PREFIX = "wdev_";
|
|
830
|
+
/** The storage key the façade persists the auto-minted id under (namespaced per `appId`). */
|
|
831
|
+
declare const deviceIdStorageKey: (appId?: string) => string;
|
|
832
|
+
/**
|
|
833
|
+
* Mint a fresh per-install device id. Dependency-free (`Date.now()` + `Math.random()`), never
|
|
834
|
+
* throws, and returns a NEW value on every call — the façade mints ONCE and persists, so this is
|
|
835
|
+
* called at most once per install (then the persisted value is reused). Two random chunks plus the
|
|
836
|
+
* timestamp keep the token wide enough that a per-install collision is not a practical concern.
|
|
837
|
+
*/
|
|
838
|
+
declare const mintDeviceId: () => string;
|
|
839
|
+
/** The persistence subset {@link resolveAutoDeviceKey} needs (a strict subset of `WireOnboardingStorage`). */
|
|
840
|
+
type DeviceKeyStorage = {
|
|
841
|
+
getItem(key: string): Promise<string | null>;
|
|
842
|
+
setItem(key: string, value: string): Promise<void>;
|
|
843
|
+
};
|
|
844
|
+
/** Options for {@link resolveAutoDeviceKey}. Omitting `storage` gives a PROCESS-scoped id, not a
|
|
845
|
+
* per-install one — see the caller notes: a caller with no persistence must decide whether a
|
|
846
|
+
* per-launch id is better or worse than no id for its metric. */
|
|
847
|
+
interface ResolveAutoDeviceKeyOptions {
|
|
848
|
+
/** Tenant/app id — namespaces both the registry entry and the storage slot. */
|
|
849
|
+
appId?: string;
|
|
850
|
+
/** Host persistence. Present → the id survives launches. Absent → process-scoped only. */
|
|
851
|
+
storage?: DeviceKeyStorage;
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* The ONE auto-minted `device_key` for an install, shared by every kit surface.
|
|
718
855
|
*
|
|
719
|
-
*
|
|
720
|
-
*
|
|
721
|
-
*
|
|
722
|
-
*
|
|
723
|
-
*
|
|
856
|
+
* SYNCHRONOUS by contract (a fire-and-forget event path cannot await): returns the current live id
|
|
857
|
+
* immediately, minting one on first call. When `storage` is supplied it also kicks off a SINGLE
|
|
858
|
+
* hydration per `appId` that adopts the persisted id (or persists the freshly minted one). Callers
|
|
859
|
+
* should call this per EVENT rather than caching the return value, so an event built after hydration
|
|
860
|
+
* carries the persisted id.
|
|
724
861
|
*
|
|
725
|
-
*
|
|
726
|
-
*
|
|
727
|
-
* `resetCurrentSessionId` clears the slot so a unit test starts from a clean registry.
|
|
862
|
+
* A host-supplied `deviceKey` always wins — callers must short-circuit before reaching this.
|
|
863
|
+
* Never throws: a missing, hung, or rejecting storage adapter degrades to the in-memory id.
|
|
728
864
|
*/
|
|
865
|
+
declare const resolveAutoDeviceKey: (opts?: ResolveAutoDeviceKeyOptions) => string;
|
|
866
|
+
/** Test-only: forget every auto id + hydration flag so a unit test starts from a clean registry. */
|
|
867
|
+
declare const resetAutoDeviceKeys: () => void;
|
|
868
|
+
|
|
729
869
|
/**
|
|
730
870
|
* Record the current per-open `session_id`. Called by `reportSessionStart` when it emits an
|
|
731
871
|
* app-open. A blank / non-string id is ignored (the previous id stays current). Idempotent.
|
|
@@ -735,5 +875,23 @@ declare const setCurrentSessionId: (id: string | undefined) => void;
|
|
|
735
875
|
declare const getCurrentSessionId: () => string | undefined;
|
|
736
876
|
/** Test-only: forget the current session id so a unit test starts from a clean registry. */
|
|
737
877
|
declare const resetCurrentSessionId: () => void;
|
|
878
|
+
/**
|
|
879
|
+
* The current per-open `session_id`, MINTING and registering one when no app-open has been
|
|
880
|
+
* registered yet. Always returns a non-empty string. Idempotent (a second call returns the same id)
|
|
881
|
+
* and never throws.
|
|
882
|
+
*
|
|
883
|
+
* Use this on every path that puts a `session_id` on the wire. The server REQUIRES a non-empty
|
|
884
|
+
* `session_id` and drops the event otherwise while still answering 200 (see the module header), so
|
|
885
|
+
* "no id yet" must never mean "send it without one".
|
|
886
|
+
*
|
|
887
|
+
* BACKWARD-COMPATIBLE BY CONSTRUCTION: when `reportSessionStart` / `useLifecycleEvents` has already
|
|
888
|
+
* registered the real per-open id, this is `getCurrentSessionId()` and nothing changes. It only ever
|
|
889
|
+
* mints in the case that used to produce a silently discarded event.
|
|
890
|
+
*
|
|
891
|
+
* A mint means the host never registered an app-open, so the minted id is one the server has not
|
|
892
|
+
* seen a `session_started` for — the events land, but the session is thinner than a real open.
|
|
893
|
+
* Hence the one-time dev warning naming the fix (mount `useLifecycleEvents` at the app root).
|
|
894
|
+
*/
|
|
895
|
+
declare const ensureCurrentSessionId: () => string;
|
|
738
896
|
|
|
739
|
-
export {
|
|
897
|
+
export { identifyOnboarding as $, AUTO_DEVICE_ID_PREFIX as A, resolveAutoDeviceKey as B, type ClearUserContextOptions as C, type DeviceKeyStorage as D, type EventQueueOptions as E, setCurrentSessionId as F, toAnalyticsEvent as G, type WireOnboardingProps as H, type WireOnboardingConfig as I, type OnboardingEvent as J, type OnboardingCopy as K, type DeviceContext as L, type DeviceFormFactor as M, EXTRA_KEY_PREFIX as N, type OnboardingResult as O, type IdentifyOnboardingOptions as P, type OnboardingProgress as Q, type ResolveAutoDeviceKeyOptions as R, type StepValidator as S, RESERVED_USER_CONTEXT_KEYS as T, type ResolveUserContextOptions as U, type ResolvedUserContext as V, type WireUserContext as W, USER_ID_MAX_LENGTH as X, activationJoinContext as Y, collectDeviceContext as Z, hashEmailFnv1a as _, type AnalyticsEvent as a, isWireScalar as a0, mintDeviceId as a1, namespaceExtra as a2, resolveUserContext as a3, sanitizeUserId as a4, type ClientEvent as b, type ClientEventTarget as c, type ClientEventType as d, type ContextEnvelope as e, type ContextEnvelopeInput as f, type EnvelopeSource as g, type EventQueue as h, WIRE_ONBOARDING_EVENTS as i, type WireOnboardingEventName as j, analyticsUserIdStorageKey as k, buildContextEnvelope as l, clearPiiFromContext as m, clearUserContext as n, createEventQueue as o, deviceIdStorageKey as p, ensureCurrentSessionId as q, getCurrentSessionId as r, looksLikeEmail as s, makeSessionId as t, reportClientEvent as u, reportClientEventAwait as v, reportClientEvents as w, reportClientEventsAwait as x, resetAutoDeviceKeys as y, resetCurrentSessionId as z };
|
|
@@ -152,6 +152,16 @@ type ClientEvent = {
|
|
|
152
152
|
* Lets the backend reconcile onboarding sessions to real users. Old servers ignore it.
|
|
153
153
|
*/
|
|
154
154
|
user_id?: string;
|
|
155
|
+
/**
|
|
156
|
+
* Client-stamped epoch-ms timestamp of when the event was ENQUEUED on the device. Optional and
|
|
157
|
+
* ADDITIVE: the offline queue stamps it at enqueue time (see `createEventQueue`) so two otherwise
|
|
158
|
+
* byte-identical events fired seconds apart (a genuine repeat, e.g. the user taps "share" twice)
|
|
159
|
+
* are NOT collapsed by the queue's identical-JSON de-dup — while two truly simultaneous
|
|
160
|
+
* re-enqueues of the same instant (a redundant re-render) still share a `ts` and collapse. A raw
|
|
161
|
+
* `Date.now()`, never a wall-clock the server trusts (the server derives its own receive time);
|
|
162
|
+
* an old/strict server that does not model it simply ignores the unknown field.
|
|
163
|
+
*/
|
|
164
|
+
ts?: number;
|
|
155
165
|
};
|
|
156
166
|
/** Where to POST. Derived from `WireOnboardingConfig` (`serverUrl` + `apiKey`). */
|
|
157
167
|
type ClientEventTarget = {
|
|
@@ -676,6 +686,57 @@ declare const hashEmailFnv1a: (email: string) => string;
|
|
|
676
686
|
* are DROPPED. Returns an object (possibly empty).
|
|
677
687
|
*/
|
|
678
688
|
declare const namespaceExtra: (extra: Record<string, unknown> | undefined) => Record<string, string | number | boolean>;
|
|
689
|
+
/**
|
|
690
|
+
* The storage key the analytics façade persists the bound opaque `user_id` under (namespaced per
|
|
691
|
+
* `appId`, mirroring {@link deviceIdStorageKey}). Exported so a logout path can target it directly.
|
|
692
|
+
*/
|
|
693
|
+
declare const analyticsUserIdStorageKey: (appId?: string) => string;
|
|
694
|
+
/**
|
|
695
|
+
* Return a COPY of a {@link WireUserContext} with every USER-scoped (PII / pseudonymous) field
|
|
696
|
+
* removed — `userId`, `userEmail`, `hashEmail`, and `extra` — while KEEPING the non-PII device-scope
|
|
697
|
+
* fields (`appVersion`, `deviceKey`). This is the in-memory half of logout: after it, the same
|
|
698
|
+
* analytics instance keeps its stable `device_key` (which groups a DEVICE, not a user) but no longer
|
|
699
|
+
* stamps the previous user's id/email onto events. Pure; never mutates the input.
|
|
700
|
+
*/
|
|
701
|
+
declare const clearPiiFromContext: (ctx?: WireUserContext) => WireUserContext;
|
|
702
|
+
/** Options for {@link clearUserContext}. */
|
|
703
|
+
interface ClearUserContextOptions {
|
|
704
|
+
/** Host persistence (AsyncStorage subset) — the persisted bound `user_id` is removed from here. */
|
|
705
|
+
storage?: WireOnboardingStorage;
|
|
706
|
+
/** Tenant/app id — namespaces the persisted key (`wireai:analytics:userId:<appId>`). */
|
|
707
|
+
appId?: string;
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* LOGOUT primitive: purge the persisted, bound opaque `user_id` for an app so the NEXT user on a
|
|
711
|
+
* shared device is not silently attributed to the previous one. Removes the
|
|
712
|
+
* `wireai:analytics:userId:<appId>` key that the analytics façade persists and reuses across
|
|
713
|
+
* launches. Fire-and-forget: a missing storage or a failing adapter resolves quietly.
|
|
714
|
+
*
|
|
715
|
+
* COVERAGE. The stateful `createAnalytics(...)` instance also exposes {@link Analytics.reset}, which
|
|
716
|
+
* does this AND clears the in-memory binding + PII in one call — prefer it when you hold the
|
|
717
|
+
* instance. This standalone helper covers the `createWireActivation` / `wire` path (whose config is
|
|
718
|
+
* captured immutably, so it has no `reset`): call `clearUserContext({ storage, appId })` on logout,
|
|
719
|
+
* and RECREATE the `wire` / analytics instance without the user's `userContext` (userId/userEmail)
|
|
720
|
+
* so no further events carry the previous user's identity. The non-PII per-install `device_key`
|
|
721
|
+
* (`wireai:analytics:deviceKey:<appId>`) is intentionally left in place — it groups a device, not a
|
|
722
|
+
* person, and stays stable across users of the same install.
|
|
723
|
+
*/
|
|
724
|
+
declare const clearUserContext: (opts?: ClearUserContextOptions) => Promise<void>;
|
|
725
|
+
/**
|
|
726
|
+
* The `userContext` value to hand `<WireOnboarding userContext={...} />` so an onboarding session
|
|
727
|
+
* and the app's later events (purchases, actions, screens) share ONE join key.
|
|
728
|
+
*
|
|
729
|
+
* WHY it exists as a named function instead of an inline object literal: the wire key is
|
|
730
|
+
* `device_key`, the prop-facing name is `deviceKey`, and the analytics surfaces auto-mint the value
|
|
731
|
+
* for you. A host that hand-writes `userContext={{ deviceKey }}` produces a bucket the server's
|
|
732
|
+
* device lookup does not read, and the resulting funnel is silently EMPTY rather than wrong. This is
|
|
733
|
+
* the one place that spelling is decided.
|
|
734
|
+
*
|
|
735
|
+
* Pass the SAME `deviceKey` you gave `createAnalytics` / `createWireActivation`. `session_id` is not
|
|
736
|
+
* a join key across those two families: an onboarding session id is the A2A `contextId` and an
|
|
737
|
+
* app-event session id is the per-open id, so intersecting them returns nothing.
|
|
738
|
+
*/
|
|
739
|
+
declare const activationJoinContext: (deviceKey: string) => Record<string, string | number | boolean>;
|
|
679
740
|
/** Options for {@link resolveUserContext}. */
|
|
680
741
|
interface ResolveUserContextOptions {
|
|
681
742
|
/**
|
|
@@ -691,41 +752,120 @@ interface ResolveUserContextOptions {
|
|
|
691
752
|
*/
|
|
692
753
|
declare const resolveUserContext: (ctx?: WireUserContext, opts?: ResolveUserContextOptions) => ResolvedUserContext;
|
|
693
754
|
|
|
755
|
+
/** Max accepted user-id length. Longer strings are truncated (never rejected). Keep in sync
|
|
756
|
+
* with the server's `USER_ID_MAX_LENGTH` (analytics/events.py). */
|
|
757
|
+
declare const USER_ID_MAX_LENGTH = 128;
|
|
758
|
+
/**
|
|
759
|
+
* Normalize a host-supplied user id: trim, drop empty, and cap at {@link USER_ID_MAX_LENGTH}.
|
|
760
|
+
* Returns `undefined` for a missing/blank/non-string value so callers can `if (id)`-gate.
|
|
761
|
+
* PII is a host concern — this only bounds length, it does not (and cannot) detect an email.
|
|
762
|
+
*/
|
|
763
|
+
declare const sanitizeUserId: (raw: unknown) => string | undefined;
|
|
764
|
+
/**
|
|
765
|
+
* A permissive email-SHAPE test (`local@domain.tld`) — NOT an RFC validator. Its ONE job is to
|
|
766
|
+
* catch the common integration mistake of binding a RAW EMAIL as the opaque `user_id`: that leaks
|
|
767
|
+
* PII into the top-level id (which the server treats as an opaque key and may surface), when the
|
|
768
|
+
* email belongs in the opt-in `user_context.user_email` field instead. `identify()` uses this to
|
|
769
|
+
* refuse an email-shaped id (with a dev warning) unless the host opts in explicitly. Trims first.
|
|
770
|
+
*/
|
|
771
|
+
declare const looksLikeEmail: (value: unknown) => boolean;
|
|
772
|
+
/** Options for {@link identifyOnboarding}. */
|
|
773
|
+
type IdentifyOnboardingOptions = {
|
|
774
|
+
/** Tenant transport, same shape as `WireOnboardingConfig` (only these two fields are used). */
|
|
775
|
+
config: {
|
|
776
|
+
serverUrl: string;
|
|
777
|
+
apiKey: string;
|
|
778
|
+
};
|
|
779
|
+
/** The host's opaque user id to bind. Trimmed + capped; NO PII. */
|
|
780
|
+
userId: string;
|
|
781
|
+
/**
|
|
782
|
+
* The onboarding session id to bind to (the A2A contextId) — the `contextId` field carried on
|
|
783
|
+
* the `started`/`resumed` `onEvent`. Pass this when you captured it there. Required after the
|
|
784
|
+
* flow COMPLETED, since completion clears the persisted session. Wins over the storage lookup.
|
|
785
|
+
*/
|
|
786
|
+
contextId?: string;
|
|
787
|
+
/**
|
|
788
|
+
* The SAME host storage you passed to `<WireOnboarding storage={…} />`. When `contextId` is
|
|
789
|
+
* omitted, the helper reads the persisted contextId from it (works while the session is still
|
|
790
|
+
* persisted — i.e. dropped or mid-flow, before completion clears it).
|
|
791
|
+
*/
|
|
792
|
+
storage?: WireOnboardingStorage;
|
|
793
|
+
/** App id, to derive the default storage key `wireai:session:<appId>` when reading from storage. */
|
|
794
|
+
appId?: string;
|
|
795
|
+
/** Storage key override — pass the same `persistKey` you gave `<WireOnboarding>`, if any. */
|
|
796
|
+
persistKey?: string;
|
|
797
|
+
};
|
|
694
798
|
/**
|
|
695
|
-
*
|
|
799
|
+
* Attach a host user id to an onboarding session AFTER the fact (post-registration), by sending
|
|
800
|
+
* an `identify` client event to `/v1/events`. Resolves the contextId from an explicit
|
|
801
|
+
* `contextId` or, failing that, from the persisted session in the host `storage`.
|
|
696
802
|
*
|
|
697
|
-
*
|
|
698
|
-
*
|
|
699
|
-
*
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
803
|
+
* Fire-and-forget under the hood (never throws, never blocks onboarding). Resolves to `true`
|
|
804
|
+
* when an identify event was dispatched, `false` when it couldn't (no user id, no server url,
|
|
805
|
+
* or no resolvable contextId).
|
|
806
|
+
*/
|
|
807
|
+
declare const identifyOnboarding: (opts: IdentifyOnboardingOptions) => Promise<boolean>;
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* deviceId — mint a stable, NON-PII, per-install device id the kit owns when the host supplies
|
|
811
|
+
* none. This is the headline of "device fully automatic": the analytics façade auto-mints ONE id,
|
|
812
|
+
* persists it via the host's `storage` abstraction, and reuses it on every subsequent open — so
|
|
813
|
+
* `user_context.device_key` is ALWAYS present and the server's review/questionnaire gating +
|
|
814
|
+
* A/B stickiness (both key on `device_key`) work out of the box, with zero host wiring.
|
|
703
815
|
*
|
|
704
|
-
*
|
|
705
|
-
*
|
|
706
|
-
*
|
|
816
|
+
* WHY it is NOT PII and adds NO dependency (the kit's hard rules):
|
|
817
|
+
* The id is a random token generated from `Date.now()` + `Math.random()` — it carries NO hardware
|
|
818
|
+
* identifier, NO IDFA/GAID, NO fingerprint. It is a first-party per-install correlation key, the
|
|
819
|
+
* same privacy category as a first-party cookie: it groups a single install's sessions and cannot
|
|
820
|
+
* identify a person or be joined across apps. There is NO `uuid` (or any) dependency — a
|
|
821
|
+
* time+random scheme is sufficient because the id is minted ONCE and then persisted, so global
|
|
822
|
+
* uniqueness across the fleet is not required (a per-install collision is astronomically unlikely
|
|
823
|
+
* and inconsequential — worst case two installs share a bucket).
|
|
707
824
|
*
|
|
708
|
-
*
|
|
709
|
-
*
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
*
|
|
717
|
-
*
|
|
825
|
+
* A host that wants its OWN device id still wins: pass `WireUserContext.deviceKey` and the kit uses
|
|
826
|
+
* that verbatim and never mints/persists an auto id.
|
|
827
|
+
*/
|
|
828
|
+
/** Prefix so an auto-minted id is visibly the kit's (distinguishable from a host-supplied `deviceKey`). */
|
|
829
|
+
declare const AUTO_DEVICE_ID_PREFIX = "wdev_";
|
|
830
|
+
/** The storage key the façade persists the auto-minted id under (namespaced per `appId`). */
|
|
831
|
+
declare const deviceIdStorageKey: (appId?: string) => string;
|
|
832
|
+
/**
|
|
833
|
+
* Mint a fresh per-install device id. Dependency-free (`Date.now()` + `Math.random()`), never
|
|
834
|
+
* throws, and returns a NEW value on every call — the façade mints ONCE and persists, so this is
|
|
835
|
+
* called at most once per install (then the persisted value is reused). Two random chunks plus the
|
|
836
|
+
* timestamp keep the token wide enough that a per-install collision is not a practical concern.
|
|
837
|
+
*/
|
|
838
|
+
declare const mintDeviceId: () => string;
|
|
839
|
+
/** The persistence subset {@link resolveAutoDeviceKey} needs (a strict subset of `WireOnboardingStorage`). */
|
|
840
|
+
type DeviceKeyStorage = {
|
|
841
|
+
getItem(key: string): Promise<string | null>;
|
|
842
|
+
setItem(key: string, value: string): Promise<void>;
|
|
843
|
+
};
|
|
844
|
+
/** Options for {@link resolveAutoDeviceKey}. Omitting `storage` gives a PROCESS-scoped id, not a
|
|
845
|
+
* per-install one — see the caller notes: a caller with no persistence must decide whether a
|
|
846
|
+
* per-launch id is better or worse than no id for its metric. */
|
|
847
|
+
interface ResolveAutoDeviceKeyOptions {
|
|
848
|
+
/** Tenant/app id — namespaces both the registry entry and the storage slot. */
|
|
849
|
+
appId?: string;
|
|
850
|
+
/** Host persistence. Present → the id survives launches. Absent → process-scoped only. */
|
|
851
|
+
storage?: DeviceKeyStorage;
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* The ONE auto-minted `device_key` for an install, shared by every kit surface.
|
|
718
855
|
*
|
|
719
|
-
*
|
|
720
|
-
*
|
|
721
|
-
*
|
|
722
|
-
*
|
|
723
|
-
*
|
|
856
|
+
* SYNCHRONOUS by contract (a fire-and-forget event path cannot await): returns the current live id
|
|
857
|
+
* immediately, minting one on first call. When `storage` is supplied it also kicks off a SINGLE
|
|
858
|
+
* hydration per `appId` that adopts the persisted id (or persists the freshly minted one). Callers
|
|
859
|
+
* should call this per EVENT rather than caching the return value, so an event built after hydration
|
|
860
|
+
* carries the persisted id.
|
|
724
861
|
*
|
|
725
|
-
*
|
|
726
|
-
*
|
|
727
|
-
* `resetCurrentSessionId` clears the slot so a unit test starts from a clean registry.
|
|
862
|
+
* A host-supplied `deviceKey` always wins — callers must short-circuit before reaching this.
|
|
863
|
+
* Never throws: a missing, hung, or rejecting storage adapter degrades to the in-memory id.
|
|
728
864
|
*/
|
|
865
|
+
declare const resolveAutoDeviceKey: (opts?: ResolveAutoDeviceKeyOptions) => string;
|
|
866
|
+
/** Test-only: forget every auto id + hydration flag so a unit test starts from a clean registry. */
|
|
867
|
+
declare const resetAutoDeviceKeys: () => void;
|
|
868
|
+
|
|
729
869
|
/**
|
|
730
870
|
* Record the current per-open `session_id`. Called by `reportSessionStart` when it emits an
|
|
731
871
|
* app-open. A blank / non-string id is ignored (the previous id stays current). Idempotent.
|
|
@@ -735,5 +875,23 @@ declare const setCurrentSessionId: (id: string | undefined) => void;
|
|
|
735
875
|
declare const getCurrentSessionId: () => string | undefined;
|
|
736
876
|
/** Test-only: forget the current session id so a unit test starts from a clean registry. */
|
|
737
877
|
declare const resetCurrentSessionId: () => void;
|
|
878
|
+
/**
|
|
879
|
+
* The current per-open `session_id`, MINTING and registering one when no app-open has been
|
|
880
|
+
* registered yet. Always returns a non-empty string. Idempotent (a second call returns the same id)
|
|
881
|
+
* and never throws.
|
|
882
|
+
*
|
|
883
|
+
* Use this on every path that puts a `session_id` on the wire. The server REQUIRES a non-empty
|
|
884
|
+
* `session_id` and drops the event otherwise while still answering 200 (see the module header), so
|
|
885
|
+
* "no id yet" must never mean "send it without one".
|
|
886
|
+
*
|
|
887
|
+
* BACKWARD-COMPATIBLE BY CONSTRUCTION: when `reportSessionStart` / `useLifecycleEvents` has already
|
|
888
|
+
* registered the real per-open id, this is `getCurrentSessionId()` and nothing changes. It only ever
|
|
889
|
+
* mints in the case that used to produce a silently discarded event.
|
|
890
|
+
*
|
|
891
|
+
* A mint means the host never registered an app-open, so the minted id is one the server has not
|
|
892
|
+
* seen a `session_started` for — the events land, but the session is thinner than a real open.
|
|
893
|
+
* Hence the one-time dev warning naming the fix (mount `useLifecycleEvents` at the app root).
|
|
894
|
+
*/
|
|
895
|
+
declare const ensureCurrentSessionId: () => string;
|
|
738
896
|
|
|
739
|
-
export {
|
|
897
|
+
export { identifyOnboarding as $, AUTO_DEVICE_ID_PREFIX as A, resolveAutoDeviceKey as B, type ClearUserContextOptions as C, type DeviceKeyStorage as D, type EventQueueOptions as E, setCurrentSessionId as F, toAnalyticsEvent as G, type WireOnboardingProps as H, type WireOnboardingConfig as I, type OnboardingEvent as J, type OnboardingCopy as K, type DeviceContext as L, type DeviceFormFactor as M, EXTRA_KEY_PREFIX as N, type OnboardingResult as O, type IdentifyOnboardingOptions as P, type OnboardingProgress as Q, type ResolveAutoDeviceKeyOptions as R, type StepValidator as S, RESERVED_USER_CONTEXT_KEYS as T, type ResolveUserContextOptions as U, type ResolvedUserContext as V, type WireUserContext as W, USER_ID_MAX_LENGTH as X, activationJoinContext as Y, collectDeviceContext as Z, hashEmailFnv1a as _, type AnalyticsEvent as a, isWireScalar as a0, mintDeviceId as a1, namespaceExtra as a2, resolveUserContext as a3, sanitizeUserId as a4, type ClientEvent as b, type ClientEventTarget as c, type ClientEventType as d, type ContextEnvelope as e, type ContextEnvelopeInput as f, type EnvelopeSource as g, type EventQueue as h, WIRE_ONBOARDING_EVENTS as i, type WireOnboardingEventName as j, analyticsUserIdStorageKey as k, buildContextEnvelope as l, clearPiiFromContext as m, clearUserContext as n, createEventQueue as o, deviceIdStorageKey as p, ensureCurrentSessionId as q, getCurrentSessionId as r, looksLikeEmail as s, makeSessionId as t, reportClientEvent as u, reportClientEventAwait as v, reportClientEvents as w, reportClientEventsAwait as x, resetAutoDeviceKeys as y, resetCurrentSessionId as z };
|