@wireai/activation 0.12.1 → 0.13.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 +146 -1
- package/README.md +5 -3
- package/dist/analytics/index.d.mts +2 -2
- package/dist/analytics/index.d.ts +2 -2
- package/dist/analytics/index.js +114 -35
- package/dist/analytics/index.js.map +1 -1
- package/dist/analytics/index.mjs +114 -36
- package/dist/analytics/index.mjs.map +1 -1
- package/dist/{currentSession-BxEB37xt.d.ts → currentSession-D7zabMXK.d.ts} +161 -9
- package/dist/{currentSession-BlCeDP0f.d.mts → currentSession-_GynvhzT.d.mts} +161 -9
- package/dist/index.d.mts +5 -15
- package/dist/index.d.ts +5 -15
- package/dist/index.js +232 -135
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +229 -134
- package/dist/index.mjs.map +1 -1
- package/dist/questionnaire/index.js.map +1 -1
- package/dist/questionnaire/index.mjs.map +1 -1
- package/dist/reviews/index.js +8 -6
- package/dist/reviews/index.js.map +1 -1
- package/dist/reviews/index.mjs +8 -6
- package/dist/reviews/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/OnboardingFlow.tsx +10 -3
- package/src/WireOnboarding.tsx +115 -32
- package/src/activation/wireActivation.ts +13 -7
- package/src/analytics/analyticsFacade.ts +11 -10
- package/src/analytics/currentSession.ts +6 -20
- package/src/analytics/eventQueue.ts +69 -1
- package/src/analytics/index.ts +1 -1
- package/src/analytics/reportClientEvent.ts +92 -29
- package/src/config/wireConfigFromEnv.ts +1 -10
- package/src/context/deviceId.ts +77 -16
- package/src/context/userContext.ts +4 -15
- package/src/identity/identityRecord.ts +123 -0
- package/src/identity/userIdentity.ts +45 -9
- package/src/index.ts +6 -4
- package/src/session-analytics/useLifecycleEvents.ts +10 -1
- package/src/types.ts +14 -0
- package/src/utils/deriveAnswers.ts +6 -2
- package/src/utils/readProgress.ts +4 -0
- package/src/utils/warnInDev.ts +33 -0
- package/src/components/DoneBlock.tsx +0 -37
package/dist/analytics/index.mjs
CHANGED
|
@@ -411,28 +411,35 @@ var buildEventsRequest = (target, events) => {
|
|
|
411
411
|
return null;
|
|
412
412
|
}
|
|
413
413
|
};
|
|
414
|
-
var
|
|
415
|
-
if (typeof __DEV__ === "undefined" || !__DEV__) return;
|
|
416
|
-
if (typeof console === "undefined" || !console.warn) return;
|
|
414
|
+
var readEventsAck = async (res) => {
|
|
417
415
|
try {
|
|
418
416
|
const json = res == null ? void 0 : res.json;
|
|
419
|
-
if (typeof json !== "function") return;
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
);
|
|
426
|
-
}).catch(() => {
|
|
427
|
-
});
|
|
417
|
+
if (typeof json !== "function") return void 0;
|
|
418
|
+
const body = await Promise.resolve(json.call(res));
|
|
419
|
+
const skipped = body == null ? void 0 : body.skipped;
|
|
420
|
+
if (typeof skipped !== "number" || !Number.isFinite(skipped)) return void 0;
|
|
421
|
+
const reasons = Array.isArray(body == null ? void 0 : body.errors) ? body.errors.map((e) => e == null ? void 0 : e.reason).filter((r) => typeof r === "string") : [];
|
|
422
|
+
return { written: typeof (body == null ? void 0 : body.written) === "number" ? body.written : void 0, skipped, reasons };
|
|
428
423
|
} catch {
|
|
424
|
+
return void 0;
|
|
429
425
|
}
|
|
430
426
|
};
|
|
427
|
+
var describeDiscarded = (ack) => `[wireai] the server ACCEPTED the /v1/events POST but DISCARDED ${ack.skipped} event(s) (skipped in the response body) \u2014 they are gone, not retried. The server reported: skipped=${ack.skipped}${ack.written !== void 0 ? `, written=${ack.written}` : ""}` + (ack.reasons.length > 0 ? `, reasons: ${ack.reasons.join(", ")}.` : ". It gave no reason (the endpoint folds every rejection into one count), so check the server's ingest log for this request rather than guessing.");
|
|
428
|
+
var warnOnSkippedEvents = (res) => {
|
|
429
|
+
if (typeof __DEV__ === "undefined" || !__DEV__) return;
|
|
430
|
+
if (typeof console === "undefined" || !console.warn) return;
|
|
431
|
+
void readEventsAck(res).then((ack) => {
|
|
432
|
+
if (!ack || ack.skipped <= 0) return;
|
|
433
|
+
console.warn(describeDiscarded(ack));
|
|
434
|
+
});
|
|
435
|
+
};
|
|
431
436
|
var reportClientEvents = (target, events) => {
|
|
432
437
|
try {
|
|
433
438
|
const req = buildEventsRequest(target, events);
|
|
434
439
|
if (!req) return;
|
|
435
|
-
void fetch(req.url, req.init).
|
|
440
|
+
void fetch(req.url, req.init).then((res) => {
|
|
441
|
+
warnOnSkippedEvents(res);
|
|
442
|
+
}).catch(() => {
|
|
436
443
|
});
|
|
437
444
|
} catch {
|
|
438
445
|
}
|
|
@@ -443,13 +450,28 @@ var reportClientEventsAwait = async (target, events) => {
|
|
|
443
450
|
const req = buildEventsRequest(target, events);
|
|
444
451
|
if (!req) return false;
|
|
445
452
|
const res = await fetch(req.url, req.init);
|
|
446
|
-
|
|
453
|
+
if (!res || !res.ok) return false;
|
|
454
|
+
const ack = await readEventsAck(res);
|
|
455
|
+
if (!ack || ack.skipped <= 0) return true;
|
|
456
|
+
if (typeof __DEV__ !== "undefined" && __DEV__ && typeof console !== "undefined" && console.warn) {
|
|
457
|
+
console.warn(describeDiscarded(ack));
|
|
458
|
+
}
|
|
459
|
+
return false;
|
|
447
460
|
} catch {
|
|
448
461
|
return false;
|
|
449
462
|
}
|
|
450
463
|
};
|
|
451
464
|
var reportClientEventAwait = (target, event) => reportClientEventsAwait(target, [event]);
|
|
452
465
|
|
|
466
|
+
// src/utils/warnInDev.ts
|
|
467
|
+
var warnInDev = (message) => {
|
|
468
|
+
if (typeof __DEV__ !== "undefined" && __DEV__ && typeof console !== "undefined" && console.warn) {
|
|
469
|
+
console.warn(message);
|
|
470
|
+
return true;
|
|
471
|
+
}
|
|
472
|
+
return false;
|
|
473
|
+
};
|
|
474
|
+
|
|
453
475
|
// src/analytics/currentSession.ts
|
|
454
476
|
var CURRENT_SESSION_ID_SLOT = /* @__PURE__ */ Symbol.for(
|
|
455
477
|
"@wireai/activation:currentSessionId"
|
|
@@ -464,13 +486,6 @@ var getCurrentSessionId = () => globalSlot[CURRENT_SESSION_ID_SLOT];
|
|
|
464
486
|
var resetCurrentSessionId = () => {
|
|
465
487
|
globalSlot[CURRENT_SESSION_ID_SLOT] = void 0;
|
|
466
488
|
};
|
|
467
|
-
var warnInDev = (message) => {
|
|
468
|
-
if (typeof __DEV__ !== "undefined" && __DEV__ && typeof console !== "undefined" && console.warn) {
|
|
469
|
-
console.warn(message);
|
|
470
|
-
return true;
|
|
471
|
-
}
|
|
472
|
-
return false;
|
|
473
|
-
};
|
|
474
489
|
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.";
|
|
475
490
|
var MINT_WARNED_SLOT = /* @__PURE__ */ Symbol.for(
|
|
476
491
|
"@wireai/activation:currentSessionIdMintWarned"
|
|
@@ -818,6 +833,31 @@ var DEFAULTS = {
|
|
|
818
833
|
maxRetries: 6
|
|
819
834
|
};
|
|
820
835
|
var READ_TIMEOUT_MS = 1500;
|
|
836
|
+
var QUEUE_KEY_SLOT = /* @__PURE__ */ Symbol.for("@wireai/activation:eventQueueKeys");
|
|
837
|
+
var queueKeyGlobal = globalThis;
|
|
838
|
+
var claimedQueueKeys = () => {
|
|
839
|
+
const existing = queueKeyGlobal[QUEUE_KEY_SLOT];
|
|
840
|
+
if (existing) return existing;
|
|
841
|
+
const created = /* @__PURE__ */ new Set();
|
|
842
|
+
queueKeyGlobal[QUEUE_KEY_SLOT] = created;
|
|
843
|
+
return created;
|
|
844
|
+
};
|
|
845
|
+
var claimQueueKey = (preferred, explicit) => {
|
|
846
|
+
const claimed = claimedQueueKeys();
|
|
847
|
+
if (explicit || !claimed.has(preferred)) {
|
|
848
|
+
claimed.add(preferred);
|
|
849
|
+
return preferred;
|
|
850
|
+
}
|
|
851
|
+
let ordinal = 2;
|
|
852
|
+
while (claimed.has(`${preferred}#${ordinal}`)) ordinal++;
|
|
853
|
+
const key = `${preferred}#${ordinal}`;
|
|
854
|
+
claimed.add(key);
|
|
855
|
+
warnInDev(
|
|
856
|
+
`[wireai] a second event queue was created for the same appId, and "${preferred}" is already claimed by a live one. Two queues sharing one storage slot overwrite each other's backlog, delete each other's pending events when one drains to empty, and double-send on relaunch, so this queue was given "${key}" instead. Prefer ONE analytics instance per app; if you really need two, pass an explicit \`storageKey\` to each so the slots are yours to reason about.`
|
|
857
|
+
);
|
|
858
|
+
return key;
|
|
859
|
+
};
|
|
860
|
+
var resetEventQueueKeys = () => claimedQueueKeys().clear();
|
|
821
861
|
var withTimeout = (p, ms) => {
|
|
822
862
|
let timer;
|
|
823
863
|
const timeout = new Promise((resolve) => {
|
|
@@ -849,7 +889,8 @@ var createEventQueue = (options) => {
|
|
|
849
889
|
var _a2, _b, _c, _d, _e, _f, _g;
|
|
850
890
|
const target = options.target;
|
|
851
891
|
const storage = options.storage;
|
|
852
|
-
const
|
|
892
|
+
const defaultKey = (_b = options.storageKey) != null ? _b : `wireai:evtq:${(_a2 = options.appId) != null ? _a2 : "default"}`;
|
|
893
|
+
const key = storage ? claimQueueKey(defaultKey, options.storageKey !== void 0) : defaultKey;
|
|
853
894
|
const maxSize = (_c = options.maxSize) != null ? _c : DEFAULTS.maxSize;
|
|
854
895
|
const batchSize = (_d = options.batchSize) != null ? _d : DEFAULTS.batchSize;
|
|
855
896
|
const baseBackoffMs = (_e = options.baseBackoffMs) != null ? _e : DEFAULTS.baseBackoffMs;
|
|
@@ -1100,6 +1141,29 @@ var resolveUserContext = (ctx = {}, opts = {}) => {
|
|
|
1100
1141
|
return result;
|
|
1101
1142
|
};
|
|
1102
1143
|
|
|
1144
|
+
// src/identity/identityRecord.ts
|
|
1145
|
+
var IDENTITY_PROVENANCE_SLOT = /* @__PURE__ */ Symbol.for("@wireai/activation:identityProvenance");
|
|
1146
|
+
var provenanceGlobal = globalThis;
|
|
1147
|
+
var provenanceRegistry = () => {
|
|
1148
|
+
const existing = provenanceGlobal[IDENTITY_PROVENANCE_SLOT];
|
|
1149
|
+
if (existing) return existing;
|
|
1150
|
+
const created = { host: /* @__PURE__ */ new Map() };
|
|
1151
|
+
provenanceGlobal[IDENTITY_PROVENANCE_SLOT] = created;
|
|
1152
|
+
return created;
|
|
1153
|
+
};
|
|
1154
|
+
var provenanceKey = (space, scope) => `${space}:${scope != null ? scope : "default"}`;
|
|
1155
|
+
var resolveIdentity = (input) => {
|
|
1156
|
+
var _a2;
|
|
1157
|
+
if (typeof input.value !== "string") return void 0;
|
|
1158
|
+
const value = input.value.trim();
|
|
1159
|
+
if (!value) return void 0;
|
|
1160
|
+
const durable = (_a2 = input.durable) != null ? _a2 : input.source === "host";
|
|
1161
|
+
{
|
|
1162
|
+
provenanceRegistry().host.set(provenanceKey(input.space, input.scope), value);
|
|
1163
|
+
}
|
|
1164
|
+
return { value, space: input.space, source: input.source, durable };
|
|
1165
|
+
};
|
|
1166
|
+
|
|
1103
1167
|
// src/context/deviceId.ts
|
|
1104
1168
|
var AUTO_DEVICE_ID_PREFIX = "wdev_";
|
|
1105
1169
|
var deviceIdStorageKey = (appId) => `wireai:analytics:deviceKey:${appId != null ? appId : "default"}`;
|
|
@@ -1122,24 +1186,37 @@ var startHydration = (registry, appId, storage, minted) => {
|
|
|
1122
1186
|
const existing = registry.pending.get(appId);
|
|
1123
1187
|
if (existing) return existing;
|
|
1124
1188
|
const slot = deviceIdStorageKey(appId);
|
|
1125
|
-
const
|
|
1189
|
+
const degraded = () => {
|
|
1126
1190
|
var _a2;
|
|
1127
|
-
return (_a2 = registry.keys.get(appId)) != null ? _a2 : minted;
|
|
1191
|
+
return { value: (_a2 = registry.keys.get(appId)) != null ? _a2 : minted, durable: false };
|
|
1128
1192
|
};
|
|
1193
|
+
const adopted = (value) => ({ value, durable: true });
|
|
1129
1194
|
let run;
|
|
1130
1195
|
try {
|
|
1131
1196
|
run = Promise.resolve(storage.getItem(slot)).then((saved) => {
|
|
1132
1197
|
const persisted = typeof saved === "string" && saved.trim() ? saved.trim() : void 0;
|
|
1133
1198
|
if (persisted) {
|
|
1134
1199
|
registry.keys.set(appId, persisted);
|
|
1135
|
-
return persisted;
|
|
1200
|
+
return adopted(persisted);
|
|
1136
1201
|
}
|
|
1137
|
-
return Promise.resolve(storage.setItem(slot, minted)).then(
|
|
1138
|
-
|
|
1202
|
+
return Promise.resolve(storage.setItem(slot, minted)).then(
|
|
1203
|
+
() => {
|
|
1204
|
+
var _a2;
|
|
1205
|
+
return adopted((_a2 = registry.keys.get(appId)) != null ? _a2 : minted);
|
|
1206
|
+
},
|
|
1207
|
+
degraded
|
|
1208
|
+
);
|
|
1209
|
+
}).catch(degraded);
|
|
1139
1210
|
} catch {
|
|
1140
|
-
run = Promise.resolve(
|
|
1211
|
+
run = Promise.resolve(degraded());
|
|
1141
1212
|
}
|
|
1142
1213
|
registry.pending.set(appId, run);
|
|
1214
|
+
void run.then((outcome) => {
|
|
1215
|
+
var _a2;
|
|
1216
|
+
if (outcome.durable) return;
|
|
1217
|
+
(_a2 = registry.pending) == null ? void 0 : _a2.delete(appId);
|
|
1218
|
+
registry.hydrating.delete(appId);
|
|
1219
|
+
});
|
|
1143
1220
|
return run;
|
|
1144
1221
|
};
|
|
1145
1222
|
var resolveAutoDeviceKey = (opts = {}) => {
|
|
@@ -1167,11 +1244,6 @@ var resetAutoDeviceKeys = () => {
|
|
|
1167
1244
|
};
|
|
1168
1245
|
|
|
1169
1246
|
// src/analytics/analyticsFacade.ts
|
|
1170
|
-
var warnInDev2 = (message) => {
|
|
1171
|
-
if (typeof __DEV__ !== "undefined" && __DEV__ && typeof console !== "undefined" && console.warn) {
|
|
1172
|
-
console.warn(message);
|
|
1173
|
-
}
|
|
1174
|
-
};
|
|
1175
1247
|
var createAnalytics = (config, options = {}) => {
|
|
1176
1248
|
var _a2, _b, _c;
|
|
1177
1249
|
const resolveSessionId = () => {
|
|
@@ -1179,13 +1251,19 @@ var createAnalytics = (config, options = {}) => {
|
|
|
1179
1251
|
return (_a3 = config.sessionId) != null ? _a3 : ensureCurrentSessionId();
|
|
1180
1252
|
};
|
|
1181
1253
|
if (config.sessionId) {
|
|
1182
|
-
|
|
1254
|
+
warnInDev(
|
|
1183
1255
|
"[wireai] createAnalytics({ sessionId }) PINS every event from this instance to that one frozen id and opts out of the live per-open session (app.session_started) \u2014 lifecycle analytics collapse onto a single device-scoped id. Remove it unless your host runs its own session lifecycle."
|
|
1184
1256
|
);
|
|
1185
1257
|
}
|
|
1186
1258
|
const detectedAppVersion = detectAppVersion();
|
|
1187
1259
|
let userContext = { ...(_a2 = config.userContext) != null ? _a2 : {} };
|
|
1188
1260
|
const hostDeviceKeyAtInit = typeof ((_b = config.userContext) == null ? void 0 : _b.deviceKey) === "string" && config.userContext.deviceKey.trim() ? config.userContext.deviceKey.trim() : void 0;
|
|
1261
|
+
resolveIdentity({
|
|
1262
|
+
value: hostDeviceKeyAtInit,
|
|
1263
|
+
space: "device",
|
|
1264
|
+
source: "host",
|
|
1265
|
+
scope: config.appId
|
|
1266
|
+
});
|
|
1189
1267
|
const autoDeviceKeyOptions = {
|
|
1190
1268
|
appId: config.appId,
|
|
1191
1269
|
// A host-supplied deviceKey opts out of minting AND persisting (unchanged contract).
|
|
@@ -1231,7 +1309,7 @@ var createAnalytics = (config, options = {}) => {
|
|
|
1231
1309
|
};
|
|
1232
1310
|
const guardUserId = (clean) => {
|
|
1233
1311
|
if (config.allowEmailAsUserId || !looksLikeEmail(clean)) return clean;
|
|
1234
|
-
|
|
1312
|
+
warnInDev(
|
|
1235
1313
|
"[wireai] identify() was called with an email-shaped id. A raw email must NOT be the opaque user_id (PII leak) \u2014 pass it as userContext.userEmail instead. Binding was skipped. Set allowEmailAsUserId:true on createAnalytics if your user id genuinely is an email."
|
|
1236
1314
|
);
|
|
1237
1315
|
return void 0;
|
|
@@ -1330,6 +1408,6 @@ var useAnalytics = (config, options = {}) => {
|
|
|
1330
1408
|
return ref.current;
|
|
1331
1409
|
};
|
|
1332
1410
|
|
|
1333
|
-
export { AUTO_DEVICE_ID_PREFIX, WIRE_ONBOARDING_EVENTS, analyticsUserIdStorageKey, buildContextEnvelope, clearPiiFromContext, clearUserContext, createAnalytics, createEventQueue, createScreenTracker, deviceIdStorageKey, ensureCurrentSessionId, getActiveRouteName, getCurrentSessionId, looksLikeEmail, makeSessionId, reportAppEvent, reportClientEvent, reportClientEventAwait, reportClientEvents, reportClientEventsAwait, resetAutoDeviceKeys, resetCurrentSessionId, resolveAutoDeviceKey, screenTrackingHandler, setCurrentSessionId, toAnalyticsEvent, useAnalytics, useScreenTracking };
|
|
1411
|
+
export { AUTO_DEVICE_ID_PREFIX, WIRE_ONBOARDING_EVENTS, analyticsUserIdStorageKey, buildContextEnvelope, clearPiiFromContext, clearUserContext, createAnalytics, createEventQueue, createScreenTracker, deviceIdStorageKey, ensureCurrentSessionId, getActiveRouteName, getCurrentSessionId, looksLikeEmail, makeSessionId, reportAppEvent, reportClientEvent, reportClientEventAwait, reportClientEvents, reportClientEventsAwait, resetAutoDeviceKeys, resetCurrentSessionId, resetEventQueueKeys, resolveAutoDeviceKey, screenTrackingHandler, setCurrentSessionId, toAnalyticsEvent, useAnalytics, useScreenTracking };
|
|
1334
1412
|
//# sourceMappingURL=index.mjs.map
|
|
1335
1413
|
//# sourceMappingURL=index.mjs.map
|