@wireai/activation 0.7.0 → 0.8.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.
package/CHANGELOG.md CHANGED
@@ -18,6 +18,41 @@ Historical entries below the rename keep the old package name on purpose.
18
18
  - Version reset to `0.1.0` to mark the start of the new package line. The GitHub repository
19
19
  stays `chohra-med/wireai-onboarding`.
20
20
 
21
+ ## [Unreleased]
22
+
23
+ ### Added: "device" is now fully automatic — auto-minted, persisted per-install `device_key`
24
+
25
+ - The analytics façade (`createAnalytics`) now auto-mints a stable, NON-PII per-install `device_key`
26
+ when the host supplies none via `WireUserContext.deviceKey`. It is minted ONCE (`context/deviceId.ts`
27
+ — `mintDeviceId`, dependency-free time+random, no `uuid`), PERSISTED via the host's existing
28
+ `storage` (`WireOnboardingStorage`) under `deviceIdStorageKey(appId)`, and REUSED on every open;
29
+ with no storage it falls back to an in-memory id. Result: `user_context.device_key` is ALWAYS
30
+ present, so the server's review/questionnaire gating + A/B stickiness (both key on `device_key`)
31
+ work out of the box with zero host wiring. A host-supplied `deviceKey` still WINS (and opts out of
32
+ auto-minting). New exports: `mintDeviceId`, `deviceIdStorageKey`, `AUTO_DEVICE_ID_PREFIX`.
33
+
34
+ ### Fixed: `WireUserContext.appVersion` now flows into `device.appVersion` (not just `user_context`)
35
+
36
+ - A host that set the app version ONLY inside `userContext` previously got it in
37
+ `user_context.app_version` but left `device.appVersion` on the auto-detected value — and the
38
+ server's `by_app_version` breakdown reads `device.appVersion`. The façade's context-envelope
39
+ provider now feeds `userContext.appVersion ?? config.appVersion` into `buildContextEnvelope`, so an
40
+ explicit `WireUserContext.appVersion` populates BOTH surfaces (explicit still wins over auto-detect).
41
+
42
+ ### Added: best-effort iOS `device.model`
43
+
44
+ - `device.model` was Android-only (`Platform.constants.Model`); iOS `Platform.constants` exposes no
45
+ model. `collectDeviceContext` now fills iOS `model` best-effort via `detectNativeModel`
46
+ (`device/deviceModel.ts`), reading `expo-device`'s `modelName`/`modelId` through the SAME guarded,
47
+ variable-specifier require the kit already uses for `appVersion` — so it adds NO dependency and is
48
+ simply omitted when `expo-device` is absent. It reads a device CLASS (not a unique id / not the
49
+ user-set device name), so it changes no App Privacy / Data Safety declaration. ASYMMETRY documented
50
+ in the `DeviceContext.model` TSDoc: without `expo-device`, iOS `model` stays omitted; no native
51
+ dependency is added for it.
52
+
53
+ - Backward compatible: no existing export renamed or removed; zero new dependencies. The one
54
+ behavior change is intentional — events now carry an auto `device_key` when the host omits one.
55
+
21
56
  ## [0.7.0] — Unreleased
22
57
 
23
58
  ### Added: `WireUserContext` — one rich user-context object → every event's `user_context`
@@ -1,6 +1,6 @@
1
1
  export { R as ReportAppEventOptions, r as reportAppEvent } from '../transport-BeO_Brcu.mjs';
2
- import { W as WireUserContext, E as EventQueueOptions } from '../currentSession-BJBB7i4-.mjs';
3
- export { A as AnalyticsEvent, C as ClientEvent, a as ClientEventTarget, b as ClientEventType, c as ContextEnvelope, d as ContextEnvelopeInput, e as EnvelopeSource, f as EventQueue, g as WIRE_ONBOARDING_EVENTS, h as WireOnboardingEventName, i as buildContextEnvelope, j as createEventQueue, k as getCurrentSessionId, m as makeSessionId, r as reportClientEvent, l as reportClientEvents, n as resetCurrentSessionId, s as setCurrentSessionId, t as toAnalyticsEvent } from '../currentSession-BJBB7i4-.mjs';
2
+ import { W as WireUserContext, E as EventQueueOptions } from '../currentSession-d9CrBxwe.mjs';
3
+ export { A as AnalyticsEvent, C as ClientEvent, a as ClientEventTarget, b as ClientEventType, c as ContextEnvelope, d as ContextEnvelopeInput, e as EnvelopeSource, f as EventQueue, g as WIRE_ONBOARDING_EVENTS, h as WireOnboardingEventName, i as buildContextEnvelope, j as createEventQueue, k as getCurrentSessionId, m as makeSessionId, r as reportClientEvent, l as reportClientEvents, n as resetCurrentSessionId, s as setCurrentSessionId, t as toAnalyticsEvent } from '../currentSession-d9CrBxwe.mjs';
4
4
  import '../types-A6pTxIZV.mjs';
5
5
  import '../types-BKfpdZzX.mjs';
6
6
  import '../types-GL_hQ0TN.mjs';
@@ -123,8 +123,12 @@ type CreateAnalyticsConfig = {
123
123
  /**
124
124
  * The rich {@link WireUserContext} to stamp onto every event's `user_context` (device key, opaque
125
125
  * user id, opt-in email, arbitrary `extra`). Passed ONCE here at init; updatable post-mount via
126
- * {@link Analytics.setUserContext} (e.g. attach `userId`/`userEmail` at login). Optional — omit it
127
- * and events carry only the device context, exactly as before.
126
+ * {@link Analytics.setUserContext} (e.g. attach `userId`/`userEmail` at login). Optional.
127
+ *
128
+ * NOTE on `deviceKey`: you do NOT need to supply one. When omitted, the kit auto-mints a stable,
129
+ * non-PII per-install `device_key`, persists it via `storage`, and reuses it every open (in-memory
130
+ * fallback without storage) — so `user_context.device_key` is ALWAYS present for the server's
131
+ * review/questionnaire gating + A/B stickiness. Supply `deviceKey` only to use your OWN id (it wins).
128
132
  */
129
133
  userContext?: WireUserContext;
130
134
  };
@@ -1,6 +1,6 @@
1
1
  export { R as ReportAppEventOptions, r as reportAppEvent } from '../transport-DLpd1v5_.js';
2
- import { W as WireUserContext, E as EventQueueOptions } from '../currentSession-CxnP7gAa.js';
3
- export { A as AnalyticsEvent, C as ClientEvent, a as ClientEventTarget, b as ClientEventType, c as ContextEnvelope, d as ContextEnvelopeInput, e as EnvelopeSource, f as EventQueue, g as WIRE_ONBOARDING_EVENTS, h as WireOnboardingEventName, i as buildContextEnvelope, j as createEventQueue, k as getCurrentSessionId, m as makeSessionId, r as reportClientEvent, l as reportClientEvents, n as resetCurrentSessionId, s as setCurrentSessionId, t as toAnalyticsEvent } from '../currentSession-CxnP7gAa.js';
2
+ import { W as WireUserContext, E as EventQueueOptions } from '../currentSession-f7LWcdWG.js';
3
+ export { A as AnalyticsEvent, C as ClientEvent, a as ClientEventTarget, b as ClientEventType, c as ContextEnvelope, d as ContextEnvelopeInput, e as EnvelopeSource, f as EventQueue, g as WIRE_ONBOARDING_EVENTS, h as WireOnboardingEventName, i as buildContextEnvelope, j as createEventQueue, k as getCurrentSessionId, m as makeSessionId, r as reportClientEvent, l as reportClientEvents, n as resetCurrentSessionId, s as setCurrentSessionId, t as toAnalyticsEvent } from '../currentSession-f7LWcdWG.js';
4
4
  import '../types-BhpXJGlg.js';
5
5
  import '../types-BKfpdZzX.js';
6
6
  import '../types-GL_hQ0TN.js';
@@ -123,8 +123,12 @@ type CreateAnalyticsConfig = {
123
123
  /**
124
124
  * The rich {@link WireUserContext} to stamp onto every event's `user_context` (device key, opaque
125
125
  * user id, opt-in email, arbitrary `extra`). Passed ONCE here at init; updatable post-mount via
126
- * {@link Analytics.setUserContext} (e.g. attach `userId`/`userEmail` at login). Optional — omit it
127
- * and events carry only the device context, exactly as before.
126
+ * {@link Analytics.setUserContext} (e.g. attach `userId`/`userEmail` at login). Optional.
127
+ *
128
+ * NOTE on `deviceKey`: you do NOT need to supply one. When omitted, the kit auto-mints a stable,
129
+ * non-PII per-install `device_key`, persists it via `storage`, and reuses it every open (in-memory
130
+ * fallback without storage) — so `user_context.device_key` is ALWAYS present for the server's
131
+ * review/questionnaire gating + A/B stickiness. Supply `deviceKey` only to use your OWN id (it wins).
128
132
  */
129
133
  userContext?: WireUserContext;
130
134
  };
@@ -214,6 +214,47 @@ var detectAppVersion = (requireModule = runtimeRequire) => {
214
214
  return void 0;
215
215
  };
216
216
 
217
+ // src/device/deviceModel.ts
218
+ var coerceModel = (value) => {
219
+ if (typeof value !== "string") return void 0;
220
+ const trimmed = value.trim();
221
+ return trimmed.length > 0 ? trimmed : void 0;
222
+ };
223
+ var runtimeRequire2 = (moduleName) => {
224
+ try {
225
+ if (typeof __require !== "function") return void 0;
226
+ return __require(moduleName);
227
+ } catch {
228
+ return void 0;
229
+ }
230
+ };
231
+ var interop2 = (mod) => {
232
+ if (!mod || typeof mod !== "object") return void 0;
233
+ const def = mod.default;
234
+ if (def && typeof def === "object") return def;
235
+ return mod;
236
+ };
237
+ var safeInterop2 = (requireModule, moduleName) => {
238
+ try {
239
+ return interop2(requireModule(moduleName));
240
+ } catch {
241
+ return void 0;
242
+ }
243
+ };
244
+ var detectNativeModel = (requireModule = runtimeRequire2) => {
245
+ try {
246
+ const device = safeInterop2(requireModule, "expo-device");
247
+ if (device) {
248
+ const modelName = coerceModel(device.modelName);
249
+ if (modelName) return modelName;
250
+ const modelId = coerceModel(device.modelId);
251
+ if (modelId) return modelId;
252
+ }
253
+ } catch {
254
+ }
255
+ return void 0;
256
+ };
257
+
217
258
  // src/device/deviceContext.ts
218
259
  var deriveFormFactor = (iosIdiom, width, height) => {
219
260
  if (iosIdiom === "pad") return "tablet";
@@ -260,6 +301,8 @@ var collectDeviceContext = () => {
260
301
  ctx.interfaceIdiom = idiom;
261
302
  iosIdiom = idiom;
262
303
  }
304
+ const iosModel = detectNativeModel();
305
+ if (iosModel) ctx.model = iosModel;
263
306
  }
264
307
  } catch {
265
308
  }
@@ -593,20 +636,46 @@ var resolveUserContext = (ctx = {}, opts = {}) => {
593
636
  return result;
594
637
  };
595
638
 
639
+ // src/context/deviceId.ts
640
+ var AUTO_DEVICE_ID_PREFIX = "wdev_";
641
+ var deviceIdStorageKey = (appId) => `wireai:analytics:deviceKey:${appId != null ? appId : "default"}`;
642
+ var randomChunk = () => Math.floor(Math.random() * 4294967296).toString(36).padStart(6, "0");
643
+ var mintDeviceId = () => {
644
+ const time = Date.now().toString(36);
645
+ return `${AUTO_DEVICE_ID_PREFIX}${time}_${randomChunk()}${randomChunk()}`;
646
+ };
647
+
596
648
  // src/analytics/analyticsFacade.ts
597
649
  var createAnalytics = (config, options = {}) => {
598
- var _a, _b, _c, _d;
650
+ var _a, _b, _c, _d, _e;
599
651
  const instanceSessionId = (_a = config.sessionId) != null ? _a : makeSessionId();
600
652
  const resolveSessionId = () => {
601
653
  var _a2, _b2;
602
654
  return (_b2 = (_a2 = config.sessionId) != null ? _a2 : getCurrentSessionId()) != null ? _b2 : instanceSessionId;
603
655
  };
604
- const envelope = () => buildContextEnvelope({
605
- sessionId: resolveSessionId(),
606
- appVersion: config.appVersion,
607
- appBuild: config.appBuild,
608
- networkType: config.networkType
609
- });
656
+ let userContext = { ...(_b = config.userContext) != null ? _b : {} };
657
+ const hostDeviceKeyAtInit = typeof ((_c = config.userContext) == null ? void 0 : _c.deviceKey) === "string" && config.userContext.deviceKey.trim() ? config.userContext.deviceKey.trim() : void 0;
658
+ let autoDeviceKey = mintDeviceId();
659
+ if (config.storage && !hostDeviceKeyAtInit) {
660
+ const storage = config.storage;
661
+ const deviceKey = deviceIdStorageKey(config.appId);
662
+ void storage.getItem(deviceKey).then((saved) => {
663
+ const persisted = typeof saved === "string" && saved.trim() ? saved.trim() : void 0;
664
+ if (persisted) autoDeviceKey = persisted;
665
+ else void storage.setItem(deviceKey, autoDeviceKey).catch(() => {
666
+ });
667
+ }).catch(() => {
668
+ });
669
+ }
670
+ const envelope = () => {
671
+ var _a2;
672
+ return buildContextEnvelope({
673
+ sessionId: resolveSessionId(),
674
+ appVersion: (_a2 = userContext.appVersion) != null ? _a2 : config.appVersion,
675
+ appBuild: config.appBuild,
676
+ networkType: config.networkType
677
+ });
678
+ };
610
679
  const queue = createEventQueue({
611
680
  target: { serverUrl: config.serverUrl, apiKey: config.apiKey },
612
681
  storage: config.storage,
@@ -614,9 +683,8 @@ var createAnalytics = (config, options = {}) => {
614
683
  envelope,
615
684
  ...options
616
685
  });
617
- let userContext = { ...(_b = config.userContext) != null ? _b : {} };
618
- let boundUserId = sanitizeUserId((_c = config.userContext) == null ? void 0 : _c.userId);
619
- const storageKey = `wireai:analytics:userId:${(_d = config.appId) != null ? _d : "default"}`;
686
+ let boundUserId = sanitizeUserId((_d = config.userContext) == null ? void 0 : _d.userId);
687
+ const storageKey = `wireai:analytics:userId:${(_e = config.appId) != null ? _e : "default"}`;
620
688
  if (config.storage) {
621
689
  void config.storage.getItem(storageKey).then((saved) => {
622
690
  if (saved && !boundUserId) boundUserId = saved;
@@ -625,7 +693,11 @@ var createAnalytics = (config, options = {}) => {
625
693
  }
626
694
  const applyContext = (event) => {
627
695
  var _a2;
628
- const resolved = resolveUserContext(userContext, { autoAppVersion: config.appVersion });
696
+ const hostDeviceKey = typeof userContext.deviceKey === "string" && userContext.deviceKey.trim() ? userContext.deviceKey : void 0;
697
+ const resolved = resolveUserContext(
698
+ { ...userContext, deviceKey: hostDeviceKey != null ? hostDeviceKey : autoDeviceKey },
699
+ { autoAppVersion: config.appVersion }
700
+ );
629
701
  if (resolved.userContext) {
630
702
  event.user_context = { ...resolved.userContext, ...(_a2 = event.user_context) != null ? _a2 : {} };
631
703
  }