@wireai/activation 0.3.0 → 0.7.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 (75) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +57 -0
  3. package/dist/analytics/index.d.mts +15 -2
  4. package/dist/analytics/index.d.ts +15 -2
  5. package/dist/analytics/index.js +224 -14
  6. package/dist/analytics/index.js.map +1 -1
  7. package/dist/analytics/index.mjs +222 -15
  8. package/dist/analytics/index.mjs.map +1 -1
  9. package/dist/coachmarks/index.js +6 -1
  10. package/dist/coachmarks/index.js.map +1 -1
  11. package/dist/coachmarks/index.mjs +6 -1
  12. package/dist/coachmarks/index.mjs.map +1 -1
  13. package/dist/{eventQueue-CA1d8Fmn.d.mts → currentSession-BJBB7i4-.d.mts} +143 -15
  14. package/dist/{eventQueue-CrNB9gzH.d.ts → currentSession-CxnP7gAa.d.ts} +143 -15
  15. package/dist/index.d.mts +40 -39
  16. package/dist/index.d.ts +40 -39
  17. package/dist/index.js +378 -154
  18. package/dist/index.js.map +1 -1
  19. package/dist/index.mjs +370 -156
  20. package/dist/index.mjs.map +1 -1
  21. package/dist/questionnaire/index.d.mts +1 -1
  22. package/dist/questionnaire/index.d.ts +1 -1
  23. package/dist/questionnaire/index.js +29 -5
  24. package/dist/questionnaire/index.js.map +1 -1
  25. package/dist/questionnaire/index.mjs +30 -6
  26. package/dist/questionnaire/index.mjs.map +1 -1
  27. package/dist/reviews/index.d.mts +1 -1
  28. package/dist/reviews/index.d.ts +1 -1
  29. package/dist/reviews/index.js +40 -9
  30. package/dist/reviews/index.js.map +1 -1
  31. package/dist/reviews/index.mjs +42 -11
  32. package/dist/reviews/index.mjs.map +1 -1
  33. package/dist/showcase/index.d.mts +1 -14
  34. package/dist/showcase/index.d.ts +1 -14
  35. package/dist/showcase/index.js +8 -2
  36. package/dist/showcase/index.js.map +1 -1
  37. package/dist/showcase/index.mjs +8 -2
  38. package/dist/showcase/index.mjs.map +1 -1
  39. package/package.json +3 -2
  40. package/src/OnboardingFlow.tsx +8 -4
  41. package/src/WireOnboarding.tsx +12 -6
  42. package/src/analytics/analyticsFacade.ts +86 -10
  43. package/src/analytics/contextEnvelope.ts +13 -7
  44. package/src/analytics/currentSession.ts +35 -0
  45. package/src/analytics/eventQueue.ts +5 -0
  46. package/src/analytics/index.ts +3 -0
  47. package/src/analytics/useAnalytics.ts +7 -1
  48. package/src/analytics/useScreenTracking.ts +23 -1
  49. package/src/cards/InterstitialCard.tsx +1 -1
  50. package/src/cards/NumberStepperCard.tsx +12 -7
  51. package/src/cards/StatusCard.tsx +13 -8
  52. package/src/cards/TextInputCard.tsx +10 -5
  53. package/src/components/AnimatedSparkle.tsx +20 -3
  54. package/src/components/Button.tsx +10 -1
  55. package/src/components/CardHandoff.tsx +2 -6
  56. package/src/components/CardLayout.tsx +16 -10
  57. package/src/components/Illustration.tsx +9 -5
  58. package/src/components/LoadingBlock.tsx +44 -29
  59. package/src/components/LoadingScreen.tsx +3 -2
  60. package/src/components/OnboardingScaffold.tsx +21 -23
  61. package/src/context/userContext.ts +210 -0
  62. package/src/device/appVersion.ts +103 -0
  63. package/src/device/deviceContext.ts +17 -5
  64. package/src/features/WireFeaturesProvider.tsx +7 -2
  65. package/src/identity/userIdentity.ts +5 -0
  66. package/src/index.ts +23 -0
  67. package/src/questionnaire/QuestionnaireGate.tsx +15 -3
  68. package/src/reviews/ReviewGate.tsx +27 -7
  69. package/src/reviews/ReviewModal.tsx +4 -0
  70. package/src/session-analytics/reportSessionStart.ts +7 -0
  71. package/src/session-analytics/useLifecycleEvents.ts +4 -2
  72. package/src/session-analytics/useSessionStart.ts +2 -1
  73. package/src/showcase/FeatureShowcase.tsx +2 -1
  74. package/src/types.ts +6 -5
  75. package/src/components/loaderChrome.ts +0 -28
@@ -1,6 +1,13 @@
1
1
  import { useRef, useEffect } from 'react';
2
2
  import { Platform, Dimensions, I18nManager } from 'react-native';
3
3
 
4
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
5
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
6
+ }) : x)(function(x) {
7
+ if (typeof require !== "undefined") return require.apply(this, arguments);
8
+ throw Error('Dynamic require of "' + x + '" is not supported');
9
+ });
10
+
4
11
  // src/reviews/transport.ts
5
12
  var reportAppEvent = (target, name, options = {}) => {
6
13
  if (!(target == null ? void 0 : target.serverUrl) || !name) return;
@@ -62,8 +69,29 @@ var createScreenTracker = (options = {}) => {
62
69
  };
63
70
  var screenTrackingHandler = (tracker) => (state) => tracker.track(getActiveRouteName(state));
64
71
  var useScreenTracking = (navigationRef, options = {}) => {
72
+ const optionsRef = useRef(options);
73
+ optionsRef.current = options;
65
74
  const trackerRef = useRef(void 0);
66
- if (!trackerRef.current) trackerRef.current = createScreenTracker(options);
75
+ if (!trackerRef.current) {
76
+ const dynamicOptions = {
77
+ get target() {
78
+ return optionsRef.current.target;
79
+ },
80
+ get sessionId() {
81
+ return optionsRef.current.sessionId;
82
+ },
83
+ get deviceKey() {
84
+ return optionsRef.current.deviceKey;
85
+ },
86
+ get onScreen() {
87
+ return optionsRef.current.onScreen;
88
+ },
89
+ get shouldTrack() {
90
+ return optionsRef.current.shouldTrack;
91
+ }
92
+ };
93
+ trackerRef.current = createScreenTracker(dynamicOptions);
94
+ }
67
95
  useEffect(() => {
68
96
  const tracker = trackerRef.current;
69
97
  if (!tracker || !(navigationRef == null ? void 0 : navigationRef.addListener)) return;
@@ -134,6 +162,57 @@ var toAnalyticsEvent = (event) => {
134
162
  }
135
163
  }
136
164
  };
165
+
166
+ // src/device/appVersion.ts
167
+ var coerceVersion = (value) => {
168
+ if (typeof value !== "string") return void 0;
169
+ const trimmed = value.trim();
170
+ return trimmed.length > 0 ? trimmed : void 0;
171
+ };
172
+ var runtimeRequire = (moduleName) => {
173
+ try {
174
+ if (typeof __require !== "function") return void 0;
175
+ return __require(moduleName);
176
+ } catch {
177
+ return void 0;
178
+ }
179
+ };
180
+ var interop = (mod) => {
181
+ if (!mod || typeof mod !== "object") return void 0;
182
+ const def = mod.default;
183
+ if (def && typeof def === "object") return def;
184
+ return mod;
185
+ };
186
+ var safeInterop = (requireModule, moduleName) => {
187
+ try {
188
+ return interop(requireModule(moduleName));
189
+ } catch {
190
+ return void 0;
191
+ }
192
+ };
193
+ var detectAppVersion = (requireModule = runtimeRequire) => {
194
+ try {
195
+ const constants = safeInterop(requireModule, "expo-constants");
196
+ if (constants) {
197
+ const expoConfig = constants.expoConfig;
198
+ if (expoConfig && typeof expoConfig === "object") {
199
+ const fromExpoConfig = coerceVersion(expoConfig.version);
200
+ if (fromExpoConfig) return fromExpoConfig;
201
+ }
202
+ const fromNative = coerceVersion(constants.nativeAppVersion);
203
+ if (fromNative) return fromNative;
204
+ }
205
+ const application = safeInterop(requireModule, "expo-application");
206
+ if (application) {
207
+ const fromApplication = coerceVersion(application.nativeApplicationVersion);
208
+ if (fromApplication) return fromApplication;
209
+ }
210
+ } catch {
211
+ }
212
+ return void 0;
213
+ };
214
+
215
+ // src/device/deviceContext.ts
137
216
  var deriveFormFactor = (iosIdiom, width, height) => {
138
217
  if (iosIdiom === "pad") return "tablet";
139
218
  if (iosIdiom === "phone") return "phone";
@@ -203,16 +282,20 @@ var collectDeviceContext = () => {
203
282
  if (resolved.timeZone) ctx.timeZone = resolved.timeZone;
204
283
  } catch {
205
284
  }
285
+ const appVersion = detectAppVersion();
286
+ if (appVersion) ctx.appVersion = appVersion;
206
287
  return ctx;
207
288
  };
208
289
 
209
290
  // src/analytics/contextEnvelope.ts
210
291
  var buildContextEnvelope = (input = {}) => {
292
+ var _a;
211
293
  const device = { ...collectDeviceContext() };
212
- if (input.appVersion && !device.appVersion) device.appVersion = input.appVersion;
294
+ if (input.appVersion) device.appVersion = input.appVersion;
295
+ const effectiveAppVersion = (_a = input.appVersion) != null ? _a : device.appVersion;
213
296
  const envelope = { device };
214
297
  if (input.sessionId) envelope.sessionId = input.sessionId;
215
- if (input.appVersion) envelope.appVersion = input.appVersion;
298
+ if (effectiveAppVersion) envelope.appVersion = effectiveAppVersion;
216
299
  if (input.appBuild) envelope.appBuild = input.appBuild;
217
300
  if (input.networkType) envelope.networkType = input.networkType;
218
301
  return envelope;
@@ -336,6 +419,8 @@ var createEventQueue = (options) => {
336
419
  })();
337
420
  const postBatch = async (events) => {
338
421
  if (!(target == null ? void 0 : target.serverUrl) || events.length === 0) return false;
422
+ const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
423
+ const timer = setTimeout(() => controller == null ? void 0 : controller.abort(), 15e3);
339
424
  try {
340
425
  const url = `${target.serverUrl.replace(/\/$/, "")}/v1/events`;
341
426
  const headers = { "Content-Type": "application/json" };
@@ -343,11 +428,14 @@ var createEventQueue = (options) => {
343
428
  const res = await fetch(url, {
344
429
  method: "POST",
345
430
  headers,
346
- body: JSON.stringify({ events })
431
+ body: JSON.stringify({ events }),
432
+ signal: controller == null ? void 0 : controller.signal
347
433
  });
348
434
  return !!(res && res.ok);
349
435
  } catch {
350
436
  return false;
437
+ } finally {
438
+ clearTimeout(timer);
351
439
  }
352
440
  };
353
441
  const clearRetry = () => {
@@ -421,6 +509,16 @@ var createEventQueue = (options) => {
421
509
  return { enqueue, flush, notifyOnline, size };
422
510
  };
423
511
 
512
+ // src/analytics/currentSession.ts
513
+ var _currentSessionId;
514
+ var setCurrentSessionId = (id) => {
515
+ if (typeof id === "string" && id.length > 0) _currentSessionId = id;
516
+ };
517
+ var getCurrentSessionId = () => _currentSessionId;
518
+ var resetCurrentSessionId = () => {
519
+ _currentSessionId = void 0;
520
+ };
521
+
424
522
  // src/identity/userIdentity.ts
425
523
  var USER_ID_MAX_LENGTH = 128;
426
524
  var sanitizeUserId = (raw) => {
@@ -430,12 +528,79 @@ var sanitizeUserId = (raw) => {
430
528
  return trimmed.length > USER_ID_MAX_LENGTH ? trimmed.slice(0, USER_ID_MAX_LENGTH) : trimmed;
431
529
  };
432
530
 
531
+ // src/context/userContext.ts
532
+ var EXTRA_KEY_PREFIX = "custom.";
533
+ var isWireScalar = (value) => {
534
+ const t = typeof value;
535
+ if (t === "string" || t === "boolean") return true;
536
+ if (t === "number") return Number.isFinite(value);
537
+ return false;
538
+ };
539
+ var hashEmailFnv1a = (email) => {
540
+ const normalized = email.trim().toLowerCase();
541
+ let hash = 2166136261;
542
+ for (let i = 0; i < normalized.length; i++) {
543
+ hash ^= normalized.charCodeAt(i);
544
+ hash = Math.imul(hash, 16777619);
545
+ }
546
+ return (hash >>> 0).toString(16).padStart(8, "0");
547
+ };
548
+ var cleanString = (value) => {
549
+ if (typeof value !== "string") return void 0;
550
+ const trimmed = value.trim();
551
+ return trimmed.length > 0 ? trimmed : void 0;
552
+ };
553
+ var namespaceExtra = (extra) => {
554
+ const out = {};
555
+ if (!extra || typeof extra !== "object") return out;
556
+ for (const [key, value] of Object.entries(extra)) {
557
+ const cleanKey = cleanString(key);
558
+ if (!cleanKey) continue;
559
+ if (!isWireScalar(value)) continue;
560
+ out[`${EXTRA_KEY_PREFIX}${cleanKey}`] = value;
561
+ }
562
+ return out;
563
+ };
564
+ var resolveUserContext = (ctx = {}, opts = {}) => {
565
+ var _a;
566
+ const result = {};
567
+ const bucket = {};
568
+ const userId = sanitizeUserId(ctx.userId);
569
+ if (userId) result.userId = userId;
570
+ const deviceKey = cleanString(ctx.deviceKey);
571
+ if (deviceKey) {
572
+ result.deviceKey = deviceKey;
573
+ bucket.device_key = deviceKey;
574
+ }
575
+ const appVersion = (_a = cleanString(ctx.appVersion)) != null ? _a : cleanString(opts.autoAppVersion);
576
+ if (appVersion) {
577
+ result.appVersion = appVersion;
578
+ bucket.app_version = appVersion;
579
+ }
580
+ const email = cleanString(ctx.userEmail);
581
+ if (email) {
582
+ if (ctx.hashEmail) {
583
+ bucket.user_email = hashEmailFnv1a(email);
584
+ bucket.user_email_hashed = true;
585
+ } else {
586
+ bucket.user_email = email;
587
+ }
588
+ }
589
+ Object.assign(bucket, namespaceExtra(ctx.extra));
590
+ if (Object.keys(bucket).length > 0) result.userContext = bucket;
591
+ return result;
592
+ };
593
+
433
594
  // src/analytics/analyticsFacade.ts
434
595
  var createAnalytics = (config, options = {}) => {
435
- var _a;
436
- const sessionId = (_a = config.sessionId) != null ? _a : makeSessionId();
596
+ var _a, _b, _c, _d;
597
+ const instanceSessionId = (_a = config.sessionId) != null ? _a : makeSessionId();
598
+ const resolveSessionId = () => {
599
+ var _a2, _b2;
600
+ return (_b2 = (_a2 = config.sessionId) != null ? _a2 : getCurrentSessionId()) != null ? _b2 : instanceSessionId;
601
+ };
437
602
  const envelope = () => buildContextEnvelope({
438
- sessionId,
603
+ sessionId: resolveSessionId(),
439
604
  appVersion: config.appVersion,
440
605
  appBuild: config.appBuild,
441
606
  networkType: config.networkType
@@ -447,16 +612,45 @@ var createAnalytics = (config, options = {}) => {
447
612
  envelope,
448
613
  ...options
449
614
  });
450
- let boundUserId;
615
+ let userContext = { ...(_b = config.userContext) != null ? _b : {} };
616
+ let boundUserId = sanitizeUserId((_c = config.userContext) == null ? void 0 : _c.userId);
617
+ const storageKey = `wireai:analytics:userId:${(_d = config.appId) != null ? _d : "default"}`;
618
+ if (config.storage) {
619
+ void config.storage.getItem(storageKey).then((saved) => {
620
+ if (saved && !boundUserId) boundUserId = saved;
621
+ }).catch(() => {
622
+ });
623
+ }
624
+ const applyContext = (event) => {
625
+ var _a2;
626
+ const resolved = resolveUserContext(userContext, { autoAppVersion: config.appVersion });
627
+ if (resolved.userContext) {
628
+ event.user_context = { ...resolved.userContext, ...(_a2 = event.user_context) != null ? _a2 : {} };
629
+ }
630
+ if (boundUserId && !event.user_id) event.user_id = boundUserId;
631
+ };
632
+ const setUserContext = (partial) => {
633
+ var _a2, _b2;
634
+ if (!partial || typeof partial !== "object") return;
635
+ const mergedExtra = partial.extra || userContext.extra ? { ...(_a2 = userContext.extra) != null ? _a2 : {}, ...(_b2 = partial.extra) != null ? _b2 : {} } : void 0;
636
+ userContext = { ...userContext, ...partial };
637
+ if (mergedExtra) userContext.extra = mergedExtra;
638
+ const uid = sanitizeUserId(partial.userId);
639
+ if (uid) {
640
+ boundUserId = uid;
641
+ if (config.storage) void config.storage.setItem(storageKey, uid).catch(() => {
642
+ });
643
+ }
644
+ };
451
645
  const track = (event, props) => {
452
646
  if (!event) return;
453
647
  const clientEvent = {
454
648
  event_type: "app_event",
455
- session_id: sessionId,
649
+ session_id: resolveSessionId(),
456
650
  question_key: event
457
651
  };
458
652
  if (props && Object.keys(props).length > 0) clientEvent.meta = JSON.stringify(props);
459
- if (boundUserId) clientEvent.user_id = boundUserId;
653
+ applyContext(clientEvent);
460
654
  queue.enqueue(clientEvent);
461
655
  };
462
656
  const screen = (name, props) => {
@@ -464,29 +658,37 @@ var createAnalytics = (config, options = {}) => {
464
658
  const meta = { screen: name, ...props != null ? props : {} };
465
659
  const clientEvent = {
466
660
  event_type: "app_event",
467
- session_id: sessionId,
661
+ session_id: resolveSessionId(),
468
662
  question_key: "screen",
469
663
  meta: JSON.stringify(meta)
470
664
  };
471
- if (boundUserId) clientEvent.user_id = boundUserId;
665
+ applyContext(clientEvent);
472
666
  queue.enqueue(clientEvent);
473
667
  };
474
668
  const identify = (userId, traits) => {
475
669
  const clean = sanitizeUserId(userId);
476
670
  if (!clean) return;
477
671
  boundUserId = clean;
672
+ if (config.storage) {
673
+ void config.storage.setItem(storageKey, clean).catch(() => {
674
+ });
675
+ }
478
676
  const clientEvent = {
479
677
  event_type: "identify",
480
- session_id: sessionId,
678
+ // Reuse the LIVE per-open session id (see `resolveSessionId`) so the server binds identity to
679
+ // the session it already saw instead of back-filling a phantom `session_started`.
680
+ session_id: resolveSessionId(),
481
681
  user_id: clean
482
682
  };
483
683
  if (traits && Object.keys(traits).length > 0) clientEvent.meta = JSON.stringify(traits);
684
+ applyContext(clientEvent);
484
685
  queue.enqueue(clientEvent);
485
686
  };
486
687
  return {
487
688
  track,
488
689
  screen,
489
690
  identify,
691
+ setUserContext,
490
692
  flush: queue.flush,
491
693
  notifyOnline: queue.notifyOnline,
492
694
  size: queue.size
@@ -494,10 +696,15 @@ var createAnalytics = (config, options = {}) => {
494
696
  };
495
697
  var useAnalytics = (config, options = {}) => {
496
698
  const ref = useRef(void 0);
497
- if (!ref.current) ref.current = createAnalytics(config, options);
699
+ const prevKeys = useRef("");
700
+ const currentKeys = `${config.serverUrl}|${config.apiKey}|${config.appId}`;
701
+ if (!ref.current || prevKeys.current !== currentKeys) {
702
+ prevKeys.current = currentKeys;
703
+ ref.current = createAnalytics(config, options);
704
+ }
498
705
  return ref.current;
499
706
  };
500
707
 
501
- export { WIRE_ONBOARDING_EVENTS, buildContextEnvelope, createAnalytics, createEventQueue, createScreenTracker, getActiveRouteName, makeSessionId, reportAppEvent, reportClientEvent, reportClientEvents, screenTrackingHandler, toAnalyticsEvent, useAnalytics, useScreenTracking };
708
+ export { WIRE_ONBOARDING_EVENTS, buildContextEnvelope, createAnalytics, createEventQueue, createScreenTracker, getActiveRouteName, getCurrentSessionId, makeSessionId, reportAppEvent, reportClientEvent, reportClientEvents, resetCurrentSessionId, screenTrackingHandler, setCurrentSessionId, toAnalyticsEvent, useAnalytics, useScreenTracking };
502
709
  //# sourceMappingURL=index.mjs.map
503
710
  //# sourceMappingURL=index.mjs.map