@wireai/activation 0.12.2 → 0.13.2
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 +3 -1
- package/CHANGELOG.md +259 -1
- package/README.md +87 -3
- package/dist/analytics/index.d.mts +2 -2
- package/dist/analytics/index.d.ts +2 -2
- package/dist/analytics/index.js +174 -36
- package/dist/analytics/index.js.map +1 -1
- package/dist/analytics/index.mjs +174 -37
- package/dist/analytics/index.mjs.map +1 -1
- package/dist/{currentSession-BlCeDP0f.d.mts → currentSession-ClkLjcJ0.d.mts} +456 -19
- package/dist/{currentSession-BxEB37xt.d.ts → currentSession-DOVZEWJl.d.ts} +456 -19
- package/dist/index.d.mts +197 -16
- package/dist/index.d.ts +197 -16
- package/dist/index.js +1056 -390
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +836 -193
- package/dist/index.mjs.map +1 -1
- package/dist/questionnaire/index.js.map +1 -1
- package/dist/questionnaire/index.mjs.map +1 -1
- package/dist/reviews/index.js +20 -7
- package/dist/reviews/index.js.map +1 -1
- package/dist/reviews/index.mjs +20 -7
- package/dist/reviews/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/OnboardingFlow.tsx +141 -3
- package/src/WireOnboarding.tsx +178 -34
- package/src/activation/wireActivation.ts +13 -7
- package/src/analytics/analyticsEvent.ts +16 -1
- package/src/analytics/analyticsFacade.ts +11 -10
- package/src/analytics/currentSession.ts +6 -20
- package/src/analytics/eventQueue.ts +71 -1
- package/src/analytics/index.ts +1 -1
- package/src/analytics/reportClientEvent.ts +157 -38
- package/src/cards/PermissionCard.tsx +438 -0
- package/src/cards/index.ts +7 -0
- package/src/config/wireConfigFromEnv.ts +1 -10
- package/src/context/deviceId.ts +77 -16
- package/src/context/userContext.ts +4 -15
- package/src/identity/identityRecord.ts +123 -0
- package/src/identity/userIdentity.ts +45 -9
- package/src/illustrations/defaultIllustrations.tsx +44 -3
- package/src/index.ts +44 -4
- package/src/permissions/index.ts +64 -0
- package/src/permissions/permissionCopy.ts +87 -0
- package/src/permissions/permissionEvents.ts +76 -0
- package/src/permissions/permissionMemory.ts +88 -0
- package/src/permissions/placement.ts +88 -0
- package/src/permissions/types.ts +131 -0
- package/src/session/persistedSession.ts +10 -3
- package/src/session-analytics/useLifecycleEvents.ts +10 -1
- package/src/types.ts +77 -1
- package/src/utils/deriveAnswers.ts +6 -2
- package/src/utils/readProgress.ts +4 -0
- package/src/utils/warnInDev.ts +33 -0
- package/src/components/DoneBlock.tsx +0 -37
package/AGENTS.md
CHANGED
|
@@ -48,6 +48,7 @@ subpaths are optional secondary feature modules; import one only if you use it.
|
|
|
48
48
|
- `themeFromBrand({ primary })`: derive a full theme from one brand color.
|
|
49
49
|
- `mergeTheme`, `defaultOnboardingTheme`, `OnboardingThemeProvider`, `useOnboardingTheme`.
|
|
50
50
|
- `defaultIllustrations`: dependency-free fallback art; spread your own over it.
|
|
51
|
+
- `WIRE_PERMISSION_EVENTS`, `resolvePermissionCopy(...)`: the canonical permission-funnel names and the shipped rationale copy behind `permissionScreens` (see the prop below). The kit imports no native permission module; the host injects `request`.
|
|
51
52
|
- `createRevenueCatBridge({ analytics, entitlementId })`: the RevenueCat purchase funnel (see "RevenueCat" below).
|
|
52
53
|
- `activationJoinContext(deviceKey)`: builds the `userContext` value that joins an onboarding session to the app's later events. Every `<WireOnboarding>` needs it (see step 7).
|
|
53
54
|
- `resolveAutoDeviceKey({ appId, storage })`: the kit's own persisted per-install `device_key`, for a host that owns none.
|
|
@@ -65,9 +66,10 @@ subpaths are optional secondary feature modules; import one only if you use it.
|
|
|
65
66
|
| `illustrations` | `Record<string, ReactNode>` | no | Host artwork for `InterstitialCard`, keyed by name. `{ ...defaultIllustrations, ...myArt }`. |
|
|
66
67
|
| `icons` | `Record<string, ReactNode>` | no | Host icon nodes keyed by the semantic vocabulary name the AI emits (`{ instagram: <BrandIg/> }`). Checked FIRST: use it to brand an icon, to add names of your own, or to supply icons without installing `@expo/vector-icons`. Unlisted names fall back to that optional peer, then to no icon. |
|
|
67
68
|
| `validators` | `Record<string, StepValidator>` | no | Per base-question key (e.g. `username`). Blocks advance + inline error. |
|
|
69
|
+
| `permissionScreens` | `PermissionScreenConfig[]` | no | Priming screens injected mid-flow (notifications first). `{ permission, placement?, request, getStatus?, openSettings?, copy?, illustration?, onResult? }`. The OS dialog opens ONLY on the primary tap, never on mount; "Maybe later" advances without spending the one native prompt. `placement` is `"start"`, `{ afterCard: n }` or `"beforeEnd"` (default), and an `afterCard` past the end of the stream clamps to `"beforeEnd"`. Zero new dependencies: the host injects `request`, the kit imports no native permission module. Shown once per session and, with `storage`, across an app kill. Not a question: no `key`/`slot_id`, nothing in `answers`, completion never blocks on a grant. |
|
|
68
70
|
| `onSkip` | `() => void` | no | Retained for back-compat. Per-question Skip is now internal (the kit advances one question on `skippable` screens), so this is no longer wired to that control. |
|
|
69
71
|
| `onError` | `(err) => void` | no | Backend error/timeout after retries. Host recovery (e.g. static onboarding). `fallbackFlow` takes precedence over it. |
|
|
70
|
-
| `onEvent` | `(e: OnboardingEvent) => void` | no | Lifecycle events for analytics (kit owns the loop). Variants: `started`, `resumed`, `turn`, `error`, `retry`, `fallback` (see `OnboardingEvent`). |
|
|
72
|
+
| `onEvent` | `(e: OnboardingEvent) => void` | no | Lifecycle events for analytics (kit owns the loop). Variants: `started`, `resumed`, `turn`, `error`, `retry`, `fallback`, `permission` (see `OnboardingEvent`). |
|
|
71
73
|
| `copy` | `Partial<OnboardingCopy>` | no | Localize built-in English strings. |
|
|
72
74
|
| `approxScreens` | `number` | no | Paces the bar; backend `progress.total` wins, so usually unneeded. |
|
|
73
75
|
| `startMessage` | `string` | no | First backend message. Default `"start"`. |
|
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,264 @@
|
|
|
3
3
|
All notable changes to `@wireai/activation` (formerly `wireai-onboarding`).
|
|
4
4
|
Historical entries below the rename keep the old package name on purpose.
|
|
5
5
|
|
|
6
|
+
## [0.13.2] — 2026-07-31
|
|
7
|
+
|
|
8
|
+
The headline is a fix: every `track()` / `screen()` `app_event` was being silently discarded by the
|
|
9
|
+
server behind an HTTP 200, on every kit version since 0.11.0. This release also ships the injectable
|
|
10
|
+
mid-flow permission screens below, and it is the FIRST version whose published tarball is clean of
|
|
11
|
+
the banned client name — the 0.13.0 publish-surface scrub now rides a build-time canary that fails
|
|
12
|
+
the pack if the name ever reappears.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- **`ts` crosses the wire as ISO8601, so `app_event`s actually land.**
|
|
17
|
+
(`analytics/reportClientEvent.ts`) The server's event model declares `ts: str | None` and pydantic
|
|
18
|
+
v2 does not coerce a number into it, while the offline queue has stamped `ts = Date.now()` (epoch
|
|
19
|
+
ms) on every queued event since 0.11.0 — so `POST /v1/events` answered HTTP 200 with
|
|
20
|
+
`{written: 0, skipped: N, errors: [{field: "ts", reason: "validation_error"}]}` and 100% of
|
|
21
|
+
`track()` / `screen()` analytics evaporated behind a green response. `buildEventsRequest` — the
|
|
22
|
+
single choke point all four send paths share (offline queue, fire-and-forget, awaitable,
|
|
23
|
+
session-start) — now serializes a numeric `ts` to an ISO8601 UTC string on its way out. The queue
|
|
24
|
+
keeps its numeric stamp (the identical-JSON de-dup signature depends on it), a persisted 0.13.0
|
|
25
|
+
backlog is converted as it drains, a caller-set ISO string passes through untouched, and a
|
|
26
|
+
non-finite or out-of-range number drops the field from that one event instead of killing the batch.
|
|
27
|
+
- **The discard warning names the field the server refused.** (`readEventsAck`) The server's
|
|
28
|
+
`errors[]` entries carry a `field` alongside `reason`; the ack reader threw it away, so a dev build
|
|
29
|
+
warned `validation_error` with no address. It now prints `validation_error (field: ts)` — the
|
|
30
|
+
difference between a one-line fix and an investigation.
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
Injectable mid-flow permission screens, starting with notifications.
|
|
35
|
+
|
|
36
|
+
Onboarding is where apps ask for notifications and where most of them lose the ask: iOS grants an
|
|
37
|
+
app exactly ONE native prompt for its entire lifetime, so firing it from a mount effect on screen
|
|
38
|
+
one spends it on a user who has been told nothing, and the only route back is a Settings trip almost
|
|
39
|
+
nobody makes. This release ships the priming pattern as a first-class part of the flow. You declare
|
|
40
|
+
a screen, it explains why in your words, and the OS dialog opens only on the user's primary tap.
|
|
41
|
+
"Maybe later" advances the flow with the prompt unspent.
|
|
42
|
+
|
|
43
|
+
Nothing changes for a host that configures no screen: the prop is optional and every path around it
|
|
44
|
+
is byte-identical.
|
|
45
|
+
|
|
46
|
+
- **`permissionScreens` on `<WireOnboarding>`** (`permissions/types.ts`, `cards/PermissionCard.tsx`) -
|
|
47
|
+
one or more priming screens injected into the server-driven card stream at a position you choose.
|
|
48
|
+
The screen carries customizable rationale copy, kit-quality defaults for notifications, optional
|
|
49
|
+
artwork through the existing illustrations registry, and an `onResult` callback per screen.
|
|
50
|
+
**DEPENDENCY-FREE, the RevenueCat-bridge idiom:** the kit imports no `expo-notifications`, no
|
|
51
|
+
`react-native-permissions`, nothing native. The host injects `request` and optionally `getStatus`
|
|
52
|
+
and `openSettings`, exactly the way it hands the RevenueCat bridge real RevenueCat objects. The
|
|
53
|
+
README shows the five-line wiring.
|
|
54
|
+
- **The priming rule, enforced in code and pinned by a canary.** There is no path from mount, from
|
|
55
|
+
an effect, from a timer or from render to the host's `request`; only the primary press handler
|
|
56
|
+
reaches it. The optional `getStatus` probe is a READ that never prompts, and all it decides is
|
|
57
|
+
which primary the screen offers: ask, open settings (status `blocked`, where an ask would show
|
|
58
|
+
nothing at all), or a plain continue (already granted, or a host that supplied no `request`, where
|
|
59
|
+
the kit declines rather than fabricating a button that does nothing).
|
|
60
|
+
- **A watchdog on the host's `request`, which fabricates nothing.** Asking sets a busy state that
|
|
61
|
+
disables both controls, which is right while an OS dialog may be open and fatal if the host's
|
|
62
|
+
`request` never settles at all (a swallowed native callback, a promise nobody resolves): the
|
|
63
|
+
screen would be a dead end with nothing tappable, and completion could never fire. A generous 90
|
|
64
|
+
second ceiling now hands the controls back so the user can retry or skip. On expiry it records NO
|
|
65
|
+
outcome and emits NO event, only a dev warning, because a pending request is not a denial and a
|
|
66
|
+
person can legitimately sit on a permission dialog for minutes. First settlement wins: if the
|
|
67
|
+
original request answers late, after the controls were handed back and possibly after a second
|
|
68
|
+
attempt already resolved, it advances nothing and emits nothing.
|
|
69
|
+
- **Placement semantics with a clamp** (`DEFAULT_PERMISSION_PLACEMENT`, `selectDuePermissionScreen`)
|
|
70
|
+
- `"start"`, `{ afterCard: n }` and `"beforeEnd"` (the default), resolved against the card about
|
|
71
|
+
to render, because the stream length is server-driven and varies per user. An `afterCard` the flow
|
|
72
|
+
never reaches degrades to `"beforeEnd"` instead of silently never showing, which is the failure
|
|
73
|
+
mode that raises no error anywhere and leaves the host looking at a funnel that reads zero.
|
|
74
|
+
- **`WIRE_PERMISSION_EVENTS`** plus `permissionEventName`, `permissionEventProps` and
|
|
75
|
+
`normalizePermissionStatus` (`permissions/permissionEvents.ts`) - the canonical funnel names
|
|
76
|
+
(`wire_permission_screen_shown`, `wire_permission_primer_accepted`, `wire_permission_granted`,
|
|
77
|
+
`wire_permission_denied`, `wire_permission_skipped`, `wire_permission_settings_opened`), mirroring
|
|
78
|
+
`WIRE_PURCHASE_EVENTS`. They land as `app_event` `question_key` values on the same `/v1/events`
|
|
79
|
+
path, carrying the same device snapshot and `user_context`, so the `device_key` join that makes
|
|
80
|
+
every other number real covers these too. Each moment is also surfaced on `onEvent` as
|
|
81
|
+
`{ type: "permission", ... }`.
|
|
82
|
+
- **`resolvePermissionCopy`, `NOTIFICATIONS_PERMISSION_COPY`, `GENERIC_PERMISSION_COPY`,
|
|
83
|
+
`DEFAULT_PERMISSION_COPY`** - the shipped English plus per-line overrides. An override that is
|
|
84
|
+
`undefined` or empty is IGNORED rather than allowed to blank a button, because hosts build this
|
|
85
|
+
object from an i18n layer where a missing translation resolves to `undefined`.
|
|
86
|
+
- **Once per session, kept true across an app KILL** (`permissionStorageKey`,
|
|
87
|
+
`loadSettledPermissions`, `saveSettledPermissions`, `readSettledPermissions`,
|
|
88
|
+
`clearSettledPermissions`). The kit resumes a killed onboarding into the SAME backend session, so
|
|
89
|
+
without a persisted record the resumed mount would re-show a screen the user already answered,
|
|
90
|
+
which on the ask path means a second attempt at the one prompt iOS grants. The record is scoped to
|
|
91
|
+
the session id, one entry, so a genuinely new onboarding still starts clean with nothing having to
|
|
92
|
+
expire it. Same host-injected `storage`, same read ceiling, same best-effort discipline: a
|
|
93
|
+
rejecting adapter costs a re-ask on resume, never a broken or gated flow.
|
|
94
|
+
- **`PermissionCard` and `PERMISSION_CARD_NAME`** - the screen registered as an SDK component, so a
|
|
95
|
+
later server-emitted placement (AI-chosen timing) adopts this exact screen with no rewrite.
|
|
96
|
+
Deliberately NOT part of `onboardingComponents`: that array is what the device advertises as
|
|
97
|
+
renderable, and a backend told it may emit a permission screen could emit one into a host that
|
|
98
|
+
injected no `request`.
|
|
99
|
+
- **A `notifications` entry in `defaultIllustrations`** so a notification screen is never a blank
|
|
100
|
+
box with no host wiring. Register your own under the same name to override it.
|
|
101
|
+
|
|
102
|
+
### Changed
|
|
103
|
+
|
|
104
|
+
- **`OnboardingEvent` gains a `permission` variant** and `toAnalyticsEvent` maps it to its own
|
|
105
|
+
canonical `wire_permission_*` name rather than folding it into an onboarding one. Additive: a host
|
|
106
|
+
that ignores the new variant is unaffected, and every existing variant maps exactly as before.
|
|
107
|
+
- **A permission screen is NOT a question, structurally.** It sends nothing to the backend, appends
|
|
108
|
+
nothing to the thread, mints no `key` and no `slot_id`, and never touches the progress step, so
|
|
109
|
+
`deriveAnswers`, `readProgress` and the completion semantics cannot tell it happened. Completion
|
|
110
|
+
never blocks on a grant: grant, deny, skip and blocked all continue the flow, and a host callback
|
|
111
|
+
that throws (a native module blowing up, a failed scheduling call) is caught rather than allowed
|
|
112
|
+
to strand the user on the screen.
|
|
113
|
+
- **Notification SCHEDULING is deliberately out of scope.** `onResult` is the seam: schedule your
|
|
114
|
+
first local reminder there, with your own call, the moment a grant lands.
|
|
115
|
+
|
|
116
|
+
## [0.13.0] — 2026-07-27
|
|
117
|
+
|
|
118
|
+
The identity-provenance release. Every id-layer defect fixed here is one omission wearing five faces:
|
|
119
|
+
`session_id` and `device_key` are bare `string`s minted independently by four subsystems, and nothing
|
|
120
|
+
anywhere recorded WHERE a given id came from. So a rejecting storage adapter's in-memory id was
|
|
121
|
+
indistinguishable from a persisted one, an app-OPEN session id could be posted into a field meaning the
|
|
122
|
+
ONBOARDING session, an auto-minted key could be injected beside a device id the host demonstrably owned
|
|
123
|
+
on another surface, and the join key could change identity mid-mount — each of them silently, each of
|
|
124
|
+
them returning success. This release gives ids provenance and teaches every consumer to refuse a
|
|
125
|
+
fabricated one.
|
|
126
|
+
|
|
127
|
+
Nothing here changes the wire for a correctly-wired host. The behaviour changes all move in one
|
|
128
|
+
direction: the kit now declines and says so, where it used to fabricate and return `true`.
|
|
129
|
+
|
|
130
|
+
### Added
|
|
131
|
+
|
|
132
|
+
- **`resolveIdentity(...)`, `hostIdentity(...)` and the `IdentityRecord` type** (`identity/identityRecord.ts`) —
|
|
133
|
+
the substrate. One record, `{ value, space, source, durable }`, parked on the same `Symbol.for`
|
|
134
|
+
registry pattern the repo already uses for the current session id, the auto device key and activation
|
|
135
|
+
revalidation, so every inlined bundle copy addresses ONE registry. `space` is
|
|
136
|
+
`onboarding-session | app-session | device`, `source` is `host | auto`, and `durable` says whether the
|
|
137
|
+
value was actually persisted. A host-sourced record is recorded on a process-wide registry, which is
|
|
138
|
+
what lets one surface notice that another surface owns a device key it was not given.
|
|
139
|
+
⚠️ Deliberately NOT a branded-types refactor — `OnboardingSessionId` / `AppSessionId` / `DeviceKey`
|
|
140
|
+
across every signature is real value and is deferred, because it touches every file.
|
|
141
|
+
- **`hydrateDeviceIdentity(...)`** (`context/deviceId.ts`) — the provenance-carrying sibling of
|
|
142
|
+
`hydrateAutoDeviceKey`. Same awaited read, but it answers "is this an id this install will KEEP?"
|
|
143
|
+
instead of only "what is the id?". `hydrateAutoDeviceKey` is unchanged and still returns a bare string.
|
|
144
|
+
- **`OnboardingProgress.slot_id`** — an optional STABLE per-slot identity for a screen, read off
|
|
145
|
+
`props.progress` by `readProgress` and preferred over `key` by `deriveAnswers`. `key` is authored from
|
|
146
|
+
the question's prompt text (tenant flows slugify it and cut at 32 chars), so re-wording a question mints
|
|
147
|
+
a NEW key and a host reading `answers.interests` silently starts reading `undefined`.
|
|
148
|
+
⚠️ ~~**INERT UNTIL THE SERVER EMITS IT.** No deployed server sends `slot_id` today.~~
|
|
149
|
+
✅ **CORRECTED 2026-07-28: this is LIVE.** The deployed server (`47dae92`) sends `slot_id` on
|
|
150
|
+
`progress` for every AI-generated question, as `adaptive_<n>`, and for a configured question
|
|
151
|
+
whenever the tenant set one. Where no slot is configured the field is absent and
|
|
152
|
+
every path degrades to byte-identical 0.12.2 behaviour. Every fallback is decided
|
|
153
|
+
PER CARD, never cached as a per-session "this backend supports slots" verdict, so a thread that mixes
|
|
154
|
+
slotted and unslotted cards — the real shape during a rollout — keys each card correctly.
|
|
155
|
+
- **`allowAppSessionFallback` on `identifyOnboarding`** — the opt-in switch for the tier-3 behaviour
|
|
156
|
+
described under Changed. Default `false`.
|
|
157
|
+
- **`resetEventQueueKeys()`** (`@wireai/activation/analytics`, test-only) — clears
|
|
158
|
+
the claimed-queue-key registry. A real relaunch is a new process; a test that simulates one in-process
|
|
159
|
+
needs this or its second queue reads as a concurrent sibling.
|
|
160
|
+
- **A first test file for `deriveAnswers`** (`utils/deriveAnswers.test.ts`), table-driven over real thread
|
|
161
|
+
shapes: progress key present and absent, slugified prose, multi-select, every NL wrapper the SDK
|
|
162
|
+
produces, and the threads that must produce NO entry.
|
|
163
|
+
|
|
164
|
+
### Changed
|
|
165
|
+
|
|
166
|
+
- **⚠️ BEHAVIOR CHANGE: a `storage` adapter that FAILS no longer earns an injected join key.** The
|
|
167
|
+
auto-join gate was a bare presence check on the `storage` prop — never the SUCCESS of the write — and
|
|
168
|
+
`hydrateAutoDeviceKey` resolves to the in-memory mint on every failure branch, so a REJECTING adapter
|
|
169
|
+
(a locked / full / permission-denied AsyncStorage, the most common real breakage) injected a FRESH
|
|
170
|
+
`wdev_*` on every launch. Probe-verified: two launches, two different keys. That is strictly worse than
|
|
171
|
+
injecting nothing — the server counts `min_sessions` by distinct opens grouped on `device_key`, so a
|
|
172
|
+
per-launch key makes that counter structurally incapable of exceeding 1 AND inflates distinct-device
|
|
173
|
+
counts. The gate now reads `IdentityRecord.durable`, true only when the id was adopted from storage or
|
|
174
|
+
written to it successfully, so a broken adapter lands on the same "declined, and here is why" path as
|
|
175
|
+
no adapter at all. The existing dev warning was widened to name this third reason.
|
|
176
|
+
Who this changes: a host whose storage was silently broken. It was already getting corrupt data; it now
|
|
177
|
+
gets no join key and a warning that names the cause.
|
|
178
|
+
- **⚠️ BEHAVIOR CHANGE: `identifyOnboarding` no longer binds to the app-open session by default, and its
|
|
179
|
+
return type widened to `"onboarding" | "app_session" | false`.** Tier 3 fell back to
|
|
180
|
+
`getCurrentSessionId()` — the per-OPEN session — and posted it in the `session_id` field, which on this
|
|
181
|
+
endpoint means the ONBOARDING session, then returned `true`. Two disjoint id spaces share that field, so
|
|
182
|
+
the row it wrote could never join the onboarding funnel while the host was told it had worked. It fired
|
|
183
|
+
in exactly the DOCUMENTED post-completion case, because completion clears the persisted session. The
|
|
184
|
+
fallback is now behind `allowAppSessionFallback` and, when it does fire, says so in the return value.
|
|
185
|
+
⚠️ `"onboarding"` is truthy, so `if (await identifyOnboarding(...))` behaves identically at runtime;
|
|
186
|
+
only an explicit `: boolean` annotation needs updating.
|
|
187
|
+
- **⚠️ BEHAVIOR CHANGE: `wire.track()` returns `false` for an event the server DISCARDED.**
|
|
188
|
+
`POST /v1/events` answers HTTP 200 with `{ ok, written, skipped }` and counts a refused event in
|
|
189
|
+
`skipped`, so `reportClientEventsAwait` — which read `res.ok` alone — resolved `true` for an event that
|
|
190
|
+
was thrown away, and `wire.track` then bumped decision revalidation, making every subscribed gate
|
|
191
|
+
re-fetch against a stream the action never entered. It now reads the ack. Backward compatible by
|
|
192
|
+
construction: ONLY an explicit positive `skipped` demotes a 200, so an old server that sends no such
|
|
193
|
+
field, an unparseable body, or a response with no `.json` at all still resolves `true` — the change can
|
|
194
|
+
produce no false negatives.
|
|
195
|
+
- **The join decision is FROZEN at the moment the loader gate opens.** `userContext` feeds the `llm` memo
|
|
196
|
+
and wireai-rn recreates its A2A adapter whenever that identity changes, resetting `contextId`. So a host
|
|
197
|
+
resolving its device key ASYNCHRONOUSLY — the default shape of any host reading its id out of async
|
|
198
|
+
storage — rendered once without it, got `wdev_*` injected, then had the real key land a tick later and
|
|
199
|
+
rebuild the adapter, DROPPING the server-learned session id and RESTARTING the user's onboarding
|
|
200
|
+
mid-flow with an orphaned session left behind. The session-start metadata now reads a frozen snapshot,
|
|
201
|
+
exactly as `startupUserIdRef` already did for the user id. Client events deliberately keep reading the
|
|
202
|
+
LIVE context: they carry no adapter, so late host context enriches them at no risk.
|
|
203
|
+
- **A host device key on ANOTHER surface is no longer silent.** 0.12.2 suppressed the missing-join-key
|
|
204
|
+
warning whenever injection succeeded — right when the host genuinely owns no device id, wrong when it
|
|
205
|
+
owns one and forgot it on this mount. That host went from a loud warning to a silent THIRD id space. Any
|
|
206
|
+
surface constructed with a host-supplied device key (`createAnalytics`, `createWireActivation`,
|
|
207
|
+
`useLifecycleEvents`, `<WireOnboarding userContext={...}>`) now records it, scoped per `appId`, and a mount
|
|
208
|
+
that injected its own key while the process demonstrably owns a host one warns and prints both. It warns
|
|
209
|
+
rather than declining, because declining would leave the onboarding side with no key at all; it does not
|
|
210
|
+
silently ADOPT the other key, because that is the kit guessing which of two ids a host meant on the
|
|
211
|
+
strength of construction order.
|
|
212
|
+
- **Two event queues for one `appId` no longer share one storage slot.** The default key was derived from
|
|
213
|
+
`appId` alone, so two `createAnalytics` instances for one tenant — the documented double-wiring — shared
|
|
214
|
+
ONE persisted backlog while keeping SEPARATE in-memory buffers: each `persist()` overwrote the other's
|
|
215
|
+
blob, a queue draining to empty called `removeItem` and DELETED a sibling's still-pending events, and on
|
|
216
|
+
relaunch the survivors were loaded by both and sent twice. The second claimant of a default key now gets
|
|
217
|
+
`…#2` and a dev warning naming the fix. Only the appId-derived DEFAULT is ever rotated — an EXPLICIT
|
|
218
|
+
`storageKey` is a host declaring which slot it owns (`useLifecycleEvents` does exactly that and
|
|
219
|
+
re-creates its queue on every remount, where rotation would be a worse bug). Only claimed when there IS
|
|
220
|
+
storage; a storage-less queue never touches the key.
|
|
221
|
+
- **A failed device-key hydration is retried instead of cached for the process lifetime.** A transient
|
|
222
|
+
cold-boot storage lock used to be permanent: the settled failure stayed parked on the registry and every
|
|
223
|
+
later caller joined it. A degraded outcome now releases its latches so the next caller starts a fresh read.
|
|
224
|
+
- **The DISCARDED warning covers all FOUR send paths and stops naming a cause the kit never checked.** It
|
|
225
|
+
used to end "the usual cause is an event with a missing or empty `session_id`" — but since
|
|
226
|
+
`ensureCurrentSessionId` shipped, no kit path can emit an event without one, making that the LEAST likely
|
|
227
|
+
explanation and sending every reader to the one place the problem is not. It now reports what the server
|
|
228
|
+
actually said (`skipped`, `written`, and any per-event `reasons` once the server sends them) and admits
|
|
229
|
+
when the endpoint gave no reason. The two `reportClientEvent*` paths — which carry `dropped`, `identify`,
|
|
230
|
+
`client_fallback` and every `wire.track` — join the offline queue and session-start in consuming the ack.
|
|
231
|
+
- **`validators` are looked up by `slot_id` first, then `progress.key`.** Host validators live in the SAME
|
|
232
|
+
key namespace `deriveAnswers` files answers under, so moving the answer key to the slot without moving
|
|
233
|
+
this lookup would have silently UNBOUND every host validator: no error, no warning, values simply stop
|
|
234
|
+
being checked. The fallback keeps a validator map written against today's keys working while a server
|
|
235
|
+
rolls slots out.
|
|
236
|
+
|
|
237
|
+
### Removed
|
|
238
|
+
|
|
239
|
+
- **`DoneBlock`** — self-`@deprecated` since 0.1.x, unused internally since the terminal screen became
|
|
240
|
+
`CompletionView`, and referenced by nothing in `src/` or the playground. The component file is deleted.
|
|
241
|
+
- **`RESERVED_USER_CONTEXT_KEYS`** — read by nothing, including its own module: the namespacing it claims
|
|
242
|
+
to govern is enforced by `EXTRA_KEY_PREFIX`. It was a documentation constant shaped like an invariant.
|
|
243
|
+
|
|
244
|
+
### Internal
|
|
245
|
+
|
|
246
|
+
- The FOUR duplicate `warnInDev` copies (`WireOnboarding`, `config/wireConfigFromEnv`,
|
|
247
|
+
`analytics/analyticsFacade`, `analytics/currentSession`) collapse into `utils/warnInDev`, which returns
|
|
248
|
+
whether it ACTUALLY warned — the property `currentSession`'s once-flag depends on, and the only reason
|
|
249
|
+
its copy differed. The stated rationale for that copy (keeping the module import-free for the
|
|
250
|
+
tree-shaken analytics bundle) had already lapsed: it imports `makeSessionId` from a sibling. The new
|
|
251
|
+
module has no imports of its own, and the `treeShake` canary still holds the real guarantee.
|
|
252
|
+
- The two byte-identical trim helpers (`context/userContext.cleanString`, `activation/wireActivation.clean`)
|
|
253
|
+
collapse into one shared `cleanString`, not re-exported from the barrel.
|
|
254
|
+
- **The canary suite can observe a payload for the first time.** Every `onComplete` mock was typed with
|
|
255
|
+
ZERO parameters, so `OnboardingResult` was structurally unobservable and every assertion could only be a
|
|
256
|
+
call count — the mechanism behind "500 green tests, zero data". `flow.test.tsx` now captures the argument
|
|
257
|
+
and asserts the answers map end to end. The scripted `wireai-rn` mock also appends the USER turn to the
|
|
258
|
+
thread, as the real SDK does; without it the mocked thread was assistant-only, so `deriveAnswers` had
|
|
259
|
+
nothing to pair and `answers` was always `{}` under test regardless of the implementation.
|
|
260
|
+
- `mountCanary` gained `rerender()`, the seam for a prop that arrives LATE. The churn loop runs entirely
|
|
261
|
+
inside the initial `act`, so without it there was no way to re-render a mount after an async gate had
|
|
262
|
+
settled — and the whole "value changed mid-flow" defect class was untestable.
|
|
263
|
+
|
|
6
264
|
## [0.12.2] — 2026-07-27
|
|
7
265
|
|
|
8
266
|
The auto-join patch. 0.12.1 taught the kit to complain about a missing join key; this one teaches it
|
|
@@ -135,7 +393,7 @@ or a client-side default.
|
|
|
135
393
|
|
|
136
394
|
## [0.12.0] — 2026-07-27
|
|
137
395
|
|
|
138
|
-
The release
|
|
396
|
+
The release the first production consumer pins. Ships the RevenueCat path (#52), the identity/counting audit,
|
|
139
397
|
the session-id fallback contract, and the transport/config fixes behind a silently dead lifecycle
|
|
140
398
|
stream (a consumer's `first_open` read 6 all-time while the emitting code was deployed).
|
|
141
399
|
|
package/README.md
CHANGED
|
@@ -234,7 +234,8 @@ import { WireOnboarding } from "@wireai/activation";
|
|
|
234
234
|
| `onComplete` | `(result: { answers, raw }) => void` | Fires when the user taps the terminal recap's CTA. **Required.** |
|
|
235
235
|
| `theme` | `Partial<OnboardingTheme>` | Brand colors/fonts/radius/spacing, deep-merged over a neutral default. |
|
|
236
236
|
| `illustrations` | `Record<string, ReactNode>` | App artwork for `InterstitialCard`, keyed by name. |
|
|
237
|
-
| `validators` | `Record<string, StepValidator>` | Per-step, keyed by base-question key (e.g. `username`). Blocks advance + shows an inline error. |
|
|
237
|
+
| `validators` | `Record<string, StepValidator>` | Per-step, keyed by base-question key (e.g. `username`). Blocks advance + shows an inline error. When the backend sends a `progress.slot_id` for a screen, the kit looks up that slot FIRST and falls back to the question key, so a validator map written against today's keys keeps working. |
|
|
238
|
+
| `permissionScreens` | `PermissionScreenConfig[]` | Priming screens injected mid-flow, starting with notifications. The screen explains why, and the OS dialog opens **only** on the primary tap, never on mount. Zero new dependencies: you inject `request`. See [Permission screens](#permission-screens-the-priming-pattern). |
|
|
238
239
|
| `onSkip` | `() => void` | User skipped. |
|
|
239
240
|
| `onError` | `(err) => void` | Backend error/timeout — host owns recovery (e.g. route to a static flow). Without it, the kit shows an inline retry. |
|
|
240
241
|
| `onEvent` | `(e: OnboardingEvent) => void` | `started` / `turn` / `error` — recover per-turn analytics since the kit owns the loop. |
|
|
@@ -247,7 +248,7 @@ import { WireOnboarding } from "@wireai/activation";
|
|
|
247
248
|
| `sessionTtlMs` | `number` | How long a persisted session id stays resumable. Default `3600000` (1h, the backend's session TTL). With `storage` only. |
|
|
248
249
|
| `persistKey` | `string` | Override the storage key (default `wireai:session:<appId>`). Scope per-user if one device can onboard multiple accounts mid-flow. With `storage` only. |
|
|
249
250
|
| `userContext` | `Record<string, string \| number \| boolean>` | **This is where the join key goes.** `user_context.device_key` is the only thing that joins this onboarding session to the app's later events, so pass `userContext={activationJoinContext(deviceKey)}` (or `activationJoinContext(resolveAutoDeviceKey({ appId, storage }))` if your app owns no device id). Since 0.12.2, omitting it no longer empties the funnel silently: with `storage`, the kit injects its own key (see [The join key](#the-join-key-device_key-never-session_id) and `autoJoinKey` below). Second job: any other non-PII context the app already knows (signup method, referral, plan, a HASHED user id), forwarded on session metadata + client events for funnel segmentation. **No PII on THIS prop** (no raw emails/names); primitives only; the server caps size/keys. See [Device & user context](#device--user-context). ⚠️ Distinct from the analytics/activation **`WireUserContext`** object (`createAnalytics` / `useWireActivation`), which additionally accepts an opt-in raw `userEmail` — see [Rich user context & PII](#rich-user-context--pii). |
|
|
250
|
-
| `autoJoinKey` | `boolean` | Opt OUT of the automatic join key. Default `true`. When this prop is left alone, `storage` is present, and `userContext` carries no `device_key`, the kit injects its own per-install key (the same id `createAnalytics` / `createWireActivation` mint and persist), so the `activated` funnel joins with no host wiring. Pass `autoJoinKey={false}` if you genuinely want an UNLINKED onboarding session: that restores the pre-0.12.2 behavior exactly, and the dev warning fires again. It never overrides a `device_key` you passed, and it cannot inject without `storage` (see [The join key](#the-join-key-device_key-never-session_id)). |
|
|
251
|
+
| `autoJoinKey` | `boolean` | Opt OUT of the automatic join key. Default `true`. When this prop is left alone, `storage` is present **and actually persists** (0.13.0: a `storage` adapter that rejects or throws is treated exactly like no `storage` — the kit declines and says so, because a key it cannot persist is a different key on every launch, which corrupts `min_sessions` rather than merely leaving the join empty), and `userContext` carries no `device_key`, the kit injects its own per-install key (the same id `createAnalytics` / `createWireActivation` mint and persist), so the `activated` funnel joins with no host wiring. Pass `autoJoinKey={false}` if you genuinely want an UNLINKED onboarding session: that restores the pre-0.12.2 behavior exactly, and the dev warning fires again. It never overrides a `device_key` you passed, and it cannot inject without `storage` (see [The join key](#the-join-key-device_key-never-session_id)). |
|
|
251
252
|
| `userId` | `string` | Your own OPAQUE user id, so onboarding sessions reconcile to real users later (console sessions to your user table / GA4 users). Optional and supports **late binding**: present at mount it rides the session-start metadata; if it changes mid-session (the user just registered) the kit emits an `identify` event; available only after the flow, use `identifyOnboarding(...)`. **No PII** (not an email/name/phone); trimmed and capped at 128 chars. See [User identity](#user-identity). |
|
|
252
253
|
|
|
253
254
|
## Helpers (the reusable substrate)
|
|
@@ -340,7 +341,9 @@ const contextIdRef = useRef<string>();
|
|
|
340
341
|
await identifyOnboarding({ config, userId: newUser.id, contextId: contextIdRef.current });
|
|
341
342
|
```
|
|
342
343
|
|
|
343
|
-
If you passed `storage` to `<WireOnboarding>`, you can omit `contextId` and hand `identifyOnboarding` the same `storage` (plus `appId` or `persistKey`) and it recovers the persisted session id itself. That works while the session is still persisted (before completion clears it), so the captured-`contextId` path above is the safe one post-completion. `identifyOnboarding` is fire-and-forget and never throws
|
|
344
|
+
If you passed `storage` to `<WireOnboarding>`, you can omit `contextId` and hand `identifyOnboarding` the same `storage` (plus `appId` or `persistKey`) and it recovers the persisted session id itself. That works while the session is still persisted (before completion clears it), so the captured-`contextId` path above is the safe one post-completion. `identifyOnboarding` is fire-and-forget and never throws. Since 0.13.0 it resolves the id SPACE it bound rather than a bare boolean: `"onboarding"` when it attached to the A2A `contextId` (the one that attributes the funnel), `"app_session"` when you opted into `allowAppSessionFallback` and it fell back to the live per-open app session, and `false` when it could not bind at all (no user id, no server url, or no resolvable session). `"onboarding"` is truthy, so an existing `if (await identifyOnboarding(...))` behaves identically.
|
|
345
|
+
|
|
346
|
+
⚠️ **The app-session fallback is now opt-in and off by default.** Before 0.13.0 it ran unconditionally: with no captured `contextId` it posted the per-OPEN session id in the `session_id` field — which on this endpoint means the ONBOARDING session — and returned `true`. Those are two different id spaces sharing one wire field, so the row it wrote could never join the onboarding funnel while the caller was told it had worked.
|
|
344
347
|
|
|
345
348
|
**No PII.** The onboarding `userId` prop is length-bound only; the kit cannot detect an email for you. Keep it opaque, the same rule as the onboarding `userContext` prop.
|
|
346
349
|
|
|
@@ -606,6 +609,87 @@ icons={{
|
|
|
606
609
|
Icons are decorative: the label carries the meaning, so an icon stays hidden from screen readers
|
|
607
610
|
and never becomes an option's accessible name.
|
|
608
611
|
|
|
612
|
+
## Permission screens (the priming pattern)
|
|
613
|
+
|
|
614
|
+
Onboarding is where apps ask for notifications, and it is where most of them lose the ask. iOS gives an app **one** native notification prompt for its entire lifetime. Fire it from a mount effect on screen one and you have spent it on a user who has been told nothing, and the only route back is a trip through the Settings app that almost nobody makes.
|
|
615
|
+
|
|
616
|
+
So the kit ships the priming pattern. You inject a screen into the flow, it explains why in your words, and the OS dialog opens **only** when the user taps the primary button. "Maybe later" advances the flow with the prompt still unspent, so you can ask again in a better moment.
|
|
617
|
+
|
|
618
|
+
```tsx
|
|
619
|
+
import * as Notifications from "expo-notifications";
|
|
620
|
+
import { WireOnboarding } from "@wireai/activation";
|
|
621
|
+
|
|
622
|
+
<WireOnboarding
|
|
623
|
+
config={config}
|
|
624
|
+
onComplete={persist}
|
|
625
|
+
permissionScreens={[
|
|
626
|
+
{
|
|
627
|
+
permission: "notifications",
|
|
628
|
+
placement: "beforeEnd",
|
|
629
|
+
request: async () => {
|
|
630
|
+
const { status, canAskAgain } = await Notifications.requestPermissionsAsync();
|
|
631
|
+
return status === "granted" ? "granted" : canAskAgain ? "denied" : "blocked";
|
|
632
|
+
},
|
|
633
|
+
getStatus: async () => {
|
|
634
|
+
const { status, canAskAgain } = await Notifications.getPermissionsAsync();
|
|
635
|
+
return status === "granted" ? "granted" : canAskAgain ? "denied" : "blocked";
|
|
636
|
+
},
|
|
637
|
+
onResult: (_permission, outcome) => {
|
|
638
|
+
if (outcome === "granted") scheduleFirstReminder();
|
|
639
|
+
},
|
|
640
|
+
},
|
|
641
|
+
]}
|
|
642
|
+
/>
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
That is the whole integration, and **the kit adds no dependency for it**. It imports no `expo-notifications`, no `react-native-permissions`, nothing native, the same way the RevenueCat bridge takes real RevenueCat objects without depending on `react-native-purchases`. You own the native call; the kit owns the screen, the timing, and the funnel.
|
|
646
|
+
|
|
647
|
+
### Where the screen lands
|
|
648
|
+
|
|
649
|
+
The stream is server-driven, so its length changes per user. A placement is therefore resolved against the card about to render, not against a fixed index:
|
|
650
|
+
|
|
651
|
+
| `placement` | Where it shows |
|
|
652
|
+
|---|---|
|
|
653
|
+
| `"beforeEnd"` (default) | Right before the terminal recap, once the user has invested in the flow. |
|
|
654
|
+
| `"start"` | Before the first question. |
|
|
655
|
+
| `{ afterCard: 2 }` | After two questions, so between card 2 and card 3. |
|
|
656
|
+
|
|
657
|
+
An `afterCard` the flow never reaches **clamps to `"beforeEnd"`** instead of silently never showing. That matters more than it sounds: a screen that never appears raises no error anywhere, and the only evidence is a permission funnel that reads zero forever.
|
|
658
|
+
|
|
659
|
+
### What it guarantees
|
|
660
|
+
|
|
661
|
+
- **The dialog is only ever reached from the primary tap.** No mount effect, no timer, no auto-fire. `getStatus` is a read and never prompts; it only decides which primary the screen offers.
|
|
662
|
+
- **Already blocked?** The primary becomes "Open settings", because an `ask` there would open nothing at all.
|
|
663
|
+
- **Once per session, and it survives an app kill.** With `storage`, a resumed session does not re-ask (the record is keyed to the session id, so a genuinely new onboarding still starts clean).
|
|
664
|
+
- **Completion never blocks on a grant.** Grant, deny, skip and blocked all continue the flow.
|
|
665
|
+
- **It is not a question.** Nothing is sent to the backend, nothing enters the thread, no `key` or `slot_id` is minted, and `onComplete`'s `answers` are identical to the same flow with no screen configured.
|
|
666
|
+
|
|
667
|
+
### Copy and artwork
|
|
668
|
+
|
|
669
|
+
The kit ships English good enough to ship. Override any single line and the rest of the default stays:
|
|
670
|
+
|
|
671
|
+
```tsx
|
|
672
|
+
copy: { title: "Never miss a session", primaryLabel: "Turn on reminders" }
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
Artwork comes from the same `illustrations` registry the cards use, keyed by the permission name, so `illustrations={{ notifications: <MyBell /> }}` is enough. The kit's dependency-free default is used when you register nothing.
|
|
676
|
+
|
|
677
|
+
### The numbers
|
|
678
|
+
|
|
679
|
+
Each screen reports through the same `/v1/events` path and the same `device_key` join as the rest of the funnel, and every moment is also surfaced on `onEvent` as `{ type: "permission", ... }`:
|
|
680
|
+
|
|
681
|
+
| Event | Fires when |
|
|
682
|
+
|---|---|
|
|
683
|
+
| `wire_permission_screen_shown` | The primer became visible. The denominator. |
|
|
684
|
+
| `wire_permission_primer_accepted` | The primary was tapped, so the OS dialog is about to open. |
|
|
685
|
+
| `wire_permission_granted` / `wire_permission_denied` | What the OS answered (a permanently blocked answer reports as denied with `status: "blocked"`). |
|
|
686
|
+
| `wire_permission_skipped` | "Maybe later". The one native prompt was not spent. |
|
|
687
|
+
| `wire_permission_settings_opened` | A blocked user was sent to Settings. |
|
|
688
|
+
|
|
689
|
+
The gap between `wire_permission_screen_shown` and `wire_permission_primer_accepted` is the number worth watching: it tells you whether your rationale copy works, and it costs nothing to be wrong about, because a user who does not tap has not burned anything.
|
|
690
|
+
|
|
691
|
+
**Scheduling notifications is deliberately out of scope.** `onResult` is the seam: schedule your first local reminder there, with your own `expo-notifications` call, the moment a grant lands.
|
|
692
|
+
|
|
609
693
|
## Backend coupling
|
|
610
694
|
|
|
611
695
|
The backend (`wire-rn/examples/dynamic-onboarding/server`) is the source of truth
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { R as ReportAppEventOptions, r as reportAppEvent } from '../transport-CF_eHwzC.mjs';
|
|
2
|
-
import { W as WireUserContext, E as EventQueueOptions } from '../currentSession-
|
|
3
|
-
export { A as AUTO_DEVICE_ID_PREFIX, a as AnalyticsEvent, C as ClearUserContextOptions, b as ClientEvent, c as ClientEventTarget, d as ClientEventType, e as ContextEnvelope, f as ContextEnvelopeInput, D as DeviceKeyStorage, g as EnvelopeSource, h as EventQueue, R as ResolveAutoDeviceKeyOptions, i as WIRE_ONBOARDING_EVENTS, j as WireOnboardingEventName, k as analyticsUserIdStorageKey, l as buildContextEnvelope, m as clearPiiFromContext, n as clearUserContext, o as createEventQueue, p as deviceIdStorageKey, q as ensureCurrentSessionId, r as getCurrentSessionId, s as looksLikeEmail, t as makeSessionId, u as reportClientEvent, v as reportClientEventAwait, w as reportClientEvents, x as reportClientEventsAwait, y as resetAutoDeviceKeys, z as resetCurrentSessionId, B as
|
|
2
|
+
import { W as WireUserContext, E as EventQueueOptions } from '../currentSession-ClkLjcJ0.mjs';
|
|
3
|
+
export { A as AUTO_DEVICE_ID_PREFIX, a as AnalyticsEvent, C as ClearUserContextOptions, b as ClientEvent, c as ClientEventTarget, d as ClientEventType, e as ContextEnvelope, f as ContextEnvelopeInput, D as DeviceKeyStorage, g as EnvelopeSource, h as EventQueue, R as ResolveAutoDeviceKeyOptions, i as WIRE_ONBOARDING_EVENTS, j as WireOnboardingEventName, k as analyticsUserIdStorageKey, l as buildContextEnvelope, m as clearPiiFromContext, n as clearUserContext, o as createEventQueue, p as deviceIdStorageKey, q as ensureCurrentSessionId, r as getCurrentSessionId, s as looksLikeEmail, t as makeSessionId, u as reportClientEvent, v as reportClientEventAwait, w as reportClientEvents, x as reportClientEventsAwait, y as resetAutoDeviceKeys, z as resetCurrentSessionId, B as resetEventQueueKeys, F as resolveAutoDeviceKey, G as setCurrentSessionId, H as toAnalyticsEvent } from '../currentSession-ClkLjcJ0.mjs';
|
|
4
4
|
import '../types-CNUqMK0D.mjs';
|
|
5
5
|
import '../types-BKfpdZzX.mjs';
|
|
6
6
|
import '../types-BcmagF6K.mjs';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { R as ReportAppEventOptions, r as reportAppEvent } from '../transport-DsRe4epC.js';
|
|
2
|
-
import { W as WireUserContext, E as EventQueueOptions } from '../currentSession-
|
|
3
|
-
export { A as AUTO_DEVICE_ID_PREFIX, a as AnalyticsEvent, C as ClearUserContextOptions, b as ClientEvent, c as ClientEventTarget, d as ClientEventType, e as ContextEnvelope, f as ContextEnvelopeInput, D as DeviceKeyStorage, g as EnvelopeSource, h as EventQueue, R as ResolveAutoDeviceKeyOptions, i as WIRE_ONBOARDING_EVENTS, j as WireOnboardingEventName, k as analyticsUserIdStorageKey, l as buildContextEnvelope, m as clearPiiFromContext, n as clearUserContext, o as createEventQueue, p as deviceIdStorageKey, q as ensureCurrentSessionId, r as getCurrentSessionId, s as looksLikeEmail, t as makeSessionId, u as reportClientEvent, v as reportClientEventAwait, w as reportClientEvents, x as reportClientEventsAwait, y as resetAutoDeviceKeys, z as resetCurrentSessionId, B as
|
|
2
|
+
import { W as WireUserContext, E as EventQueueOptions } from '../currentSession-DOVZEWJl.js';
|
|
3
|
+
export { A as AUTO_DEVICE_ID_PREFIX, a as AnalyticsEvent, C as ClearUserContextOptions, b as ClientEvent, c as ClientEventTarget, d as ClientEventType, e as ContextEnvelope, f as ContextEnvelopeInput, D as DeviceKeyStorage, g as EnvelopeSource, h as EventQueue, R as ResolveAutoDeviceKeyOptions, i as WIRE_ONBOARDING_EVENTS, j as WireOnboardingEventName, k as analyticsUserIdStorageKey, l as buildContextEnvelope, m as clearPiiFromContext, n as clearUserContext, o as createEventQueue, p as deviceIdStorageKey, q as ensureCurrentSessionId, r as getCurrentSessionId, s as looksLikeEmail, t as makeSessionId, u as reportClientEvent, v as reportClientEventAwait, w as reportClientEvents, x as reportClientEventsAwait, y as resetAutoDeviceKeys, z as resetCurrentSessionId, B as resetEventQueueKeys, F as resolveAutoDeviceKey, G as setCurrentSessionId, H as toAnalyticsEvent } from '../currentSession-DOVZEWJl.js';
|
|
4
4
|
import '../types-Buj9Lw9t.js';
|
|
5
5
|
import '../types-BKfpdZzX.js';
|
|
6
6
|
import '../types-BcmagF6K.js';
|