@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
@@ -4,7 +4,7 @@ export { e as RatingRoute, f as ReviewConfig, g as ReviewDecisionResponse, h as
4
4
  export { C as ReviewModal, a as ReviewModalHandle, b as ReviewModalProps } from '../CenteredModal-C3qQBHsA.mjs';
5
5
  export { G as GateRules, a as GateSignals, b as buildReviewSubmission, d as decideReview, e as evaluateGate, r as resolveRules, c as routeRating } from '../decision-Bl_M2y3r.mjs';
6
6
  import { c as OnboardingFonts } from '../types-BKfpdZzX.mjs';
7
- export { F as FetchReviewDecisionOptions, R as ReportAppEventOptions, f as fetchReviewDecision, r as reportAppEvent, s as submitReview } from '../transport-BGW9uXZJ.mjs';
7
+ export { F as FetchReviewDecisionOptions, R as ReportAppEventOptions, f as fetchReviewDecision, r as reportAppEvent, s as submitReview } from '../transport-CF_eHwzC.mjs';
8
8
  import '../types-BcmagF6K.mjs';
9
9
  import '../types-CKFhyrMu.mjs';
10
10
 
@@ -19,7 +19,7 @@ import '../types-CKFhyrMu.mjs';
19
19
  declare const reviewSeenKey: (id: string, version?: string) => string;
20
20
  /** Last-shown timestamp key (epoch ms), for the cooldown rule. */
21
21
  declare const reviewLastShownKey: (id: string) => string;
22
- /** Session-count key, incremented once per gate mount, for the min-sessions rule. */
22
+ /** Session-count key, incremented once per APP-OPEN (not per mount), for the min-sessions rule. */
23
23
  declare const reviewSessionsKey: (id: string) => string;
24
24
 
25
25
  /**
@@ -4,7 +4,7 @@ export { e as RatingRoute, f as ReviewConfig, g as ReviewDecisionResponse, h as
4
4
  export { C as ReviewModal, a as ReviewModalHandle, b as ReviewModalProps } from '../CenteredModal-Cdgns6--.js';
5
5
  export { G as GateRules, a as GateSignals, b as buildReviewSubmission, d as decideReview, e as evaluateGate, r as resolveRules, c as routeRating } from '../decision-Cau5KmP6.js';
6
6
  import { c as OnboardingFonts } from '../types-BKfpdZzX.js';
7
- export { F as FetchReviewDecisionOptions, R as ReportAppEventOptions, f as fetchReviewDecision, r as reportAppEvent, s as submitReview } from '../transport-jUJd5kxu.js';
7
+ export { F as FetchReviewDecisionOptions, R as ReportAppEventOptions, f as fetchReviewDecision, r as reportAppEvent, s as submitReview } from '../transport-DsRe4epC.js';
8
8
  import '../types-BcmagF6K.js';
9
9
  import '../types-CKFhyrMu.js';
10
10
 
@@ -19,7 +19,7 @@ import '../types-CKFhyrMu.js';
19
19
  declare const reviewSeenKey: (id: string, version?: string) => string;
20
20
  /** Last-shown timestamp key (epoch ms), for the cooldown rule. */
21
21
  declare const reviewLastShownKey: (id: string) => string;
22
- /** Session-count key, incremented once per gate mount, for the min-sessions rule. */
22
+ /** Session-count key, incremented once per APP-OPEN (not per mount), for the min-sessions rule. */
23
23
  declare const reviewSessionsKey: (id: string) => string;
24
24
 
25
25
  /**
@@ -736,6 +736,7 @@ var requestStoreReview = async (store) => {
736
736
  };
737
737
 
738
738
  // src/analytics/reportClientEvent.ts
739
+ var makeSessionId = () => `wire_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
739
740
  var buildEventsRequest = (target, events) => {
740
741
  if (!(target == null ? void 0 : target.serverUrl) || events.length === 0) return null;
741
742
  try {
@@ -748,6 +749,35 @@ var buildEventsRequest = (target, events) => {
748
749
  }
749
750
  };
750
751
 
752
+ // src/analytics/currentSession.ts
753
+ var CURRENT_SESSION_ID_SLOT = /* @__PURE__ */ Symbol.for(
754
+ "@wireai/activation:currentSessionId"
755
+ );
756
+ var globalSlot = globalThis;
757
+ var getCurrentSessionId = () => globalSlot[CURRENT_SESSION_ID_SLOT];
758
+ var warnInDev = (message) => {
759
+ if (typeof __DEV__ !== "undefined" && __DEV__ && typeof console !== "undefined" && console.warn) {
760
+ console.warn(message);
761
+ return true;
762
+ }
763
+ return false;
764
+ };
765
+ 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.";
766
+ var MINT_WARNED_SLOT = /* @__PURE__ */ Symbol.for(
767
+ "@wireai/activation:currentSessionIdMintWarned"
768
+ );
769
+ var warnSlot = globalThis;
770
+ var ensureCurrentSessionId = () => {
771
+ const existing = globalSlot[CURRENT_SESSION_ID_SLOT];
772
+ if (typeof existing === "string" && existing.length > 0) return existing;
773
+ const minted = makeSessionId();
774
+ globalSlot[CURRENT_SESSION_ID_SLOT] = minted;
775
+ if (!warnSlot[MINT_WARNED_SLOT] && warnInDev(MINT_WARNING)) {
776
+ warnSlot[MINT_WARNED_SLOT] = true;
777
+ }
778
+ return minted;
779
+ };
780
+
751
781
  // src/reviews/transport.ts
752
782
  var submitReview = (target, review) => {
753
783
  if (!(target == null ? void 0 : target.serverUrl)) return;
@@ -785,13 +815,15 @@ var fetchReviewDecision = async (target, options = {}) => {
785
815
  }
786
816
  };
787
817
  var reportAppEvent = (target, name, options = {}) => {
818
+ var _a;
788
819
  if (!(target == null ? void 0 : target.serverUrl) || !name) return;
789
820
  try {
821
+ const supplied = (_a = options.sessionId) != null ? _a : "";
790
822
  const event = {
791
823
  event_type: "app_event",
792
- question_key: name
824
+ question_key: name,
825
+ session_id: supplied.trim().length > 0 ? supplied : ensureCurrentSessionId()
793
826
  };
794
- if (options.sessionId) event.session_id = options.sessionId;
795
827
  if (options.deviceKey) event.user_context = { device_key: options.deviceKey };
796
828
  if (options.meta && Object.keys(options.meta).length > 0) {
797
829
  event.meta = JSON.stringify(options.meta);
@@ -1055,14 +1087,21 @@ var styles6 = reactNative.StyleSheet.create({
1055
1087
  });
1056
1088
 
1057
1089
  // src/coachmarks/runtime.ts
1058
- var storageSingleton = null;
1059
- var testing = false;
1060
- var getCoachmarkStorage = () => storageSingleton;
1061
- var isCoachmarkTesting = () => testing;
1090
+ var COACHMARK_RUNTIME_SLOT = /* @__PURE__ */ Symbol.for("@wireai/activation:coachmarkRuntime");
1091
+ var runtimeGlobal = globalThis;
1092
+ var coachmarkRuntime = () => {
1093
+ const existing = runtimeGlobal[COACHMARK_RUNTIME_SLOT];
1094
+ if (existing) return existing;
1095
+ const created = { storage: null, testing: false, coachmarksEnabled: true };
1096
+ runtimeGlobal[COACHMARK_RUNTIME_SLOT] = created;
1097
+ return created;
1098
+ };
1099
+ var getCoachmarkStorage = () => coachmarkRuntime().storage;
1100
+ var isCoachmarkTesting = () => coachmarkRuntime().testing;
1062
1101
  var SEEN_VALUE = "1";
1063
1102
  var hasSeenGate = (key, storageOverride, testingOverride) => {
1064
- if (testingOverride != null ? testingOverride : testing) return false;
1065
- const storage = storageOverride != null ? storageOverride : storageSingleton;
1103
+ if (testingOverride != null ? testingOverride : isCoachmarkTesting()) return false;
1104
+ const storage = storageOverride != null ? storageOverride : getCoachmarkStorage();
1066
1105
  if (!storage) return false;
1067
1106
  try {
1068
1107
  return storage.getItem(key) === SEEN_VALUE;
@@ -1071,8 +1110,8 @@ var hasSeenGate = (key, storageOverride, testingOverride) => {
1071
1110
  }
1072
1111
  };
1073
1112
  var markSeenGate = (key, storageOverride, testingOverride) => {
1074
- if (testingOverride != null ? testingOverride : testing) return;
1075
- const storage = storageOverride != null ? storageOverride : storageSingleton;
1113
+ if (testingOverride != null ? testingOverride : isCoachmarkTesting()) return;
1114
+ const storage = storageOverride != null ? storageOverride : getCoachmarkStorage();
1076
1115
  if (!storage) return;
1077
1116
  try {
1078
1117
  storage.setItem(key, SEEN_VALUE);
@@ -1258,7 +1297,22 @@ var sameDecision = (a, b) => {
1258
1297
  var reviewSeenKey = (id, version) => version ? `wire_review_${id}_${version}_seen` : `wire_review_${id}_seen`;
1259
1298
  var reviewLastShownKey = (id) => `wire_review_${id}_last`;
1260
1299
  var reviewSessionsKey = (id) => `wire_review_${id}_sessions`;
1300
+ var reviewSessionOpenKey = (id) => `wire_review_${id}_open`;
1261
1301
  var resolveStorage = (override) => override != null ? override : getCoachmarkStorage();
1302
+ var NO_STORAGE_WARNED_SLOT = /* @__PURE__ */ Symbol.for(
1303
+ "@wireai/activation:gateStorageWarned"
1304
+ );
1305
+ var gateWarnGlobal = globalThis;
1306
+ var warnMissingGateStorage = (storage, gate) => {
1307
+ if (storage) return;
1308
+ if (gateWarnGlobal[NO_STORAGE_WARNED_SLOT]) return;
1309
+ if (typeof __DEV__ === "undefined" || !__DEV__) return;
1310
+ if (typeof console === "undefined" || !console.warn) return;
1311
+ gateWarnGlobal[NO_STORAGE_WARNED_SLOT] = true;
1312
+ console.warn(
1313
+ `[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.`
1314
+ );
1315
+ };
1262
1316
  var readInt = (storage, key) => {
1263
1317
  if (!storage) return 0;
1264
1318
  try {
@@ -1276,6 +1330,39 @@ var writeInt = (storage, key, value) => {
1276
1330
  } catch {
1277
1331
  }
1278
1332
  };
1333
+ var readStr = (storage, key) => {
1334
+ if (!storage) return void 0;
1335
+ try {
1336
+ const raw = storage.getItem(key);
1337
+ return typeof raw === "string" && raw.length > 0 ? raw : void 0;
1338
+ } catch {
1339
+ return void 0;
1340
+ }
1341
+ };
1342
+ var PROCESS_OPEN_ID_SLOT = /* @__PURE__ */ Symbol.for("@wireai/activation:processOpenId");
1343
+ var openIdGlobal = globalThis;
1344
+ var currentOpenId = () => {
1345
+ var _a;
1346
+ const existing = openIdGlobal[PROCESS_OPEN_ID_SLOT];
1347
+ if (existing) return existing;
1348
+ const resolved = (_a = getCurrentSessionId()) != null ? _a : makeSessionId();
1349
+ openIdGlobal[PROCESS_OPEN_ID_SLOT] = resolved;
1350
+ return resolved;
1351
+ };
1352
+ var bumpSessionCount = (storage, sessionsKey, openKey, openId = currentOpenId()) => {
1353
+ const lastOpen = readStr(storage, openKey);
1354
+ const stored = readInt(storage, sessionsKey);
1355
+ if (lastOpen === openId) return stored > 0 ? stored : 1;
1356
+ const next = stored + 1;
1357
+ writeInt(storage, sessionsKey, next);
1358
+ if (storage) {
1359
+ try {
1360
+ storage.setItem(openKey, openId);
1361
+ } catch {
1362
+ }
1363
+ }
1364
+ return next;
1365
+ };
1279
1366
 
1280
1367
  // src/reviews/useReviewGate.ts
1281
1368
  var useStableValue2 = (value, isEqual) => {
@@ -1298,15 +1385,15 @@ var useReviewGate = ({
1298
1385
  (a, b) => sameDecision(a, b)
1299
1386
  );
1300
1387
  const featureEnabled = useResolvedFeatures({ flags: features, config: featuresConfig }).review.enabled;
1301
- const testing2 = isTesting != null ? isTesting : isCoachmarkTesting();
1388
+ const testing = isTesting != null ? isTesting : isCoachmarkTesting();
1302
1389
  const seenKey = reviewSeenKey(config.id, config.oncePerVersion === false ? void 0 : config.appVersion);
1303
1390
  const lastKey = reviewLastShownKey(config.id);
1304
1391
  const sessionsKey = reviewSessionsKey(config.id);
1392
+ const sessionOpenKey = reviewSessionOpenKey(config.id);
1305
1393
  const sessions = React7.useState(() => {
1306
1394
  const store = resolveStorage(storage);
1307
- const next = readInt(store, sessionsKey) + 1;
1308
- writeInt(store, sessionsKey, next);
1309
- return next;
1395
+ warnMissingGateStorage(store, "review");
1396
+ return bumpSessionCount(store, sessionsKey, sessionOpenKey);
1310
1397
  })[0];
1311
1398
  const hasServerDecision = decision != null;
1312
1399
  const timeoutMs = config.timeoutFallbackMs;
@@ -1322,7 +1409,7 @@ var useReviewGate = ({
1322
1409
  }, [waits, timeoutMs]);
1323
1410
  const { visible, verdict } = React7.useMemo(() => {
1324
1411
  if (!featureEnabled) return { visible: false, verdict: { fire: false, reason: "feature_disabled" } };
1325
- if (testing2) return { visible: true, verdict: { fire: true, reason: "testing" } };
1412
+ if (testing) return { visible: true, verdict: { fire: true, reason: "testing" } };
1326
1413
  const store = resolveStorage(storage);
1327
1414
  const local = evaluateGate(resolveRules(config), {
1328
1415
  sessions,
@@ -1335,7 +1422,7 @@ var useReviewGate = ({
1335
1422
  const alreadySeen = hasSeenGate(seenKey, storage, isTesting);
1336
1423
  const ready = hasServerDecision || elapsed;
1337
1424
  return { visible: ready && resolved.fire && !alreadySeen, verdict: resolved };
1338
- }, [featureEnabled, testing2, config, decision, events, sessions, elapsed, hasServerDecision, seenKey, lastKey, storage, isTesting]);
1425
+ }, [featureEnabled, testing, config, decision, events, sessions, elapsed, hasServerDecision, seenKey, lastKey, storage, isTesting]);
1339
1426
  const shownRef = React7.useRef(false);
1340
1427
  const markShown = React7.useCallback(() => {
1341
1428
  if (shownRef.current) return;