@wireai/activation 0.11.0 → 0.12.1

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 (73) hide show
  1. package/AGENTS.md +62 -8
  2. package/CHANGELOG.md +188 -3
  3. package/INTEGRATION_PROMPT.md +25 -2
  4. package/README.md +112 -1
  5. package/dist/analytics/index.d.mts +18 -6
  6. package/dist/analytics/index.d.ts +18 -6
  7. package/dist/analytics/index.js +151 -41
  8. package/dist/analytics/index.js.map +1 -1
  9. package/dist/analytics/index.mjs +147 -42
  10. package/dist/analytics/index.mjs.map +1 -1
  11. package/dist/coachmarks/index.d.mts +16 -0
  12. package/dist/coachmarks/index.d.ts +16 -0
  13. package/dist/coachmarks/index.js +19 -13
  14. package/dist/coachmarks/index.js.map +1 -1
  15. package/dist/coachmarks/index.mjs +19 -13
  16. package/dist/coachmarks/index.mjs.map +1 -1
  17. package/dist/{currentSession-C0_odnIW.d.mts → currentSession-BlCeDP0f.d.mts} +145 -33
  18. package/dist/{currentSession-DdnUq2HQ.d.ts → currentSession-BxEB37xt.d.ts} +145 -33
  19. package/dist/index.d.mts +243 -53
  20. package/dist/index.d.ts +243 -53
  21. package/dist/index.js +531 -156
  22. package/dist/index.js.map +1 -1
  23. package/dist/index.mjs +517 -157
  24. package/dist/index.mjs.map +1 -1
  25. package/dist/questionnaire/index.d.mts +1 -1
  26. package/dist/questionnaire/index.d.ts +1 -1
  27. package/dist/questionnaire/index.js +79 -14
  28. package/dist/questionnaire/index.js.map +1 -1
  29. package/dist/questionnaire/index.mjs +79 -14
  30. package/dist/questionnaire/index.mjs.map +1 -1
  31. package/dist/reviews/index.d.mts +2 -2
  32. package/dist/reviews/index.d.ts +2 -2
  33. package/dist/reviews/index.js +103 -16
  34. package/dist/reviews/index.js.map +1 -1
  35. package/dist/reviews/index.mjs +103 -16
  36. package/dist/reviews/index.mjs.map +1 -1
  37. package/dist/showcase/index.js +15 -6
  38. package/dist/showcase/index.js.map +1 -1
  39. package/dist/showcase/index.mjs +15 -6
  40. package/dist/showcase/index.mjs.map +1 -1
  41. package/dist/{transport-BGW9uXZJ.d.mts → transport-CF_eHwzC.d.mts} +15 -1
  42. package/dist/{transport-jUJd5kxu.d.ts → transport-DsRe4epC.d.ts} +15 -1
  43. package/llms.txt +3 -0
  44. package/package.json +1 -1
  45. package/src/WireOnboarding.tsx +140 -5
  46. package/src/activation/useWireActivation.ts +12 -1
  47. package/src/activation/wireActivation.ts +44 -25
  48. package/src/analytics/analyticsFacade.ts +54 -29
  49. package/src/analytics/currentSession.ts +83 -0
  50. package/src/analytics/eventQueue.ts +9 -1
  51. package/src/analytics/index.ts +20 -1
  52. package/src/analytics/reportClientEvent.ts +42 -0
  53. package/src/analytics/screenTracking.ts +6 -1
  54. package/src/analytics/useAnalytics.ts +22 -1
  55. package/src/coachmarks/runtime.ts +53 -17
  56. package/src/config/wireConfigFromEnv.ts +46 -2
  57. package/src/context/deviceId.ts +173 -0
  58. package/src/context/userContext.ts +18 -0
  59. package/src/index.ts +54 -2
  60. package/src/questionnaire/runtime.ts +13 -2
  61. package/src/questionnaire/useQuestionnaireGate.ts +11 -4
  62. package/src/revenuecat/index.ts +55 -0
  63. package/src/revenuecat/purchaseEvents.ts +167 -0
  64. package/src/revenuecat/revenueCatBridge.ts +221 -0
  65. package/src/revenuecat/types.ts +95 -0
  66. package/src/reviews/runtime.ts +153 -1
  67. package/src/reviews/transport.ts +21 -2
  68. package/src/reviews/useReviewGate.ts +14 -4
  69. package/src/session-analytics/lifecycle.ts +9 -2
  70. package/src/session-analytics/reportSessionStart.ts +15 -4
  71. package/src/session-analytics/useLifecycleEvents.ts +83 -27
  72. package/src/session-analytics/useSessionStart.ts +37 -1
  73. package/src/types.ts +41 -4
@@ -730,6 +730,7 @@ var requestStoreReview = async (store) => {
730
730
  };
731
731
 
732
732
  // src/analytics/reportClientEvent.ts
733
+ var makeSessionId = () => `wire_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
733
734
  var buildEventsRequest = (target, events) => {
734
735
  if (!(target == null ? void 0 : target.serverUrl) || events.length === 0) return null;
735
736
  try {
@@ -742,6 +743,35 @@ var buildEventsRequest = (target, events) => {
742
743
  }
743
744
  };
744
745
 
746
+ // src/analytics/currentSession.ts
747
+ var CURRENT_SESSION_ID_SLOT = /* @__PURE__ */ Symbol.for(
748
+ "@wireai/activation:currentSessionId"
749
+ );
750
+ var globalSlot = globalThis;
751
+ var getCurrentSessionId = () => globalSlot[CURRENT_SESSION_ID_SLOT];
752
+ var warnInDev = (message) => {
753
+ if (typeof __DEV__ !== "undefined" && __DEV__ && typeof console !== "undefined" && console.warn) {
754
+ console.warn(message);
755
+ return true;
756
+ }
757
+ return false;
758
+ };
759
+ 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.";
760
+ var MINT_WARNED_SLOT = /* @__PURE__ */ Symbol.for(
761
+ "@wireai/activation:currentSessionIdMintWarned"
762
+ );
763
+ var warnSlot = globalThis;
764
+ var ensureCurrentSessionId = () => {
765
+ const existing = globalSlot[CURRENT_SESSION_ID_SLOT];
766
+ if (typeof existing === "string" && existing.length > 0) return existing;
767
+ const minted = makeSessionId();
768
+ globalSlot[CURRENT_SESSION_ID_SLOT] = minted;
769
+ if (!warnSlot[MINT_WARNED_SLOT] && warnInDev(MINT_WARNING)) {
770
+ warnSlot[MINT_WARNED_SLOT] = true;
771
+ }
772
+ return minted;
773
+ };
774
+
745
775
  // src/reviews/transport.ts
746
776
  var submitReview = (target, review) => {
747
777
  if (!(target == null ? void 0 : target.serverUrl)) return;
@@ -779,13 +809,15 @@ var fetchReviewDecision = async (target, options = {}) => {
779
809
  }
780
810
  };
781
811
  var reportAppEvent = (target, name, options = {}) => {
812
+ var _a;
782
813
  if (!(target == null ? void 0 : target.serverUrl) || !name) return;
783
814
  try {
815
+ const supplied = (_a = options.sessionId) != null ? _a : "";
784
816
  const event = {
785
817
  event_type: "app_event",
786
- question_key: name
818
+ question_key: name,
819
+ session_id: supplied.trim().length > 0 ? supplied : ensureCurrentSessionId()
787
820
  };
788
- if (options.sessionId) event.session_id = options.sessionId;
789
821
  if (options.deviceKey) event.user_context = { device_key: options.deviceKey };
790
822
  if (options.meta && Object.keys(options.meta).length > 0) {
791
823
  event.meta = JSON.stringify(options.meta);
@@ -1049,14 +1081,21 @@ var styles6 = StyleSheet.create({
1049
1081
  });
1050
1082
 
1051
1083
  // src/coachmarks/runtime.ts
1052
- var storageSingleton = null;
1053
- var testing = false;
1054
- var getCoachmarkStorage = () => storageSingleton;
1055
- var isCoachmarkTesting = () => testing;
1084
+ var COACHMARK_RUNTIME_SLOT = /* @__PURE__ */ Symbol.for("@wireai/activation:coachmarkRuntime");
1085
+ var runtimeGlobal = globalThis;
1086
+ var coachmarkRuntime = () => {
1087
+ const existing = runtimeGlobal[COACHMARK_RUNTIME_SLOT];
1088
+ if (existing) return existing;
1089
+ const created = { storage: null, testing: false, coachmarksEnabled: true };
1090
+ runtimeGlobal[COACHMARK_RUNTIME_SLOT] = created;
1091
+ return created;
1092
+ };
1093
+ var getCoachmarkStorage = () => coachmarkRuntime().storage;
1094
+ var isCoachmarkTesting = () => coachmarkRuntime().testing;
1056
1095
  var SEEN_VALUE = "1";
1057
1096
  var hasSeenGate = (key, storageOverride, testingOverride) => {
1058
- if (testingOverride != null ? testingOverride : testing) return false;
1059
- const storage = storageOverride != null ? storageOverride : storageSingleton;
1097
+ if (testingOverride != null ? testingOverride : isCoachmarkTesting()) return false;
1098
+ const storage = storageOverride != null ? storageOverride : getCoachmarkStorage();
1060
1099
  if (!storage) return false;
1061
1100
  try {
1062
1101
  return storage.getItem(key) === SEEN_VALUE;
@@ -1065,8 +1104,8 @@ var hasSeenGate = (key, storageOverride, testingOverride) => {
1065
1104
  }
1066
1105
  };
1067
1106
  var markSeenGate = (key, storageOverride, testingOverride) => {
1068
- if (testingOverride != null ? testingOverride : testing) return;
1069
- const storage = storageOverride != null ? storageOverride : storageSingleton;
1107
+ if (testingOverride != null ? testingOverride : isCoachmarkTesting()) return;
1108
+ const storage = storageOverride != null ? storageOverride : getCoachmarkStorage();
1070
1109
  if (!storage) return;
1071
1110
  try {
1072
1111
  storage.setItem(key, SEEN_VALUE);
@@ -1252,7 +1291,22 @@ var sameDecision = (a, b) => {
1252
1291
  var reviewSeenKey = (id, version) => version ? `wire_review_${id}_${version}_seen` : `wire_review_${id}_seen`;
1253
1292
  var reviewLastShownKey = (id) => `wire_review_${id}_last`;
1254
1293
  var reviewSessionsKey = (id) => `wire_review_${id}_sessions`;
1294
+ var reviewSessionOpenKey = (id) => `wire_review_${id}_open`;
1255
1295
  var resolveStorage = (override) => override != null ? override : getCoachmarkStorage();
1296
+ var NO_STORAGE_WARNED_SLOT = /* @__PURE__ */ Symbol.for(
1297
+ "@wireai/activation:gateStorageWarned"
1298
+ );
1299
+ var gateWarnGlobal = globalThis;
1300
+ var warnMissingGateStorage = (storage, gate) => {
1301
+ if (storage) return;
1302
+ if (gateWarnGlobal[NO_STORAGE_WARNED_SLOT]) return;
1303
+ if (typeof __DEV__ === "undefined" || !__DEV__) return;
1304
+ if (typeof console === "undefined" || !console.warn) return;
1305
+ gateWarnGlobal[NO_STORAGE_WARNED_SLOT] = true;
1306
+ console.warn(
1307
+ `[wireai] the ${gate} gate has no sync storage, so its app-open counter is stuck at 1 and the fail-closed minSessions rule can never be satisfied \u2014 the gate will never fire. Mount CoachmarkProvider with a sync storage adapter at your app root, or pass \`storage\` to the gate.`
1308
+ );
1309
+ };
1256
1310
  var readInt = (storage, key) => {
1257
1311
  if (!storage) return 0;
1258
1312
  try {
@@ -1270,6 +1324,39 @@ var writeInt = (storage, key, value) => {
1270
1324
  } catch {
1271
1325
  }
1272
1326
  };
1327
+ var readStr = (storage, key) => {
1328
+ if (!storage) return void 0;
1329
+ try {
1330
+ const raw = storage.getItem(key);
1331
+ return typeof raw === "string" && raw.length > 0 ? raw : void 0;
1332
+ } catch {
1333
+ return void 0;
1334
+ }
1335
+ };
1336
+ var PROCESS_OPEN_ID_SLOT = /* @__PURE__ */ Symbol.for("@wireai/activation:processOpenId");
1337
+ var openIdGlobal = globalThis;
1338
+ var currentOpenId = () => {
1339
+ var _a;
1340
+ const existing = openIdGlobal[PROCESS_OPEN_ID_SLOT];
1341
+ if (existing) return existing;
1342
+ const resolved = (_a = getCurrentSessionId()) != null ? _a : makeSessionId();
1343
+ openIdGlobal[PROCESS_OPEN_ID_SLOT] = resolved;
1344
+ return resolved;
1345
+ };
1346
+ var bumpSessionCount = (storage, sessionsKey, openKey, openId = currentOpenId()) => {
1347
+ const lastOpen = readStr(storage, openKey);
1348
+ const stored = readInt(storage, sessionsKey);
1349
+ if (lastOpen === openId) return stored > 0 ? stored : 1;
1350
+ const next = stored + 1;
1351
+ writeInt(storage, sessionsKey, next);
1352
+ if (storage) {
1353
+ try {
1354
+ storage.setItem(openKey, openId);
1355
+ } catch {
1356
+ }
1357
+ }
1358
+ return next;
1359
+ };
1273
1360
 
1274
1361
  // src/reviews/useReviewGate.ts
1275
1362
  var useStableValue2 = (value, isEqual) => {
@@ -1292,15 +1379,15 @@ var useReviewGate = ({
1292
1379
  (a, b) => sameDecision(a, b)
1293
1380
  );
1294
1381
  const featureEnabled = useResolvedFeatures({ flags: features, config: featuresConfig }).review.enabled;
1295
- const testing2 = isTesting != null ? isTesting : isCoachmarkTesting();
1382
+ const testing = isTesting != null ? isTesting : isCoachmarkTesting();
1296
1383
  const seenKey = reviewSeenKey(config.id, config.oncePerVersion === false ? void 0 : config.appVersion);
1297
1384
  const lastKey = reviewLastShownKey(config.id);
1298
1385
  const sessionsKey = reviewSessionsKey(config.id);
1386
+ const sessionOpenKey = reviewSessionOpenKey(config.id);
1299
1387
  const sessions = useState(() => {
1300
1388
  const store = resolveStorage(storage);
1301
- const next = readInt(store, sessionsKey) + 1;
1302
- writeInt(store, sessionsKey, next);
1303
- return next;
1389
+ warnMissingGateStorage(store, "review");
1390
+ return bumpSessionCount(store, sessionsKey, sessionOpenKey);
1304
1391
  })[0];
1305
1392
  const hasServerDecision = decision != null;
1306
1393
  const timeoutMs = config.timeoutFallbackMs;
@@ -1316,7 +1403,7 @@ var useReviewGate = ({
1316
1403
  }, [waits, timeoutMs]);
1317
1404
  const { visible, verdict } = useMemo(() => {
1318
1405
  if (!featureEnabled) return { visible: false, verdict: { fire: false, reason: "feature_disabled" } };
1319
- if (testing2) return { visible: true, verdict: { fire: true, reason: "testing" } };
1406
+ if (testing) return { visible: true, verdict: { fire: true, reason: "testing" } };
1320
1407
  const store = resolveStorage(storage);
1321
1408
  const local = evaluateGate(resolveRules(config), {
1322
1409
  sessions,
@@ -1329,7 +1416,7 @@ var useReviewGate = ({
1329
1416
  const alreadySeen = hasSeenGate(seenKey, storage, isTesting);
1330
1417
  const ready = hasServerDecision || elapsed;
1331
1418
  return { visible: ready && resolved.fire && !alreadySeen, verdict: resolved };
1332
- }, [featureEnabled, testing2, config, decision, events, sessions, elapsed, hasServerDecision, seenKey, lastKey, storage, isTesting]);
1419
+ }, [featureEnabled, testing, config, decision, events, sessions, elapsed, hasServerDecision, seenKey, lastKey, storage, isTesting]);
1333
1420
  const shownRef = useRef(false);
1334
1421
  const markShown = useCallback(() => {
1335
1422
  if (shownRef.current) return;