@wireai/activation 0.4.0 → 0.8.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/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import React__default, { ReactNode } from 'react';
3
- import { k as WireOnboardingProps, l as WireOnboardingConfig, O as OnboardingResult, S as StepValidator, n as OnboardingEvent, o as OnboardingCopy, a as ClientEventTarget, D as DeviceContext, C as ClientEvent, e as EnvelopeSource } from './eventQueue-CrNB9gzH.js';
4
- export { A as AnalyticsEvent, b as ClientEventType, p as DeviceFormFactor, q as OnboardingProgress, W as WIRE_ONBOARDING_EVENTS, g as WireOnboardingEventName, s as collectDeviceContext, m as makeSessionId, r as reportClientEvent, j as reportClientEvents, t as toAnalyticsEvent } from './eventQueue-CrNB9gzH.js';
3
+ import { o as WireOnboardingProps, p as WireOnboardingConfig, O as OnboardingResult, S as StepValidator, q as OnboardingEvent, u as OnboardingCopy, a as ClientEventTarget, D as DeviceContext, C as ClientEvent, e as EnvelopeSource } from './currentSession-f7LWcdWG.js';
4
+ export { A as AnalyticsEvent, b as ClientEventType, v as DeviceFormFactor, w as EXTRA_KEY_PREFIX, x as OnboardingProgress, R as RESERVED_USER_CONTEXT_KEYS, y as ResolveUserContextOptions, z as ResolvedUserContext, g as WIRE_ONBOARDING_EVENTS, h as WireOnboardingEventName, W as WireUserContext, B as collectDeviceContext, k as getCurrentSessionId, F as hashEmailFnv1a, G as isWireScalar, m as makeSessionId, H as namespaceExtra, r as reportClientEvent, l as reportClientEvents, n as resetCurrentSessionId, I as resolveUserContext, s as setCurrentSessionId, t as toAnalyticsEvent } from './currentSession-f7LWcdWG.js';
5
5
  import { O as OnboardingTheme } from './types-BKfpdZzX.js';
6
6
  export { a as OnboardingButtonStyle, b as OnboardingColors, c as OnboardingFonts, d as OnboardingRadius, e as OnboardingSpacing } from './types-BKfpdZzX.js';
7
7
  export { C as CenteredModal, a as CenteredModalHandle, b as CenteredModalProps } from './CenteredModal-Cdgns6--.js';
@@ -826,6 +826,73 @@ declare const attributionMetadata: (a: OnboardingAttribution) => {
826
826
  attribution: Record<string, unknown>;
827
827
  };
828
828
 
829
+ /**
830
+ * appVersion — best-effort, DEPENDENCY-FREE auto-detection of the host app's version string.
831
+ *
832
+ * WHY this exists: analytics segments the funnel `by_app_version`, but that breakdown is only
833
+ * populated when a `device.appVersion` rides the event. `config.appVersion` (see types.ts) has
834
+ * always been the way to supply it — but it is easy for a host to forget, and then the release
835
+ * breakdown is silently empty. This module fills that gap: when the host does NOT pass a version,
836
+ * the kit makes a best-effort read of the app version the host already ships in its Expo config,
837
+ * so the breakdown works out of the box. An explicit `config.appVersion` always WINS over this.
838
+ *
839
+ * WHY it adds NO dependency (the kit's hard rule): `expo-constants` / `expo-application` are read
840
+ * through a GUARDED, VARIABLE-specifier `require`. Passing a variable (not a string literal) keeps
841
+ * Metro/esbuild from statically resolving the module, so a host that does NOT have it installed
842
+ * (e.g. bare React Native) never fails to bundle — the require simply throws at runtime and is
843
+ * swallowed. Nothing is added to `package.json`; nothing is forced on the host.
844
+ *
845
+ * PRIVACY: an app version string is not PII and identifies no user or device, so surfacing it
846
+ * changes no App Privacy / Data Safety declaration (same guarantee as the rest of deviceContext).
847
+ *
848
+ * NEVER THROWS: every read is guarded; a missing/odd value yields `undefined`, never an exception.
849
+ * Analytics must never be able to break onboarding.
850
+ */
851
+ /** A `require`-like resolver. Injectable in tests; production uses the guarded runtime require. */
852
+ type OptionalRequire$1 = (moduleName: string) => unknown;
853
+ /**
854
+ * Detect the host app version, preferring `expo-constants` (`expoConfig.version`, then
855
+ * `nativeAppVersion`) and finally `expo-application` (`nativeApplicationVersion`). Returns the
856
+ * first real string, or `undefined` when none of those are available. Pure and never throws.
857
+ *
858
+ * `requireModule` is injectable so tests can exercise the "found" path without the native modules;
859
+ * production defaults to the guarded runtime require above.
860
+ */
861
+ declare const detectAppVersion: (requireModule?: OptionalRequire$1) => string | undefined;
862
+
863
+ /**
864
+ * deviceModel — best-effort, DEPENDENCY-FREE detection of the device MODEL on iOS.
865
+ *
866
+ * WHY this exists (the asymmetry it closes): `collectDeviceContext` already reports `device.model`
867
+ * on Android straight from `Platform.constants.Model` (e.g. "SM-G991B"), but iOS `Platform.constants`
868
+ * exposes NO model — only `osVersion` / `interfaceIdiom`. So the analytics `by_model` breakdown was
869
+ * Android-only. This module fills the iOS gap with the SAME zero-dependency technique the kit already
870
+ * uses for `appVersion` (see device/appVersion.ts): a GUARDED, VARIABLE-specifier `require` of the
871
+ * common Expo `expo-device` module. If the host has it, iOS gets a model out of the box; if not, the
872
+ * require simply throws and is swallowed and iOS `model` stays omitted — nothing is forced, nothing
873
+ * is added to `package.json`, and a host without `expo-device` never fails to bundle.
874
+ *
875
+ * WHY it is NOT PII: `expo-device`'s `modelName` ("iPhone 14 Pro") and `modelId` ("iPhone15,2") are a
876
+ * device CLASS shared by millions of units — the same privacy category as the Android `Model` the kit
877
+ * already sends. It is NOT a unique device id / IDFA / fingerprint, so surfacing it changes no App
878
+ * Privacy / Data Safety declaration (identical guarantee to the rest of deviceContext). It deliberately
879
+ * does NOT read `expo-device`'s `deviceName` (that is the user-set name, e.g. "Malik's iPhone", and IS
880
+ * personal data).
881
+ *
882
+ * NEVER THROWS: every read is guarded; a missing/odd value yields `undefined`, never an exception.
883
+ */
884
+ /** A `require`-like resolver. Injectable in tests; production uses the guarded runtime require. */
885
+ type OptionalRequire = (moduleName: string) => unknown;
886
+ /**
887
+ * Detect the device model via `expo-device`, preferring the human-readable `modelName`
888
+ * ("iPhone 14 Pro") and falling back to the identifier `modelId` ("iPhone15,2"). Returns the first
889
+ * real string, or `undefined` when `expo-device` is absent. Pure and never throws.
890
+ *
891
+ * `requireModule` is injectable so tests can exercise the "found" path without the native module;
892
+ * production defaults to the guarded runtime require above.
893
+ */
894
+ declare const detectNativeModel: (requireModule?: OptionalRequire) => string | undefined;
895
+
829
896
  /** Max accepted user-id length. Longer strings are truncated (never rejected). Keep in sync
830
897
  * with the server's `USER_ID_MAX_LENGTH` (analytics/events.py). */
831
898
  declare const USER_ID_MAX_LENGTH = 128;
@@ -873,38 +940,35 @@ type IdentifyOnboardingOptions = {
873
940
  declare const identifyOnboarding: (opts: IdentifyOnboardingOptions) => Promise<boolean>;
874
941
 
875
942
  /**
876
- * reportSessionStartthe ONE standard emitter for "the user opened the app again".
877
- *
878
- * Every app-open posts a single `app.session_started` event to the Wire analytics backend
879
- * (`POST {serverUrl}/v1/events`). That event does two jobs at once, with ZERO server changes:
880
- *
881
- * 1. SESSION MAPPING. It carries the host's opaque `userId` + a stable `deviceKey`, so the
882
- * backend can group a user's (or a pre-auth device's) opens over time — "when did this
883
- * user last use the app, and how many times". The device_key links the pre-auth opens to
884
- * the user once `userId` arrives (here or via `identifyOnboarding`).
885
- * 2. QUESTIONNAIRE / RETENTION FIRING. It flows into the SAME event stream the server's
886
- * decision engines read, so a questionnaire trigger `{event:"app.session_started", min_count:N}`
887
- * (matched within a session) or a questionnaire `min_sessions:N` (distinct device sessions)
888
- * fires on the user's Nth open with no new server endpoint.
943
+ * deviceIdmint a stable, NON-PII, per-install device id the kit owns when the host supplies
944
+ * none. This is the headline of "device fully automatic": the analytics façade auto-mints ONE id,
945
+ * persists it via the host's `storage` abstraction, and reuses it on every subsequent open — so
946
+ * `user_context.device_key` is ALWAYS present and the server's review/questionnaire gating +
947
+ * A/B stickiness (both key on `device_key`) work out of the box, with zero host wiring.
889
948
  *
890
- * WIRE CONTRACT (server: analytics/events.py + routers/onboarding.py `POST /v1/events`):
891
- * The event is stored as `event_type='app_event'`, `question_key='app.session_started'` — the
892
- * generic app.* namespace the reviews wave shipped. The server's `_event_name(ev)` returns the
893
- * `question_key` for an `app_event`, which is what a trigger's `event` string matches. We do NOT
894
- * send `event_type:'app.session_started'` that name is not in the server's EVENT_TYPES and
895
- * would be rejected. `device_key` rides in the non-PII `user_context` bucket, where the server's
896
- * `_event_device_key(ev)` reads it to group a device's sessions. `app_id` + `environment` are
897
- * filled server-side from the resolving key (never sent here).
949
+ * WHY it is NOT PII and adds NO dependency (the kit's hard rules):
950
+ * The id is a random token generated from `Date.now()` + `Math.random()` — it carries NO hardware
951
+ * identifier, NO IDFA/GAID, NO fingerprint. It is a first-party per-install correlation key, the
952
+ * same privacy category as a first-party cookie: it groups a single install's sessions and cannot
953
+ * identify a person or be joined across apps. There is NO `uuid` (or any) dependency — a
954
+ * time+random scheme is sufficient because the id is minted ONCE and then persisted, so global
955
+ * uniqueness across the fleet is not required (a per-install collision is astronomically unlikely
956
+ * and inconsequential worst case two installs share a bucket).
898
957
  *
899
- * SESSION ID DISTINCTION (important): the `session_id` on this event is a PER-OPEN id (a fresh
900
- * `makeSessionId()` each app-open) it is NOT the onboarding A2A `contextId`. Onboarding runs
901
- * ONCE (first launch) and owns its own context id; session-start fires on EVERY open, so it needs
902
- * its own per-open id. Grouping over time is done by `device_key`/`user_id`, not by session_id.
903
- *
904
- * Fire-and-forget: like every analytics path in the kit, this never throws into the UI, never
905
- * awaits, and swallows a missing target / bad URL / missing fetch / network error. Analytics must
906
- * never be able to break the app.
958
+ * A host that wants its OWN device id still wins: pass `WireUserContext.deviceKey` and the kit uses
959
+ * that verbatim and never mints/persists an auto id.
960
+ */
961
+ /** Prefix so an auto-minted id is visibly the kit's (distinguishable from a host-supplied `deviceKey`). */
962
+ declare const AUTO_DEVICE_ID_PREFIX = "wdev_";
963
+ /** The storage key the façade persists the auto-minted id under (namespaced per `appId`). */
964
+ declare const deviceIdStorageKey: (appId?: string) => string;
965
+ /**
966
+ * Mint a fresh per-install device id. Dependency-free (`Date.now()` + `Math.random()`), never
967
+ * throws, and returns a NEW value on every call — the façade mints ONCE and persists, so this is
968
+ * called at most once per install (then the persisted value is reused). Two random chunks plus the
969
+ * timestamp keep the token wide enough that a per-install collision is not a practical concern.
907
970
  */
971
+ declare const mintDeviceId: () => string;
908
972
 
909
973
  /** The canonical event name for an app-open. A trigger keys off this exact string. */
910
974
  declare const SESSION_STARTED_EVENT: "app.session_started";
@@ -1132,4 +1196,4 @@ interface UseLifecycleEventsOptions {
1132
1196
  */
1133
1197
  declare const useLifecycleEvents: (config: LifecycleConfig | undefined, options?: UseLifecycleEventsOptions) => void;
1134
1198
 
1135
- export { AnimatedSparkle, BACKGROUND_SESSION_MS, type CachedFeatures, CardHandoff, type CardHandoffProps, type CardHandoffVariant, ChipSelectCard, ClientEvent, ClientEventTarget, CompletionView, DEFAULT_FEATURES_TTL_MS, DemoOnboarding, type DemoOnboardingProps, DeviceContext, DoneBlock, ErrorBlock, FIRST_OPEN_EVENT, type IdentifyOnboardingOptions, IllustrationProvider, type IllustrationRegistry, InterstitialCard, type LifecycleConfig, type LifecycleEventInput, LoadingBlock, LoadingScreen, NumberStepperCard, type OnboardingAttribution, Button as OnboardingButton, OnboardingCopy, OnboardingEvent, type OnboardingFlagOptions, OnboardingFlow, OnboardingResult, OnboardingScaffold, OnboardingTheme, OnboardingThemeProvider, type ReportFirstOpenOptions, type ReportSessionStartOptions, type ResolveFeaturesOptions, SESSION_STARTED_EVENT, SelectionCard, type SessionStartConfig, StatusCard, StepProgress, StepValidator, TextInputCard, type ThemeFromBrandInput, USER_ID_MAX_LENGTH, type UseLifecycleEventsOptions, type UseSessionStartOptions, type WireConfigOverrides, WireFeatures, WireFeaturesConfig, WireFeaturesProvider, type WireFeaturesProviderProps, type WireLifecycleOptions, WireOnboarding, WireOnboardingConfig, WireOnboardingProps, WireOnboardingStorage, attributionMetadata, defaultIllustrations, defaultOnboardingTheme, defaultWireFeatures, deriveAnswers, featuresCacheKey, featuresEqual, fetchWireFeatures, firstOpenStorageKey, identifyOnboarding, isFeaturesFresh, isOnboardingEnabled, mergeTheme, motionSpec, onboardingComponents, parseWireFeatures, readCachedFeatures, readProgress, reportFirstOpen, reportSessionStart, resetFirstOpenLatch, resetSessionStartGuard, sanitizeUserId, themeFromBrand, useIllustration, useLifecycleEvents, useOnboardingTheme, useReducedMotion, useResolvedFeatures, useSessionStart, useWireFeatures, useWireFeaturesContext, wireConfigFromEnv, wireLifecycleEvents, writeCachedFeatures };
1199
+ export { AUTO_DEVICE_ID_PREFIX, AnimatedSparkle, BACKGROUND_SESSION_MS, type CachedFeatures, CardHandoff, type CardHandoffProps, type CardHandoffVariant, ChipSelectCard, ClientEvent, ClientEventTarget, CompletionView, DEFAULT_FEATURES_TTL_MS, DemoOnboarding, type DemoOnboardingProps, DeviceContext, DoneBlock, ErrorBlock, FIRST_OPEN_EVENT, type IdentifyOnboardingOptions, IllustrationProvider, type IllustrationRegistry, InterstitialCard, type LifecycleConfig, type LifecycleEventInput, LoadingBlock, LoadingScreen, NumberStepperCard, type OnboardingAttribution, Button as OnboardingButton, OnboardingCopy, OnboardingEvent, type OnboardingFlagOptions, OnboardingFlow, OnboardingResult, OnboardingScaffold, OnboardingTheme, OnboardingThemeProvider, type ReportFirstOpenOptions, type ReportSessionStartOptions, type ResolveFeaturesOptions, SESSION_STARTED_EVENT, SelectionCard, type SessionStartConfig, StatusCard, StepProgress, StepValidator, TextInputCard, type ThemeFromBrandInput, USER_ID_MAX_LENGTH, type UseLifecycleEventsOptions, type UseSessionStartOptions, type WireConfigOverrides, WireFeatures, WireFeaturesConfig, WireFeaturesProvider, type WireFeaturesProviderProps, type WireLifecycleOptions, WireOnboarding, WireOnboardingConfig, WireOnboardingProps, WireOnboardingStorage, attributionMetadata, defaultIllustrations, defaultOnboardingTheme, defaultWireFeatures, deriveAnswers, detectAppVersion, detectNativeModel, deviceIdStorageKey, featuresCacheKey, featuresEqual, fetchWireFeatures, firstOpenStorageKey, identifyOnboarding, isFeaturesFresh, isOnboardingEnabled, mergeTheme, mintDeviceId, motionSpec, onboardingComponents, parseWireFeatures, readCachedFeatures, readProgress, reportFirstOpen, reportSessionStart, resetFirstOpenLatch, resetSessionStartGuard, sanitizeUserId, themeFromBrand, useIllustration, useLifecycleEvents, useOnboardingTheme, useReducedMotion, useResolvedFeatures, useSessionStart, useWireFeatures, useWireFeaturesContext, wireConfigFromEnv, wireLifecycleEvents, writeCachedFeatures };
package/dist/index.js CHANGED
@@ -12,6 +12,12 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
12
  var React14__default = /*#__PURE__*/_interopDefault(React14);
13
13
 
14
14
  var __defProp = Object.defineProperty;
15
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
16
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
17
+ }) : x)(function(x) {
18
+ if (typeof require !== "undefined") return require.apply(this, arguments);
19
+ throw Error('Dynamic require of "' + x + '" is not supported');
20
+ });
15
21
  var __export = (target, all) => {
16
22
  for (var name in all)
17
23
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -2349,6 +2355,98 @@ var onboardingComponents = [
2349
2355
  NumberStepperCard,
2350
2356
  InterstitialCard
2351
2357
  ];
2358
+
2359
+ // src/device/appVersion.ts
2360
+ var coerceVersion = (value) => {
2361
+ if (typeof value !== "string") return void 0;
2362
+ const trimmed = value.trim();
2363
+ return trimmed.length > 0 ? trimmed : void 0;
2364
+ };
2365
+ var runtimeRequire = (moduleName) => {
2366
+ try {
2367
+ if (typeof __require !== "function") return void 0;
2368
+ return __require(moduleName);
2369
+ } catch {
2370
+ return void 0;
2371
+ }
2372
+ };
2373
+ var interop = (mod) => {
2374
+ if (!mod || typeof mod !== "object") return void 0;
2375
+ const def = mod.default;
2376
+ if (def && typeof def === "object") return def;
2377
+ return mod;
2378
+ };
2379
+ var safeInterop = (requireModule, moduleName) => {
2380
+ try {
2381
+ return interop(requireModule(moduleName));
2382
+ } catch {
2383
+ return void 0;
2384
+ }
2385
+ };
2386
+ var detectAppVersion = (requireModule = runtimeRequire) => {
2387
+ try {
2388
+ const constants = safeInterop(requireModule, "expo-constants");
2389
+ if (constants) {
2390
+ const expoConfig = constants.expoConfig;
2391
+ if (expoConfig && typeof expoConfig === "object") {
2392
+ const fromExpoConfig = coerceVersion(expoConfig.version);
2393
+ if (fromExpoConfig) return fromExpoConfig;
2394
+ }
2395
+ const fromNative = coerceVersion(constants.nativeAppVersion);
2396
+ if (fromNative) return fromNative;
2397
+ }
2398
+ const application = safeInterop(requireModule, "expo-application");
2399
+ if (application) {
2400
+ const fromApplication = coerceVersion(application.nativeApplicationVersion);
2401
+ if (fromApplication) return fromApplication;
2402
+ }
2403
+ } catch {
2404
+ }
2405
+ return void 0;
2406
+ };
2407
+
2408
+ // src/device/deviceModel.ts
2409
+ var coerceModel = (value) => {
2410
+ if (typeof value !== "string") return void 0;
2411
+ const trimmed = value.trim();
2412
+ return trimmed.length > 0 ? trimmed : void 0;
2413
+ };
2414
+ var runtimeRequire2 = (moduleName) => {
2415
+ try {
2416
+ if (typeof __require !== "function") return void 0;
2417
+ return __require(moduleName);
2418
+ } catch {
2419
+ return void 0;
2420
+ }
2421
+ };
2422
+ var interop2 = (mod) => {
2423
+ if (!mod || typeof mod !== "object") return void 0;
2424
+ const def = mod.default;
2425
+ if (def && typeof def === "object") return def;
2426
+ return mod;
2427
+ };
2428
+ var safeInterop2 = (requireModule, moduleName) => {
2429
+ try {
2430
+ return interop2(requireModule(moduleName));
2431
+ } catch {
2432
+ return void 0;
2433
+ }
2434
+ };
2435
+ var detectNativeModel = (requireModule = runtimeRequire2) => {
2436
+ try {
2437
+ const device = safeInterop2(requireModule, "expo-device");
2438
+ if (device) {
2439
+ const modelName = coerceModel(device.modelName);
2440
+ if (modelName) return modelName;
2441
+ const modelId = coerceModel(device.modelId);
2442
+ if (modelId) return modelId;
2443
+ }
2444
+ } catch {
2445
+ }
2446
+ return void 0;
2447
+ };
2448
+
2449
+ // src/device/deviceContext.ts
2352
2450
  var deriveFormFactor = (iosIdiom, width, height) => {
2353
2451
  if (iosIdiom === "pad") return "tablet";
2354
2452
  if (iosIdiom === "phone") return "phone";
@@ -2394,6 +2492,8 @@ var collectDeviceContext = () => {
2394
2492
  ctx.interfaceIdiom = idiom;
2395
2493
  iosIdiom = idiom;
2396
2494
  }
2495
+ const iosModel = detectNativeModel();
2496
+ if (iosModel) ctx.model = iosModel;
2397
2497
  }
2398
2498
  } catch {
2399
2499
  }
@@ -2418,9 +2518,21 @@ var collectDeviceContext = () => {
2418
2518
  if (resolved.timeZone) ctx.timeZone = resolved.timeZone;
2419
2519
  } catch {
2420
2520
  }
2521
+ const appVersion = detectAppVersion();
2522
+ if (appVersion) ctx.appVersion = appVersion;
2421
2523
  return ctx;
2422
2524
  };
2423
2525
 
2526
+ // src/analytics/currentSession.ts
2527
+ var _currentSessionId;
2528
+ var setCurrentSessionId = (id) => {
2529
+ if (typeof id === "string" && id.length > 0) _currentSessionId = id;
2530
+ };
2531
+ var getCurrentSessionId = () => _currentSessionId;
2532
+ var resetCurrentSessionId = () => {
2533
+ _currentSessionId = void 0;
2534
+ };
2535
+
2424
2536
  // src/session/persistedSession.ts
2425
2537
  var DEFAULT_SESSION_TTL_MS = 36e5;
2426
2538
  var READ_TIMEOUT_MS = 1500;
@@ -2499,6 +2611,7 @@ var identifyOnboarding = async (opts) => {
2499
2611
  contextId = stored == null ? void 0 : stored.id;
2500
2612
  }
2501
2613
  }
2614
+ if (!contextId) contextId = getCurrentSessionId();
2502
2615
  if (!contextId) return false;
2503
2616
  reportClientEvent(
2504
2617
  { serverUrl: opts.config.serverUrl, apiKey: opts.config.apiKey },
@@ -3155,6 +3268,89 @@ var attributionMetadata = (a) => {
3155
3268
  return { attribution };
3156
3269
  };
3157
3270
 
3271
+ // src/context/userContext.ts
3272
+ var RESERVED_USER_CONTEXT_KEYS = [
3273
+ "device_key",
3274
+ "app_version",
3275
+ "app_build",
3276
+ "network_type",
3277
+ "session_count",
3278
+ "returning",
3279
+ "platform",
3280
+ "user_email",
3281
+ "user_email_hashed"
3282
+ ];
3283
+ var EXTRA_KEY_PREFIX = "custom.";
3284
+ var isWireScalar = (value) => {
3285
+ const t = typeof value;
3286
+ if (t === "string" || t === "boolean") return true;
3287
+ if (t === "number") return Number.isFinite(value);
3288
+ return false;
3289
+ };
3290
+ var hashEmailFnv1a = (email) => {
3291
+ const normalized = email.trim().toLowerCase();
3292
+ let hash = 2166136261;
3293
+ for (let i = 0; i < normalized.length; i++) {
3294
+ hash ^= normalized.charCodeAt(i);
3295
+ hash = Math.imul(hash, 16777619);
3296
+ }
3297
+ return (hash >>> 0).toString(16).padStart(8, "0");
3298
+ };
3299
+ var cleanString = (value) => {
3300
+ if (typeof value !== "string") return void 0;
3301
+ const trimmed = value.trim();
3302
+ return trimmed.length > 0 ? trimmed : void 0;
3303
+ };
3304
+ var namespaceExtra = (extra) => {
3305
+ const out = {};
3306
+ if (!extra || typeof extra !== "object") return out;
3307
+ for (const [key, value] of Object.entries(extra)) {
3308
+ const cleanKey = cleanString(key);
3309
+ if (!cleanKey) continue;
3310
+ if (!isWireScalar(value)) continue;
3311
+ out[`${EXTRA_KEY_PREFIX}${cleanKey}`] = value;
3312
+ }
3313
+ return out;
3314
+ };
3315
+ var resolveUserContext = (ctx = {}, opts = {}) => {
3316
+ var _a;
3317
+ const result = {};
3318
+ const bucket = {};
3319
+ const userId = sanitizeUserId(ctx.userId);
3320
+ if (userId) result.userId = userId;
3321
+ const deviceKey = cleanString(ctx.deviceKey);
3322
+ if (deviceKey) {
3323
+ result.deviceKey = deviceKey;
3324
+ bucket.device_key = deviceKey;
3325
+ }
3326
+ const appVersion = (_a = cleanString(ctx.appVersion)) != null ? _a : cleanString(opts.autoAppVersion);
3327
+ if (appVersion) {
3328
+ result.appVersion = appVersion;
3329
+ bucket.app_version = appVersion;
3330
+ }
3331
+ const email = cleanString(ctx.userEmail);
3332
+ if (email) {
3333
+ if (ctx.hashEmail) {
3334
+ bucket.user_email = hashEmailFnv1a(email);
3335
+ bucket.user_email_hashed = true;
3336
+ } else {
3337
+ bucket.user_email = email;
3338
+ }
3339
+ }
3340
+ Object.assign(bucket, namespaceExtra(ctx.extra));
3341
+ if (Object.keys(bucket).length > 0) result.userContext = bucket;
3342
+ return result;
3343
+ };
3344
+
3345
+ // src/context/deviceId.ts
3346
+ var AUTO_DEVICE_ID_PREFIX = "wdev_";
3347
+ var deviceIdStorageKey = (appId) => `wireai:analytics:deviceKey:${appId != null ? appId : "default"}`;
3348
+ var randomChunk = () => Math.floor(Math.random() * 4294967296).toString(36).padStart(6, "0");
3349
+ var mintDeviceId = () => {
3350
+ const time = Date.now().toString(36);
3351
+ return `${AUTO_DEVICE_ID_PREFIX}${time}_${randomChunk()}${randomChunk()}`;
3352
+ };
3353
+
3158
3354
  // src/session-analytics/reportSessionStart.ts
3159
3355
  var SESSION_STARTED_EVENT = "app.session_started";
3160
3356
  var _emitted = /* @__PURE__ */ new Set();
@@ -3167,6 +3363,7 @@ var reportSessionStart = (opts) => {
3167
3363
  const target = opts.target;
3168
3364
  if (!opts.sink && !(target == null ? void 0 : target.serverUrl)) return;
3169
3365
  const sessionId = (_a = opts.sessionId) != null ? _a : makeSessionId();
3366
+ setCurrentSessionId(sessionId);
3170
3367
  if (opts.once !== false) {
3171
3368
  if (_emitted.has(sessionId)) return;
3172
3369
  if (_emitted.size >= _GUARD_MAX) {
@@ -3223,7 +3420,7 @@ var useSessionStart = (config, options = {}) => {
3223
3420
  if (opts.enabled === false) return;
3224
3421
  const target = { serverUrl: cfg.serverUrl, apiKey: (_a = cfg.apiKey) != null ? _a : "" };
3225
3422
  const device = collectDeviceContext();
3226
- if (cfg.appVersion && !device.appVersion) device.appVersion = cfg.appVersion;
3423
+ if (cfg.appVersion) device.appVersion = cfg.appVersion;
3227
3424
  reportSessionStart({
3228
3425
  target,
3229
3426
  // A fresh per-open id each fire; the emitter's once-guard dedupes within the open.
@@ -3597,7 +3794,7 @@ var useLifecycleEvents = (config, options = {}) => {
3597
3794
  const { config: cfg, options: opts } = latest.current;
3598
3795
  if (opts.enabled !== false) {
3599
3796
  const device = collectDeviceContext();
3600
- if ((cfg == null ? void 0 : cfg.appVersion) && !device.appVersion) device.appVersion = cfg.appVersion;
3797
+ if (cfg == null ? void 0 : cfg.appVersion) device.appVersion = cfg.appVersion;
3601
3798
  reportFirstOpen({
3602
3799
  target: targetOf(cfg),
3603
3800
  sink: resolveSink(),
@@ -3619,7 +3816,7 @@ var useLifecycleEvents = (config, options = {}) => {
3619
3816
  if (opts.enabled === false) return;
3620
3817
  if (!(cfg == null ? void 0 : cfg.serverUrl) && !opts.sink) return;
3621
3818
  const device = collectDeviceContext();
3622
- if ((cfg == null ? void 0 : cfg.appVersion) && !device.appVersion) device.appVersion = cfg.appVersion;
3819
+ if (cfg == null ? void 0 : cfg.appVersion) device.appVersion = cfg.appVersion;
3623
3820
  reportSessionStart({
3624
3821
  target: targetOf(cfg),
3625
3822
  sink: resolveSink(),
@@ -3653,6 +3850,7 @@ var useLifecycleEvents = (config, options = {}) => {
3653
3850
  }, []);
3654
3851
  };
3655
3852
 
3853
+ exports.AUTO_DEVICE_ID_PREFIX = AUTO_DEVICE_ID_PREFIX;
3656
3854
  exports.AnimatedSparkle = AnimatedSparkle;
3657
3855
  exports.BACKGROUND_SESSION_MS = BACKGROUND_SESSION_MS;
3658
3856
  exports.CardHandoff = CardHandoff;
@@ -3663,6 +3861,7 @@ exports.DEFAULT_FEATURES_TTL_MS = DEFAULT_FEATURES_TTL_MS;
3663
3861
  exports.DEFAULT_SESSION_TTL_MS = DEFAULT_SESSION_TTL_MS;
3664
3862
  exports.DemoOnboarding = DemoOnboarding;
3665
3863
  exports.DoneBlock = DoneBlock;
3864
+ exports.EXTRA_KEY_PREFIX = EXTRA_KEY_PREFIX;
3666
3865
  exports.ErrorBlock = ErrorBlock;
3667
3866
  exports.FIRST_OPEN_EVENT = FIRST_OPEN_EVENT;
3668
3867
  exports.IllustrationProvider = IllustrationProvider;
@@ -3674,6 +3873,7 @@ exports.OnboardingButton = Button;
3674
3873
  exports.OnboardingFlow = OnboardingFlow;
3675
3874
  exports.OnboardingScaffold = OnboardingScaffold;
3676
3875
  exports.OnboardingThemeProvider = OnboardingThemeProvider;
3876
+ exports.RESERVED_USER_CONTEXT_KEYS = RESERVED_USER_CONTEXT_KEYS;
3677
3877
  exports.SESSION_STARTED_EVENT = SESSION_STARTED_EVENT;
3678
3878
  exports.SelectionCard = SelectionCard;
3679
3879
  exports.StatusCard = StatusCard;
@@ -3690,17 +3890,25 @@ exports.defaultIllustrations = defaultIllustrations;
3690
3890
  exports.defaultOnboardingTheme = defaultOnboardingTheme;
3691
3891
  exports.defaultWireFeatures = defaultWireFeatures;
3692
3892
  exports.deriveAnswers = deriveAnswers;
3893
+ exports.detectAppVersion = detectAppVersion;
3894
+ exports.detectNativeModel = detectNativeModel;
3895
+ exports.deviceIdStorageKey = deviceIdStorageKey;
3693
3896
  exports.featuresCacheKey = featuresCacheKey;
3694
3897
  exports.featuresEqual = featuresEqual;
3695
3898
  exports.fetchWireFeatures = fetchWireFeatures;
3696
3899
  exports.firstOpenStorageKey = firstOpenStorageKey;
3900
+ exports.getCurrentSessionId = getCurrentSessionId;
3901
+ exports.hashEmailFnv1a = hashEmailFnv1a;
3697
3902
  exports.identifyOnboarding = identifyOnboarding;
3698
3903
  exports.isFeaturesFresh = isFeaturesFresh;
3699
3904
  exports.isOnboardingEnabled = isOnboardingEnabled;
3905
+ exports.isWireScalar = isWireScalar;
3700
3906
  exports.loadPersistedSession = loadPersistedSession;
3701
3907
  exports.makeSessionId = makeSessionId;
3702
3908
  exports.mergeTheme = mergeTheme;
3909
+ exports.mintDeviceId = mintDeviceId;
3703
3910
  exports.motionSpec = motionSpec_exports;
3911
+ exports.namespaceExtra = namespaceExtra;
3704
3912
  exports.onboardingComponents = onboardingComponents;
3705
3913
  exports.parseWireFeatures = parseWireFeatures;
3706
3914
  exports.peekPersistedSession = peekPersistedSession;
@@ -3710,11 +3918,14 @@ exports.reportClientEvent = reportClientEvent;
3710
3918
  exports.reportClientEvents = reportClientEvents;
3711
3919
  exports.reportFirstOpen = reportFirstOpen;
3712
3920
  exports.reportSessionStart = reportSessionStart;
3921
+ exports.resetCurrentSessionId = resetCurrentSessionId;
3713
3922
  exports.resetFirstOpenLatch = resetFirstOpenLatch;
3714
3923
  exports.resetSessionStartGuard = resetSessionStartGuard;
3924
+ exports.resolveUserContext = resolveUserContext;
3715
3925
  exports.sanitizeUserId = sanitizeUserId;
3716
3926
  exports.savePersistedSession = savePersistedSession;
3717
3927
  exports.sessionStorageKey = sessionStorageKey;
3928
+ exports.setCurrentSessionId = setCurrentSessionId;
3718
3929
  exports.themeFromBrand = themeFromBrand;
3719
3930
  exports.toAnalyticsEvent = toAnalyticsEvent;
3720
3931
  exports.useIllustration = useIllustration;