@wireai/activation 0.12.1 → 0.13.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +146 -1
  2. package/README.md +5 -3
  3. package/dist/analytics/index.d.mts +2 -2
  4. package/dist/analytics/index.d.ts +2 -2
  5. package/dist/analytics/index.js +114 -35
  6. package/dist/analytics/index.js.map +1 -1
  7. package/dist/analytics/index.mjs +114 -36
  8. package/dist/analytics/index.mjs.map +1 -1
  9. package/dist/{currentSession-BxEB37xt.d.ts → currentSession-D7zabMXK.d.ts} +161 -9
  10. package/dist/{currentSession-BlCeDP0f.d.mts → currentSession-_GynvhzT.d.mts} +161 -9
  11. package/dist/index.d.mts +5 -15
  12. package/dist/index.d.ts +5 -15
  13. package/dist/index.js +232 -135
  14. package/dist/index.js.map +1 -1
  15. package/dist/index.mjs +229 -134
  16. package/dist/index.mjs.map +1 -1
  17. package/dist/questionnaire/index.js.map +1 -1
  18. package/dist/questionnaire/index.mjs.map +1 -1
  19. package/dist/reviews/index.js +8 -6
  20. package/dist/reviews/index.js.map +1 -1
  21. package/dist/reviews/index.mjs +8 -6
  22. package/dist/reviews/index.mjs.map +1 -1
  23. package/package.json +1 -1
  24. package/src/OnboardingFlow.tsx +10 -3
  25. package/src/WireOnboarding.tsx +115 -32
  26. package/src/activation/wireActivation.ts +13 -7
  27. package/src/analytics/analyticsFacade.ts +11 -10
  28. package/src/analytics/currentSession.ts +6 -20
  29. package/src/analytics/eventQueue.ts +69 -1
  30. package/src/analytics/index.ts +1 -1
  31. package/src/analytics/reportClientEvent.ts +92 -29
  32. package/src/config/wireConfigFromEnv.ts +1 -10
  33. package/src/context/deviceId.ts +77 -16
  34. package/src/context/userContext.ts +4 -15
  35. package/src/identity/identityRecord.ts +123 -0
  36. package/src/identity/userIdentity.ts +45 -9
  37. package/src/index.ts +6 -4
  38. package/src/session-analytics/useLifecycleEvents.ts +10 -1
  39. package/src/types.ts +14 -0
  40. package/src/utils/deriveAnswers.ts +6 -2
  41. package/src/utils/readProgress.ts +4 -0
  42. package/src/utils/warnInDev.ts +33 -0
  43. package/src/components/DoneBlock.tsx +0 -37
package/dist/index.mjs CHANGED
@@ -1006,26 +1006,26 @@ var _OnboardingScaffold = ({
1006
1006
  children
1007
1007
  }) => {
1008
1008
  const t = useOnboardingTheme();
1009
- const styles18 = useMemo(() => makeStyles(t), [t]);
1009
+ const styles17 = useMemo(() => makeStyles(t), [t]);
1010
1010
  const hit = { top: 12, bottom: 12, left: 12, right: 12 };
1011
1011
  return /* @__PURE__ */ jsxs(
1012
1012
  SafeAreaView,
1013
1013
  {
1014
1014
  edges: ["top", "bottom"],
1015
- style: [styles18.flex, { backgroundColor: t.colors.background }],
1015
+ style: [styles17.flex, { backgroundColor: t.colors.background }],
1016
1016
  children: [
1017
- /* @__PURE__ */ jsxs(View, { style: [styles18.header, { paddingHorizontal: t.spacing.md, paddingTop: t.spacing.md }], children: [
1018
- /* @__PURE__ */ jsx(View, { style: styles18.progressWrap, children: /* @__PURE__ */ jsx(StepProgress, { step, complete, approxScreens }) }),
1019
- onSkip ? /* @__PURE__ */ jsx(TouchableOpacity, { onPress: onSkip, hitSlop: hit, style: styles18.skip, children: /* @__PURE__ */ jsx(Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted }], children: skipLabel }) }) : null
1017
+ /* @__PURE__ */ jsxs(View, { style: [styles17.header, { paddingHorizontal: t.spacing.md, paddingTop: t.spacing.md }], children: [
1018
+ /* @__PURE__ */ jsx(View, { style: styles17.progressWrap, children: /* @__PURE__ */ jsx(StepProgress, { step, complete, approxScreens }) }),
1019
+ onSkip ? /* @__PURE__ */ jsx(TouchableOpacity, { onPress: onSkip, hitSlop: hit, style: styles17.skip, children: /* @__PURE__ */ jsx(Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted }], children: skipLabel }) }) : null
1020
1020
  ] }),
1021
1021
  /* @__PURE__ */ jsx(
1022
1022
  View,
1023
1023
  {
1024
- style: [styles18.body, { paddingHorizontal: t.spacing.md, paddingTop: t.spacing.md }],
1024
+ style: [styles17.body, { paddingHorizontal: t.spacing.md, paddingTop: t.spacing.md }],
1025
1025
  children
1026
1026
  }
1027
1027
  ),
1028
- onBack ? /* @__PURE__ */ jsx(View, { style: [styles18.footer, { paddingHorizontal: t.spacing.md, paddingBottom: t.spacing.sm }], children: /* @__PURE__ */ jsx(TouchableOpacity, { onPress: onBack, hitSlop: hit, children: /* @__PURE__ */ jsx(Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted }], children: backLabel }) }) }) : null
1028
+ onBack ? /* @__PURE__ */ jsx(View, { style: [styles17.footer, { paddingHorizontal: t.spacing.md, paddingBottom: t.spacing.sm }], children: /* @__PURE__ */ jsx(TouchableOpacity, { onPress: onBack, hitSlop: hit, children: /* @__PURE__ */ jsx(Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted }], children: backLabel }) }) }) : null
1029
1029
  ]
1030
1030
  }
1031
1031
  );
@@ -1162,10 +1162,10 @@ var _CardLayout = ({
1162
1162
  titleNode
1163
1163
  }) => {
1164
1164
  const t = useOnboardingTheme();
1165
- const styles18 = useMemo(() => makeStyles2(t), [t]);
1165
+ const styles17 = useMemo(() => makeStyles2(t), [t]);
1166
1166
  const hasHeader = !!title || !!subtitle || !!titleNode;
1167
- return /* @__PURE__ */ jsxs(KeyboardAvoidingView, { style: styles18.fill, behavior: "padding", children: [
1168
- hasHeader ? /* @__PURE__ */ jsxs(View, { style: styles18.header, children: [
1167
+ return /* @__PURE__ */ jsxs(KeyboardAvoidingView, { style: styles17.fill, behavior: "padding", children: [
1168
+ hasHeader ? /* @__PURE__ */ jsxs(View, { style: styles17.header, children: [
1169
1169
  titleNode != null ? titleNode : title ? /* @__PURE__ */ jsx(Text, { style: [labelStyle(t.fonts), { color: t.colors.text }], children: title }) : null,
1170
1170
  subtitle ? /* @__PURE__ */ jsx(
1171
1171
  Text,
@@ -1181,10 +1181,10 @@ var _CardLayout = ({
1181
1181
  /* @__PURE__ */ jsx(
1182
1182
  ScrollView,
1183
1183
  {
1184
- style: styles18.fill,
1184
+ style: styles17.fill,
1185
1185
  contentContainerStyle: [
1186
- styles18.scrollContent,
1187
- align === "center" && styles18.center
1186
+ styles17.scrollContent,
1187
+ align === "center" && styles17.center
1188
1188
  ],
1189
1189
  keyboardShouldPersistTaps: "handled",
1190
1190
  keyboardDismissMode: "on-drag",
@@ -1192,7 +1192,7 @@ var _CardLayout = ({
1192
1192
  children
1193
1193
  }
1194
1194
  ),
1195
- footer ? /* @__PURE__ */ jsx(View, { style: styles18.footer, children: footer }) : null
1195
+ footer ? /* @__PURE__ */ jsx(View, { style: styles17.footer, children: footer }) : null
1196
1196
  ] });
1197
1197
  };
1198
1198
  var CardLayout = React18.memo(_CardLayout);
@@ -1474,7 +1474,7 @@ var deriveAnswers = (messages) => {
1474
1474
  const props = (_a2 = r.props) != null ? _a2 : {};
1475
1475
  const progress = props.progress;
1476
1476
  const questionText = (_c = (_b = props.title) != null ? _b : props.label) != null ? _c : props.question;
1477
- const key = (progress == null ? void 0 : progress.key) || questionText;
1477
+ const key = (progress == null ? void 0 : progress.slot_id) || (progress == null ? void 0 : progress.key) || questionText;
1478
1478
  const reply = messages[i + 1];
1479
1479
  if (key && (reply == null ? void 0 : reply.role) === "user") {
1480
1480
  out[key] = tryParse(stripPrefix(reply.content));
@@ -1495,6 +1495,8 @@ var readProgress = (response) => {
1495
1495
  step: typeof p.step === "number" ? p.step : void 0,
1496
1496
  total: typeof p.total === "number" ? p.total : void 0,
1497
1497
  key: typeof p.key === "string" ? p.key : void 0,
1498
+ // Whitelisted the same way as every other field: an old backend simply omits it.
1499
+ slot_id: typeof p.slot_id === "string" ? p.slot_id : void 0,
1498
1500
  skippable: typeof p.skippable === "boolean" ? p.skippable : void 0
1499
1501
  };
1500
1502
  };
@@ -1512,28 +1514,35 @@ var buildEventsRequest = (target, events) => {
1512
1514
  return null;
1513
1515
  }
1514
1516
  };
1515
- var warnOnSkippedEvents = (res) => {
1516
- if (typeof __DEV__ === "undefined" || !__DEV__) return;
1517
- if (typeof console === "undefined" || !console.warn) return;
1517
+ var readEventsAck = async (res) => {
1518
1518
  try {
1519
1519
  const json = res == null ? void 0 : res.json;
1520
- if (typeof json !== "function") return;
1521
- void Promise.resolve(json.call(res)).then((body) => {
1522
- const skipped = body == null ? void 0 : body.skipped;
1523
- if (typeof skipped !== "number" || skipped <= 0) return;
1524
- console.warn(
1525
- `[wireai] the server ACCEPTED the /v1/events POST but DISCARDED ${skipped} event(s) (skipped in the response body) \u2014 they are gone, not retried. The usual cause is an event with a missing or empty session_id.`
1526
- );
1527
- }).catch(() => {
1528
- });
1520
+ if (typeof json !== "function") return void 0;
1521
+ const body = await Promise.resolve(json.call(res));
1522
+ const skipped = body == null ? void 0 : body.skipped;
1523
+ if (typeof skipped !== "number" || !Number.isFinite(skipped)) return void 0;
1524
+ const reasons = Array.isArray(body == null ? void 0 : body.errors) ? body.errors.map((e) => e == null ? void 0 : e.reason).filter((r) => typeof r === "string") : [];
1525
+ return { written: typeof (body == null ? void 0 : body.written) === "number" ? body.written : void 0, skipped, reasons };
1529
1526
  } catch {
1527
+ return void 0;
1530
1528
  }
1531
1529
  };
1530
+ var describeDiscarded = (ack) => `[wireai] the server ACCEPTED the /v1/events POST but DISCARDED ${ack.skipped} event(s) (skipped in the response body) \u2014 they are gone, not retried. The server reported: skipped=${ack.skipped}${ack.written !== void 0 ? `, written=${ack.written}` : ""}` + (ack.reasons.length > 0 ? `, reasons: ${ack.reasons.join(", ")}.` : ". It gave no reason (the endpoint folds every rejection into one count), so check the server's ingest log for this request rather than guessing.");
1531
+ var warnOnSkippedEvents = (res) => {
1532
+ if (typeof __DEV__ === "undefined" || !__DEV__) return;
1533
+ if (typeof console === "undefined" || !console.warn) return;
1534
+ void readEventsAck(res).then((ack) => {
1535
+ if (!ack || ack.skipped <= 0) return;
1536
+ console.warn(describeDiscarded(ack));
1537
+ });
1538
+ };
1532
1539
  var reportClientEvents = (target, events) => {
1533
1540
  try {
1534
1541
  const req = buildEventsRequest(target, events);
1535
1542
  if (!req) return;
1536
- void fetch(req.url, req.init).catch(() => {
1543
+ void fetch(req.url, req.init).then((res) => {
1544
+ warnOnSkippedEvents(res);
1545
+ }).catch(() => {
1537
1546
  });
1538
1547
  } catch {
1539
1548
  }
@@ -1544,7 +1553,13 @@ var reportClientEventsAwait = async (target, events) => {
1544
1553
  const req = buildEventsRequest(target, events);
1545
1554
  if (!req) return false;
1546
1555
  const res = await fetch(req.url, req.init);
1547
- return Boolean(res && res.ok);
1556
+ if (!res || !res.ok) return false;
1557
+ const ack = await readEventsAck(res);
1558
+ if (!ack || ack.skipped <= 0) return true;
1559
+ if (typeof __DEV__ !== "undefined" && __DEV__ && typeof console !== "undefined" && console.warn) {
1560
+ console.warn(describeDiscarded(ack));
1561
+ }
1562
+ return false;
1548
1563
  } catch {
1549
1564
  return false;
1550
1565
  }
@@ -1764,8 +1779,8 @@ var OnboardingFlow = ({
1764
1779
  (messageId) => {
1765
1780
  var _a3;
1766
1781
  const base = makeActions(messageId);
1767
- const key = progress.key;
1768
- const validator = key ? (_a3 = validatorsRef.current) == null ? void 0 : _a3[key] : void 0;
1782
+ const validators2 = validatorsRef.current;
1783
+ const validator = (_a3 = progress.slot_id ? validators2 == null ? void 0 : validators2[progress.slot_id] : void 0) != null ? _a3 : progress.key ? validators2 == null ? void 0 : validators2[progress.key] : void 0;
1769
1784
  if (!validator) return base;
1770
1785
  const out = { ...base };
1771
1786
  for (const name2 of ANSWER_CALLBACKS) {
@@ -1790,7 +1805,7 @@ var OnboardingFlow = ({
1790
1805
  }
1791
1806
  return out;
1792
1807
  },
1793
- [makeActions, progress.key]
1808
+ [makeActions, progress.key, progress.slot_id]
1794
1809
  );
1795
1810
  if (degraded) {
1796
1811
  if (fallbackFlow !== void 0) return /* @__PURE__ */ jsx(Fragment, { children: fallbackFlow });
@@ -2101,7 +2116,7 @@ var _TextInputCard = ({
2101
2116
  lines = 6
2102
2117
  }) => {
2103
2118
  const t = useOnboardingTheme();
2104
- const styles18 = useMemo(() => makeStyles3(t), [t]);
2119
+ const styles17 = useMemo(() => makeStyles3(t), [t]);
2105
2120
  const { height: screenHeight } = useWindowDimensions();
2106
2121
  const [value, setValue] = useState("");
2107
2122
  const [submitted, setSubmitted] = useState(false);
@@ -2128,7 +2143,7 @@ var _TextInputCard = ({
2128
2143
  disabled: !value.trim() || locked
2129
2144
  }
2130
2145
  ),
2131
- children: /* @__PURE__ */ jsxs(View, { style: styles18.row, children: [
2146
+ children: /* @__PURE__ */ jsxs(View, { style: styles17.row, children: [
2132
2147
  /* @__PURE__ */ jsx(
2133
2148
  TextInput,
2134
2149
  {
@@ -2142,7 +2157,7 @@ var _TextInputCard = ({
2142
2157
  textAlignVertical: multiline ? "top" : "center",
2143
2158
  style: [
2144
2159
  bodyStyle(t.fonts),
2145
- styles18.input,
2160
+ styles17.input,
2146
2161
  {
2147
2162
  color: t.colors.text,
2148
2163
  backgroundColor: t.colors.surface,
@@ -2868,7 +2883,7 @@ var _NumberStepperCard = ({
2868
2883
  onSubmit
2869
2884
  }) => {
2870
2885
  const t = useOnboardingTheme();
2871
- const styles18 = useMemo(() => makeStyles4(t), [t]);
2886
+ const styles17 = useMemo(() => makeStyles4(t), [t]);
2872
2887
  const safeMin = safeNum(min, 1);
2873
2888
  const safeMaxRaw = safeNum(max, 30);
2874
2889
  const safeMax = safeMaxRaw < safeMin ? safeMin : safeMaxRaw;
@@ -2892,7 +2907,7 @@ var _NumberStepperCard = ({
2892
2907
  const atMin = value <= safeMin;
2893
2908
  const atMax = value >= safeMax;
2894
2909
  const stepBtnStyle = (disabled) => [
2895
- styles18.stepBtn,
2910
+ styles17.stepBtn,
2896
2911
  { borderRadius: t.radius.full, backgroundColor: t.colors.background, borderColor: disabled ? t.colors.border : t.colors.primary }
2897
2912
  ];
2898
2913
  return /* @__PURE__ */ jsx(
@@ -2901,10 +2916,10 @@ var _NumberStepperCard = ({
2901
2916
  title: label,
2902
2917
  align: "center",
2903
2918
  footer: /* @__PURE__ */ jsx(Button, { title: submitLabel, onPress: handleSubmit, variant: "primary", disabled: submitted }),
2904
- children: /* @__PURE__ */ jsxs(View, { style: styles18.wrap, children: [
2905
- /* @__PURE__ */ jsxs(View, { style: [styles18.stepper, { gap: t.spacing.lg }], children: [
2919
+ children: /* @__PURE__ */ jsxs(View, { style: styles17.wrap, children: [
2920
+ /* @__PURE__ */ jsxs(View, { style: [styles17.stepper, { gap: t.spacing.lg }], children: [
2906
2921
  /* @__PURE__ */ jsx(Pressable, { onPress: atMin || submitted ? void 0 : handleDecrement, disabled: atMin || submitted, style: stepBtnStyle(atMin), children: /* @__PURE__ */ jsx(Text, { style: [headingStyle(t.fonts), { color: atMin ? t.colors.textMuted : t.colors.primary }], children: "\u2212" }) }),
2907
- /* @__PURE__ */ jsxs(View, { style: styles18.valueBox, children: [
2922
+ /* @__PURE__ */ jsxs(View, { style: styles17.valueBox, children: [
2908
2923
  /* @__PURE__ */ jsx(Text, { style: [headingStyle(t.fonts), { color: t.colors.primary }], children: String(value) }),
2909
2924
  unit ? /* @__PURE__ */ jsx(Text, { style: [captionStyle(t.fonts), { color: t.colors.textMuted }], children: unit }) : null
2910
2925
  ] }),
@@ -3311,6 +3326,33 @@ var collectDeviceContext = () => {
3311
3326
  return ctx;
3312
3327
  };
3313
3328
 
3329
+ // src/identity/identityRecord.ts
3330
+ var IDENTITY_PROVENANCE_SLOT = /* @__PURE__ */ Symbol.for("@wireai/activation:identityProvenance");
3331
+ var provenanceGlobal = globalThis;
3332
+ var provenanceRegistry = () => {
3333
+ const existing = provenanceGlobal[IDENTITY_PROVENANCE_SLOT];
3334
+ if (existing) return existing;
3335
+ const created = { host: /* @__PURE__ */ new Map() };
3336
+ provenanceGlobal[IDENTITY_PROVENANCE_SLOT] = created;
3337
+ return created;
3338
+ };
3339
+ var provenanceKey = (space, scope) => `${space}:${scope != null ? scope : "default"}`;
3340
+ var resolveIdentity = (input) => {
3341
+ var _a2;
3342
+ if (typeof input.value !== "string") return void 0;
3343
+ const value = input.value.trim();
3344
+ if (!value) return void 0;
3345
+ const durable = (_a2 = input.durable) != null ? _a2 : input.source === "host";
3346
+ if (input.source === "host") {
3347
+ provenanceRegistry().host.set(provenanceKey(input.space, input.scope), value);
3348
+ }
3349
+ return { value, space: input.space, source: input.source, durable };
3350
+ };
3351
+ var hostIdentity = (space, scope) => provenanceRegistry().host.get(provenanceKey(space, scope));
3352
+ var resetIdentityProvenance = () => {
3353
+ provenanceRegistry().host.clear();
3354
+ };
3355
+
3314
3356
  // src/context/deviceId.ts
3315
3357
  var AUTO_DEVICE_ID_PREFIX = "wdev_";
3316
3358
  var deviceIdStorageKey = (appId) => `wireai:analytics:deviceKey:${appId != null ? appId : "default"}`;
@@ -3333,24 +3375,37 @@ var startHydration = (registry, appId, storage, minted) => {
3333
3375
  const existing = registry.pending.get(appId);
3334
3376
  if (existing) return existing;
3335
3377
  const slot = deviceIdStorageKey(appId);
3336
- const settled = () => {
3378
+ const degraded = () => {
3337
3379
  var _a2;
3338
- return (_a2 = registry.keys.get(appId)) != null ? _a2 : minted;
3380
+ return { value: (_a2 = registry.keys.get(appId)) != null ? _a2 : minted, durable: false };
3339
3381
  };
3382
+ const adopted = (value) => ({ value, durable: true });
3340
3383
  let run;
3341
3384
  try {
3342
3385
  run = Promise.resolve(storage.getItem(slot)).then((saved) => {
3343
3386
  const persisted = typeof saved === "string" && saved.trim() ? saved.trim() : void 0;
3344
3387
  if (persisted) {
3345
3388
  registry.keys.set(appId, persisted);
3346
- return persisted;
3389
+ return adopted(persisted);
3347
3390
  }
3348
- return Promise.resolve(storage.setItem(slot, minted)).then(settled, settled);
3349
- }).catch(settled);
3391
+ return Promise.resolve(storage.setItem(slot, minted)).then(
3392
+ () => {
3393
+ var _a2;
3394
+ return adopted((_a2 = registry.keys.get(appId)) != null ? _a2 : minted);
3395
+ },
3396
+ degraded
3397
+ );
3398
+ }).catch(degraded);
3350
3399
  } catch {
3351
- run = Promise.resolve(settled());
3400
+ run = Promise.resolve(degraded());
3352
3401
  }
3353
3402
  registry.pending.set(appId, run);
3403
+ void run.then((outcome) => {
3404
+ var _a2;
3405
+ if (outcome.durable) return;
3406
+ (_a2 = registry.pending) == null ? void 0 : _a2.delete(appId);
3407
+ registry.hydrating.delete(appId);
3408
+ });
3354
3409
  return run;
3355
3410
  };
3356
3411
  var resolveAutoDeviceKey = (opts = {}) => {
@@ -3370,14 +3425,20 @@ var resolveAutoDeviceKey = (opts = {}) => {
3370
3425
  return (_b = registry.keys.get(appId)) != null ? _b : id;
3371
3426
  };
3372
3427
  var hydrateAutoDeviceKey = async (opts = {}) => {
3428
+ var _a2, _b;
3429
+ return (_b = (_a2 = await hydrateDeviceIdentity(opts)) == null ? void 0 : _a2.value) != null ? _b : resolveAutoDeviceKey(opts);
3430
+ };
3431
+ var hydrateDeviceIdentity = async (opts = {}) => {
3373
3432
  var _a2, _b, _c;
3374
3433
  const id = resolveAutoDeviceKey(opts);
3375
- if (!opts.storage) return id;
3376
- const registry = autoDeviceKeyRegistry();
3377
3434
  const appId = (_a2 = opts.appId) != null ? _a2 : "default";
3435
+ const record = (value, durable) => resolveIdentity({ value, space: "device", source: "auto", durable, scope: appId });
3436
+ if (!opts.storage) return record(id, false);
3437
+ const registry = autoDeviceKeyRegistry();
3378
3438
  const pending = (_b = registry.pending) == null ? void 0 : _b.get(appId);
3379
- if (pending) await pending;
3380
- return (_c = registry.keys.get(appId)) != null ? _c : id;
3439
+ if (!pending) return record((_c = registry.keys.get(appId)) != null ? _c : id, false);
3440
+ const outcome = await pending;
3441
+ return record(outcome.value, outcome.durable);
3381
3442
  };
3382
3443
  var resetAutoDeviceKeys = () => {
3383
3444
  var _a2;
@@ -3387,6 +3448,15 @@ var resetAutoDeviceKeys = () => {
3387
3448
  (_a2 = registry.pending) == null ? void 0 : _a2.clear();
3388
3449
  };
3389
3450
 
3451
+ // src/utils/warnInDev.ts
3452
+ var warnInDev = (message) => {
3453
+ if (typeof __DEV__ !== "undefined" && __DEV__ && typeof console !== "undefined" && console.warn) {
3454
+ console.warn(message);
3455
+ return true;
3456
+ }
3457
+ return false;
3458
+ };
3459
+
3390
3460
  // src/analytics/currentSession.ts
3391
3461
  var CURRENT_SESSION_ID_SLOT = /* @__PURE__ */ Symbol.for(
3392
3462
  "@wireai/activation:currentSessionId"
@@ -3401,13 +3471,6 @@ var getCurrentSessionId = () => globalSlot[CURRENT_SESSION_ID_SLOT];
3401
3471
  var resetCurrentSessionId = () => {
3402
3472
  globalSlot[CURRENT_SESSION_ID_SLOT] = void 0;
3403
3473
  };
3404
- var warnInDev = (message) => {
3405
- if (typeof __DEV__ !== "undefined" && __DEV__ && typeof console !== "undefined" && console.warn) {
3406
- console.warn(message);
3407
- return true;
3408
- }
3409
- return false;
3410
- };
3411
3474
  var MINT_WARNING = "[wireai] No app-open session was registered, so a session id was minted for this event (the server drops an event that has no session_id, and still answers 200). Mount useLifecycleEvents at your app root so events correlate to a real app-open.";
3412
3475
  var MINT_WARNED_SLOT = /* @__PURE__ */ Symbol.for(
3413
3476
  "@wireai/activation:currentSessionIdMintWarned"
@@ -3504,27 +3567,20 @@ var identifyOnboarding = async (opts) => {
3504
3567
  contextId = stored == null ? void 0 : stored.id;
3505
3568
  }
3506
3569
  }
3507
- if (!contextId) contextId = getCurrentSessionId();
3570
+ let space = "onboarding";
3571
+ if (!contextId && opts.allowAppSessionFallback) {
3572
+ contextId = getCurrentSessionId();
3573
+ space = "app_session";
3574
+ }
3508
3575
  if (!contextId) return false;
3509
3576
  reportClientEvent(
3510
3577
  { serverUrl: opts.config.serverUrl, apiKey: opts.config.apiKey },
3511
3578
  { event_type: "identify", session_id: contextId, user_id: userId }
3512
3579
  );
3513
- return true;
3580
+ return space;
3514
3581
  };
3515
3582
 
3516
3583
  // src/context/userContext.ts
3517
- var RESERVED_USER_CONTEXT_KEYS = [
3518
- "device_key",
3519
- "app_version",
3520
- "app_build",
3521
- "network_type",
3522
- "session_count",
3523
- "returning",
3524
- "platform",
3525
- "user_email",
3526
- "user_email_hashed"
3527
- ];
3528
3584
  var EXTRA_KEY_PREFIX = "custom.";
3529
3585
  var isWireScalar = (value) => {
3530
3586
  const t = typeof value;
@@ -3605,11 +3661,6 @@ var resolveUserContext = (ctx = {}, opts = {}) => {
3605
3661
  if (Object.keys(bucket).length > 0) result.userContext = bucket;
3606
3662
  return result;
3607
3663
  };
3608
- var warnInDev2 = (message) => {
3609
- if (typeof __DEV__ !== "undefined" && __DEV__ && typeof console !== "undefined" && console.warn) {
3610
- console.warn(message);
3611
- }
3612
- };
3613
3664
  var AUTO_JOIN_HYDRATION_TIMEOUT_MS = 1500;
3614
3665
  var WireOnboarding = ({
3615
3666
  config,
@@ -3636,7 +3687,7 @@ var WireOnboarding = ({
3636
3687
  userId,
3637
3688
  autoJoinKey = true
3638
3689
  }) => {
3639
- var _a2, _b, _c;
3690
+ var _a2, _b, _c, _d, _e;
3640
3691
  const boundUserId = useMemo(() => sanitizeUserId(userId), [userId]);
3641
3692
  const device = useMemo(() => {
3642
3693
  const collected = collectDeviceContext();
@@ -3647,44 +3698,72 @@ var WireOnboarding = ({
3647
3698
  () => userContextKey ? JSON.parse(userContextKey) : void 0,
3648
3699
  [userContextKey]
3649
3700
  );
3650
- const missingJoinKey = typeof (userContextStable == null ? void 0 : userContextStable.device_key) !== "string" || !userContextStable.device_key.trim();
3701
+ const hostJoinIdentity = useMemo(
3702
+ () => resolveIdentity({
3703
+ value: userContextStable == null ? void 0 : userContextStable.device_key,
3704
+ space: "device",
3705
+ source: "host",
3706
+ scope: config.appId
3707
+ }),
3708
+ [userContextStable, config.appId]
3709
+ );
3710
+ const missingJoinKey = !hostJoinIdentity;
3651
3711
  const autoJoinPossible = Boolean(storage) && autoJoinKey !== false;
3652
3712
  const wantsAutoJoin = missingJoinKey && autoJoinPossible;
3653
- const [autoJoinValue, setAutoJoinValue] = useState(void 0);
3713
+ const [autoJoinOutcome, setAutoJoinOutcome] = useState(void 0);
3654
3714
  useEffect(() => {
3655
- if (!wantsAutoJoin || autoJoinValue !== void 0 || !storage) return;
3715
+ if (!wantsAutoJoin || autoJoinOutcome !== void 0 || !storage) return;
3656
3716
  let cancelled = false;
3657
3717
  const timer = setTimeout(() => {
3658
- if (!cancelled) setAutoJoinValue(null);
3718
+ if (!cancelled) setAutoJoinOutcome({ key: null, reason: "timeout" });
3659
3719
  }, AUTO_JOIN_HYDRATION_TIMEOUT_MS);
3660
- void hydrateAutoDeviceKey({ appId: config.appId, storage }).then((key) => {
3720
+ void hydrateDeviceIdentity({ appId: config.appId, storage }).then((identity) => {
3661
3721
  if (cancelled) return;
3662
3722
  clearTimeout(timer);
3663
- setAutoJoinValue(key || null);
3723
+ setAutoJoinOutcome(
3724
+ (identity == null ? void 0 : identity.durable) ? { key: identity.value } : { key: null, reason: "not-durable" }
3725
+ );
3664
3726
  });
3665
3727
  return () => {
3666
3728
  cancelled = true;
3667
3729
  clearTimeout(timer);
3668
3730
  };
3669
- }, [wantsAutoJoin, autoJoinValue, config.appId, storage]);
3670
- const injectedJoinKey = wantsAutoJoin && typeof autoJoinValue === "string" ? autoJoinValue : void 0;
3671
- const autoJoinPending = wantsAutoJoin && autoJoinValue === void 0;
3731
+ }, [wantsAutoJoin, autoJoinOutcome, config.appId, storage]);
3732
+ const injectedJoinKey = wantsAutoJoin ? (_a2 = autoJoinOutcome == null ? void 0 : autoJoinOutcome.key) != null ? _a2 : void 0 : void 0;
3733
+ const autoJoinPending = wantsAutoJoin && autoJoinOutcome === void 0;
3672
3734
  const effectiveUserContext = useMemo(
3673
3735
  () => injectedJoinKey ? { ...userContextStable, ...activationJoinContext(injectedJoinKey) } : userContextStable,
3674
3736
  [userContextStable, injectedJoinKey]
3675
3737
  );
3738
+ const frozenWireContextRef = useRef(null);
3739
+ if (!autoJoinPending && frozenWireContextRef.current === null) {
3740
+ frozenWireContextRef.current = effectiveUserContext ? JSON.stringify(effectiveUserContext) : "";
3741
+ }
3742
+ const startupUserContextKey = (_b = frozenWireContextRef.current) != null ? _b : "";
3743
+ const startupUserContext = useMemo(
3744
+ () => startupUserContextKey ? JSON.parse(startupUserContextKey) : void 0,
3745
+ [startupUserContextKey]
3746
+ );
3676
3747
  const warnMissingJoinKey = missingJoinKey && !injectedJoinKey && !autoJoinPending;
3677
- const autoJoinReason = autoJoinKey === false ? "you passed autoJoinKey={false}." : !storage ? "it got no `storage` prop, and without persistence its key would be different on every launch, which corrupts min_sessions instead of merely leaving the join empty." : "your `storage` adapter did not answer in time.";
3748
+ const autoJoinReason = autoJoinKey === false ? "you passed autoJoinKey={false}." : !storage ? "it got no `storage` prop, and without persistence its key would be different on every launch, which corrupts min_sessions instead of merely leaving the join empty." : (autoJoinOutcome == null ? void 0 : autoJoinOutcome.key) === null && autoJoinOutcome.reason === "not-durable" ? "your `storage` adapter answered but could not persist the key (the read or the write failed), so the key would be different on every launch, which corrupts min_sessions instead of merely leaving the join empty. Check the adapter you passed as `storage`." : "your `storage` adapter did not answer in time.";
3678
3749
  useEffect(() => {
3679
3750
  if (!warnMissingJoinKey) return;
3680
- warnInDev2(
3751
+ warnInDev(
3681
3752
  "[wireai] <WireOnboarding> got no user_context.device_key, so this onboarding session can never be joined to the app's later events and the `activated` funnel will read zero. Pass userContext={activationJoinContext(deviceKey)} \u2014 and if your app owns no device id, userContext={activationJoinContext(resolveAutoDeviceKey({ appId, storage }))} returns the SAME id the analytics side stamps. Never hand-write userContext={{ deviceKey }}. The kit did NOT auto-inject its own key here because " + autoJoinReason
3682
3753
  );
3683
3754
  }, [warnMissingJoinKey, autoJoinReason]);
3755
+ const hostKeyElsewhere = missingJoinKey ? hostIdentity("device", config.appId) : void 0;
3756
+ const warnHostKeyElsewhere = Boolean(hostKeyElsewhere) && Boolean(injectedJoinKey);
3757
+ useEffect(() => {
3758
+ if (!warnHostKeyElsewhere) return;
3759
+ warnInDev(
3760
+ `[wireai] <WireOnboarding> got no user_context.device_key, so the kit injected its own \u2014 but another surface in this app was constructed WITH a host-supplied device key ("${hostKeyElsewhere}"). Your app events carry that id and this onboarding session carries the kit's, so the two land in disjoint id spaces and the \`activated\` funnel still reads zero. Pass the SAME id here: userContext={activationJoinContext(deviceKey)}.`
3761
+ );
3762
+ }, [warnHostKeyElsewhere, hostKeyElsewhere]);
3684
3763
  const misspelledJoinKey = missingJoinKey && typeof (userContextStable == null ? void 0 : userContextStable.deviceKey) === "string" && Boolean(userContextStable.deviceKey.trim());
3685
3764
  useEffect(() => {
3686
3765
  if (!misspelledJoinKey || !injectedJoinKey) return;
3687
- warnInDev2(
3766
+ warnInDev(
3688
3767
  "[wireai] <WireOnboarding> got userContext={{ deviceKey }}, which is NOT the wire key \u2014 the server's device lookup reads `device_key`. The kit auto-injected its own device_key so this session is at least self-consistent, but your app's own events carry YOUR id, so the two still will not join and the `activated` funnel stays zero. Pass userContext={activationJoinContext(deviceKey)} instead."
3689
3768
  );
3690
3769
  }, [misspelledJoinKey, injectedJoinKey]);
@@ -3713,7 +3792,7 @@ var WireOnboarding = ({
3713
3792
  },
3714
3793
  [storage, storageKey, retainSessionOnComplete, onComplete]
3715
3794
  );
3716
- const sessionId = (_a2 = session == null ? void 0 : session.id) != null ? _a2 : "";
3795
+ const sessionId = (_c = session == null ? void 0 : session.id) != null ? _c : "";
3717
3796
  const startupUserIdRef = useRef(boundUserId);
3718
3797
  const metadataKey = config.metadata ? JSON.stringify(config.metadata) : "";
3719
3798
  const metadataStable = useMemo(() => metadataKey ? JSON.parse(metadataKey) : void 0, [metadataKey]);
@@ -3737,7 +3816,9 @@ var WireOnboarding = ({
3737
3816
  // Device snapshot + host-injected user context ride the session-start metadata so the
3738
3817
  // backend can segment the funnel. Old servers ignore these unknown keys (backward compat).
3739
3818
  device,
3740
- ...effectiveUserContext ? { userContext: effectiveUserContext } : {},
3819
+ // The FROZEN wire context, never the live one — see the freeze note above. A change here
3820
+ // recreates the A2A adapter and drops `contextId`.
3821
+ ...startupUserContext ? { userContext: startupUserContext } : {},
3741
3822
  // The session-start user id (from the ref) rides the session-start metadata so the
3742
3823
  // server binds the session to a real user at creation. Reading the ref — not
3743
3824
  // `boundUserId` — keeps this memo off the userId dependency, so a mid-session change
@@ -3746,7 +3827,7 @@ var WireOnboarding = ({
3746
3827
  },
3747
3828
  timeoutMs: 6e4
3748
3829
  };
3749
- }, [config.serverUrl, config.appId, config.apiKey, metadataStable, sessionId, componentsStable, device, effectiveUserContext]);
3830
+ }, [config.serverUrl, config.appId, config.apiKey, metadataStable, sessionId, componentsStable, device, startupUserContext]);
3750
3831
  const reportTarget = useMemo(
3751
3832
  () => ({ serverUrl: config.serverUrl, apiKey: config.apiKey }),
3752
3833
  [config.serverUrl, config.apiKey]
@@ -3772,8 +3853,8 @@ var WireOnboarding = ({
3772
3853
  return /* @__PURE__ */ jsx(OnboardingThemeProvider, { theme, children: /* @__PURE__ */ jsx(
3773
3854
  LoadingScreen,
3774
3855
  {
3775
- title: (_b = copy == null ? void 0 : copy.restoringTitle) != null ? _b : DEFAULT_COPY.restoringTitle,
3776
- hint: (_c = copy == null ? void 0 : copy.restoringHint) != null ? _c : DEFAULT_COPY.restoringHint
3856
+ title: (_d = copy == null ? void 0 : copy.restoringTitle) != null ? _d : DEFAULT_COPY.restoringTitle,
3857
+ hint: (_e = copy == null ? void 0 : copy.restoringHint) != null ? _e : DEFAULT_COPY.restoringHint
3777
3858
  }
3778
3859
  ) });
3779
3860
  }
@@ -3811,7 +3892,7 @@ function DemoOnboarding({
3811
3892
  const [open, setOpen] = useState(false);
3812
3893
  const [runId, setRunId] = useState(0);
3813
3894
  const t = useMemo(() => mergeTheme(theme != null ? theme : {}), [theme]);
3814
- const styles18 = useMemo(() => makeStyles5(t), [t]);
3895
+ const styles17 = useMemo(() => makeStyles5(t), [t]);
3815
3896
  const openDemo = useCallback(() => {
3816
3897
  setRunId((n) => n + 1);
3817
3898
  setOpen(true);
@@ -3825,7 +3906,7 @@ function DemoOnboarding({
3825
3906
  [onComplete]
3826
3907
  );
3827
3908
  return /* @__PURE__ */ jsxs(Fragment, { children: [
3828
- renderTrigger ? renderTrigger(openDemo) : /* @__PURE__ */ jsx(Pressable, { onPress: openDemo, style: styles18.trigger, accessibilityRole: "button", children: /* @__PURE__ */ jsx(Text, { style: styles18.triggerText, children: label }) }),
3909
+ renderTrigger ? renderTrigger(openDemo) : /* @__PURE__ */ jsx(Pressable, { onPress: openDemo, style: styles17.trigger, accessibilityRole: "button", children: /* @__PURE__ */ jsx(Text, { style: styles17.triggerText, children: label }) }),
3829
3910
  /* @__PURE__ */ jsx(
3830
3911
  Modal,
3831
3912
  {
@@ -3833,7 +3914,7 @@ function DemoOnboarding({
3833
3914
  animationType: "slide",
3834
3915
  presentationStyle: "fullScreen",
3835
3916
  onRequestClose: close,
3836
- children: /* @__PURE__ */ jsx(View, { style: styles18.modal, children: config ? /* @__PURE__ */ jsx(
3917
+ children: /* @__PURE__ */ jsx(View, { style: styles17.modal, children: config ? /* @__PURE__ */ jsx(
3837
3918
  WireOnboarding,
3838
3919
  {
3839
3920
  config,
@@ -3846,10 +3927,10 @@ function DemoOnboarding({
3846
3927
  onError: close
3847
3928
  },
3848
3929
  runId
3849
- ) : /* @__PURE__ */ jsxs(View, { style: styles18.notice, children: [
3850
- /* @__PURE__ */ jsx(Text, { style: styles18.noticeTitle, children: "Wire AI not configured" }),
3851
- /* @__PURE__ */ jsx(Text, { style: styles18.noticeBody, children: "Set the Wire AI key + server URL (e.g. via wireConfigFromEnv) to demo the AI onboarding here." }),
3852
- /* @__PURE__ */ jsx(Pressable, { onPress: close, style: styles18.trigger, accessibilityRole: "button", children: /* @__PURE__ */ jsx(Text, { style: styles18.triggerText, children: "Close" }) })
3930
+ ) : /* @__PURE__ */ jsxs(View, { style: styles17.notice, children: [
3931
+ /* @__PURE__ */ jsx(Text, { style: styles17.noticeTitle, children: "Wire AI not configured" }),
3932
+ /* @__PURE__ */ jsx(Text, { style: styles17.noticeBody, children: "Set the Wire AI key + server URL (e.g. via wireConfigFromEnv) to demo the AI onboarding here." }),
3933
+ /* @__PURE__ */ jsx(Pressable, { onPress: close, style: styles17.trigger, accessibilityRole: "button", children: /* @__PURE__ */ jsx(Text, { style: styles17.triggerText, children: "Close" }) })
3853
3934
  ] }) })
3854
3935
  }
3855
3936
  )
@@ -3923,20 +4004,6 @@ var themeFromBrand = (input) => {
3923
4004
  button: { ...base.button, ...extra.button }
3924
4005
  };
3925
4006
  };
3926
- var _DoneBlock = ({
3927
- title = "You're all set",
3928
- message = "Personalizing your experience\u2026"
3929
- }) => {
3930
- const t = useOnboardingTheme();
3931
- return /* @__PURE__ */ jsxs(View, { style: [styles16.center, { gap: t.spacing.sm, padding: t.spacing.lg }], children: [
3932
- /* @__PURE__ */ jsx(Text, { style: [headingStyle(t.fonts), { color: t.colors.success, textAlign: "center" }], children: title }),
3933
- /* @__PURE__ */ jsx(Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted, textAlign: "center" }], children: message })
3934
- ] });
3935
- };
3936
- var DoneBlock = React18.memo(_DoneBlock);
3937
- var styles16 = StyleSheet.create({
3938
- center: { flex: 1, alignItems: "center", justifyContent: "center" }
3939
- });
3940
4007
 
3941
4008
  // src/motion/centeredModalMotion.ts
3942
4009
  var CENTERED_MODAL_ANIM = {
@@ -4013,25 +4080,25 @@ var _CenteredModal = forwardRef(
4013
4080
  /* @__PURE__ */ jsx(
4014
4081
  Animated.View,
4015
4082
  {
4016
- style: [styles17.backdrop, { opacity: backdropOpacity }],
4083
+ style: [styles16.backdrop, { opacity: backdropOpacity }],
4017
4084
  pointerEvents: "none"
4018
4085
  }
4019
4086
  ),
4020
4087
  /* @__PURE__ */ jsx(
4021
4088
  Pressable,
4022
4089
  {
4023
- style: styles17.fill,
4090
+ style: styles16.fill,
4024
4091
  onPress: onBackdrop,
4025
4092
  accessibilityRole: "button",
4026
4093
  accessibilityLabel: "Dismiss",
4027
- children: /* @__PURE__ */ jsx(SafeAreaView, { style: styles17.center, pointerEvents: "box-none", children: /* @__PURE__ */ jsx(Pressable, { onPress: () => {
4028
- }, style: styles17.cardWrap, children: /* @__PURE__ */ jsx(
4094
+ children: /* @__PURE__ */ jsx(SafeAreaView, { style: styles16.center, pointerEvents: "box-none", children: /* @__PURE__ */ jsx(Pressable, { onPress: () => {
4095
+ }, style: styles16.cardWrap, children: /* @__PURE__ */ jsx(
4029
4096
  Animated.View,
4030
4097
  {
4031
4098
  accessibilityViewIsModal: true,
4032
4099
  accessibilityRole: "alert",
4033
4100
  style: [
4034
- styles17.card,
4101
+ styles16.card,
4035
4102
  {
4036
4103
  backgroundColor: theme.colors.surface,
4037
4104
  borderRadius: theme.radius.lg,
@@ -4053,7 +4120,7 @@ var _CenteredModal = forwardRef(
4053
4120
  );
4054
4121
  _CenteredModal.displayName = "CenteredModal";
4055
4122
  var CenteredModal = React18.memo(_CenteredModal);
4056
- var styles17 = StyleSheet.create({
4123
+ var styles16 = StyleSheet.create({
4057
4124
  fill: { flex: 1 },
4058
4125
  backdrop: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, backgroundColor: "#000" },
4059
4126
  center: { flex: 1, alignItems: "center", justifyContent: "center", padding: 24 },
@@ -4239,11 +4306,6 @@ var WIRE_ENV_VARS = [
4239
4306
  "EXPO_PUBLIC_WIREAI_SERVER_URL",
4240
4307
  "EXPO_PUBLIC_WIREAI_APP_ID"
4241
4308
  ];
4242
- var warnInDev3 = (message) => {
4243
- if (typeof __DEV__ !== "undefined" && __DEV__ && typeof console !== "undefined" && console.warn) {
4244
- console.warn(message);
4245
- }
4246
- };
4247
4309
  var wireConfigFromEnv = (overrides) => {
4248
4310
  var _a2, _b, _c, _d, _e;
4249
4311
  const apiKey = (_a2 = overrides == null ? void 0 : overrides.apiKey) != null ? _a2 : process.env.EXPO_PUBLIC_WIREAI_API_KEY;
@@ -4254,7 +4316,7 @@ var wireConfigFromEnv = (overrides) => {
4254
4316
  apiKey ? void 0 : "EXPO_PUBLIC_WIREAI_API_KEY",
4255
4317
  serverUrl ? void 0 : "EXPO_PUBLIC_WIREAI_SERVER_URL"
4256
4318
  ].filter(Boolean);
4257
- warnInDev3(
4319
+ warnInDev(
4258
4320
  `[wireai] wireConfigFromEnv() returned null: ${missing.join(" and ")} ${missing.length > 1 ? "are" : "is"} missing. The kit is now FULLY DISABLED (no onboarding, no analytics, no gates) and nothing else will report an error. Set the var(s) in your env / EAS secrets.`
4259
4321
  );
4260
4322
  return null;
@@ -4362,15 +4424,17 @@ var resetActivationRevalidation = () => {
4362
4424
  };
4363
4425
 
4364
4426
  // src/activation/wireActivation.ts
4365
- var clean = (value) => {
4366
- if (typeof value !== "string") return void 0;
4367
- const trimmed = value.trim();
4368
- return trimmed.length > 0 ? trimmed : void 0;
4369
- };
4427
+ var clean = cleanString;
4370
4428
  var createWireActivation = (config) => {
4371
4429
  var _a2, _b;
4372
4430
  const target = { serverUrl: config.serverUrl, apiKey: config.apiKey };
4373
4431
  const explicitDeviceKey = (_b = clean(config.deviceKey)) != null ? _b : clean((_a2 = config.userContext) == null ? void 0 : _a2.deviceKey);
4432
+ resolveIdentity({
4433
+ value: explicitDeviceKey,
4434
+ space: "device",
4435
+ source: "host",
4436
+ scope: config.appId
4437
+ });
4374
4438
  const autoDeviceKeyOptions = {
4375
4439
  appId: config.appId,
4376
4440
  // An explicit key opts out of minting AND persisting (unchanged contract).
@@ -4804,6 +4868,30 @@ var DEFAULTS = {
4804
4868
  maxRetries: 6
4805
4869
  };
4806
4870
  var READ_TIMEOUT_MS4 = 1500;
4871
+ var QUEUE_KEY_SLOT = /* @__PURE__ */ Symbol.for("@wireai/activation:eventQueueKeys");
4872
+ var queueKeyGlobal = globalThis;
4873
+ var claimedQueueKeys = () => {
4874
+ const existing = queueKeyGlobal[QUEUE_KEY_SLOT];
4875
+ if (existing) return existing;
4876
+ const created = /* @__PURE__ */ new Set();
4877
+ queueKeyGlobal[QUEUE_KEY_SLOT] = created;
4878
+ return created;
4879
+ };
4880
+ var claimQueueKey = (preferred, explicit) => {
4881
+ const claimed = claimedQueueKeys();
4882
+ if (explicit || !claimed.has(preferred)) {
4883
+ claimed.add(preferred);
4884
+ return preferred;
4885
+ }
4886
+ let ordinal = 2;
4887
+ while (claimed.has(`${preferred}#${ordinal}`)) ordinal++;
4888
+ const key = `${preferred}#${ordinal}`;
4889
+ claimed.add(key);
4890
+ warnInDev(
4891
+ `[wireai] a second event queue was created for the same appId, and "${preferred}" is already claimed by a live one. Two queues sharing one storage slot overwrite each other's backlog, delete each other's pending events when one drains to empty, and double-send on relaunch, so this queue was given "${key}" instead. Prefer ONE analytics instance per app; if you really need two, pass an explicit \`storageKey\` to each so the slots are yours to reason about.`
4892
+ );
4893
+ return key;
4894
+ };
4807
4895
  var withTimeout4 = (p, ms) => {
4808
4896
  let timer;
4809
4897
  const timeout = new Promise((resolve) => {
@@ -4835,7 +4923,8 @@ var createEventQueue = (options) => {
4835
4923
  var _a2, _b, _c, _d, _e, _f, _g;
4836
4924
  const target = options.target;
4837
4925
  const storage = options.storage;
4838
- const key = (_b = options.storageKey) != null ? _b : `wireai:evtq:${(_a2 = options.appId) != null ? _a2 : "default"}`;
4926
+ const defaultKey = (_b = options.storageKey) != null ? _b : `wireai:evtq:${(_a2 = options.appId) != null ? _a2 : "default"}`;
4927
+ const key = storage ? claimQueueKey(defaultKey, options.storageKey !== void 0) : defaultKey;
4839
4928
  const maxSize = (_c = options.maxSize) != null ? _c : DEFAULTS.maxSize;
4840
4929
  const batchSize = (_d = options.batchSize) != null ? _d : DEFAULTS.batchSize;
4841
4930
  const baseBackoffMs = (_e = options.baseBackoffMs) != null ? _e : DEFAULTS.baseBackoffMs;
@@ -5025,7 +5114,13 @@ var useLifecycleEvents = (config, options = {}) => {
5025
5114
  return (cfg == null ? void 0 : cfg.serverUrl) ? { serverUrl: cfg.serverUrl, apiKey: (_a2 = cfg.apiKey) != null ? _a2 : "" } : void 0;
5026
5115
  };
5027
5116
  const resolveDeviceKey = (cfg, opts) => {
5028
- const host = typeof opts.deviceKey === "string" && opts.deviceKey.trim() ? opts.deviceKey : void 0;
5117
+ var _a2;
5118
+ const host = (_a2 = resolveIdentity({
5119
+ value: opts.deviceKey,
5120
+ space: "device",
5121
+ source: "host",
5122
+ scope: cfg == null ? void 0 : cfg.appId
5123
+ })) == null ? void 0 : _a2.value;
5029
5124
  if (host) return host;
5030
5125
  if (!(cfg == null ? void 0 : cfg.storage)) return void 0;
5031
5126
  return resolveAutoDeviceKey({ appId: cfg.appId, storage: cfg.storage });
@@ -5103,6 +5198,6 @@ var useLifecycleEvents = (config, options = {}) => {
5103
5198
  }, []);
5104
5199
  };
5105
5200
 
5106
- export { AUTO_DEVICE_ID_PREFIX, AnimatedSparkle, BACKGROUND_SESSION_MS, CardGridSelectCard, CardHandoff, CenteredModal, ChipSelectCard, CompletionView, DEFAULT_FEATURES_TTL_MS, DEFAULT_SESSION_TTL_MS, DemoOnboarding, DoneBlock, EXTRA_KEY_PREFIX, ErrorBlock, FIRST_OPEN_EVENT, IconRegistryProvider, IllustrationProvider, InterstitialCard, LoadingBlock, LoadingScreen, NumberStepperCard, Button as OnboardingButton, OnboardingFlow, OnboardingScaffold, OnboardingThemeProvider, PLAN_TIER_CONTEXT_KEY, RESERVED_USER_CONTEXT_KEYS, SESSION_STARTED_EVENT, SelectionCard, StatusCard, StepProgress, TextInputCard, USER_ID_MAX_LENGTH, WIRE_ENV_VARS, WIRE_ICON_GLYPHS, WIRE_ICON_NAMES, WIRE_ONBOARDING_EVENTS, WIRE_PURCHASE_EVENTS, WireFeaturesProvider, WireIcon, WireOnboarding, activationJoinContext, activeEntitlement, analyticsUserIdStorageKey, attributionMetadata, bumpActivationRevalidation, clearPersistedSession, clearPiiFromContext, clearUserContext, collectDeviceContext, createRevenueCatBridge, createWireActivation, defaultIllustrations, defaultOnboardingTheme, defaultWireFeatures, deriveAnswers, describeEntitlement, describeFailure, describePackage, detectAppVersion, detectNativeModel, deviceIdStorageKey, ensureCurrentSessionId, featuresCacheKey, featuresEqual, fetchWireFeatures, firstOpenStorageKey, getActivationRevalidationVersion, getCurrentSessionId, hashEmailFnv1a, hydrateAutoDeviceKey, identifyOnboarding, isFeaturesFresh, isOnboardingEnabled, isUserCancelled, isWireScalar, loadPersistedSession, looksLikeEmail, lookupIconGlyph, makeSessionId, mergeTheme, mintDeviceId, motionSpec_exports as motionSpec, namespaceExtra, onboardingComponents, parseWireFeatures, peekPersistedSession, readCachedFeatures, readProgress, reportClientEvent, reportClientEventAwait, reportClientEvents, reportClientEventsAwait, reportFirstOpen, reportSessionStart, resetActivationRevalidation, resetAutoDeviceKeys, resetCurrentSessionId, resetFirstOpenLatch, resetSessionStartGuard, resolveAutoDeviceKey, resolvePlanTier, resolveUserContext, sanitizeUserId, savePersistedSession, sessionStorageKey, setCurrentSessionId, subscribeActivationRevalidation, themeFromBrand, toAnalyticsEvent, useActivationRevalidation, useHostIcon, useIllustration, useLifecycleEvents, useOnboardingTheme, useReducedMotion, useResolvedFeatures, useSessionStart, useWireActivation, useWireFeatures, useWireFeaturesContext, wireConfigFromEnv, wireLifecycleEvents, writeCachedFeatures };
5201
+ export { AUTO_DEVICE_ID_PREFIX, AnimatedSparkle, BACKGROUND_SESSION_MS, CardGridSelectCard, CardHandoff, CenteredModal, ChipSelectCard, CompletionView, DEFAULT_FEATURES_TTL_MS, DEFAULT_SESSION_TTL_MS, DemoOnboarding, EXTRA_KEY_PREFIX, ErrorBlock, FIRST_OPEN_EVENT, IconRegistryProvider, IllustrationProvider, InterstitialCard, LoadingBlock, LoadingScreen, NumberStepperCard, Button as OnboardingButton, OnboardingFlow, OnboardingScaffold, OnboardingThemeProvider, PLAN_TIER_CONTEXT_KEY, SESSION_STARTED_EVENT, SelectionCard, StatusCard, StepProgress, TextInputCard, USER_ID_MAX_LENGTH, WIRE_ENV_VARS, WIRE_ICON_GLYPHS, WIRE_ICON_NAMES, WIRE_ONBOARDING_EVENTS, WIRE_PURCHASE_EVENTS, WireFeaturesProvider, WireIcon, WireOnboarding, activationJoinContext, activeEntitlement, analyticsUserIdStorageKey, attributionMetadata, bumpActivationRevalidation, clearPersistedSession, clearPiiFromContext, clearUserContext, collectDeviceContext, createRevenueCatBridge, createWireActivation, defaultIllustrations, defaultOnboardingTheme, defaultWireFeatures, deriveAnswers, describeEntitlement, describeFailure, describePackage, detectAppVersion, detectNativeModel, deviceIdStorageKey, ensureCurrentSessionId, featuresCacheKey, featuresEqual, fetchWireFeatures, firstOpenStorageKey, getActivationRevalidationVersion, getCurrentSessionId, hashEmailFnv1a, hostIdentity, hydrateAutoDeviceKey, hydrateDeviceIdentity, identifyOnboarding, isFeaturesFresh, isOnboardingEnabled, isUserCancelled, isWireScalar, loadPersistedSession, looksLikeEmail, lookupIconGlyph, makeSessionId, mergeTheme, mintDeviceId, motionSpec_exports as motionSpec, namespaceExtra, onboardingComponents, parseWireFeatures, peekPersistedSession, readCachedFeatures, readProgress, reportClientEvent, reportClientEventAwait, reportClientEvents, reportClientEventsAwait, reportFirstOpen, reportSessionStart, resetActivationRevalidation, resetAutoDeviceKeys, resetCurrentSessionId, resetFirstOpenLatch, resetIdentityProvenance, resetSessionStartGuard, resolveAutoDeviceKey, resolveIdentity, resolvePlanTier, resolveUserContext, sanitizeUserId, savePersistedSession, sessionStorageKey, setCurrentSessionId, subscribeActivationRevalidation, themeFromBrand, toAnalyticsEvent, useActivationRevalidation, useHostIcon, useIllustration, useLifecycleEvents, useOnboardingTheme, useReducedMotion, useResolvedFeatures, useSessionStart, useWireActivation, useWireFeatures, useWireFeaturesContext, wireConfigFromEnv, wireLifecycleEvents, writeCachedFeatures };
5107
5202
  //# sourceMappingURL=index.mjs.map
5108
5203
  //# sourceMappingURL=index.mjs.map