@wireai/activation 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/AGENTS.md +51 -0
  2. package/CHANGELOG.md +111 -1
  3. package/INTEGRATION_PROMPT.md +13 -1
  4. package/README.md +106 -4
  5. package/dist/analytics/index.d.mts +35 -6
  6. package/dist/analytics/index.d.ts +35 -6
  7. package/dist/analytics/index.js +222 -94
  8. package/dist/analytics/index.js.map +1 -1
  9. package/dist/analytics/index.mjs +214 -95
  10. package/dist/analytics/index.mjs.map +1 -1
  11. package/dist/{currentSession-D0Vq7_VE.d.ts → currentSession-D6RiVtc8.d.ts} +187 -29
  12. package/dist/{currentSession-DdDkprpM.d.mts → currentSession-DsSDHqor.d.mts} +187 -29
  13. package/dist/index.d.mts +236 -82
  14. package/dist/index.d.ts +236 -82
  15. package/dist/index.js +330 -60
  16. package/dist/index.js.map +1 -1
  17. package/dist/index.mjs +314 -61
  18. package/dist/index.mjs.map +1 -1
  19. package/dist/questionnaire/index.d.mts +1 -1
  20. package/dist/questionnaire/index.d.ts +1 -1
  21. package/dist/questionnaire/index.js +59 -8
  22. package/dist/questionnaire/index.js.map +1 -1
  23. package/dist/questionnaire/index.mjs +59 -8
  24. package/dist/questionnaire/index.mjs.map +1 -1
  25. package/dist/reviews/index.d.mts +2 -2
  26. package/dist/reviews/index.d.ts +2 -2
  27. package/dist/reviews/index.js +97 -17
  28. package/dist/reviews/index.js.map +1 -1
  29. package/dist/reviews/index.mjs +97 -17
  30. package/dist/reviews/index.mjs.map +1 -1
  31. package/dist/{transport-Bzb-bcB2.d.mts → transport-CF_eHwzC.d.mts} +15 -16
  32. package/dist/{transport-B31G0Cib.d.ts → transport-DsRe4epC.d.ts} +15 -16
  33. package/llms.txt +9 -0
  34. package/package.json +1 -1
  35. package/src/activation/useWireActivation.ts +12 -1
  36. package/src/activation/wireActivation.ts +36 -24
  37. package/src/analytics/analyticsFacade.ts +113 -29
  38. package/src/analytics/currentSession.ts +83 -0
  39. package/src/analytics/eventQueue.ts +20 -11
  40. package/src/analytics/index.ts +29 -1
  41. package/src/analytics/reportClientEvent.ts +50 -2
  42. package/src/analytics/screenTracking.ts +6 -1
  43. package/src/analytics/useAnalytics.ts +22 -1
  44. package/src/context/deviceId.ts +109 -0
  45. package/src/context/userContext.ts +73 -0
  46. package/src/identity/userIdentity.ts +10 -0
  47. package/src/index.ts +50 -2
  48. package/src/questionnaire/runtime.ts +12 -2
  49. package/src/questionnaire/transport.ts +5 -1
  50. package/src/questionnaire/useQuestionnaireGate.ts +9 -7
  51. package/src/revenuecat/index.ts +55 -0
  52. package/src/revenuecat/purchaseEvents.ts +167 -0
  53. package/src/revenuecat/revenueCatBridge.ts +221 -0
  54. package/src/revenuecat/types.ts +95 -0
  55. package/src/reviews/decision.ts +8 -1
  56. package/src/reviews/runtime.ts +92 -1
  57. package/src/reviews/transport.ts +27 -10
  58. package/src/reviews/useReviewGate.ts +12 -7
  59. package/src/session-analytics/lifecycle.ts +15 -13
  60. package/src/session-analytics/reportSessionStart.ts +15 -4
  61. package/src/session-analytics/useLifecycleEvents.ts +68 -28
@@ -400,27 +400,113 @@ var init_Constants = __esm({
400
400
  }
401
401
  });
402
402
 
403
- // src/reviews/transport.ts
404
- var reportAppEvent = (target, name2, options = {}) => {
405
- if (!(target == null ? void 0 : target.serverUrl) || !name2) return;
403
+ // src/analytics/reportClientEvent.ts
404
+ var makeSessionId = () => `wire_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
405
+ var buildEventsRequest = (target, events) => {
406
+ if (!(target == null ? void 0 : target.serverUrl) || events.length === 0) return null;
406
407
  try {
407
408
  const url = `${target.serverUrl.replace(/\/$/, "")}/v1/events`;
408
409
  const headers = { "Content-Type": "application/json" };
409
410
  if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
411
+ return { url, init: { method: "POST", headers, body: JSON.stringify({ events }) } };
412
+ } catch {
413
+ return null;
414
+ }
415
+ };
416
+ var warnOnSkippedEvents = (res) => {
417
+ try {
418
+ const json = res == null ? void 0 : res.json;
419
+ if (typeof json !== "function") return;
420
+ void Promise.resolve(json.call(res)).then((body) => {
421
+ const skipped = body == null ? void 0 : body.skipped;
422
+ if (typeof skipped !== "number" || skipped <= 0) return;
423
+ if (typeof __DEV__ !== "undefined" && __DEV__ && typeof console !== "undefined" && console.warn) {
424
+ console.warn(
425
+ `[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.`
426
+ );
427
+ }
428
+ }).catch(() => {
429
+ });
430
+ } catch {
431
+ }
432
+ };
433
+ var reportClientEvents = (target, events) => {
434
+ try {
435
+ const req = buildEventsRequest(target, events);
436
+ if (!req) return;
437
+ void fetch(req.url, req.init).catch(() => {
438
+ });
439
+ } catch {
440
+ }
441
+ };
442
+ var reportClientEvent = (target, event) => reportClientEvents(target, [event]);
443
+ var reportClientEventsAwait = async (target, events) => {
444
+ try {
445
+ const req = buildEventsRequest(target, events);
446
+ if (!req) return false;
447
+ const res = await fetch(req.url, req.init);
448
+ return Boolean(res && res.ok);
449
+ } catch {
450
+ return false;
451
+ }
452
+ };
453
+ var reportClientEventAwait = (target, event) => reportClientEventsAwait(target, [event]);
454
+
455
+ // src/analytics/currentSession.ts
456
+ var CURRENT_SESSION_ID_SLOT = /* @__PURE__ */ Symbol.for(
457
+ "@wireai/activation:currentSessionId"
458
+ );
459
+ var globalSlot = globalThis;
460
+ var setCurrentSessionId = (id) => {
461
+ if (typeof id === "string" && id.length > 0) {
462
+ globalSlot[CURRENT_SESSION_ID_SLOT] = id;
463
+ }
464
+ };
465
+ var getCurrentSessionId = () => globalSlot[CURRENT_SESSION_ID_SLOT];
466
+ var resetCurrentSessionId = () => {
467
+ globalSlot[CURRENT_SESSION_ID_SLOT] = void 0;
468
+ };
469
+ var warnInDev = (message) => {
470
+ if (typeof __DEV__ !== "undefined" && __DEV__ && typeof console !== "undefined" && console.warn) {
471
+ console.warn(message);
472
+ return true;
473
+ }
474
+ return false;
475
+ };
476
+ 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.";
477
+ var MINT_WARNED_SLOT = /* @__PURE__ */ Symbol.for(
478
+ "@wireai/activation:currentSessionIdMintWarned"
479
+ );
480
+ var warnSlot = globalThis;
481
+ var ensureCurrentSessionId = () => {
482
+ const existing = globalSlot[CURRENT_SESSION_ID_SLOT];
483
+ if (typeof existing === "string" && existing.length > 0) return existing;
484
+ const minted = makeSessionId();
485
+ globalSlot[CURRENT_SESSION_ID_SLOT] = minted;
486
+ if (!warnSlot[MINT_WARNED_SLOT] && warnInDev(MINT_WARNING)) {
487
+ warnSlot[MINT_WARNED_SLOT] = true;
488
+ }
489
+ return minted;
490
+ };
491
+
492
+ // src/reviews/transport.ts
493
+ var reportAppEvent = (target, name2, options = {}) => {
494
+ var _a2;
495
+ if (!(target == null ? void 0 : target.serverUrl) || !name2) return;
496
+ try {
497
+ const supplied = (_a2 = options.sessionId) != null ? _a2 : "";
410
498
  const event = {
411
499
  event_type: "app_event",
412
- question_key: name2
500
+ question_key: name2,
501
+ session_id: supplied.trim().length > 0 ? supplied : ensureCurrentSessionId()
413
502
  };
414
- if (options.sessionId) event.session_id = options.sessionId;
415
503
  if (options.deviceKey) event.user_context = { device_key: options.deviceKey };
416
504
  if (options.meta && Object.keys(options.meta).length > 0) {
417
505
  event.meta = JSON.stringify(options.meta);
418
506
  }
419
- void fetch(url, {
420
- method: "POST",
421
- headers,
422
- body: JSON.stringify({ events: [event] })
423
- }).catch(() => {
507
+ const req = buildEventsRequest(target, [event]);
508
+ if (!req) return;
509
+ void fetch(req.url, req.init).catch(() => {
424
510
  });
425
511
  } catch {
426
512
  }
@@ -497,41 +583,6 @@ var useScreenTracking = (navigationRef, options = {}) => {
497
583
  }, [navigationRef]);
498
584
  };
499
585
 
500
- // src/analytics/reportClientEvent.ts
501
- var makeSessionId = () => `wire_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
502
- var buildEventsRequest = (target, events) => {
503
- if (!(target == null ? void 0 : target.serverUrl) || events.length === 0) return null;
504
- try {
505
- const url = `${target.serverUrl.replace(/\/$/, "")}/v1/events`;
506
- const headers = { "Content-Type": "application/json" };
507
- if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
508
- return { url, init: { method: "POST", headers, body: JSON.stringify({ events }) } };
509
- } catch {
510
- return null;
511
- }
512
- };
513
- var reportClientEvents = (target, events) => {
514
- try {
515
- const req = buildEventsRequest(target, events);
516
- if (!req) return;
517
- void fetch(req.url, req.init).catch(() => {
518
- });
519
- } catch {
520
- }
521
- };
522
- var reportClientEvent = (target, event) => reportClientEvents(target, [event]);
523
- var reportClientEventsAwait = async (target, events) => {
524
- try {
525
- const req = buildEventsRequest(target, events);
526
- if (!req) return false;
527
- const res = await fetch(req.url, req.init);
528
- return Boolean(res && res.ok);
529
- } catch {
530
- return false;
531
- }
532
- };
533
- var reportClientEventAwait = (target, event) => reportClientEventsAwait(target, [event]);
534
-
535
586
  // src/analytics/analyticsEvent.ts
536
587
  var WIRE_ONBOARDING_EVENTS = {
537
588
  started: "wire_onboarding_started",
@@ -822,6 +873,7 @@ var createEventQueue = (options) => {
822
873
  var _a3;
823
874
  const env = resolveEnvelope();
824
875
  const stamped = { ...event };
876
+ if (stamped.ts === void 0) stamped.ts = Date.now();
825
877
  if (!env) return stamped;
826
878
  if (!stamped.device && env.device) stamped.device = env.device;
827
879
  if (!stamped.session_id && env.sessionId) stamped.session_id = env.sessionId;
@@ -877,19 +929,13 @@ var createEventQueue = (options) => {
877
929
  }
878
930
  })();
879
931
  const postBatch = async (events) => {
880
- if (!(target == null ? void 0 : target.serverUrl) || events.length === 0) return false;
932
+ const req = buildEventsRequest(target, events);
933
+ if (!req) return false;
881
934
  const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
882
935
  const timer = setTimeout(() => controller == null ? void 0 : controller.abort(), 15e3);
883
936
  try {
884
- const url = `${target.serverUrl.replace(/\/$/, "")}/v1/events`;
885
- const headers = { "Content-Type": "application/json" };
886
- if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
887
- const res = await fetch(url, {
888
- method: "POST",
889
- headers,
890
- body: JSON.stringify({ events }),
891
- signal: controller == null ? void 0 : controller.signal
892
- });
937
+ const res = await fetch(req.url, { ...req.init, signal: controller == null ? void 0 : controller.signal });
938
+ warnOnSkippedEvents(res);
893
939
  return !!(res && res.ok);
894
940
  } catch {
895
941
  return false;
@@ -968,21 +1014,6 @@ var createEventQueue = (options) => {
968
1014
  return { enqueue, flush, notifyOnline, size };
969
1015
  };
970
1016
 
971
- // src/analytics/currentSession.ts
972
- var CURRENT_SESSION_ID_SLOT = /* @__PURE__ */ Symbol.for(
973
- "@wireai/activation:currentSessionId"
974
- );
975
- var globalSlot = globalThis;
976
- var setCurrentSessionId = (id) => {
977
- if (typeof id === "string" && id.length > 0) {
978
- globalSlot[CURRENT_SESSION_ID_SLOT] = id;
979
- }
980
- };
981
- var getCurrentSessionId = () => globalSlot[CURRENT_SESSION_ID_SLOT];
982
- var resetCurrentSessionId = () => {
983
- globalSlot[CURRENT_SESSION_ID_SLOT] = void 0;
984
- };
985
-
986
1017
  // src/identity/userIdentity.ts
987
1018
  var USER_ID_MAX_LENGTH = 128;
988
1019
  var sanitizeUserId = (raw) => {
@@ -991,6 +1022,7 @@ var sanitizeUserId = (raw) => {
991
1022
  if (!trimmed) return void 0;
992
1023
  return trimmed.length > USER_ID_MAX_LENGTH ? trimmed.slice(0, USER_ID_MAX_LENGTH) : trimmed;
993
1024
  };
1025
+ var looksLikeEmail = (value) => typeof value === "string" && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value.trim());
994
1026
 
995
1027
  // src/context/userContext.ts
996
1028
  var EXTRA_KEY_PREFIX = "custom.";
@@ -1025,6 +1057,21 @@ var namespaceExtra = (extra) => {
1025
1057
  }
1026
1058
  return out;
1027
1059
  };
1060
+ var analyticsUserIdStorageKey = (appId) => `wireai:analytics:userId:${appId != null ? appId : "default"}`;
1061
+ var clearPiiFromContext = (ctx = {}) => {
1062
+ const rest = {};
1063
+ if (typeof ctx.appVersion === "string") rest.appVersion = ctx.appVersion;
1064
+ if (typeof ctx.deviceKey === "string") rest.deviceKey = ctx.deviceKey;
1065
+ return rest;
1066
+ };
1067
+ var clearUserContext = async (opts = {}) => {
1068
+ const storage = opts.storage;
1069
+ if (!storage) return;
1070
+ try {
1071
+ await storage.removeItem(analyticsUserIdStorageKey(opts.appId));
1072
+ } catch {
1073
+ }
1074
+ };
1028
1075
  var resolveUserContext = (ctx = {}, opts = {}) => {
1029
1076
  var _a2;
1030
1077
  const result = {};
@@ -1063,29 +1110,75 @@ var mintDeviceId = () => {
1063
1110
  const time = Date.now().toString(36);
1064
1111
  return `${AUTO_DEVICE_ID_PREFIX}${time}_${randomChunk()}${randomChunk()}`;
1065
1112
  };
1113
+ var AUTO_DEVICE_KEY_SLOT = /* @__PURE__ */ Symbol.for("@wireai/activation:autoDeviceKeys");
1114
+ var deviceKeyGlobal = globalThis;
1115
+ var autoDeviceKeyRegistry = () => {
1116
+ const existing = deviceKeyGlobal[AUTO_DEVICE_KEY_SLOT];
1117
+ if (existing) return existing;
1118
+ const created = { keys: /* @__PURE__ */ new Map(), hydrating: /* @__PURE__ */ new Set() };
1119
+ deviceKeyGlobal[AUTO_DEVICE_KEY_SLOT] = created;
1120
+ return created;
1121
+ };
1122
+ var resolveAutoDeviceKey = (opts = {}) => {
1123
+ var _a2, _b;
1124
+ const registry = autoDeviceKeyRegistry();
1125
+ const appId = (_a2 = opts.appId) != null ? _a2 : "default";
1126
+ let id = registry.keys.get(appId);
1127
+ if (!id) {
1128
+ id = mintDeviceId();
1129
+ registry.keys.set(appId, id);
1130
+ }
1131
+ const storage = opts.storage;
1132
+ if (storage && !registry.hydrating.has(appId)) {
1133
+ registry.hydrating.add(appId);
1134
+ const slot = deviceIdStorageKey(appId);
1135
+ const minted = id;
1136
+ try {
1137
+ void Promise.resolve(storage.getItem(slot)).then((saved) => {
1138
+ const persisted = typeof saved === "string" && saved.trim() ? saved.trim() : void 0;
1139
+ if (persisted) registry.keys.set(appId, persisted);
1140
+ else void Promise.resolve(storage.setItem(slot, minted)).catch(() => {
1141
+ });
1142
+ }).catch(() => {
1143
+ });
1144
+ } catch {
1145
+ }
1146
+ }
1147
+ return (_b = registry.keys.get(appId)) != null ? _b : id;
1148
+ };
1149
+ var resetAutoDeviceKeys = () => {
1150
+ const registry = autoDeviceKeyRegistry();
1151
+ registry.keys.clear();
1152
+ registry.hydrating.clear();
1153
+ };
1066
1154
 
1067
1155
  // src/analytics/analyticsFacade.ts
1156
+ var warnInDev2 = (message) => {
1157
+ if (typeof __DEV__ !== "undefined" && __DEV__ && typeof console !== "undefined" && console.warn) {
1158
+ console.warn(message);
1159
+ }
1160
+ };
1068
1161
  var createAnalytics = (config, options = {}) => {
1069
- var _a2, _b, _c, _d, _e;
1162
+ var _a2, _b, _c, _d;
1070
1163
  const instanceSessionId = (_a2 = config.sessionId) != null ? _a2 : makeSessionId();
1071
1164
  const resolveSessionId = () => {
1072
1165
  var _a3, _b2;
1073
1166
  return (_b2 = (_a3 = config.sessionId) != null ? _a3 : getCurrentSessionId()) != null ? _b2 : instanceSessionId;
1074
1167
  };
1168
+ if (config.sessionId) {
1169
+ warnInDev2(
1170
+ "[wireai] createAnalytics({ sessionId }) PINS every event from this instance to that one frozen id and opts out of the live per-open session (app.session_started) \u2014 lifecycle analytics collapse onto a single device-scoped id. Remove it unless your host runs its own session lifecycle."
1171
+ );
1172
+ }
1173
+ const detectedAppVersion = detectAppVersion();
1075
1174
  let userContext = { ...(_b = config.userContext) != null ? _b : {} };
1076
1175
  const hostDeviceKeyAtInit = typeof ((_c = config.userContext) == null ? void 0 : _c.deviceKey) === "string" && config.userContext.deviceKey.trim() ? config.userContext.deviceKey.trim() : void 0;
1077
- let autoDeviceKey = mintDeviceId();
1078
- if (config.storage && !hostDeviceKeyAtInit) {
1079
- const storage = config.storage;
1080
- const deviceKey = deviceIdStorageKey(config.appId);
1081
- void storage.getItem(deviceKey).then((saved) => {
1082
- const persisted = typeof saved === "string" && saved.trim() ? saved.trim() : void 0;
1083
- if (persisted) autoDeviceKey = persisted;
1084
- else void storage.setItem(deviceKey, autoDeviceKey).catch(() => {
1085
- });
1086
- }).catch(() => {
1087
- });
1088
- }
1176
+ const autoDeviceKeyOptions = {
1177
+ appId: config.appId,
1178
+ // A host-supplied deviceKey opts out of minting AND persisting (unchanged contract).
1179
+ storage: hostDeviceKeyAtInit ? void 0 : config.storage
1180
+ };
1181
+ if (!hostDeviceKeyAtInit) resolveAutoDeviceKey(autoDeviceKeyOptions);
1089
1182
  const envelope = () => {
1090
1183
  var _a3;
1091
1184
  return buildContextEnvelope({
@@ -1103,7 +1196,7 @@ var createAnalytics = (config, options = {}) => {
1103
1196
  ...options
1104
1197
  });
1105
1198
  let boundUserId = sanitizeUserId((_d = config.userContext) == null ? void 0 : _d.userId);
1106
- const storageKey = `wireai:analytics:userId:${(_e = config.appId) != null ? _e : "default"}`;
1199
+ const storageKey = analyticsUserIdStorageKey(config.appId);
1107
1200
  if (config.storage) {
1108
1201
  void config.storage.getItem(storageKey).then((saved) => {
1109
1202
  if (saved && !boundUserId) boundUserId = saved;
@@ -1111,17 +1204,25 @@ var createAnalytics = (config, options = {}) => {
1111
1204
  });
1112
1205
  }
1113
1206
  const applyContext = (event) => {
1114
- var _a3;
1207
+ var _a3, _b2;
1115
1208
  const hostDeviceKey = typeof userContext.deviceKey === "string" && userContext.deviceKey.trim() ? userContext.deviceKey : void 0;
1116
1209
  const resolved = resolveUserContext(
1117
- { ...userContext, deviceKey: hostDeviceKey != null ? hostDeviceKey : autoDeviceKey },
1118
- { autoAppVersion: config.appVersion }
1210
+ // `??` is lazy on purpose: a host-supplied key must never even touch the auto registry.
1211
+ { ...userContext, deviceKey: hostDeviceKey != null ? hostDeviceKey : resolveAutoDeviceKey(autoDeviceKeyOptions) },
1212
+ { autoAppVersion: (_a3 = config.appVersion) != null ? _a3 : detectedAppVersion }
1119
1213
  );
1120
1214
  if (resolved.userContext) {
1121
- event.user_context = { ...resolved.userContext, ...(_a3 = event.user_context) != null ? _a3 : {} };
1215
+ event.user_context = { ...resolved.userContext, ...(_b2 = event.user_context) != null ? _b2 : {} };
1122
1216
  }
1123
1217
  if (boundUserId && !event.user_id) event.user_id = boundUserId;
1124
1218
  };
1219
+ const guardUserId = (clean) => {
1220
+ if (config.allowEmailAsUserId || !looksLikeEmail(clean)) return clean;
1221
+ warnInDev2(
1222
+ "[wireai] identify() was called with an email-shaped id. A raw email must NOT be the opaque user_id (PII leak) \u2014 pass it as userContext.userEmail instead. Binding was skipped. Set allowEmailAsUserId:true on createAnalytics if your user id genuinely is an email."
1223
+ );
1224
+ return void 0;
1225
+ };
1125
1226
  const setUserContext = (partial) => {
1126
1227
  var _a3, _b2;
1127
1228
  if (!partial || typeof partial !== "object") return;
@@ -1130,11 +1231,20 @@ var createAnalytics = (config, options = {}) => {
1130
1231
  if (mergedExtra) userContext.extra = mergedExtra;
1131
1232
  const uid = sanitizeUserId(partial.userId);
1132
1233
  if (uid) {
1133
- boundUserId = uid;
1134
- if (config.storage) void config.storage.setItem(storageKey, uid).catch(() => {
1135
- });
1234
+ const bindable = guardUserId(uid);
1235
+ if (bindable) {
1236
+ boundUserId = bindable;
1237
+ if (config.storage) void config.storage.setItem(storageKey, bindable).catch(() => {
1238
+ });
1239
+ }
1136
1240
  }
1137
1241
  };
1242
+ const reset = () => {
1243
+ boundUserId = void 0;
1244
+ userContext = clearPiiFromContext(userContext);
1245
+ if (config.storage) void config.storage.removeItem(storageKey).catch(() => {
1246
+ });
1247
+ };
1138
1248
  const track = (event, props) => {
1139
1249
  if (!event) return;
1140
1250
  const clientEvent = {
@@ -1161,9 +1271,11 @@ var createAnalytics = (config, options = {}) => {
1161
1271
  const identify = (userId, traits) => {
1162
1272
  const clean = sanitizeUserId(userId);
1163
1273
  if (!clean) return;
1164
- boundUserId = clean;
1274
+ const bindable = guardUserId(clean);
1275
+ if (!bindable) return;
1276
+ boundUserId = bindable;
1165
1277
  if (config.storage) {
1166
- void config.storage.setItem(storageKey, clean).catch(() => {
1278
+ void config.storage.setItem(storageKey, bindable).catch(() => {
1167
1279
  });
1168
1280
  }
1169
1281
  const clientEvent = {
@@ -1171,7 +1283,7 @@ var createAnalytics = (config, options = {}) => {
1171
1283
  // Reuse the LIVE per-open session id (see `resolveSessionId`) so the server binds identity to
1172
1284
  // the session it already saw instead of back-filling a phantom `session_started`.
1173
1285
  session_id: resolveSessionId(),
1174
- user_id: clean
1286
+ user_id: bindable
1175
1287
  };
1176
1288
  if (traits && Object.keys(traits).length > 0) clientEvent.meta = JSON.stringify(traits);
1177
1289
  applyContext(clientEvent);
@@ -1182,12 +1294,14 @@ var createAnalytics = (config, options = {}) => {
1182
1294
  screen,
1183
1295
  identify,
1184
1296
  setUserContext,
1297
+ reset,
1185
1298
  flush: queue.flush,
1186
1299
  notifyOnline: queue.notifyOnline,
1187
1300
  size: queue.size
1188
1301
  };
1189
1302
  };
1190
1303
  var useAnalytics = (config, options = {}) => {
1304
+ var _a2;
1191
1305
  const ref = react.useRef(void 0);
1192
1306
  const prevKeys = react.useRef("");
1193
1307
  const currentKeys = `${config.serverUrl}|${config.apiKey}|${config.appId}`;
@@ -1195,23 +1309,37 @@ var useAnalytics = (config, options = {}) => {
1195
1309
  prevKeys.current = currentKeys;
1196
1310
  ref.current = createAnalytics(config, options);
1197
1311
  }
1312
+ const hostDeviceKey = typeof ((_a2 = config.userContext) == null ? void 0 : _a2.deviceKey) === "string" && config.userContext.deviceKey.trim() ? config.userContext.deviceKey.trim() : void 0;
1313
+ react.useEffect(() => {
1314
+ var _a3;
1315
+ if (hostDeviceKey) (_a3 = ref.current) == null ? void 0 : _a3.setUserContext({ deviceKey: hostDeviceKey });
1316
+ }, [hostDeviceKey]);
1198
1317
  return ref.current;
1199
1318
  };
1200
1319
 
1320
+ exports.AUTO_DEVICE_ID_PREFIX = AUTO_DEVICE_ID_PREFIX;
1201
1321
  exports.WIRE_ONBOARDING_EVENTS = WIRE_ONBOARDING_EVENTS;
1322
+ exports.analyticsUserIdStorageKey = analyticsUserIdStorageKey;
1202
1323
  exports.buildContextEnvelope = buildContextEnvelope;
1324
+ exports.clearPiiFromContext = clearPiiFromContext;
1325
+ exports.clearUserContext = clearUserContext;
1203
1326
  exports.createAnalytics = createAnalytics;
1204
1327
  exports.createEventQueue = createEventQueue;
1205
1328
  exports.createScreenTracker = createScreenTracker;
1329
+ exports.deviceIdStorageKey = deviceIdStorageKey;
1330
+ exports.ensureCurrentSessionId = ensureCurrentSessionId;
1206
1331
  exports.getActiveRouteName = getActiveRouteName;
1207
1332
  exports.getCurrentSessionId = getCurrentSessionId;
1333
+ exports.looksLikeEmail = looksLikeEmail;
1208
1334
  exports.makeSessionId = makeSessionId;
1209
1335
  exports.reportAppEvent = reportAppEvent;
1210
1336
  exports.reportClientEvent = reportClientEvent;
1211
1337
  exports.reportClientEventAwait = reportClientEventAwait;
1212
1338
  exports.reportClientEvents = reportClientEvents;
1213
1339
  exports.reportClientEventsAwait = reportClientEventsAwait;
1340
+ exports.resetAutoDeviceKeys = resetAutoDeviceKeys;
1214
1341
  exports.resetCurrentSessionId = resetCurrentSessionId;
1342
+ exports.resolveAutoDeviceKey = resolveAutoDeviceKey;
1215
1343
  exports.screenTrackingHandler = screenTrackingHandler;
1216
1344
  exports.setCurrentSessionId = setCurrentSessionId;
1217
1345
  exports.toAnalyticsEvent = toAnalyticsEvent;