@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
@@ -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-Bzb-bcB2.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-B31G0Cib.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
  /**
@@ -504,7 +504,14 @@ var resolveRules = (config) => {
504
504
  var _a, _b, _c, _d, _e;
505
505
  return {
506
506
  enabled: (_a = config.enabled) != null ? _a : true,
507
- minSessions: (_b = config.minSessions) != null ? _b : 0,
507
+ // FAIL-CLOSED on the first session (behavior change, 0.11.0). An unconfigured host with no
508
+ // server decision must NOT prompt on the very first mount: `minSessions` defaults to 2 so the
509
+ // gate needs at least a second session before the LOCAL rules can fire. This is the client-side
510
+ // floor only — a server `decision` (from `fetchReviewDecision`) still OVERRIDES everything, and a
511
+ // host that genuinely wants first-session prompting can set `minSessions: 1` (or `0`) explicitly.
512
+ // Mirrors the 2026-07-16 incident: a first-session user, no server decision, got the prompt and
513
+ // left 1 star. See `evaluateGate` (`min_sessions` reason) and the CHANGELOG.
514
+ minSessions: (_b = config.minSessions) != null ? _b : 2,
508
515
  minEvents: (_c = config.minEvents) != null ? _c : 0,
509
516
  cooldownDays: (_d = config.cooldownDays) != null ? _d : 0,
510
517
  oncePerVersion: (_e = config.oncePerVersion) != null ? _e : true
@@ -728,6 +735,49 @@ var requestStoreReview = async (store) => {
728
735
  return opened ? "store_url" : "none";
729
736
  };
730
737
 
738
+ // src/analytics/reportClientEvent.ts
739
+ var makeSessionId = () => `wire_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
740
+ var buildEventsRequest = (target, events) => {
741
+ if (!(target == null ? void 0 : target.serverUrl) || events.length === 0) return null;
742
+ try {
743
+ const url = `${target.serverUrl.replace(/\/$/, "")}/v1/events`;
744
+ const headers = { "Content-Type": "application/json" };
745
+ if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
746
+ return { url, init: { method: "POST", headers, body: JSON.stringify({ events }) } };
747
+ } catch {
748
+ return null;
749
+ }
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
+
731
781
  // src/reviews/transport.ts
732
782
  var submitReview = (target, review) => {
733
783
  if (!(target == null ? void 0 : target.serverUrl)) return;
@@ -765,25 +815,22 @@ var fetchReviewDecision = async (target, options = {}) => {
765
815
  }
766
816
  };
767
817
  var reportAppEvent = (target, name, options = {}) => {
818
+ var _a;
768
819
  if (!(target == null ? void 0 : target.serverUrl) || !name) return;
769
820
  try {
770
- const url = `${target.serverUrl.replace(/\/$/, "")}/v1/events`;
771
- const headers = { "Content-Type": "application/json" };
772
- if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
821
+ const supplied = (_a = options.sessionId) != null ? _a : "";
773
822
  const event = {
774
823
  event_type: "app_event",
775
- question_key: name
824
+ question_key: name,
825
+ session_id: supplied.trim().length > 0 ? supplied : ensureCurrentSessionId()
776
826
  };
777
- if (options.sessionId) event.session_id = options.sessionId;
778
827
  if (options.deviceKey) event.user_context = { device_key: options.deviceKey };
779
828
  if (options.meta && Object.keys(options.meta).length > 0) {
780
829
  event.meta = JSON.stringify(options.meta);
781
830
  }
782
- void fetch(url, {
783
- method: "POST",
784
- headers,
785
- body: JSON.stringify({ events: [event] })
786
- }).catch(() => {
831
+ const req = buildEventsRequest(target, [event]);
832
+ if (!req) return;
833
+ void fetch(req.url, req.init).catch(() => {
787
834
  });
788
835
  } catch {
789
836
  }
@@ -1243,6 +1290,7 @@ var sameDecision = (a, b) => {
1243
1290
  var reviewSeenKey = (id, version) => version ? `wire_review_${id}_${version}_seen` : `wire_review_${id}_seen`;
1244
1291
  var reviewLastShownKey = (id) => `wire_review_${id}_last`;
1245
1292
  var reviewSessionsKey = (id) => `wire_review_${id}_sessions`;
1293
+ var reviewSessionOpenKey = (id) => `wire_review_${id}_open`;
1246
1294
  var resolveStorage = (override) => override != null ? override : getCoachmarkStorage();
1247
1295
  var readInt = (storage, key) => {
1248
1296
  if (!storage) return 0;
@@ -1261,6 +1309,40 @@ var writeInt = (storage, key, value) => {
1261
1309
  } catch {
1262
1310
  }
1263
1311
  };
1312
+ var readStr = (storage, key) => {
1313
+ if (!storage) return void 0;
1314
+ try {
1315
+ const raw = storage.getItem(key);
1316
+ return typeof raw === "string" && raw.length > 0 ? raw : void 0;
1317
+ } catch {
1318
+ return void 0;
1319
+ }
1320
+ };
1321
+ var PROCESS_OPEN_ID_SLOT = /* @__PURE__ */ Symbol.for("@wireai/activation:processOpenId");
1322
+ var openIdGlobal = globalThis;
1323
+ var currentOpenId = () => {
1324
+ const live = getCurrentSessionId();
1325
+ if (live) return live;
1326
+ const existing = openIdGlobal[PROCESS_OPEN_ID_SLOT];
1327
+ if (existing) return existing;
1328
+ const created = makeSessionId();
1329
+ openIdGlobal[PROCESS_OPEN_ID_SLOT] = created;
1330
+ return created;
1331
+ };
1332
+ var bumpSessionCount = (storage, sessionsKey, openKey, openId = currentOpenId()) => {
1333
+ const lastOpen = readStr(storage, openKey);
1334
+ const stored = readInt(storage, sessionsKey);
1335
+ if (lastOpen === openId) return stored > 0 ? stored : 1;
1336
+ const next = stored + 1;
1337
+ writeInt(storage, sessionsKey, next);
1338
+ if (storage) {
1339
+ try {
1340
+ storage.setItem(openKey, openId);
1341
+ } catch {
1342
+ }
1343
+ }
1344
+ return next;
1345
+ };
1264
1346
 
1265
1347
  // src/reviews/useReviewGate.ts
1266
1348
  var useStableValue2 = (value, isEqual) => {
@@ -1287,12 +1369,10 @@ var useReviewGate = ({
1287
1369
  const seenKey = reviewSeenKey(config.id, config.oncePerVersion === false ? void 0 : config.appVersion);
1288
1370
  const lastKey = reviewLastShownKey(config.id);
1289
1371
  const sessionsKey = reviewSessionsKey(config.id);
1290
- const sessions = React7.useState(() => {
1291
- const store = resolveStorage(storage);
1292
- const next = readInt(store, sessionsKey) + 1;
1293
- writeInt(store, sessionsKey, next);
1294
- return next;
1295
- })[0];
1372
+ const sessionOpenKey = reviewSessionOpenKey(config.id);
1373
+ const sessions = React7.useState(
1374
+ () => bumpSessionCount(resolveStorage(storage), sessionsKey, sessionOpenKey)
1375
+ )[0];
1296
1376
  const hasServerDecision = decision != null;
1297
1377
  const timeoutMs = config.timeoutFallbackMs;
1298
1378
  const waits = !hasServerDecision && timeoutMs != null && timeoutMs > 0;