@wireai/activation 0.1.1 → 0.3.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/dist/index.js CHANGED
@@ -3101,7 +3101,7 @@ var resetSessionStartGuard = () => {
3101
3101
  var reportSessionStart = (opts) => {
3102
3102
  var _a;
3103
3103
  const target = opts.target;
3104
- if (!(target == null ? void 0 : target.serverUrl)) return;
3104
+ if (!opts.sink && !(target == null ? void 0 : target.serverUrl)) return;
3105
3105
  const sessionId = (_a = opts.sessionId) != null ? _a : makeSessionId();
3106
3106
  if (opts.once !== false) {
3107
3107
  if (_emitted.has(sessionId)) return;
@@ -3112,9 +3112,6 @@ var reportSessionStart = (opts) => {
3112
3112
  _emitted.add(sessionId);
3113
3113
  }
3114
3114
  try {
3115
- const url = `${target.serverUrl.replace(/\/$/, "")}/v1/events`;
3116
- const headers = { "Content-Type": "application/json" };
3117
- if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
3118
3115
  const userContext = {};
3119
3116
  if (opts.deviceKey) userContext.device_key = opts.deviceKey;
3120
3117
  if (typeof opts.sessionCount === "number" && Number.isFinite(opts.sessionCount)) {
@@ -3133,6 +3130,14 @@ var reportSessionStart = (opts) => {
3133
3130
  if (Object.keys(userContext).length > 0) event.user_context = userContext;
3134
3131
  if (opts.device) event.device = opts.device;
3135
3132
  if (opts.meta && Object.keys(opts.meta).length > 0) event.meta = JSON.stringify(opts.meta);
3133
+ if (opts.sink) {
3134
+ opts.sink(event);
3135
+ return;
3136
+ }
3137
+ if (!(target == null ? void 0 : target.serverUrl)) return;
3138
+ const url = `${target.serverUrl.replace(/\/$/, "")}/v1/events`;
3139
+ const headers = { "Content-Type": "application/json" };
3140
+ if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
3136
3141
  void fetch(url, {
3137
3142
  method: "POST",
3138
3143
  headers,
@@ -3187,6 +3192,398 @@ var useSessionStart = (config, options = {}) => {
3187
3192
  }, []);
3188
3193
  };
3189
3194
 
3195
+ // src/session-analytics/lifecycle.ts
3196
+ var FIRST_OPEN_EVENT = "app.first_open";
3197
+ var firstOpenStorageKey = (appId) => `wireai:first_open:${appId}`;
3198
+ var READ_TIMEOUT_MS3 = 1500;
3199
+ var withTimeout3 = (p, ms) => {
3200
+ let timer;
3201
+ const timeout = new Promise((resolve) => {
3202
+ timer = setTimeout(() => resolve(void 0), ms);
3203
+ });
3204
+ return Promise.race([p, timeout]).finally(() => clearTimeout(timer));
3205
+ };
3206
+ var _firstOpenLatched = /* @__PURE__ */ new Set();
3207
+ var resetFirstOpenLatch = () => {
3208
+ _firstOpenLatched.clear();
3209
+ };
3210
+ var buildLifecycleEvent = (questionKey, opts) => {
3211
+ var _a;
3212
+ const userContext = {};
3213
+ if (opts.deviceKey) userContext.device_key = opts.deviceKey;
3214
+ if (typeof opts.sessionCount === "number" && Number.isFinite(opts.sessionCount)) {
3215
+ userContext.session_count = opts.sessionCount;
3216
+ userContext.returning = opts.sessionCount > 1;
3217
+ }
3218
+ if (opts.appVersion) userContext.app_version = opts.appVersion;
3219
+ if (opts.platform) userContext.platform = opts.platform;
3220
+ const event = {
3221
+ event_type: "app_event",
3222
+ question_key: questionKey,
3223
+ session_id: (_a = opts.sessionId) != null ? _a : makeSessionId()
3224
+ };
3225
+ const userId = sanitizeUserId(opts.userId);
3226
+ if (userId) event.user_id = userId;
3227
+ if (Object.keys(userContext).length > 0) event.user_context = userContext;
3228
+ if (opts.device) event.device = opts.device;
3229
+ if (opts.meta && Object.keys(opts.meta).length > 0) event.meta = JSON.stringify(opts.meta);
3230
+ return event;
3231
+ };
3232
+ var routeLifecycleEvent = (event, opts) => {
3233
+ try {
3234
+ if (opts.sink) {
3235
+ opts.sink(event);
3236
+ return;
3237
+ }
3238
+ const target = opts.target;
3239
+ if (!(target == null ? void 0 : target.serverUrl)) return;
3240
+ const url = `${target.serverUrl.replace(/\/$/, "")}/v1/events`;
3241
+ const headers = { "Content-Type": "application/json" };
3242
+ if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
3243
+ void fetch(url, {
3244
+ method: "POST",
3245
+ headers,
3246
+ body: JSON.stringify({ events: [event] })
3247
+ }).catch(() => {
3248
+ });
3249
+ } catch {
3250
+ }
3251
+ };
3252
+ var emitFirstOpen = (opts) => {
3253
+ routeLifecycleEvent(buildLifecycleEvent(FIRST_OPEN_EVENT, opts), opts);
3254
+ };
3255
+ var reportFirstOpen = (opts) => {
3256
+ var _a;
3257
+ const appId = (_a = opts.appId) != null ? _a : "default";
3258
+ if (_firstOpenLatched.has(appId)) return;
3259
+ _firstOpenLatched.add(appId);
3260
+ const storage = opts.storage;
3261
+ if (!storage) {
3262
+ emitFirstOpen(opts);
3263
+ return;
3264
+ }
3265
+ const key = firstOpenStorageKey(appId);
3266
+ void (async () => {
3267
+ try {
3268
+ const seen = await withTimeout3(storage.getItem(key), READ_TIMEOUT_MS3);
3269
+ if (seen) return;
3270
+ emitFirstOpen(opts);
3271
+ try {
3272
+ void storage.setItem(key, JSON.stringify({ ts: Date.now() })).catch(() => {
3273
+ });
3274
+ } catch {
3275
+ }
3276
+ } catch {
3277
+ emitFirstOpen(opts);
3278
+ }
3279
+ })();
3280
+ };
3281
+ var wireLifecycleEvents = (opts) => {
3282
+ reportFirstOpen(opts);
3283
+ reportSessionStart({
3284
+ target: opts.target,
3285
+ sink: opts.sink,
3286
+ sessionId: opts.sessionId,
3287
+ userId: opts.userId,
3288
+ deviceKey: opts.deviceKey,
3289
+ sessionCount: opts.sessionCount,
3290
+ appVersion: opts.appVersion,
3291
+ platform: opts.platform,
3292
+ device: opts.device,
3293
+ meta: opts.meta
3294
+ });
3295
+ };
3296
+
3297
+ // src/analytics/eventQueue.ts
3298
+ var DEFAULTS = {
3299
+ maxSize: 200,
3300
+ batchSize: 20,
3301
+ baseBackoffMs: 1e3,
3302
+ maxBackoffMs: 3e4,
3303
+ maxRetries: 6
3304
+ };
3305
+ var READ_TIMEOUT_MS4 = 1500;
3306
+ var withTimeout4 = (p, ms) => {
3307
+ let timer;
3308
+ const timeout = new Promise((resolve) => {
3309
+ timer = setTimeout(() => resolve(void 0), ms);
3310
+ });
3311
+ return Promise.race([p, timeout]).finally(() => clearTimeout(timer));
3312
+ };
3313
+ var unrefTimer = (timer) => {
3314
+ const t = timer;
3315
+ if (typeof t.unref === "function") t.unref();
3316
+ };
3317
+ var parsePersisted2 = (raw) => {
3318
+ if (!raw) return [];
3319
+ try {
3320
+ const parsed = JSON.parse(raw);
3321
+ if (!Array.isArray(parsed)) return [];
3322
+ const items = [];
3323
+ for (const entry of parsed) {
3324
+ if (entry && typeof entry === "object" && typeof entry.id === "number" && entry.event && typeof entry.event === "object") {
3325
+ items.push(entry);
3326
+ }
3327
+ }
3328
+ return items;
3329
+ } catch {
3330
+ return [];
3331
+ }
3332
+ };
3333
+ var createEventQueue = (options) => {
3334
+ var _a, _b, _c, _d, _e, _f, _g;
3335
+ const target = options.target;
3336
+ const storage = options.storage;
3337
+ const key = (_b = options.storageKey) != null ? _b : `wireai:evtq:${(_a = options.appId) != null ? _a : "default"}`;
3338
+ const maxSize = (_c = options.maxSize) != null ? _c : DEFAULTS.maxSize;
3339
+ const batchSize = (_d = options.batchSize) != null ? _d : DEFAULTS.batchSize;
3340
+ const baseBackoffMs = (_e = options.baseBackoffMs) != null ? _e : DEFAULTS.baseBackoffMs;
3341
+ const maxBackoffMs = (_f = options.maxBackoffMs) != null ? _f : DEFAULTS.maxBackoffMs;
3342
+ const maxRetries = (_g = options.maxRetries) != null ? _g : DEFAULTS.maxRetries;
3343
+ let pending = [];
3344
+ let nextId = 0;
3345
+ let flushing = false;
3346
+ let attempt = 0;
3347
+ let retryTimer;
3348
+ const resolveEnvelope = () => {
3349
+ try {
3350
+ return typeof options.envelope === "function" ? options.envelope() : options.envelope;
3351
+ } catch {
3352
+ return void 0;
3353
+ }
3354
+ };
3355
+ const stamp = (event) => {
3356
+ var _a2;
3357
+ const env = resolveEnvelope();
3358
+ const stamped = { ...event };
3359
+ if (!env) return stamped;
3360
+ if (!stamped.device && env.device) stamped.device = env.device;
3361
+ if (!stamped.session_id && env.sessionId) stamped.session_id = env.sessionId;
3362
+ const uc = { ...(_a2 = stamped.user_context) != null ? _a2 : {} };
3363
+ if (env.appVersion && uc.app_version === void 0) uc.app_version = env.appVersion;
3364
+ if (env.appBuild && uc.app_build === void 0) uc.app_build = env.appBuild;
3365
+ if (env.networkType && uc.network_type === void 0) uc.network_type = env.networkType;
3366
+ if (Object.keys(uc).length > 0) stamped.user_context = uc;
3367
+ return stamped;
3368
+ };
3369
+ const persist = () => {
3370
+ if (!storage) return;
3371
+ try {
3372
+ if (pending.length === 0) {
3373
+ void storage.removeItem(key).catch(() => {
3374
+ });
3375
+ return;
3376
+ }
3377
+ const payload = pending.map((item) => ({ id: item.id, event: item.event }));
3378
+ void storage.setItem(key, JSON.stringify(payload)).catch(() => {
3379
+ });
3380
+ } catch {
3381
+ }
3382
+ };
3383
+ const enforceSizeCap = () => {
3384
+ if (pending.length > maxSize) pending.splice(0, pending.length - maxSize);
3385
+ };
3386
+ const safeSig = (event) => {
3387
+ try {
3388
+ return JSON.stringify(event);
3389
+ } catch {
3390
+ return `__nosig_${nextId}_${Math.random()}`;
3391
+ }
3392
+ };
3393
+ const loadPromise = (async () => {
3394
+ if (!storage) return;
3395
+ try {
3396
+ const persistedItems = parsePersisted2(await withTimeout4(storage.getItem(key), READ_TIMEOUT_MS4));
3397
+ if (persistedItems.length === 0) return;
3398
+ const events = [...persistedItems.map((p) => p.event), ...pending.map((p) => p.event)];
3399
+ pending = [];
3400
+ nextId = 0;
3401
+ const seen = /* @__PURE__ */ new Set();
3402
+ for (const event of events) {
3403
+ const sig = safeSig(event);
3404
+ if (seen.has(sig)) continue;
3405
+ seen.add(sig);
3406
+ pending.push({ id: nextId++, event, sig });
3407
+ }
3408
+ enforceSizeCap();
3409
+ persist();
3410
+ } catch {
3411
+ }
3412
+ })();
3413
+ const postBatch = async (events) => {
3414
+ if (!(target == null ? void 0 : target.serverUrl) || events.length === 0) return false;
3415
+ try {
3416
+ const url = `${target.serverUrl.replace(/\/$/, "")}/v1/events`;
3417
+ const headers = { "Content-Type": "application/json" };
3418
+ if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
3419
+ const res = await fetch(url, {
3420
+ method: "POST",
3421
+ headers,
3422
+ body: JSON.stringify({ events })
3423
+ });
3424
+ return !!(res && res.ok);
3425
+ } catch {
3426
+ return false;
3427
+ }
3428
+ };
3429
+ const clearRetry = () => {
3430
+ if (retryTimer !== void 0) {
3431
+ clearTimeout(retryTimer);
3432
+ retryTimer = void 0;
3433
+ }
3434
+ };
3435
+ const scheduleRetry = () => {
3436
+ if (attempt >= maxRetries) return;
3437
+ const delay = Math.min(baseBackoffMs * 2 ** attempt, maxBackoffMs);
3438
+ attempt++;
3439
+ clearRetry();
3440
+ retryTimer = setTimeout(() => {
3441
+ retryTimer = void 0;
3442
+ void drain();
3443
+ }, delay);
3444
+ unrefTimer(retryTimer);
3445
+ };
3446
+ const drain = async () => {
3447
+ try {
3448
+ await loadPromise;
3449
+ } catch {
3450
+ }
3451
+ if (flushing) return;
3452
+ flushing = true;
3453
+ try {
3454
+ while (pending.length > 0) {
3455
+ const batch = pending.slice(0, batchSize);
3456
+ const ok = await postBatch(batch.map((item) => item.event));
3457
+ if (!ok) {
3458
+ scheduleRetry();
3459
+ return;
3460
+ }
3461
+ const acked = new Set(batch.map((item) => item.id));
3462
+ pending = pending.filter((item) => !acked.has(item.id));
3463
+ persist();
3464
+ attempt = 0;
3465
+ clearRetry();
3466
+ }
3467
+ } finally {
3468
+ flushing = false;
3469
+ }
3470
+ };
3471
+ const flush = () => {
3472
+ try {
3473
+ void drain();
3474
+ } catch {
3475
+ }
3476
+ };
3477
+ const enqueue = (event) => {
3478
+ try {
3479
+ const stamped = stamp(event);
3480
+ const sig = safeSig(stamped);
3481
+ for (const item of pending) {
3482
+ if (item.sig === sig) return;
3483
+ }
3484
+ pending.push({ id: nextId++, event: stamped, sig });
3485
+ enforceSizeCap();
3486
+ persist();
3487
+ if (retryTimer === void 0) flush();
3488
+ } catch {
3489
+ }
3490
+ };
3491
+ const notifyOnline = () => {
3492
+ attempt = 0;
3493
+ clearRetry();
3494
+ flush();
3495
+ };
3496
+ const size = () => pending.length;
3497
+ return { enqueue, flush, notifyOnline, size };
3498
+ };
3499
+
3500
+ // src/session-analytics/useLifecycleEvents.ts
3501
+ var useLifecycleEvents = (config, options = {}) => {
3502
+ const latest = React14.useRef({ config, options });
3503
+ latest.current = { config, options };
3504
+ const queueRef = React14.useRef(void 0);
3505
+ React14.useEffect(() => {
3506
+ var _a;
3507
+ const resolveSink = () => {
3508
+ var _a2, _b;
3509
+ const { config: cfg, options: opts } = latest.current;
3510
+ if (opts.sink) return opts.sink;
3511
+ if (!(cfg == null ? void 0 : cfg.serverUrl)) return void 0;
3512
+ if (!queueRef.current) {
3513
+ queueRef.current = createEventQueue({
3514
+ target: { serverUrl: cfg.serverUrl, apiKey: (_a2 = cfg.apiKey) != null ? _a2 : "" },
3515
+ storage: cfg.storage,
3516
+ // Dedicated key so the hook's internal queue never collides with a host's main queue.
3517
+ storageKey: `wireai:evtq:lifecycle:${(_b = cfg.appId) != null ? _b : "default"}`,
3518
+ envelope: opts.envelope
3519
+ });
3520
+ }
3521
+ return queueRef.current.enqueue;
3522
+ };
3523
+ const targetOf = (cfg) => {
3524
+ var _a2;
3525
+ return (cfg == null ? void 0 : cfg.serverUrl) ? { serverUrl: cfg.serverUrl, apiKey: (_a2 = cfg.apiKey) != null ? _a2 : "" } : void 0;
3526
+ };
3527
+ {
3528
+ const { config: cfg, options: opts } = latest.current;
3529
+ if (opts.enabled !== false) {
3530
+ const device = collectDeviceContext();
3531
+ if ((cfg == null ? void 0 : cfg.appVersion) && !device.appVersion) device.appVersion = cfg.appVersion;
3532
+ reportFirstOpen({
3533
+ target: targetOf(cfg),
3534
+ sink: resolveSink(),
3535
+ storage: cfg == null ? void 0 : cfg.storage,
3536
+ appId: cfg == null ? void 0 : cfg.appId,
3537
+ userId: opts.userId,
3538
+ deviceKey: opts.deviceKey,
3539
+ sessionCount: opts.sessionCount,
3540
+ appVersion: (_a = cfg == null ? void 0 : cfg.appVersion) != null ? _a : device.appVersion,
3541
+ platform: reactNative.Platform.OS,
3542
+ device,
3543
+ meta: opts.meta
3544
+ });
3545
+ }
3546
+ }
3547
+ const fireSession = () => {
3548
+ var _a2;
3549
+ const { config: cfg, options: opts } = latest.current;
3550
+ if (opts.enabled === false) return;
3551
+ if (!(cfg == null ? void 0 : cfg.serverUrl) && !opts.sink) return;
3552
+ const device = collectDeviceContext();
3553
+ if ((cfg == null ? void 0 : cfg.appVersion) && !device.appVersion) device.appVersion = cfg.appVersion;
3554
+ reportSessionStart({
3555
+ target: targetOf(cfg),
3556
+ sink: resolveSink(),
3557
+ // A fresh per-open id each fire; the emitter's once-guard dedupes within the open.
3558
+ userId: opts.userId,
3559
+ deviceKey: opts.deviceKey,
3560
+ sessionCount: opts.sessionCount,
3561
+ appVersion: (_a2 = cfg == null ? void 0 : cfg.appVersion) != null ? _a2 : device.appVersion,
3562
+ platform: reactNative.Platform.OS,
3563
+ device,
3564
+ meta: opts.meta
3565
+ });
3566
+ };
3567
+ fireSession();
3568
+ let backgroundedAt = null;
3569
+ const onChange = (state) => {
3570
+ if (state === "background" || state === "inactive") {
3571
+ if (backgroundedAt == null) backgroundedAt = Date.now();
3572
+ return;
3573
+ }
3574
+ if (state === "active") {
3575
+ const since = backgroundedAt;
3576
+ backgroundedAt = null;
3577
+ if (since != null && Date.now() - since >= BACKGROUND_SESSION_MS) fireSession();
3578
+ }
3579
+ };
3580
+ const sub = reactNative.AppState.addEventListener("change", onChange);
3581
+ return () => {
3582
+ if (sub && typeof sub.remove === "function") sub.remove();
3583
+ };
3584
+ }, []);
3585
+ };
3586
+
3190
3587
  exports.AnimatedSparkle = AnimatedSparkle;
3191
3588
  exports.BACKGROUND_SESSION_MS = BACKGROUND_SESSION_MS;
3192
3589
  exports.CardHandoff = CardHandoff;
@@ -3198,6 +3595,7 @@ exports.DEFAULT_SESSION_TTL_MS = DEFAULT_SESSION_TTL_MS;
3198
3595
  exports.DemoOnboarding = DemoOnboarding;
3199
3596
  exports.DoneBlock = DoneBlock;
3200
3597
  exports.ErrorBlock = ErrorBlock;
3598
+ exports.FIRST_OPEN_EVENT = FIRST_OPEN_EVENT;
3201
3599
  exports.IllustrationProvider = IllustrationProvider;
3202
3600
  exports.InterstitialCard = InterstitialCard;
3203
3601
  exports.LoadingBlock = LoadingBlock;
@@ -3226,6 +3624,7 @@ exports.deriveAnswers = deriveAnswers;
3226
3624
  exports.featuresCacheKey = featuresCacheKey;
3227
3625
  exports.featuresEqual = featuresEqual;
3228
3626
  exports.fetchWireFeatures = fetchWireFeatures;
3627
+ exports.firstOpenStorageKey = firstOpenStorageKey;
3229
3628
  exports.identifyOnboarding = identifyOnboarding;
3230
3629
  exports.isFeaturesFresh = isFeaturesFresh;
3231
3630
  exports.isOnboardingEnabled = isOnboardingEnabled;
@@ -3240,7 +3639,9 @@ exports.readCachedFeatures = readCachedFeatures;
3240
3639
  exports.readProgress = readProgress;
3241
3640
  exports.reportClientEvent = reportClientEvent;
3242
3641
  exports.reportClientEvents = reportClientEvents;
3642
+ exports.reportFirstOpen = reportFirstOpen;
3243
3643
  exports.reportSessionStart = reportSessionStart;
3644
+ exports.resetFirstOpenLatch = resetFirstOpenLatch;
3244
3645
  exports.resetSessionStartGuard = resetSessionStartGuard;
3245
3646
  exports.sanitizeUserId = sanitizeUserId;
3246
3647
  exports.savePersistedSession = savePersistedSession;
@@ -3248,6 +3649,7 @@ exports.sessionStorageKey = sessionStorageKey;
3248
3649
  exports.themeFromBrand = themeFromBrand;
3249
3650
  exports.toAnalyticsEvent = toAnalyticsEvent;
3250
3651
  exports.useIllustration = useIllustration;
3652
+ exports.useLifecycleEvents = useLifecycleEvents;
3251
3653
  exports.useOnboardingTheme = useOnboardingTheme;
3252
3654
  exports.useReducedMotion = useReducedMotion;
3253
3655
  exports.useResolvedFeatures = useResolvedFeatures;
@@ -3255,6 +3657,7 @@ exports.useSessionStart = useSessionStart;
3255
3657
  exports.useWireFeatures = useWireFeatures;
3256
3658
  exports.useWireFeaturesContext = useWireFeaturesContext;
3257
3659
  exports.wireConfigFromEnv = wireConfigFromEnv;
3660
+ exports.wireLifecycleEvents = wireLifecycleEvents;
3258
3661
  exports.writeCachedFeatures = writeCachedFeatures;
3259
3662
  //# sourceMappingURL=index.js.map
3260
3663
  //# sourceMappingURL=index.js.map