@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
@@ -498,7 +498,14 @@ var resolveRules = (config) => {
498
498
  var _a, _b, _c, _d, _e;
499
499
  return {
500
500
  enabled: (_a = config.enabled) != null ? _a : true,
501
- minSessions: (_b = config.minSessions) != null ? _b : 0,
501
+ // FAIL-CLOSED on the first session (behavior change, 0.11.0). An unconfigured host with no
502
+ // server decision must NOT prompt on the very first mount: `minSessions` defaults to 2 so the
503
+ // gate needs at least a second session before the LOCAL rules can fire. This is the client-side
504
+ // floor only — a server `decision` (from `fetchReviewDecision`) still OVERRIDES everything, and a
505
+ // host that genuinely wants first-session prompting can set `minSessions: 1` (or `0`) explicitly.
506
+ // Mirrors the 2026-07-16 incident: a first-session user, no server decision, got the prompt and
507
+ // left 1 star. See `evaluateGate` (`min_sessions` reason) and the CHANGELOG.
508
+ minSessions: (_b = config.minSessions) != null ? _b : 2,
502
509
  minEvents: (_c = config.minEvents) != null ? _c : 0,
503
510
  cooldownDays: (_d = config.cooldownDays) != null ? _d : 0,
504
511
  oncePerVersion: (_e = config.oncePerVersion) != null ? _e : true
@@ -722,6 +729,49 @@ var requestStoreReview = async (store) => {
722
729
  return opened ? "store_url" : "none";
723
730
  };
724
731
 
732
+ // src/analytics/reportClientEvent.ts
733
+ var makeSessionId = () => `wire_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
734
+ var buildEventsRequest = (target, events) => {
735
+ if (!(target == null ? void 0 : target.serverUrl) || events.length === 0) return null;
736
+ try {
737
+ const url = `${target.serverUrl.replace(/\/$/, "")}/v1/events`;
738
+ const headers = { "Content-Type": "application/json" };
739
+ if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
740
+ return { url, init: { method: "POST", headers, body: JSON.stringify({ events }) } };
741
+ } catch {
742
+ return null;
743
+ }
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
+
725
775
  // src/reviews/transport.ts
726
776
  var submitReview = (target, review) => {
727
777
  if (!(target == null ? void 0 : target.serverUrl)) return;
@@ -759,25 +809,22 @@ var fetchReviewDecision = async (target, options = {}) => {
759
809
  }
760
810
  };
761
811
  var reportAppEvent = (target, name, options = {}) => {
812
+ var _a;
762
813
  if (!(target == null ? void 0 : target.serverUrl) || !name) return;
763
814
  try {
764
- const url = `${target.serverUrl.replace(/\/$/, "")}/v1/events`;
765
- const headers = { "Content-Type": "application/json" };
766
- if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
815
+ const supplied = (_a = options.sessionId) != null ? _a : "";
767
816
  const event = {
768
817
  event_type: "app_event",
769
- question_key: name
818
+ question_key: name,
819
+ session_id: supplied.trim().length > 0 ? supplied : ensureCurrentSessionId()
770
820
  };
771
- if (options.sessionId) event.session_id = options.sessionId;
772
821
  if (options.deviceKey) event.user_context = { device_key: options.deviceKey };
773
822
  if (options.meta && Object.keys(options.meta).length > 0) {
774
823
  event.meta = JSON.stringify(options.meta);
775
824
  }
776
- void fetch(url, {
777
- method: "POST",
778
- headers,
779
- body: JSON.stringify({ events: [event] })
780
- }).catch(() => {
825
+ const req = buildEventsRequest(target, [event]);
826
+ if (!req) return;
827
+ void fetch(req.url, req.init).catch(() => {
781
828
  });
782
829
  } catch {
783
830
  }
@@ -1237,6 +1284,7 @@ var sameDecision = (a, b) => {
1237
1284
  var reviewSeenKey = (id, version) => version ? `wire_review_${id}_${version}_seen` : `wire_review_${id}_seen`;
1238
1285
  var reviewLastShownKey = (id) => `wire_review_${id}_last`;
1239
1286
  var reviewSessionsKey = (id) => `wire_review_${id}_sessions`;
1287
+ var reviewSessionOpenKey = (id) => `wire_review_${id}_open`;
1240
1288
  var resolveStorage = (override) => override != null ? override : getCoachmarkStorage();
1241
1289
  var readInt = (storage, key) => {
1242
1290
  if (!storage) return 0;
@@ -1255,6 +1303,40 @@ var writeInt = (storage, key, value) => {
1255
1303
  } catch {
1256
1304
  }
1257
1305
  };
1306
+ var readStr = (storage, key) => {
1307
+ if (!storage) return void 0;
1308
+ try {
1309
+ const raw = storage.getItem(key);
1310
+ return typeof raw === "string" && raw.length > 0 ? raw : void 0;
1311
+ } catch {
1312
+ return void 0;
1313
+ }
1314
+ };
1315
+ var PROCESS_OPEN_ID_SLOT = /* @__PURE__ */ Symbol.for("@wireai/activation:processOpenId");
1316
+ var openIdGlobal = globalThis;
1317
+ var currentOpenId = () => {
1318
+ const live = getCurrentSessionId();
1319
+ if (live) return live;
1320
+ const existing = openIdGlobal[PROCESS_OPEN_ID_SLOT];
1321
+ if (existing) return existing;
1322
+ const created = makeSessionId();
1323
+ openIdGlobal[PROCESS_OPEN_ID_SLOT] = created;
1324
+ return created;
1325
+ };
1326
+ var bumpSessionCount = (storage, sessionsKey, openKey, openId = currentOpenId()) => {
1327
+ const lastOpen = readStr(storage, openKey);
1328
+ const stored = readInt(storage, sessionsKey);
1329
+ if (lastOpen === openId) return stored > 0 ? stored : 1;
1330
+ const next = stored + 1;
1331
+ writeInt(storage, sessionsKey, next);
1332
+ if (storage) {
1333
+ try {
1334
+ storage.setItem(openKey, openId);
1335
+ } catch {
1336
+ }
1337
+ }
1338
+ return next;
1339
+ };
1258
1340
 
1259
1341
  // src/reviews/useReviewGate.ts
1260
1342
  var useStableValue2 = (value, isEqual) => {
@@ -1281,12 +1363,10 @@ var useReviewGate = ({
1281
1363
  const seenKey = reviewSeenKey(config.id, config.oncePerVersion === false ? void 0 : config.appVersion);
1282
1364
  const lastKey = reviewLastShownKey(config.id);
1283
1365
  const sessionsKey = reviewSessionsKey(config.id);
1284
- const sessions = useState(() => {
1285
- const store = resolveStorage(storage);
1286
- const next = readInt(store, sessionsKey) + 1;
1287
- writeInt(store, sessionsKey, next);
1288
- return next;
1289
- })[0];
1366
+ const sessionOpenKey = reviewSessionOpenKey(config.id);
1367
+ const sessions = useState(
1368
+ () => bumpSessionCount(resolveStorage(storage), sessionsKey, sessionOpenKey)
1369
+ )[0];
1290
1370
  const hasServerDecision = decision != null;
1291
1371
  const timeoutMs = config.timeoutFallbackMs;
1292
1372
  const waits = !hasServerDecision && timeoutMs != null && timeoutMs > 0;