@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
package/AGENTS.md
CHANGED
|
@@ -78,7 +78,7 @@ There is no `analytics` subpath: the app-event / analytics API is exported from
|
|
|
78
78
|
| `serverUrl` | `string` | yes | Base server URL; the kit appends `/a2a`. |
|
|
79
79
|
| `appId` | `string` | yes | Passed as the A2A `model` (informational; the key resolves the app). |
|
|
80
80
|
| `metadata` | `Record<string, unknown>` | no | Merged into every A2A request (e.g. install attribution). The kit reserves `sessionId` + `supportedComponents`; do not override them. |
|
|
81
|
-
| `appVersion` | `string` | no |
|
|
81
|
+
| `appVersion` | `string` | no | App version string (e.g. `"1.4.2"`); forwarded to the backend + client events so analytics can segment the funnel by app version. Auto-detected best-effort from `expo-constants`/`expo-application` (dependency-free) when omitted; pass it to override (yours wins). |
|
|
82
82
|
|
|
83
83
|
## App events / analytics (root export)
|
|
84
84
|
|
package/CHANGELOG.md
CHANGED
|
@@ -20,6 +20,63 @@ Historical entries below the rename keep the old package name on purpose.
|
|
|
20
20
|
|
|
21
21
|
## [0.7.0] — Unreleased
|
|
22
22
|
|
|
23
|
+
### Added: `WireUserContext` — one rich user-context object → every event's `user_context`
|
|
24
|
+
|
|
25
|
+
- New `context/userContext.ts` + public export `WireUserContext`: a single, extensible object a host
|
|
26
|
+
passes ONCE (`appVersion?`, `deviceKey?`, `userId?`, `userEmail?` + `hashEmail?`, `extra?`) that the
|
|
27
|
+
kit flows into every analytics event. Pure `resolveUserContext(ctx, { autoAppVersion })` merges it
|
|
28
|
+
with ONE precedence rule — an explicit field WINS over the auto-detected `device`/`appVersion` —
|
|
29
|
+
and omits missing fields (never sent empty). Also exported: `namespaceExtra`, `hashEmailFnv1a`,
|
|
30
|
+
`isWireScalar`, `RESERVED_USER_CONTEXT_KEYS`, `EXTRA_KEY_PREFIX`.
|
|
31
|
+
- Deliberate bucket separation (nothing leaks): `userId` → the event's TOP-LEVEL opaque `user_id`
|
|
32
|
+
(via `sanitizeUserId`, capped 128) and NEVER the `user_context` bucket; `deviceKey` →
|
|
33
|
+
`user_context.device_key` (NOT `session_id`); `appVersion` → `user_context.app_version`; `extra`
|
|
34
|
+
entries are coerced to `string | number | boolean` (non-scalars/NaN/Infinity dropped) and
|
|
35
|
+
NAMESPACED under a `custom.` key prefix so a host extra can never collide with a reserved key.
|
|
36
|
+
- **`userEmail` is OPT-IN PII in its OWN field** (`user_context.user_email`) — never merged into
|
|
37
|
+
`userId`. The kit NEVER auto-collects it; a host passes it only with the user's consent (EU users:
|
|
38
|
+
personal data). Set `hashEmail: true` to send a dependency-free FNV-1a fold instead of the raw
|
|
39
|
+
address (`user_context.user_email_hashed: true`). NOTE: FNV-1a is a lightweight non-cryptographic
|
|
40
|
+
fold; for a cryptographic digest, pre-hash host-side (e.g. `expo-crypto` SHA-256) and pass that as
|
|
41
|
+
`userEmail` with `hashEmail` falsy. Documented in the field's TSDoc.
|
|
42
|
+
- The analytics façade (`createAnalytics`) now accepts `userContext` at init AND exposes
|
|
43
|
+
`setUserContext(partial)` so a host can attach `userId`/`userEmail` at LOGIN without remounting;
|
|
44
|
+
both flow into subsequent `track`/`screen`/`identify` events' `user_context`. Backward compatible:
|
|
45
|
+
`createAnalytics({ serverUrl, apiKey })` with no `userContext` behaves exactly as before.
|
|
46
|
+
|
|
47
|
+
### Fixed: `identify` + host app-events reuse the live per-open session (kills the phantom-session)
|
|
48
|
+
|
|
49
|
+
- New `analytics/currentSession.ts` (`getCurrentSessionId` / `setCurrentSessionId` /
|
|
50
|
+
`resetCurrentSessionId`): a process-local registry of the CURRENT per-open `session_id`.
|
|
51
|
+
`reportSessionStart` registers each per-open id here on every app-open.
|
|
52
|
+
- The façade's `identify` (and `track`/`screen` app-events) now source their `session_id` from
|
|
53
|
+
`config.sessionId ?? getCurrentSessionId() ?? <stable per-instance id>` — so identity binds to the
|
|
54
|
+
session the server ALREADY saw (via `app.session_started`) instead of minting a fresh id the server
|
|
55
|
+
back-fills into a synthetic session. This is what inflated the Morrow/Myelino session counts. An
|
|
56
|
+
explicit `config.sessionId` still freezes the id (opt-out). This is also the kit half of the #205
|
|
57
|
+
"align all app-events to the per-open session" follow-up. **No server change required** for the
|
|
58
|
+
normal flow (session-start fires on mount, identify at login); the only residual is an `identify`
|
|
59
|
+
fired before any open this launch, which falls back to the instance id.
|
|
60
|
+
- `identifyOnboarding` gains a last-resort fallback to `getCurrentSessionId()` when no `contextId`
|
|
61
|
+
is captured or resolvable from storage, so a post-flow identify binds to the live open session
|
|
62
|
+
instead of no-oping. An explicit `contextId` (or a stored one) still wins.
|
|
63
|
+
|
|
64
|
+
### Added: `device.appVersion` auto-detection — populate the `by_app_version` breakdown by default
|
|
65
|
+
|
|
66
|
+
- `collectDeviceContext()` now carries a best-effort `appVersion` so the analytics
|
|
67
|
+
`by_app_version` funnel breakdown works out of the box, instead of staying empty until a host
|
|
68
|
+
remembers to pass `config.appVersion`. New `device/appVersion.ts` reads the version from
|
|
69
|
+
`expo-constants` (`expoConfig.version`, then `nativeAppVersion`) or `expo-application`
|
|
70
|
+
(`nativeApplicationVersion`) when present.
|
|
71
|
+
- ADDS NO DEPENDENCY: the Expo modules are read through a guarded, variable-specifier `require`,
|
|
72
|
+
so a host WITHOUT them (e.g. bare React Native) never fails to bundle — the read simply yields
|
|
73
|
+
`undefined`. Nothing is added to `package.json`; no peer is forced.
|
|
74
|
+
- An explicit host `config.appVersion` still WINS everywhere (the `WireOnboarding` device memo, the
|
|
75
|
+
`useSessionStart` / `useLifecycleEvents` hooks, and the analytics context envelope overwrite the
|
|
76
|
+
auto-detected value with the host's). The context envelope also mirrors the effective version
|
|
77
|
+
onto its outer `appVersion` scalar so `user_context.app_version` populates on facade events too.
|
|
78
|
+
- New public export `detectAppVersion()` (alongside `collectDeviceContext`). Never throws.
|
|
79
|
+
|
|
23
80
|
### Added: session mapping — know when a user opens the app again
|
|
24
81
|
|
|
25
82
|
- New `session-analytics/` module. `reportSessionStart(opts)` posts ONE standard
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { R as ReportAppEventOptions, r as reportAppEvent } from '../transport-BeO_Brcu.mjs';
|
|
2
|
-
import { E as EventQueueOptions } from '../
|
|
3
|
-
export { A as AnalyticsEvent, C as ClientEvent, a as ClientEventTarget, b as ClientEventType, c as ContextEnvelope, d as ContextEnvelopeInput, e as EnvelopeSource, f as EventQueue,
|
|
2
|
+
import { W as WireUserContext, E as EventQueueOptions } from '../currentSession-BJBB7i4-.mjs';
|
|
3
|
+
export { A as AnalyticsEvent, C as ClientEvent, a as ClientEventTarget, b as ClientEventType, c as ContextEnvelope, d as ContextEnvelopeInput, e as EnvelopeSource, f as EventQueue, g as WIRE_ONBOARDING_EVENTS, h as WireOnboardingEventName, i as buildContextEnvelope, j as createEventQueue, k as getCurrentSessionId, m as makeSessionId, r as reportClientEvent, l as reportClientEvents, n as resetCurrentSessionId, s as setCurrentSessionId, t as toAnalyticsEvent } from '../currentSession-BJBB7i4-.mjs';
|
|
4
4
|
import '../types-A6pTxIZV.mjs';
|
|
5
5
|
import '../types-BKfpdZzX.mjs';
|
|
6
6
|
import '../types-GL_hQ0TN.mjs';
|
|
@@ -120,6 +120,13 @@ type CreateAnalyticsConfig = {
|
|
|
120
120
|
appBuild?: string;
|
|
121
121
|
/** Host connectivity signal, e.g. "wifi" | "cellular" — read fresh per event via the provider. */
|
|
122
122
|
networkType?: string;
|
|
123
|
+
/**
|
|
124
|
+
* The rich {@link WireUserContext} to stamp onto every event's `user_context` (device key, opaque
|
|
125
|
+
* user id, opt-in email, arbitrary `extra`). Passed ONCE here at init; updatable post-mount via
|
|
126
|
+
* {@link Analytics.setUserContext} (e.g. attach `userId`/`userEmail` at login). Optional — omit it
|
|
127
|
+
* and events carry only the device context, exactly as before.
|
|
128
|
+
*/
|
|
129
|
+
userContext?: WireUserContext;
|
|
123
130
|
};
|
|
124
131
|
/** Optional queue tuning knobs, forwarded verbatim to {@link createEventQueue}. */
|
|
125
132
|
type AnalyticsOptions = Partial<Pick<EventQueueOptions, "maxSize" | "batchSize" | "baseBackoffMs" | "maxBackoffMs" | "maxRetries">>;
|
|
@@ -135,6 +142,12 @@ type Analytics = {
|
|
|
135
142
|
screen(name: string, props?: AnalyticsProps): void;
|
|
136
143
|
/** Bind the host's opaque user id (per-session, in-memory) and emit an `identify` event. */
|
|
137
144
|
identify(userId: string, traits?: AnalyticsProps): void;
|
|
145
|
+
/**
|
|
146
|
+
* Update the {@link WireUserContext} after init (e.g. attach `userId`/`userEmail` at login). Shallow
|
|
147
|
+
* merges the partial over the current context (`extra` is deep-merged); a supplied `userId` also
|
|
148
|
+
* binds like {@link identify}. Takes effect on subsequent events. Fire-and-forget.
|
|
149
|
+
*/
|
|
150
|
+
setUserContext(partial: Partial<WireUserContext>): void;
|
|
138
151
|
/** Attempt an immediate drain of the pending buffer. Fire-and-forget. */
|
|
139
152
|
flush(): void;
|
|
140
153
|
/** Host reconnect signal: reset backoff and drain now. Fire-and-forget. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { R as ReportAppEventOptions, r as reportAppEvent } from '../transport-DLpd1v5_.js';
|
|
2
|
-
import { E as EventQueueOptions } from '../
|
|
3
|
-
export { A as AnalyticsEvent, C as ClientEvent, a as ClientEventTarget, b as ClientEventType, c as ContextEnvelope, d as ContextEnvelopeInput, e as EnvelopeSource, f as EventQueue,
|
|
2
|
+
import { W as WireUserContext, E as EventQueueOptions } from '../currentSession-CxnP7gAa.js';
|
|
3
|
+
export { A as AnalyticsEvent, C as ClientEvent, a as ClientEventTarget, b as ClientEventType, c as ContextEnvelope, d as ContextEnvelopeInput, e as EnvelopeSource, f as EventQueue, g as WIRE_ONBOARDING_EVENTS, h as WireOnboardingEventName, i as buildContextEnvelope, j as createEventQueue, k as getCurrentSessionId, m as makeSessionId, r as reportClientEvent, l as reportClientEvents, n as resetCurrentSessionId, s as setCurrentSessionId, t as toAnalyticsEvent } from '../currentSession-CxnP7gAa.js';
|
|
4
4
|
import '../types-BhpXJGlg.js';
|
|
5
5
|
import '../types-BKfpdZzX.js';
|
|
6
6
|
import '../types-GL_hQ0TN.js';
|
|
@@ -120,6 +120,13 @@ type CreateAnalyticsConfig = {
|
|
|
120
120
|
appBuild?: string;
|
|
121
121
|
/** Host connectivity signal, e.g. "wifi" | "cellular" — read fresh per event via the provider. */
|
|
122
122
|
networkType?: string;
|
|
123
|
+
/**
|
|
124
|
+
* The rich {@link WireUserContext} to stamp onto every event's `user_context` (device key, opaque
|
|
125
|
+
* user id, opt-in email, arbitrary `extra`). Passed ONCE here at init; updatable post-mount via
|
|
126
|
+
* {@link Analytics.setUserContext} (e.g. attach `userId`/`userEmail` at login). Optional — omit it
|
|
127
|
+
* and events carry only the device context, exactly as before.
|
|
128
|
+
*/
|
|
129
|
+
userContext?: WireUserContext;
|
|
123
130
|
};
|
|
124
131
|
/** Optional queue tuning knobs, forwarded verbatim to {@link createEventQueue}. */
|
|
125
132
|
type AnalyticsOptions = Partial<Pick<EventQueueOptions, "maxSize" | "batchSize" | "baseBackoffMs" | "maxBackoffMs" | "maxRetries">>;
|
|
@@ -135,6 +142,12 @@ type Analytics = {
|
|
|
135
142
|
screen(name: string, props?: AnalyticsProps): void;
|
|
136
143
|
/** Bind the host's opaque user id (per-session, in-memory) and emit an `identify` event. */
|
|
137
144
|
identify(userId: string, traits?: AnalyticsProps): void;
|
|
145
|
+
/**
|
|
146
|
+
* Update the {@link WireUserContext} after init (e.g. attach `userId`/`userEmail` at login). Shallow
|
|
147
|
+
* merges the partial over the current context (`extra` is deep-merged); a supplied `userId` also
|
|
148
|
+
* binds like {@link identify}. Takes effect on subsequent events. Fire-and-forget.
|
|
149
|
+
*/
|
|
150
|
+
setUserContext(partial: Partial<WireUserContext>): void;
|
|
138
151
|
/** Attempt an immediate drain of the pending buffer. Fire-and-forget. */
|
|
139
152
|
flush(): void;
|
|
140
153
|
/** Host reconnect signal: reset backoff and drain now. Fire-and-forget. */
|
package/dist/analytics/index.js
CHANGED
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
var react = require('react');
|
|
4
4
|
var reactNative = require('react-native');
|
|
5
5
|
|
|
6
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
7
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
8
|
+
}) : x)(function(x) {
|
|
9
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
10
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
11
|
+
});
|
|
12
|
+
|
|
6
13
|
// src/reviews/transport.ts
|
|
7
14
|
var reportAppEvent = (target, name, options = {}) => {
|
|
8
15
|
if (!(target == null ? void 0 : target.serverUrl) || !name) return;
|
|
@@ -157,6 +164,57 @@ var toAnalyticsEvent = (event) => {
|
|
|
157
164
|
}
|
|
158
165
|
}
|
|
159
166
|
};
|
|
167
|
+
|
|
168
|
+
// src/device/appVersion.ts
|
|
169
|
+
var coerceVersion = (value) => {
|
|
170
|
+
if (typeof value !== "string") return void 0;
|
|
171
|
+
const trimmed = value.trim();
|
|
172
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
173
|
+
};
|
|
174
|
+
var runtimeRequire = (moduleName) => {
|
|
175
|
+
try {
|
|
176
|
+
if (typeof __require !== "function") return void 0;
|
|
177
|
+
return __require(moduleName);
|
|
178
|
+
} catch {
|
|
179
|
+
return void 0;
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
var interop = (mod) => {
|
|
183
|
+
if (!mod || typeof mod !== "object") return void 0;
|
|
184
|
+
const def = mod.default;
|
|
185
|
+
if (def && typeof def === "object") return def;
|
|
186
|
+
return mod;
|
|
187
|
+
};
|
|
188
|
+
var safeInterop = (requireModule, moduleName) => {
|
|
189
|
+
try {
|
|
190
|
+
return interop(requireModule(moduleName));
|
|
191
|
+
} catch {
|
|
192
|
+
return void 0;
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
var detectAppVersion = (requireModule = runtimeRequire) => {
|
|
196
|
+
try {
|
|
197
|
+
const constants = safeInterop(requireModule, "expo-constants");
|
|
198
|
+
if (constants) {
|
|
199
|
+
const expoConfig = constants.expoConfig;
|
|
200
|
+
if (expoConfig && typeof expoConfig === "object") {
|
|
201
|
+
const fromExpoConfig = coerceVersion(expoConfig.version);
|
|
202
|
+
if (fromExpoConfig) return fromExpoConfig;
|
|
203
|
+
}
|
|
204
|
+
const fromNative = coerceVersion(constants.nativeAppVersion);
|
|
205
|
+
if (fromNative) return fromNative;
|
|
206
|
+
}
|
|
207
|
+
const application = safeInterop(requireModule, "expo-application");
|
|
208
|
+
if (application) {
|
|
209
|
+
const fromApplication = coerceVersion(application.nativeApplicationVersion);
|
|
210
|
+
if (fromApplication) return fromApplication;
|
|
211
|
+
}
|
|
212
|
+
} catch {
|
|
213
|
+
}
|
|
214
|
+
return void 0;
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// src/device/deviceContext.ts
|
|
160
218
|
var deriveFormFactor = (iosIdiom, width, height) => {
|
|
161
219
|
if (iosIdiom === "pad") return "tablet";
|
|
162
220
|
if (iosIdiom === "phone") return "phone";
|
|
@@ -226,16 +284,20 @@ var collectDeviceContext = () => {
|
|
|
226
284
|
if (resolved.timeZone) ctx.timeZone = resolved.timeZone;
|
|
227
285
|
} catch {
|
|
228
286
|
}
|
|
287
|
+
const appVersion = detectAppVersion();
|
|
288
|
+
if (appVersion) ctx.appVersion = appVersion;
|
|
229
289
|
return ctx;
|
|
230
290
|
};
|
|
231
291
|
|
|
232
292
|
// src/analytics/contextEnvelope.ts
|
|
233
293
|
var buildContextEnvelope = (input = {}) => {
|
|
294
|
+
var _a;
|
|
234
295
|
const device = { ...collectDeviceContext() };
|
|
235
|
-
if (input.appVersion
|
|
296
|
+
if (input.appVersion) device.appVersion = input.appVersion;
|
|
297
|
+
const effectiveAppVersion = (_a = input.appVersion) != null ? _a : device.appVersion;
|
|
236
298
|
const envelope = { device };
|
|
237
299
|
if (input.sessionId) envelope.sessionId = input.sessionId;
|
|
238
|
-
if (
|
|
300
|
+
if (effectiveAppVersion) envelope.appVersion = effectiveAppVersion;
|
|
239
301
|
if (input.appBuild) envelope.appBuild = input.appBuild;
|
|
240
302
|
if (input.networkType) envelope.networkType = input.networkType;
|
|
241
303
|
return envelope;
|
|
@@ -449,6 +511,16 @@ var createEventQueue = (options) => {
|
|
|
449
511
|
return { enqueue, flush, notifyOnline, size };
|
|
450
512
|
};
|
|
451
513
|
|
|
514
|
+
// src/analytics/currentSession.ts
|
|
515
|
+
var _currentSessionId;
|
|
516
|
+
var setCurrentSessionId = (id) => {
|
|
517
|
+
if (typeof id === "string" && id.length > 0) _currentSessionId = id;
|
|
518
|
+
};
|
|
519
|
+
var getCurrentSessionId = () => _currentSessionId;
|
|
520
|
+
var resetCurrentSessionId = () => {
|
|
521
|
+
_currentSessionId = void 0;
|
|
522
|
+
};
|
|
523
|
+
|
|
452
524
|
// src/identity/userIdentity.ts
|
|
453
525
|
var USER_ID_MAX_LENGTH = 128;
|
|
454
526
|
var sanitizeUserId = (raw) => {
|
|
@@ -458,12 +530,79 @@ var sanitizeUserId = (raw) => {
|
|
|
458
530
|
return trimmed.length > USER_ID_MAX_LENGTH ? trimmed.slice(0, USER_ID_MAX_LENGTH) : trimmed;
|
|
459
531
|
};
|
|
460
532
|
|
|
533
|
+
// src/context/userContext.ts
|
|
534
|
+
var EXTRA_KEY_PREFIX = "custom.";
|
|
535
|
+
var isWireScalar = (value) => {
|
|
536
|
+
const t = typeof value;
|
|
537
|
+
if (t === "string" || t === "boolean") return true;
|
|
538
|
+
if (t === "number") return Number.isFinite(value);
|
|
539
|
+
return false;
|
|
540
|
+
};
|
|
541
|
+
var hashEmailFnv1a = (email) => {
|
|
542
|
+
const normalized = email.trim().toLowerCase();
|
|
543
|
+
let hash = 2166136261;
|
|
544
|
+
for (let i = 0; i < normalized.length; i++) {
|
|
545
|
+
hash ^= normalized.charCodeAt(i);
|
|
546
|
+
hash = Math.imul(hash, 16777619);
|
|
547
|
+
}
|
|
548
|
+
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
549
|
+
};
|
|
550
|
+
var cleanString = (value) => {
|
|
551
|
+
if (typeof value !== "string") return void 0;
|
|
552
|
+
const trimmed = value.trim();
|
|
553
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
554
|
+
};
|
|
555
|
+
var namespaceExtra = (extra) => {
|
|
556
|
+
const out = {};
|
|
557
|
+
if (!extra || typeof extra !== "object") return out;
|
|
558
|
+
for (const [key, value] of Object.entries(extra)) {
|
|
559
|
+
const cleanKey = cleanString(key);
|
|
560
|
+
if (!cleanKey) continue;
|
|
561
|
+
if (!isWireScalar(value)) continue;
|
|
562
|
+
out[`${EXTRA_KEY_PREFIX}${cleanKey}`] = value;
|
|
563
|
+
}
|
|
564
|
+
return out;
|
|
565
|
+
};
|
|
566
|
+
var resolveUserContext = (ctx = {}, opts = {}) => {
|
|
567
|
+
var _a;
|
|
568
|
+
const result = {};
|
|
569
|
+
const bucket = {};
|
|
570
|
+
const userId = sanitizeUserId(ctx.userId);
|
|
571
|
+
if (userId) result.userId = userId;
|
|
572
|
+
const deviceKey = cleanString(ctx.deviceKey);
|
|
573
|
+
if (deviceKey) {
|
|
574
|
+
result.deviceKey = deviceKey;
|
|
575
|
+
bucket.device_key = deviceKey;
|
|
576
|
+
}
|
|
577
|
+
const appVersion = (_a = cleanString(ctx.appVersion)) != null ? _a : cleanString(opts.autoAppVersion);
|
|
578
|
+
if (appVersion) {
|
|
579
|
+
result.appVersion = appVersion;
|
|
580
|
+
bucket.app_version = appVersion;
|
|
581
|
+
}
|
|
582
|
+
const email = cleanString(ctx.userEmail);
|
|
583
|
+
if (email) {
|
|
584
|
+
if (ctx.hashEmail) {
|
|
585
|
+
bucket.user_email = hashEmailFnv1a(email);
|
|
586
|
+
bucket.user_email_hashed = true;
|
|
587
|
+
} else {
|
|
588
|
+
bucket.user_email = email;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
Object.assign(bucket, namespaceExtra(ctx.extra));
|
|
592
|
+
if (Object.keys(bucket).length > 0) result.userContext = bucket;
|
|
593
|
+
return result;
|
|
594
|
+
};
|
|
595
|
+
|
|
461
596
|
// src/analytics/analyticsFacade.ts
|
|
462
597
|
var createAnalytics = (config, options = {}) => {
|
|
463
|
-
var _a, _b;
|
|
464
|
-
const
|
|
598
|
+
var _a, _b, _c, _d;
|
|
599
|
+
const instanceSessionId = (_a = config.sessionId) != null ? _a : makeSessionId();
|
|
600
|
+
const resolveSessionId = () => {
|
|
601
|
+
var _a2, _b2;
|
|
602
|
+
return (_b2 = (_a2 = config.sessionId) != null ? _a2 : getCurrentSessionId()) != null ? _b2 : instanceSessionId;
|
|
603
|
+
};
|
|
465
604
|
const envelope = () => buildContextEnvelope({
|
|
466
|
-
sessionId,
|
|
605
|
+
sessionId: resolveSessionId(),
|
|
467
606
|
appVersion: config.appVersion,
|
|
468
607
|
appBuild: config.appBuild,
|
|
469
608
|
networkType: config.networkType
|
|
@@ -475,23 +614,45 @@ var createAnalytics = (config, options = {}) => {
|
|
|
475
614
|
envelope,
|
|
476
615
|
...options
|
|
477
616
|
});
|
|
478
|
-
let
|
|
479
|
-
|
|
617
|
+
let userContext = { ...(_b = config.userContext) != null ? _b : {} };
|
|
618
|
+
let boundUserId = sanitizeUserId((_c = config.userContext) == null ? void 0 : _c.userId);
|
|
619
|
+
const storageKey = `wireai:analytics:userId:${(_d = config.appId) != null ? _d : "default"}`;
|
|
480
620
|
if (config.storage) {
|
|
481
621
|
void config.storage.getItem(storageKey).then((saved) => {
|
|
482
|
-
if (saved) boundUserId = saved;
|
|
622
|
+
if (saved && !boundUserId) boundUserId = saved;
|
|
483
623
|
}).catch(() => {
|
|
484
624
|
});
|
|
485
625
|
}
|
|
626
|
+
const applyContext = (event) => {
|
|
627
|
+
var _a2;
|
|
628
|
+
const resolved = resolveUserContext(userContext, { autoAppVersion: config.appVersion });
|
|
629
|
+
if (resolved.userContext) {
|
|
630
|
+
event.user_context = { ...resolved.userContext, ...(_a2 = event.user_context) != null ? _a2 : {} };
|
|
631
|
+
}
|
|
632
|
+
if (boundUserId && !event.user_id) event.user_id = boundUserId;
|
|
633
|
+
};
|
|
634
|
+
const setUserContext = (partial) => {
|
|
635
|
+
var _a2, _b2;
|
|
636
|
+
if (!partial || typeof partial !== "object") return;
|
|
637
|
+
const mergedExtra = partial.extra || userContext.extra ? { ...(_a2 = userContext.extra) != null ? _a2 : {}, ...(_b2 = partial.extra) != null ? _b2 : {} } : void 0;
|
|
638
|
+
userContext = { ...userContext, ...partial };
|
|
639
|
+
if (mergedExtra) userContext.extra = mergedExtra;
|
|
640
|
+
const uid = sanitizeUserId(partial.userId);
|
|
641
|
+
if (uid) {
|
|
642
|
+
boundUserId = uid;
|
|
643
|
+
if (config.storage) void config.storage.setItem(storageKey, uid).catch(() => {
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
};
|
|
486
647
|
const track = (event, props) => {
|
|
487
648
|
if (!event) return;
|
|
488
649
|
const clientEvent = {
|
|
489
650
|
event_type: "app_event",
|
|
490
|
-
session_id:
|
|
651
|
+
session_id: resolveSessionId(),
|
|
491
652
|
question_key: event
|
|
492
653
|
};
|
|
493
654
|
if (props && Object.keys(props).length > 0) clientEvent.meta = JSON.stringify(props);
|
|
494
|
-
|
|
655
|
+
applyContext(clientEvent);
|
|
495
656
|
queue.enqueue(clientEvent);
|
|
496
657
|
};
|
|
497
658
|
const screen = (name, props) => {
|
|
@@ -499,11 +660,11 @@ var createAnalytics = (config, options = {}) => {
|
|
|
499
660
|
const meta = { screen: name, ...props != null ? props : {} };
|
|
500
661
|
const clientEvent = {
|
|
501
662
|
event_type: "app_event",
|
|
502
|
-
session_id:
|
|
663
|
+
session_id: resolveSessionId(),
|
|
503
664
|
question_key: "screen",
|
|
504
665
|
meta: JSON.stringify(meta)
|
|
505
666
|
};
|
|
506
|
-
|
|
667
|
+
applyContext(clientEvent);
|
|
507
668
|
queue.enqueue(clientEvent);
|
|
508
669
|
};
|
|
509
670
|
const identify = (userId, traits) => {
|
|
@@ -516,16 +677,20 @@ var createAnalytics = (config, options = {}) => {
|
|
|
516
677
|
}
|
|
517
678
|
const clientEvent = {
|
|
518
679
|
event_type: "identify",
|
|
519
|
-
|
|
680
|
+
// Reuse the LIVE per-open session id (see `resolveSessionId`) so the server binds identity to
|
|
681
|
+
// the session it already saw instead of back-filling a phantom `session_started`.
|
|
682
|
+
session_id: resolveSessionId(),
|
|
520
683
|
user_id: clean
|
|
521
684
|
};
|
|
522
685
|
if (traits && Object.keys(traits).length > 0) clientEvent.meta = JSON.stringify(traits);
|
|
686
|
+
applyContext(clientEvent);
|
|
523
687
|
queue.enqueue(clientEvent);
|
|
524
688
|
};
|
|
525
689
|
return {
|
|
526
690
|
track,
|
|
527
691
|
screen,
|
|
528
692
|
identify,
|
|
693
|
+
setUserContext,
|
|
529
694
|
flush: queue.flush,
|
|
530
695
|
notifyOnline: queue.notifyOnline,
|
|
531
696
|
size: queue.size
|
|
@@ -548,11 +713,14 @@ exports.createAnalytics = createAnalytics;
|
|
|
548
713
|
exports.createEventQueue = createEventQueue;
|
|
549
714
|
exports.createScreenTracker = createScreenTracker;
|
|
550
715
|
exports.getActiveRouteName = getActiveRouteName;
|
|
716
|
+
exports.getCurrentSessionId = getCurrentSessionId;
|
|
551
717
|
exports.makeSessionId = makeSessionId;
|
|
552
718
|
exports.reportAppEvent = reportAppEvent;
|
|
553
719
|
exports.reportClientEvent = reportClientEvent;
|
|
554
720
|
exports.reportClientEvents = reportClientEvents;
|
|
721
|
+
exports.resetCurrentSessionId = resetCurrentSessionId;
|
|
555
722
|
exports.screenTrackingHandler = screenTrackingHandler;
|
|
723
|
+
exports.setCurrentSessionId = setCurrentSessionId;
|
|
556
724
|
exports.toAnalyticsEvent = toAnalyticsEvent;
|
|
557
725
|
exports.useAnalytics = useAnalytics;
|
|
558
726
|
exports.useScreenTracking = useScreenTracking;
|