@wireai/activation 0.11.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 (58) hide show
  1. package/AGENTS.md +51 -0
  2. package/CHANGELOG.md +58 -3
  3. package/INTEGRATION_PROMPT.md +13 -1
  4. package/README.md +73 -0
  5. package/dist/analytics/index.d.mts +17 -6
  6. package/dist/analytics/index.d.ts +17 -6
  7. package/dist/analytics/index.js +130 -35
  8. package/dist/analytics/index.js.map +1 -1
  9. package/dist/analytics/index.mjs +126 -36
  10. package/dist/analytics/index.mjs.map +1 -1
  11. package/dist/{currentSession-DdnUq2HQ.d.ts → currentSession-D6RiVtc8.d.ts} +88 -30
  12. package/dist/{currentSession-C0_odnIW.d.mts → currentSession-DsSDHqor.d.mts} +88 -30
  13. package/dist/index.d.mts +236 -36
  14. package/dist/index.d.ts +236 -36
  15. package/dist/index.js +281 -20
  16. package/dist/index.js.map +1 -1
  17. package/dist/index.mjs +269 -21
  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 +49 -6
  22. package/dist/questionnaire/index.js.map +1 -1
  23. package/dist/questionnaire/index.mjs +49 -6
  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 +73 -8
  28. package/dist/reviews/index.js.map +1 -1
  29. package/dist/reviews/index.mjs +73 -8
  30. package/dist/reviews/index.mjs.map +1 -1
  31. package/dist/{transport-BGW9uXZJ.d.mts → transport-CF_eHwzC.d.mts} +15 -1
  32. package/dist/{transport-jUJd5kxu.d.ts → transport-DsRe4epC.d.ts} +15 -1
  33. package/llms.txt +1 -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 +41 -20
  38. package/src/analytics/currentSession.ts +83 -0
  39. package/src/analytics/eventQueue.ts +9 -1
  40. package/src/analytics/index.ts +20 -1
  41. package/src/analytics/reportClientEvent.ts +38 -0
  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 +18 -0
  46. package/src/index.ts +45 -1
  47. package/src/questionnaire/runtime.ts +12 -2
  48. package/src/questionnaire/useQuestionnaireGate.ts +9 -7
  49. package/src/revenuecat/index.ts +55 -0
  50. package/src/revenuecat/purchaseEvents.ts +167 -0
  51. package/src/revenuecat/revenueCatBridge.ts +221 -0
  52. package/src/revenuecat/types.ts +95 -0
  53. package/src/reviews/runtime.ts +92 -1
  54. package/src/reviews/transport.ts +21 -2
  55. package/src/reviews/useReviewGate.ts +12 -7
  56. package/src/session-analytics/lifecycle.ts +9 -2
  57. package/src/session-analytics/reportSessionStart.ts +15 -4
  58. package/src/session-analytics/useLifecycleEvents.ts +28 -2
@@ -411,6 +411,23 @@ var buildEventsRequest = (target, events) => {
411
411
  return null;
412
412
  }
413
413
  };
414
+ var warnOnSkippedEvents = (res) => {
415
+ try {
416
+ const json = res == null ? void 0 : res.json;
417
+ if (typeof json !== "function") return;
418
+ void Promise.resolve(json.call(res)).then((body) => {
419
+ const skipped = body == null ? void 0 : body.skipped;
420
+ if (typeof skipped !== "number" || skipped <= 0) return;
421
+ if (typeof __DEV__ !== "undefined" && __DEV__ && typeof console !== "undefined" && console.warn) {
422
+ console.warn(
423
+ `[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.`
424
+ );
425
+ }
426
+ }).catch(() => {
427
+ });
428
+ } catch {
429
+ }
430
+ };
414
431
  var reportClientEvents = (target, events) => {
415
432
  try {
416
433
  const req = buildEventsRequest(target, events);
@@ -433,15 +450,54 @@ var reportClientEventsAwait = async (target, events) => {
433
450
  };
434
451
  var reportClientEventAwait = (target, event) => reportClientEventsAwait(target, [event]);
435
452
 
453
+ // src/analytics/currentSession.ts
454
+ var CURRENT_SESSION_ID_SLOT = /* @__PURE__ */ Symbol.for(
455
+ "@wireai/activation:currentSessionId"
456
+ );
457
+ var globalSlot = globalThis;
458
+ var setCurrentSessionId = (id) => {
459
+ if (typeof id === "string" && id.length > 0) {
460
+ globalSlot[CURRENT_SESSION_ID_SLOT] = id;
461
+ }
462
+ };
463
+ var getCurrentSessionId = () => globalSlot[CURRENT_SESSION_ID_SLOT];
464
+ var resetCurrentSessionId = () => {
465
+ globalSlot[CURRENT_SESSION_ID_SLOT] = void 0;
466
+ };
467
+ var warnInDev = (message) => {
468
+ if (typeof __DEV__ !== "undefined" && __DEV__ && typeof console !== "undefined" && console.warn) {
469
+ console.warn(message);
470
+ return true;
471
+ }
472
+ return false;
473
+ };
474
+ 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.";
475
+ var MINT_WARNED_SLOT = /* @__PURE__ */ Symbol.for(
476
+ "@wireai/activation:currentSessionIdMintWarned"
477
+ );
478
+ var warnSlot = globalThis;
479
+ var ensureCurrentSessionId = () => {
480
+ const existing = globalSlot[CURRENT_SESSION_ID_SLOT];
481
+ if (typeof existing === "string" && existing.length > 0) return existing;
482
+ const minted = makeSessionId();
483
+ globalSlot[CURRENT_SESSION_ID_SLOT] = minted;
484
+ if (!warnSlot[MINT_WARNED_SLOT] && warnInDev(MINT_WARNING)) {
485
+ warnSlot[MINT_WARNED_SLOT] = true;
486
+ }
487
+ return minted;
488
+ };
489
+
436
490
  // src/reviews/transport.ts
437
491
  var reportAppEvent = (target, name2, options = {}) => {
492
+ var _a2;
438
493
  if (!(target == null ? void 0 : target.serverUrl) || !name2) return;
439
494
  try {
495
+ const supplied = (_a2 = options.sessionId) != null ? _a2 : "";
440
496
  const event = {
441
497
  event_type: "app_event",
442
- question_key: name2
498
+ question_key: name2,
499
+ session_id: supplied.trim().length > 0 ? supplied : ensureCurrentSessionId()
443
500
  };
444
- if (options.sessionId) event.session_id = options.sessionId;
445
501
  if (options.deviceKey) event.user_context = { device_key: options.deviceKey };
446
502
  if (options.meta && Object.keys(options.meta).length > 0) {
447
503
  event.meta = JSON.stringify(options.meta);
@@ -877,6 +933,7 @@ var createEventQueue = (options) => {
877
933
  const timer = setTimeout(() => controller == null ? void 0 : controller.abort(), 15e3);
878
934
  try {
879
935
  const res = await fetch(req.url, { ...req.init, signal: controller == null ? void 0 : controller.signal });
936
+ warnOnSkippedEvents(res);
880
937
  return !!(res && res.ok);
881
938
  } catch {
882
939
  return false;
@@ -955,21 +1012,6 @@ var createEventQueue = (options) => {
955
1012
  return { enqueue, flush, notifyOnline, size };
956
1013
  };
957
1014
 
958
- // src/analytics/currentSession.ts
959
- var CURRENT_SESSION_ID_SLOT = /* @__PURE__ */ Symbol.for(
960
- "@wireai/activation:currentSessionId"
961
- );
962
- var globalSlot = globalThis;
963
- var setCurrentSessionId = (id) => {
964
- if (typeof id === "string" && id.length > 0) {
965
- globalSlot[CURRENT_SESSION_ID_SLOT] = id;
966
- }
967
- };
968
- var getCurrentSessionId = () => globalSlot[CURRENT_SESSION_ID_SLOT];
969
- var resetCurrentSessionId = () => {
970
- globalSlot[CURRENT_SESSION_ID_SLOT] = void 0;
971
- };
972
-
973
1015
  // src/identity/userIdentity.ts
974
1016
  var USER_ID_MAX_LENGTH = 128;
975
1017
  var sanitizeUserId = (raw) => {
@@ -1066,9 +1108,50 @@ var mintDeviceId = () => {
1066
1108
  const time = Date.now().toString(36);
1067
1109
  return `${AUTO_DEVICE_ID_PREFIX}${time}_${randomChunk()}${randomChunk()}`;
1068
1110
  };
1111
+ var AUTO_DEVICE_KEY_SLOT = /* @__PURE__ */ Symbol.for("@wireai/activation:autoDeviceKeys");
1112
+ var deviceKeyGlobal = globalThis;
1113
+ var autoDeviceKeyRegistry = () => {
1114
+ const existing = deviceKeyGlobal[AUTO_DEVICE_KEY_SLOT];
1115
+ if (existing) return existing;
1116
+ const created = { keys: /* @__PURE__ */ new Map(), hydrating: /* @__PURE__ */ new Set() };
1117
+ deviceKeyGlobal[AUTO_DEVICE_KEY_SLOT] = created;
1118
+ return created;
1119
+ };
1120
+ var resolveAutoDeviceKey = (opts = {}) => {
1121
+ var _a2, _b;
1122
+ const registry = autoDeviceKeyRegistry();
1123
+ const appId = (_a2 = opts.appId) != null ? _a2 : "default";
1124
+ let id = registry.keys.get(appId);
1125
+ if (!id) {
1126
+ id = mintDeviceId();
1127
+ registry.keys.set(appId, id);
1128
+ }
1129
+ const storage = opts.storage;
1130
+ if (storage && !registry.hydrating.has(appId)) {
1131
+ registry.hydrating.add(appId);
1132
+ const slot = deviceIdStorageKey(appId);
1133
+ const minted = id;
1134
+ try {
1135
+ void Promise.resolve(storage.getItem(slot)).then((saved) => {
1136
+ const persisted = typeof saved === "string" && saved.trim() ? saved.trim() : void 0;
1137
+ if (persisted) registry.keys.set(appId, persisted);
1138
+ else void Promise.resolve(storage.setItem(slot, minted)).catch(() => {
1139
+ });
1140
+ }).catch(() => {
1141
+ });
1142
+ } catch {
1143
+ }
1144
+ }
1145
+ return (_b = registry.keys.get(appId)) != null ? _b : id;
1146
+ };
1147
+ var resetAutoDeviceKeys = () => {
1148
+ const registry = autoDeviceKeyRegistry();
1149
+ registry.keys.clear();
1150
+ registry.hydrating.clear();
1151
+ };
1069
1152
 
1070
1153
  // src/analytics/analyticsFacade.ts
1071
- var warnInDev = (message) => {
1154
+ var warnInDev2 = (message) => {
1072
1155
  if (typeof __DEV__ !== "undefined" && __DEV__ && typeof console !== "undefined" && console.warn) {
1073
1156
  console.warn(message);
1074
1157
  }
@@ -1080,20 +1163,20 @@ var createAnalytics = (config, options = {}) => {
1080
1163
  var _a3, _b2;
1081
1164
  return (_b2 = (_a3 = config.sessionId) != null ? _a3 : getCurrentSessionId()) != null ? _b2 : instanceSessionId;
1082
1165
  };
1166
+ if (config.sessionId) {
1167
+ warnInDev2(
1168
+ "[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."
1169
+ );
1170
+ }
1171
+ const detectedAppVersion = detectAppVersion();
1083
1172
  let userContext = { ...(_b = config.userContext) != null ? _b : {} };
1084
1173
  const hostDeviceKeyAtInit = typeof ((_c = config.userContext) == null ? void 0 : _c.deviceKey) === "string" && config.userContext.deviceKey.trim() ? config.userContext.deviceKey.trim() : void 0;
1085
- let autoDeviceKey = mintDeviceId();
1086
- if (config.storage && !hostDeviceKeyAtInit) {
1087
- const storage = config.storage;
1088
- const deviceKey = deviceIdStorageKey(config.appId);
1089
- void storage.getItem(deviceKey).then((saved) => {
1090
- const persisted = typeof saved === "string" && saved.trim() ? saved.trim() : void 0;
1091
- if (persisted) autoDeviceKey = persisted;
1092
- else void storage.setItem(deviceKey, autoDeviceKey).catch(() => {
1093
- });
1094
- }).catch(() => {
1095
- });
1096
- }
1174
+ const autoDeviceKeyOptions = {
1175
+ appId: config.appId,
1176
+ // A host-supplied deviceKey opts out of minting AND persisting (unchanged contract).
1177
+ storage: hostDeviceKeyAtInit ? void 0 : config.storage
1178
+ };
1179
+ if (!hostDeviceKeyAtInit) resolveAutoDeviceKey(autoDeviceKeyOptions);
1097
1180
  const envelope = () => {
1098
1181
  var _a3;
1099
1182
  return buildContextEnvelope({
@@ -1119,20 +1202,21 @@ var createAnalytics = (config, options = {}) => {
1119
1202
  });
1120
1203
  }
1121
1204
  const applyContext = (event) => {
1122
- var _a3;
1205
+ var _a3, _b2;
1123
1206
  const hostDeviceKey = typeof userContext.deviceKey === "string" && userContext.deviceKey.trim() ? userContext.deviceKey : void 0;
1124
1207
  const resolved = resolveUserContext(
1125
- { ...userContext, deviceKey: hostDeviceKey != null ? hostDeviceKey : autoDeviceKey },
1126
- { autoAppVersion: config.appVersion }
1208
+ // `??` is lazy on purpose: a host-supplied key must never even touch the auto registry.
1209
+ { ...userContext, deviceKey: hostDeviceKey != null ? hostDeviceKey : resolveAutoDeviceKey(autoDeviceKeyOptions) },
1210
+ { autoAppVersion: (_a3 = config.appVersion) != null ? _a3 : detectedAppVersion }
1127
1211
  );
1128
1212
  if (resolved.userContext) {
1129
- event.user_context = { ...resolved.userContext, ...(_a3 = event.user_context) != null ? _a3 : {} };
1213
+ event.user_context = { ...resolved.userContext, ...(_b2 = event.user_context) != null ? _b2 : {} };
1130
1214
  }
1131
1215
  if (boundUserId && !event.user_id) event.user_id = boundUserId;
1132
1216
  };
1133
1217
  const guardUserId = (clean) => {
1134
1218
  if (config.allowEmailAsUserId || !looksLikeEmail(clean)) return clean;
1135
- warnInDev(
1219
+ warnInDev2(
1136
1220
  "[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."
1137
1221
  );
1138
1222
  return void 0;
@@ -1215,6 +1299,7 @@ var createAnalytics = (config, options = {}) => {
1215
1299
  };
1216
1300
  };
1217
1301
  var useAnalytics = (config, options = {}) => {
1302
+ var _a2;
1218
1303
  const ref = useRef(void 0);
1219
1304
  const prevKeys = useRef("");
1220
1305
  const currentKeys = `${config.serverUrl}|${config.apiKey}|${config.appId}`;
@@ -1222,9 +1307,14 @@ var useAnalytics = (config, options = {}) => {
1222
1307
  prevKeys.current = currentKeys;
1223
1308
  ref.current = createAnalytics(config, options);
1224
1309
  }
1310
+ const hostDeviceKey = typeof ((_a2 = config.userContext) == null ? void 0 : _a2.deviceKey) === "string" && config.userContext.deviceKey.trim() ? config.userContext.deviceKey.trim() : void 0;
1311
+ useEffect(() => {
1312
+ var _a3;
1313
+ if (hostDeviceKey) (_a3 = ref.current) == null ? void 0 : _a3.setUserContext({ deviceKey: hostDeviceKey });
1314
+ }, [hostDeviceKey]);
1225
1315
  return ref.current;
1226
1316
  };
1227
1317
 
1228
- export { WIRE_ONBOARDING_EVENTS, analyticsUserIdStorageKey, buildContextEnvelope, clearPiiFromContext, clearUserContext, createAnalytics, createEventQueue, createScreenTracker, getActiveRouteName, getCurrentSessionId, looksLikeEmail, makeSessionId, reportAppEvent, reportClientEvent, reportClientEventAwait, reportClientEvents, reportClientEventsAwait, resetCurrentSessionId, screenTrackingHandler, setCurrentSessionId, toAnalyticsEvent, useAnalytics, useScreenTracking };
1318
+ export { AUTO_DEVICE_ID_PREFIX, WIRE_ONBOARDING_EVENTS, analyticsUserIdStorageKey, buildContextEnvelope, clearPiiFromContext, clearUserContext, createAnalytics, createEventQueue, createScreenTracker, deviceIdStorageKey, ensureCurrentSessionId, getActiveRouteName, getCurrentSessionId, looksLikeEmail, makeSessionId, reportAppEvent, reportClientEvent, reportClientEventAwait, reportClientEvents, reportClientEventsAwait, resetAutoDeviceKeys, resetCurrentSessionId, resolveAutoDeviceKey, screenTrackingHandler, setCurrentSessionId, toAnalyticsEvent, useAnalytics, useScreenTracking };
1229
1319
  //# sourceMappingURL=index.mjs.map
1230
1320
  //# sourceMappingURL=index.mjs.map