@goliapkg/sentori-react-native 6.3.0 → 6.4.1

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.
@@ -57,5 +57,5 @@ constructor(
57
57
  * direction of a lie.
58
58
  */
59
59
  object SentoriVersion {
60
- const val CURRENT = "1.7.1"
60
+ const val CURRENT = "1.7.2"
61
61
  }
@@ -273,17 +273,24 @@ object SentoriPush {
273
273
  */
274
274
  private fun identityChanged(appContext: Context) {
275
275
  val config = SentoriConfig.current ?: return
276
- val prefs = appContext.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
277
- val token = prefs.getString(TOKEN_KEY, null) ?: return
278
- if (prefs.getString(HANDLE_KEY, null) == null) return
276
+ // Everything, including the preference reads, on the worker.
277
+ // `Sentori.user` is synchronous and is called from wherever
278
+ // the host signs someone in — usually the main thread — and
279
+ // reading preferences there is disk I/O on the thread that
280
+ // draws frames. Warm in practice, since this listener is only
281
+ // installed after a registration has already touched them; on
282
+ // the worker it cannot be anything else.
283
+ worker.execute {
284
+ val prefs = appContext.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
285
+ val token = prefs.getString(TOKEN_KEY, null) ?: return@execute
286
+ if (prefs.getString(HANDLE_KEY, null) == null) return@execute
279
287
 
280
- // `Sentori.user` is a verb an app may call on every screen,
281
- // and one request per call is not free to a host.
282
- val identity = "${SentoriScope.userKey}\u0000${SentoriScope.traits}"
283
- if (identity == lastSentIdentity) return
284
- lastSentIdentity = identity
288
+ // `Sentori.user` is a verb an app may call on every screen,
289
+ // and one request per call is not free to a host.
290
+ val identity = "${SentoriScope.userKey}\u0000${SentoriScope.traits}"
291
+ if (identity == lastSentIdentity) return@execute
292
+ lastSentIdentity = identity
285
293
 
286
- worker.execute {
287
294
  when (val r = registerWithServer(appContext, token, config)) {
288
295
  is Result.Success -> rememberHandle(appContext, r.handle, token)
289
296
  is Result.Failure -> {
@@ -319,6 +326,9 @@ object SentoriPush {
319
326
  }
320
327
  }
321
328
 
329
+ /** Hand one drained batch to the host's callbacks, for tests. */
330
+ internal fun drainForTests(state: Map<String, Any?>) = flush(state)
331
+
322
332
  /** This installation's id, minted on first use and kept after. */
323
333
  internal fun installId(ctx: Context): String {
324
334
  val prefs = ctx.applicationContext.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
@@ -350,7 +360,12 @@ object SentoriPush {
350
360
  "push register failed (${result.reason.reason}): ${result.message}",
351
361
  )
352
362
  }
353
- completion(result)
363
+ // The host's callback is the host's code. A registration that
364
+ // succeeded must not be undone by what the host does with the
365
+ // news, and this runs on the single worker thread — an
366
+ // exception here takes the thread, and with it every push
367
+ // request the process makes afterwards.
368
+ safely("register callback") { completion(result) }
354
369
  }
355
370
 
356
371
  private fun finishRegister(
@@ -558,7 +573,11 @@ object SentoriPush {
558
573
  if (drainTask != null) return
559
574
  drainTask =
560
575
  worker.scheduleWithFixedDelay(
561
- { flush(SentoriPushNotifications.drainState()) },
576
+ // Belt as well as braces: `flush` contains the
577
+ // host's code, and this contains everything else,
578
+ // because a task that throws is a task the
579
+ // executor never runs again.
580
+ { safely("drain") { flush(SentoriPushNotifications.drainState()) } },
562
581
  1,
563
582
  1,
564
583
  TimeUnit.SECONDS,
@@ -567,11 +586,36 @@ object SentoriPush {
567
586
  }
568
587
 
569
588
  @Suppress("UNCHECKED_CAST")
589
+ /**
590
+ * Hand a drained batch to the host, without letting the host reach
591
+ * back.
592
+ *
593
+ * Push is allowed to fail silently; it is never allowed to make
594
+ * the host app fail. The handlers are the host's own code running
595
+ * inside our loop, and the loop is a `scheduleWithFixedDelay` —
596
+ * where an exception out of the task makes the executor **cancel
597
+ * every future run**, silently. One throwing `onMessage` ended
598
+ * push delivery for the life of the process, and nothing anywhere
599
+ * said so.
600
+ */
570
601
  private fun flush(state: Map<String, Any?>) {
571
602
  val (message, tap) = synchronized(lock) { onMessage to onTap }
572
603
  if (message == null && tap == null) return
573
- (state["notifications"] as? List<Map<String, Any?>>)?.forEach { message?.invoke(it) }
574
- (state["taps"] as? List<Map<String, Any?>>)?.forEach { tap?.invoke(it) }
604
+ (state["notifications"] as? List<Map<String, Any?>>)?.forEach {
605
+ safely("onMessage") { message?.invoke(it) }
606
+ }
607
+ (state["taps"] as? List<Map<String, Any?>>)?.forEach {
608
+ safely("onTap") { tap?.invoke(it) }
609
+ }
610
+ }
611
+
612
+ /** Run the host's own code without letting it reach back into ours. */
613
+ private inline fun safely(what: String, body: () -> Unit) {
614
+ try {
615
+ body()
616
+ } catch (t: Throwable) {
617
+ android.util.Log.w("sentori", "push $what threw — carrying on", t)
618
+ }
575
619
  }
576
620
 
577
621
  internal fun resetForTests() {
@@ -704,4 +704,73 @@ class SentoriPushTest {
704
704
  assertEquals(0, rec.bodies.size)
705
705
  }
706
706
  }
707
+
708
+ /**
709
+ * Push is allowed to fail silently. It is never allowed to make
710
+ * the host app fail.
711
+ *
712
+ * The host's own handlers are the sharp edge: they are its code
713
+ * and they run inside our loop. Worse here than anywhere — the
714
+ * drain is a `scheduleWithFixedDelay`, and an exception out of a
715
+ * scheduled task makes the executor **cancel every future run**,
716
+ * silently. One throwing `onMessage` used to end push delivery for
717
+ * the life of the process, with nothing anywhere to say so.
718
+ */
719
+ @Test
720
+ fun aThrowingHandlerDoesNotStopDelivery() {
721
+ val ctx = ApplicationProvider.getApplicationContext<android.content.Context>()
722
+ SentoriConfig.set(
723
+ SentoriConfig(token = "st_test", ingestUrl = "http://127.0.0.1:9",
724
+ release = "app@1.0.0", environment = "test"),
725
+ )
726
+ val seen = mutableListOf<String>()
727
+ val latch = CountDownLatch(1)
728
+ SentoriPush.register(
729
+ ctx,
730
+ activity = null,
731
+ timeoutMs = 200,
732
+ onMessage = {
733
+ seen.add(it["title"] as? String ?: "?")
734
+ if (it["title"] == "one") error("the host threw")
735
+ },
736
+ onTap = { seen.add("tap") },
737
+ ) { latch.countDown() }
738
+ assertTrue(latch.await(30, TimeUnit.SECONDS))
739
+
740
+ // One bad handler must not swallow the rest of the batch.
741
+ SentoriPush.drainForTests(
742
+ mapOf(
743
+ "notifications" to listOf(
744
+ mapOf("title" to "one"),
745
+ mapOf("title" to "two"),
746
+ ),
747
+ "taps" to listOf(mapOf<String, Any?>("tapped" to true)),
748
+ ),
749
+ )
750
+ assertEquals(listOf("one", "two", "tap"), seen)
751
+
752
+ // And the next batch still arrives, which is what would have
753
+ // been lost when the executor cancelled the task.
754
+ SentoriPush.drainForTests(
755
+ mapOf("notifications" to listOf(mapOf("title" to "three")), "taps" to null),
756
+ )
757
+ assertEquals(listOf("one", "two", "tap", "three"), seen)
758
+ }
759
+
760
+ /** A host whose completion throws must not take the worker with it. */
761
+ @Test
762
+ fun aThrowingCompletionIsContained() {
763
+ val ctx = ApplicationProvider.getApplicationContext<android.content.Context>()
764
+ SentoriConfig.set(
765
+ SentoriConfig(token = "st_test", ingestUrl = "http://127.0.0.1:9",
766
+ release = "app@1.0.0", environment = "test"),
767
+ )
768
+ val second = CountDownLatch(1)
769
+ SentoriPush.register(ctx, activity = null, timeoutMs = 200) { error("the host threw") }
770
+ Thread.sleep(300)
771
+ // The worker is a single thread. If the first completion took
772
+ // it down, nothing after it ever runs.
773
+ SentoriPush.register(ctx, activity = null, timeoutMs = 200) { second.countDown() }
774
+ assertTrue("the worker did not survive a throwing completion", second.await(30, TimeUnit.SECONDS))
775
+ }
707
776
  }
package/lib/push.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"push.d.ts","sourceRoot":"","sources":["../src/push.ts"],"names":[],"mappings":"AAqCA,OAAO,EAEL,aAAa,EAEb,qBAAqB,EAEtB,MAAM,aAAa,CAAA;AA6BpB,MAAM,MAAM,mBAAmB,GAAG;IAChC;;;;;;mEAM+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAClC;kDAC8C;IAC9C,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,uBAAuB,KAAK,IAAI,CAAA;IACtD,sDAAsD;IACtD,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IAC/B;iEAC6D;IAC7D,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/B;;yBAEqB;IACrB,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAA;IAC9B;;;kBAGc;IACd,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB,CAAA;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,mBAAmB,GAC3B,iBAAiB,GACjB,cAAc,GACd,mBAAmB,GACnB,iBAAiB,GACjB,eAAe,CAAA;AAEnB;;;;mCAImC;AACnC,MAAM,MAAM,kBAAkB,GAC1B;IACE,EAAE,EAAE,IAAI,CAAA;IACR;;oDAEgD;IAChD,GAAG,EAAE,MAAM,CAAA;CACZ,GACD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,mBAAmB,CAAA;CAAE,CAAA;AAE/D,MAAM,MAAM,uBAAuB,GAAG;IACpC,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAClC,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,CAAA;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,QAAQ,CAAC,IAAI,GAAE,mBAAwB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAyC1F;AAgDD;;;GAGG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAoBhD;AAED;4DAC4D;AAC5D,wBAAgB,YAAY,IAAI,IAAI,GAAG,MAAM,CAE5C;AAED,sDAAsD;AACtD,OAAO,EAAE,aAAa,IAAI,SAAS,EAAE,qBAAqB,IAAI,iBAAiB,EAAE,CAAA;AA4NjF;oEACoE;AACpE,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAEhE;AA+ED;;+DAE+D;AAC/D,wBAAgB,eAAe,IAAI,IAAI,CAUtC;AAID;qCACqC;AACrC,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,KAAK,GAAG,SAAS,GAAG,SAAS,GAAG,IAAI,GAAG,IAAI,CAEnF"}
1
+ {"version":3,"file":"push.d.ts","sourceRoot":"","sources":["../src/push.ts"],"names":[],"mappings":"AAqCA,OAAO,EAEL,aAAa,EAEb,qBAAqB,EAEtB,MAAM,aAAa,CAAA;AAsCpB,MAAM,MAAM,mBAAmB,GAAG;IAChC;;;;;;mEAM+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAClC;kDAC8C;IAC9C,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,uBAAuB,KAAK,IAAI,CAAA;IACtD,sDAAsD;IACtD,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IAC/B;iEAC6D;IAC7D,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/B;;yBAEqB;IACrB,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAA;IAC9B;;;kBAGc;IACd,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB,CAAA;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,mBAAmB,GAC3B,iBAAiB,GACjB,cAAc,GACd,mBAAmB,GACnB,iBAAiB,GACjB,eAAe,CAAA;AAEnB;;;;mCAImC;AACnC,MAAM,MAAM,kBAAkB,GAC1B;IACE,EAAE,EAAE,IAAI,CAAA;IACR;;oDAEgD;IAChD,GAAG,EAAE,MAAM,CAAA;CACZ,GACD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,mBAAmB,CAAA;CAAE,CAAA;AAE/D,MAAM,MAAM,uBAAuB,GAAG;IACpC,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAClC,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,CAAA;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,QAAQ,CAAC,IAAI,GAAE,mBAAwB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA6C1F;AAmDD;;;GAGG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAoBhD;AAED;4DAC4D;AAC5D,wBAAgB,YAAY,IAAI,IAAI,GAAG,MAAM,CAE5C;AAED,sDAAsD;AACtD,OAAO,EAAE,aAAa,IAAI,SAAS,EAAE,qBAAqB,IAAI,iBAAiB,EAAE,CAAA;AA+RjF;oEACoE;AACpE,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAEhE;AA4HD;;+DAE+D;AAC/D,wBAAgB,eAAe,IAAI,IAAI,CAWtC;AAID;qCACqC;AACrC,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,KAAK,GAAG,SAAS,GAAG,SAAS,GAAG,IAAI,GAAG,IAAI,CAEnF"}
package/lib/push.js CHANGED
@@ -24,6 +24,14 @@ import { logger, pushSignal } from '@goliapkg/sentori-core';
24
24
  import { currentUserKey, currentUserTraits, onIdentityChange } from './scope.js';
25
25
  import { pushDrainState, pushGetStatus, pushRegister as nativePushRegister, pushRequestPermission, pushUnregister as nativePushUnregister, } from './native.js';
26
26
  const STORAGE_KEY = 'sentori.push.ipt';
27
+ /// Which installation this is.
28
+ ///
29
+ /// Minted once and kept, so the server can key the device row on it
30
+ /// rather than on the vendor's token. Without one a rotation writes a
31
+ /// *new* row under a *new* spToken, and every backend holding the old
32
+ /// one is addressing nothing — with nothing to tell it. The native
33
+ /// SDKs were given this; this package registers from JS and was not.
34
+ const INSTALL_KEY = 'sentori.push.install';
27
35
  let _cachedIpt = null;
28
36
  // The last token the vendor handed us, kept so a sign-in can re-send
29
37
  // the registration without asking the OS for a token again.
@@ -33,6 +41,7 @@ let _lastOptions = {};
33
41
  // the same thing is not sent: `user()` is a verb an app may call on
34
42
  // every screen, and one HTTP request per call is not free to a host.
35
43
  let _lastSentIdentity = null;
44
+ let _installId = null;
36
45
  let _drainInterval = null;
37
46
  let _appStateSubscription = null;
38
47
  let _backgrounded = false;
@@ -88,7 +97,11 @@ export async function register(opts = {}) {
88
97
  _lastNativeToken = token;
89
98
  _lastOptions = opts;
90
99
  void persistIpt(ipt);
91
- opts.onToken?.(ipt);
100
+ // The registration reached the server and came back with a handle.
101
+ // What the host does with it afterwards is not our outcome — this
102
+ // used to sit inside the try, so a throwing `onToken` reported a
103
+ // successful registration as `server-rejected`.
104
+ safely('onToken', () => opts.onToken?.(ipt));
92
105
  bindBufferDrain(opts.onMessage, opts.onTap);
93
106
  // From here on, signing in or out updates the row by itself.
94
107
  onIdentityChange(reRegisterAfterIdentityChange);
@@ -104,7 +117,10 @@ export async function register(opts = {}) {
104
117
  * wants a callback, hand back the reason. */
105
118
  function fail(opts, reason, message) {
106
119
  logger.warn('push', `register failed (${reason}):`, message);
107
- opts.onError?.(new Error(message));
120
+ // `register()` never throws is the contract, and this is the one
121
+ // path that hands the host an error to look at — so it is also the
122
+ // one place where the host's own code could have broken it.
123
+ safely('onError', () => opts.onError?.(new Error(message)));
108
124
  return { ok: false, message, reason };
109
125
  }
110
126
  /**
@@ -218,6 +234,10 @@ async function registerWithServer(cfg, nativeToken, opts) {
218
234
  // `kind`, not `provider` — the server's field name, which this
219
235
  // sent for a year as `provider` and got a 422 for every time.
220
236
  const body = {
237
+ // Which installation this is. The server keys the row on it, so a
238
+ // rotated token updates this device rather than creating a second
239
+ // one under a new address.
240
+ installId: await installId(),
221
241
  kind: isAndroid ? 'fcm' : 'apns',
222
242
  nativeToken,
223
243
  // The same salted identity hash every event carries, so the
@@ -287,20 +307,87 @@ function teardownBufferDrain() {
287
307
  _ackQueue = [];
288
308
  }
289
309
  async function pumpOnce() {
290
- const state = await pushDrainState();
291
- flushBuffered(state.notifications, state.taps);
310
+ // Nothing in a tick may reject. The caller is `void pumpOnce()` on
311
+ // an interval, so a rejection here is an unhandled rejection in the
312
+ // host's process — a red box in development, and on some setups
313
+ // worse — for something the host is entitled to have fail quietly.
314
+ try {
315
+ const state = await pushDrainState();
316
+ flushBuffered(state.notifications, state.taps);
317
+ await reportRotationIfAny(state.token);
318
+ }
319
+ catch (e) {
320
+ logger.warn('push', 'a drain tick failed — the next one still runs', e);
321
+ }
322
+ }
323
+ /// Tell the server when the vendor has handed the app a different
324
+ /// token from the one it registered with.
325
+ ///
326
+ /// Both native layers already route a rotation into the *native*
327
+ /// `SentoriPush.handleRotatedToken`, and that refuses to act on a
328
+ /// device the native side never registered — which, for a React
329
+ /// Native app, is every device: registration goes through JS and the
330
+ /// handle lives in AsyncStorage. So the rotation arrived nowhere, and
331
+ /// the server held a dead token until the host next called
332
+ /// `register()`. For an app that stays resident that is not a bounded
333
+ /// wait.
334
+ ///
335
+ /// This is the drain loop that already runs at 1 Hz and already reads
336
+ /// the token; it threw it away. Foreground only, like the rest of the
337
+ /// loop — a rotation while backgrounded is reported on the next tick
338
+ /// after the app comes back, which is bounded.
339
+ async function reportRotationIfAny(token) {
340
+ if (token == null || token === '' || token === _lastNativeToken)
341
+ return;
342
+ // Only a device that has registered: a token arriving for one that
343
+ // never did is not something to act on unasked, which is the rule
344
+ // both native SDKs follow.
345
+ if (_lastNativeToken == null)
346
+ return;
347
+ const cfg = tryGetRuntimeConfig();
348
+ if (cfg == null)
349
+ return;
350
+ const previous = _lastNativeToken;
351
+ _lastNativeToken = token;
352
+ try {
353
+ const ipt = await registerWithServer(cfg, token, _lastOptions);
354
+ _cachedIpt = ipt;
355
+ void persistIpt(ipt);
356
+ }
357
+ catch (e) {
358
+ // Put it back, or one failed report means the rotation is never
359
+ // mentioned again and the device is unreachable for good.
360
+ _lastNativeToken = previous;
361
+ logger.warn('push', 'reporting a rotated token failed', e);
362
+ }
363
+ }
364
+ /// Run the host's own code without letting it reach back into ours.
365
+ ///
366
+ /// A push that does not arrive is a product decision the host can live
367
+ /// with. An exception out of an SDK it merely opted into is not — and
368
+ /// the host's handlers are the sharp edge here: they are its code,
369
+ /// they run inside our loop, and JavaScript throws for a living. One
370
+ /// throwing handler used to take the rest of the batch with it, then
371
+ /// the tick, then every later tick as an unhandled rejection.
372
+ function safely(what, fn) {
373
+ try {
374
+ fn();
375
+ }
376
+ catch (e) {
377
+ logger.warn('push', `${what} threw — carrying on`, e);
378
+ }
292
379
  }
293
380
  function flushBuffered(notifications, taps) {
294
381
  for (const raw of notifications) {
295
382
  // v2.26 — Observability link-through (rule #4). If the server
296
383
  // injected `_sentori.msgId` in v2.25+, drop a `push` breadcrumb,
297
384
  // emit `sentori.push.received` track, and queue the ack.
298
- autoCorrelate(raw, 'received');
299
- _onMessage?.(coerceNotification(raw));
385
+ safely('autoCorrelate', () => autoCorrelate(raw, 'received'));
386
+ safely('onMessage', () => _onMessage?.(coerceNotification(raw)));
300
387
  }
301
388
  for (const raw of taps) {
302
- autoCorrelate(raw, 'opened');
303
- _onTap?.(raw.userInfo ?? raw);
389
+ safely('autoCorrelate', () => autoCorrelate(raw, 'opened'));
390
+ safely('onTap', () => _onTap?.(raw.userInfo ?? raw));
304
391
  }
305
392
  }
306
393
  /** v2.26 — process one drained notification or tap for downstream
@@ -407,6 +494,54 @@ function startAppStateWatch() {
407
494
  /* react-native unavailable (unit test) */
408
495
  }
409
496
  }
497
+ /// This installation's id, minted on first use and kept after.
498
+ ///
499
+ /// Falls back to a module-scoped value when there is no AsyncStorage,
500
+ /// which is the same fallback the handle uses: worse than persistent,
501
+ /// better than nothing, and it still keeps one launch's rotations
502
+ /// pointed at one row.
503
+ async function installId() {
504
+ if (_installId != null)
505
+ return _installId;
506
+ const storage = await tryAsyncStorage();
507
+ if (storage) {
508
+ try {
509
+ const seen = await storage.getItem(INSTALL_KEY);
510
+ if (typeof seen === 'string' && seen.length > 0) {
511
+ _installId = seen;
512
+ return seen;
513
+ }
514
+ }
515
+ catch {
516
+ // An unreadable store is a store with nothing in it.
517
+ }
518
+ }
519
+ const fresh = randomId();
520
+ _installId = fresh;
521
+ if (storage) {
522
+ try {
523
+ await storage.setItem(INSTALL_KEY, fresh);
524
+ }
525
+ catch (e) {
526
+ logger.warn('push', 'AsyncStorage.setItem failed', e);
527
+ }
528
+ }
529
+ return fresh;
530
+ }
531
+ /// A uuid, without requiring one to exist.
532
+ ///
533
+ /// `crypto.randomUUID` is present on newer Hermes and absent on older
534
+ /// ones, and this must not be the line that decides whether push
535
+ /// works on an older engine.
536
+ function randomId() {
537
+ const c = globalThis.crypto;
538
+ if (typeof c?.randomUUID === 'function')
539
+ return c.randomUUID();
540
+ let out = '';
541
+ for (let i = 0; i < 32; i++)
542
+ out += Math.floor(Math.random() * 16).toString(16);
543
+ return out;
544
+ }
410
545
  async function persistIpt(ipt) {
411
546
  const storage = await tryAsyncStorage();
412
547
  if (!storage)
@@ -455,6 +590,7 @@ function joinUrl(base, path) {
455
590
  * into the next. Production code paths must not call this. */
456
591
  export function __resetForTests() {
457
592
  _cachedIpt = null;
593
+ _installId = null;
458
594
  _onMessage = undefined;
459
595
  _onTap = undefined;
460
596
  _ackQueue = [];
package/lib/push.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"push.js","sourceRoot":"","sources":["../src/push.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"push.js","sourceRoot":"","sources":["../src/push.ts"],"names":[],"mappings":"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"}
package/lib/transport.js CHANGED
@@ -15,7 +15,7 @@ const MAX_RETRY = 3;
15
15
  const STORAGE_KEY = '@sentori/pending';
16
16
  const MAX_PERSISTED = 1000;
17
17
  // Pinned to package.json by a test — bump both together.
18
- const SDK_VERSION = '6.3.0';
18
+ const SDK_VERSION = '6.4.1';
19
19
  let _queue = [];
20
20
  let _assertStats = new Map();
21
21
  let _flushTimer = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goliapkg/sentori-react-native",
3
- "version": "6.3.0",
3
+ "version": "6.4.1",
4
4
  "description": "Sentori SDK for React Native — JS-layer error capture, native crash handlers (iOS / Android), batched transport, fetch + react-navigation tracing.",
5
5
  "license": "Apache-2.0 OR MIT",
6
6
  "author": "GOLIA K.K. <takagi@golia.jp> (https://golia.jp)",
@@ -17,6 +17,14 @@ import {
17
17
  startTransport,
18
18
  } from '../transport';
19
19
  import { verbs } from '../verbs';
20
+ import { __setNativeForTests as setNative } from '../native';
21
+ import {
22
+ __resetForTests as resetPush,
23
+ __setPlatformForTests as setPlatform,
24
+ getCachedIpt,
25
+ register as pushRegister,
26
+ unregister as pushUnregister,
27
+ } from '../push';
20
28
 
21
29
  const baseConfig = {
22
30
  token: 'st_test',
@@ -126,3 +134,113 @@ describe('iron rule: init timing', () => {
126
134
  expect(ms).toBeLessThan(50);
127
135
  });
128
136
  });
137
+
138
+ // Dimension 3, for push specifically.
139
+ //
140
+ // The rule was written down and the gate did not reach here: this file
141
+ // covered the five event verbs and mentioned push nowhere, while push
142
+ // is the surface with the most ways to go wrong — an OS permission, a
143
+ // vendor token, a network round trip, a background loop, and the
144
+ // host's own handlers running inside it.
145
+ //
146
+ // A notification that does not arrive is something the host can live
147
+ // with. An exception out of an SDK it merely opted into is not.
148
+ describe('iron rule: push never reaches the host', () => {
149
+ beforeEach(() => {
150
+ resetAll();
151
+ resetPush();
152
+ setPlatform('ios');
153
+ setConfig(baseConfig);
154
+ });
155
+ afterEach(() => {
156
+ globalThis.fetch = originalFetch;
157
+ setPlatform(null);
158
+ setNative(undefined);
159
+ resetPush();
160
+ resetAll();
161
+ });
162
+
163
+ const granting = {
164
+ pushRequestPermission: () => Promise.resolve('granted'),
165
+ pushGetStatus: () => Promise.resolve('granted'),
166
+ pushRegister: () => undefined,
167
+ pushUnregister: () => undefined,
168
+ pushDrainState: () => Promise.resolve({ notifications: [], taps: [], token: 'abc' }),
169
+ };
170
+
171
+ it('a server that answers 500 is an outcome, not an exception', async () => {
172
+ setNative(granting);
173
+ globalThis.fetch = (async () => new Response('boom', { status: 500 })) as typeof fetch;
174
+ const r = await pushRegister();
175
+ expect(r.ok).toBe(false);
176
+ });
177
+
178
+ it('a network that is down is an outcome, not an exception', async () => {
179
+ setNative(granting);
180
+ globalThis.fetch = (async () => {
181
+ throw new Error('offline');
182
+ }) as typeof fetch;
183
+ const r = await pushRegister();
184
+ expect(r.ok).toBe(false);
185
+ });
186
+
187
+ it('a server that answers nonsense is an outcome, not an exception', async () => {
188
+ setNative(granting);
189
+ globalThis.fetch = (async () => new Response('<html>not json</html>', { status: 200 })) as typeof fetch;
190
+ const r = await pushRegister();
191
+ expect(r.ok).toBe(false);
192
+ });
193
+
194
+ it('a native module that throws is an outcome, not an exception', async () => {
195
+ setNative({
196
+ ...granting,
197
+ pushRequestPermission: () => {
198
+ throw new Error('bridge died');
199
+ },
200
+ });
201
+ const r = await pushRegister();
202
+ expect(r.ok).toBe(false);
203
+ });
204
+
205
+ it('fuzz: the push verbs accept garbage without throwing', async () => {
206
+ setNative(granting);
207
+ globalThis.fetch = (async () =>
208
+ new Response(JSON.stringify({ spToken: 'dev-1' }), { status: 200 })) as typeof fetch;
209
+ const circular: Record<string, unknown> = { a: 1 };
210
+ circular.self = circular;
211
+ const garbage = [
212
+ undefined,
213
+ null,
214
+ {},
215
+ { metadata: circular },
216
+ { metadata: { huge: 'x'.repeat(200_000) } },
217
+ { onMessage: 'not a function' },
218
+ { tokenTimeoutMs: -1 },
219
+ { tokenTimeoutMs: Number.NaN },
220
+ ];
221
+ for (const g of garbage) {
222
+ const r = await pushRegister(g as never);
223
+ expect(typeof r.ok).toBe('boolean');
224
+ }
225
+ });
226
+
227
+ it('every push verb before init is a quiet outcome', async () => {
228
+ resetConfig();
229
+ setNative(granting);
230
+ const r = await pushRegister();
231
+ expect(r).toEqual({ ok: false, message: 'sentori.init() has not run', reason: 'not-initialised' });
232
+ await pushUnregister();
233
+ expect(await getCachedIpt()).toBe(null);
234
+ });
235
+
236
+ it('unregister cannot fail, whatever the server does', async () => {
237
+ setNative(granting);
238
+ globalThis.fetch = (async () =>
239
+ new Response(JSON.stringify({ spToken: 'dev-1' }), { status: 200 })) as typeof fetch;
240
+ await pushRegister();
241
+ globalThis.fetch = (async () => {
242
+ throw new Error('offline');
243
+ }) as typeof fetch;
244
+ await pushUnregister();
245
+ });
246
+ });
@@ -13,6 +13,7 @@ import {
13
13
  __resetForTests as resetPush,
14
14
  __setPlatformForTests as setPlatform,
15
15
  register,
16
+ unregister,
16
17
  } from '../push';
17
18
  import { __resetForTests as resetConfig, setConfig } from '../config';
18
19
  import { __resetForTests as resetScope, setUser } from '../scope';
@@ -300,3 +301,251 @@ describe('push registration follows the person', () => {
300
301
  expect(seen).toHaveLength(0);
301
302
  });
302
303
  });
304
+
305
+ // A vendor token is not an address. It rotates — on a reinstall, a
306
+ // restore, cleared app data — and the server keys the row on it, so a
307
+ // rotation wrote a *new* row under a *new* spToken and every backend
308
+ // holding the old one was addressing nothing.
309
+ //
310
+ // The native SDKs were given an installId and a rotation report. This
311
+ // package was not: its registration goes through JS, and the native
312
+ // rotation hook it does have refuses to act on a device the *native*
313
+ // side never registered — which, for a React Native app, is every
314
+ // device.
315
+ describe('push survives the vendor rotating its token', () => {
316
+ function recordingFetch(): Array<Record<string, unknown>> {
317
+ const seen: Array<Record<string, unknown>> = [];
318
+ globalThis.fetch = ((_url: string, init?: { body?: string }) => {
319
+ if (init?.body != null) {
320
+ seen.push(JSON.parse(init.body) as Record<string, unknown>);
321
+ }
322
+ return Promise.resolve({
323
+ ok: true,
324
+ status: 202,
325
+ json: () => Promise.resolve({ spToken: 'dev-1', token_id: 'dev-1' }),
326
+ });
327
+ }) as unknown as typeof fetch;
328
+ return seen;
329
+ }
330
+
331
+ beforeEach(() => {
332
+ resetPush();
333
+ resetConfig();
334
+ resetScope();
335
+ setPlatform('ios');
336
+ setConfig(baseConfig);
337
+ });
338
+ afterEach(() => {
339
+ resetPush();
340
+ resetConfig();
341
+ resetScope();
342
+ setPlatform(null);
343
+ __setNativeForTests(undefined);
344
+ globalThis.fetch = realFetch;
345
+ });
346
+
347
+ it('tells the server which installation this is', async () => {
348
+ __setNativeForTests(grantingNative());
349
+ const seen = recordingFetch();
350
+ await register();
351
+ const installId = seen[0]?.installId;
352
+ expect(typeof installId).toBe('string');
353
+ expect(String(installId).length).toBeGreaterThan(8);
354
+ });
355
+
356
+ it('keeps the same installation across registrations', async () => {
357
+ __setNativeForTests(grantingNative());
358
+ const seen = recordingFetch();
359
+ await register();
360
+ await register();
361
+ expect(seen).toHaveLength(2);
362
+ // Asserting equality alone passes when neither call sent one —
363
+ // `undefined === undefined` — which is the state this test
364
+ // exists to reject. So: a string, and the same string.
365
+ expect(typeof seen[0]?.installId).toBe('string');
366
+ expect(seen[1]?.installId).toBe(seen[0]?.installId);
367
+ });
368
+
369
+ it('reports a token that rotated while the app was running', async () => {
370
+ // The vendor hands back a different token on the second drain,
371
+ // which is what a rotation looks like from here.
372
+ let handed = 0;
373
+ __setNativeForTests(
374
+ grantingNative({
375
+ pushDrainState: () => {
376
+ handed += 1;
377
+ return Promise.resolve({
378
+ notifications: [],
379
+ taps: [],
380
+ token: handed <= 1 ? 'abc123' : 'rotated456',
381
+ });
382
+ },
383
+ }),
384
+ );
385
+ const seen = recordingFetch();
386
+ await register();
387
+ expect(seen).toHaveLength(1);
388
+
389
+ // One turn of the drain loop, which already runs at 1 Hz and
390
+ // already reads the token — it just threw it away.
391
+ await new Promise((r) => setTimeout(r, 1400));
392
+
393
+ expect(seen).toHaveLength(2);
394
+ expect(seen[1]?.nativeToken).toBe('rotated456');
395
+ // Same installation, or the server writes a second row and the
396
+ // rotation has achieved exactly what it used to.
397
+ expect(seen[1]?.installId).toBe(seen[0]?.installId);
398
+ });
399
+
400
+ it('does not report the same token twice', async () => {
401
+ __setNativeForTests(grantingNative());
402
+ const seen = recordingFetch();
403
+ await register();
404
+ await new Promise((r) => setTimeout(r, 2400));
405
+ expect(seen).toHaveLength(1);
406
+ });
407
+ });
408
+
409
+ // Push is allowed to fail silently. It is never allowed to make the
410
+ // host app fail — a notification that does not arrive is a product
411
+ // decision the host can live with; an exception out of an SDK the host
412
+ // merely opted into is not.
413
+ //
414
+ // The host's own handlers are the sharp edge: they are its code, they
415
+ // run inside our loop, and JavaScript throws for a living.
416
+ describe('nothing the host does can reach back into the app', () => {
417
+ const boom = () => {
418
+ throw new Error('the host threw');
419
+ };
420
+
421
+ /** Hands the batch over once and is empty after, the way the real
422
+ * native drain is — a stub that keeps re-delivering makes a second
423
+ * tick look like a bug in the SDK. */
424
+ function drainingNative(notifications: unknown[], taps: unknown[]) {
425
+ let drained = false;
426
+ return grantingNative({
427
+ pushDrainState: () => {
428
+ const batch = drained
429
+ ? { notifications: [], taps: [] }
430
+ : { notifications, taps };
431
+ drained = true;
432
+ return Promise.resolve({ ...batch, token: 'abc123' });
433
+ },
434
+ });
435
+ }
436
+
437
+ function okFetch(): void {
438
+ globalThis.fetch = (() =>
439
+ Promise.resolve({
440
+ ok: true,
441
+ status: 202,
442
+ json: () => Promise.resolve({ spToken: 'dev-1', token_id: 'dev-1' }),
443
+ })) as unknown as typeof fetch;
444
+ }
445
+
446
+ beforeEach(() => {
447
+ resetPush();
448
+ resetConfig();
449
+ resetScope();
450
+ setPlatform('ios');
451
+ setConfig(baseConfig);
452
+ okFetch();
453
+ });
454
+ afterEach(() => {
455
+ resetPush();
456
+ resetConfig();
457
+ resetScope();
458
+ setPlatform(null);
459
+ __setNativeForTests(undefined);
460
+ globalThis.fetch = realFetch;
461
+ });
462
+
463
+ it('keeps delivering after a handler throws', async () => {
464
+ const seen: string[] = [];
465
+ __setNativeForTests(
466
+ drainingNative(
467
+ [{ title: 'one' }, { title: 'two' }],
468
+ [{ tapped: true }],
469
+ ),
470
+ );
471
+ const r = await register({
472
+ onMessage: (n) => {
473
+ seen.push(String(n.title));
474
+ // The first one throws. The second one, and the tap after it,
475
+ // still have to arrive: one bad handler must not swallow the
476
+ // rest of the batch.
477
+ if (n.title === 'one') boom();
478
+ },
479
+ onTap: () => seen.push('tap'),
480
+ });
481
+ expect(r.ok).toBe(true);
482
+ await new Promise((res) => setTimeout(res, 1400));
483
+ expect(seen).toEqual(['one', 'two', 'tap']);
484
+ });
485
+
486
+ it('keeps ticking after a handler throws', async () => {
487
+ let ticks = 0;
488
+ __setNativeForTests(
489
+ grantingNative({
490
+ pushDrainState: () => {
491
+ ticks += 1;
492
+ return Promise.resolve({
493
+ notifications: [{ title: 'n' }],
494
+ taps: [],
495
+ token: 'abc123',
496
+ });
497
+ },
498
+ }),
499
+ );
500
+ await register({ onMessage: boom });
501
+ const first = ticks;
502
+ await new Promise((res) => setTimeout(res, 2400));
503
+ // A throwing handler that stops the loop is worse than one that
504
+ // drops a notification: every later push is gone too, silently.
505
+ expect(ticks).toBeGreaterThan(first + 1);
506
+ });
507
+
508
+ it('survives a native module that rejects', async () => {
509
+ let ticks = 0;
510
+ __setNativeForTests(
511
+ grantingNative({
512
+ pushDrainState: () => {
513
+ ticks += 1;
514
+ return ticks === 1
515
+ ? Promise.resolve({ notifications: [], taps: [], token: 'abc123' })
516
+ : Promise.reject(new Error('bridge died'));
517
+ },
518
+ }),
519
+ );
520
+ await register();
521
+ await new Promise((res) => setTimeout(res, 2400));
522
+ expect(ticks).toBeGreaterThan(2);
523
+ });
524
+
525
+ it('does not report a failure because onToken threw', async () => {
526
+ __setNativeForTests(grantingNative());
527
+ const r = await register({ onToken: boom });
528
+ // The registration reached the server and came back with a handle.
529
+ // What the host did with it afterwards is not our outcome.
530
+ expect(r).toEqual({ ok: true, ipt: 'dev-1' });
531
+ });
532
+
533
+ it('still resolves when onError throws', async () => {
534
+ // `register()` never throws is the documented contract, and the
535
+ // one path that reports failure is the one that hands the host an
536
+ // error to look at.
537
+ __setNativeForTests(
538
+ grantingNative({ pushRequestPermission: () => Promise.resolve('denied') }),
539
+ );
540
+ const r = await register({ onError: boom });
541
+ expect(r.ok).toBe(false);
542
+ });
543
+
544
+ it('unregister does not throw when the server is unreachable', async () => {
545
+ __setNativeForTests(grantingNative());
546
+ await register();
547
+ globalThis.fetch = (() => Promise.reject(new Error('offline'))) as unknown as typeof fetch;
548
+ // Signing out must not be something that can fail.
549
+ await unregister();
550
+ });
551
+ });
package/src/push.ts CHANGED
@@ -44,6 +44,14 @@ import {
44
44
  } from './native.js'
45
45
 
46
46
  const STORAGE_KEY = 'sentori.push.ipt'
47
+ /// Which installation this is.
48
+ ///
49
+ /// Minted once and kept, so the server can key the device row on it
50
+ /// rather than on the vendor's token. Without one a rotation writes a
51
+ /// *new* row under a *new* spToken, and every backend holding the old
52
+ /// one is addressing nothing — with nothing to tell it. The native
53
+ /// SDKs were given this; this package registers from JS and was not.
54
+ const INSTALL_KEY = 'sentori.push.install'
47
55
 
48
56
  let _cachedIpt: null | string = null
49
57
  // The last token the vendor handed us, kept so a sign-in can re-send
@@ -54,6 +62,7 @@ let _lastOptions: PushRegisterOptions = {}
54
62
  // the same thing is not sent: `user()` is a verb an app may call on
55
63
  // every screen, and one HTTP request per call is not free to a host.
56
64
  let _lastSentIdentity: string | null = null
65
+ let _installId: null | string = null
57
66
  let _drainInterval: ReturnType<typeof setInterval> | null = null
58
67
  let _appStateSubscription: { remove: () => void } | null = null
59
68
  let _backgrounded = false
@@ -187,7 +196,11 @@ export async function register(opts: PushRegisterOptions = {}): Promise<PushRegi
187
196
  _lastNativeToken = token
188
197
  _lastOptions = opts
189
198
  void persistIpt(ipt)
190
- opts.onToken?.(ipt)
199
+ // The registration reached the server and came back with a handle.
200
+ // What the host does with it afterwards is not our outcome — this
201
+ // used to sit inside the try, so a throwing `onToken` reported a
202
+ // successful registration as `server-rejected`.
203
+ safely('onToken', () => opts.onToken?.(ipt))
191
204
  bindBufferDrain(opts.onMessage, opts.onTap)
192
205
  // From here on, signing in or out updates the row by itself.
193
206
  onIdentityChange(reRegisterAfterIdentityChange)
@@ -207,7 +220,10 @@ function fail(
207
220
  message: string,
208
221
  ): PushRegisterResult {
209
222
  logger.warn('push', `register failed (${reason}):`, message)
210
- opts.onError?.(new Error(message))
223
+ // `register()` never throws is the contract, and this is the one
224
+ // path that hands the host an error to look at — so it is also the
225
+ // one place where the host's own code could have broken it.
226
+ safely('onError', () => opts.onError?.(new Error(message)))
211
227
  return { ok: false, message, reason }
212
228
  }
213
229
 
@@ -333,6 +349,10 @@ async function registerWithServer(
333
349
  // `kind`, not `provider` — the server's field name, which this
334
350
  // sent for a year as `provider` and got a 422 for every time.
335
351
  const body: Record<string, unknown> = {
352
+ // Which installation this is. The server keys the row on it, so a
353
+ // rotated token updates this device rather than creating a second
354
+ // one under a new address.
355
+ installId: await installId(),
336
356
  kind: isAndroid ? 'fcm' : 'apns',
337
357
  nativeToken,
338
358
  // The same salted identity hash every event carries, so the
@@ -404,8 +424,71 @@ function teardownBufferDrain(): void {
404
424
  }
405
425
 
406
426
  async function pumpOnce(): Promise<void> {
407
- const state = await pushDrainState()
408
- flushBuffered(state.notifications, state.taps)
427
+ // Nothing in a tick may reject. The caller is `void pumpOnce()` on
428
+ // an interval, so a rejection here is an unhandled rejection in the
429
+ // host's process — a red box in development, and on some setups
430
+ // worse — for something the host is entitled to have fail quietly.
431
+ try {
432
+ const state = await pushDrainState()
433
+ flushBuffered(state.notifications, state.taps)
434
+ await reportRotationIfAny(state.token)
435
+ } catch (e) {
436
+ logger.warn('push', 'a drain tick failed — the next one still runs', e)
437
+ }
438
+ }
439
+
440
+ /// Tell the server when the vendor has handed the app a different
441
+ /// token from the one it registered with.
442
+ ///
443
+ /// Both native layers already route a rotation into the *native*
444
+ /// `SentoriPush.handleRotatedToken`, and that refuses to act on a
445
+ /// device the native side never registered — which, for a React
446
+ /// Native app, is every device: registration goes through JS and the
447
+ /// handle lives in AsyncStorage. So the rotation arrived nowhere, and
448
+ /// the server held a dead token until the host next called
449
+ /// `register()`. For an app that stays resident that is not a bounded
450
+ /// wait.
451
+ ///
452
+ /// This is the drain loop that already runs at 1 Hz and already reads
453
+ /// the token; it threw it away. Foreground only, like the rest of the
454
+ /// loop — a rotation while backgrounded is reported on the next tick
455
+ /// after the app comes back, which is bounded.
456
+ async function reportRotationIfAny(token: string | undefined): Promise<void> {
457
+ if (token == null || token === '' || token === _lastNativeToken) return
458
+ // Only a device that has registered: a token arriving for one that
459
+ // never did is not something to act on unasked, which is the rule
460
+ // both native SDKs follow.
461
+ if (_lastNativeToken == null) return
462
+ const cfg = tryGetRuntimeConfig()
463
+ if (cfg == null) return
464
+ const previous = _lastNativeToken
465
+ _lastNativeToken = token
466
+ try {
467
+ const ipt = await registerWithServer(cfg, token, _lastOptions)
468
+ _cachedIpt = ipt
469
+ void persistIpt(ipt)
470
+ } catch (e) {
471
+ // Put it back, or one failed report means the rotation is never
472
+ // mentioned again and the device is unreachable for good.
473
+ _lastNativeToken = previous
474
+ logger.warn('push', 'reporting a rotated token failed', e)
475
+ }
476
+ }
477
+
478
+ /// Run the host's own code without letting it reach back into ours.
479
+ ///
480
+ /// A push that does not arrive is a product decision the host can live
481
+ /// with. An exception out of an SDK it merely opted into is not — and
482
+ /// the host's handlers are the sharp edge here: they are its code,
483
+ /// they run inside our loop, and JavaScript throws for a living. One
484
+ /// throwing handler used to take the rest of the batch with it, then
485
+ /// the tick, then every later tick as an unhandled rejection.
486
+ function safely(what: string, fn: () => void): void {
487
+ try {
488
+ fn()
489
+ } catch (e) {
490
+ logger.warn('push', `${what} threw — carrying on`, e)
491
+ }
409
492
  }
410
493
 
411
494
  function flushBuffered(
@@ -416,12 +499,12 @@ function flushBuffered(
416
499
  // v2.26 — Observability link-through (rule #4). If the server
417
500
  // injected `_sentori.msgId` in v2.25+, drop a `push` breadcrumb,
418
501
  // emit `sentori.push.received` track, and queue the ack.
419
- autoCorrelate(raw, 'received')
420
- _onMessage?.(coerceNotification(raw))
502
+ safely('autoCorrelate', () => autoCorrelate(raw, 'received'))
503
+ safely('onMessage', () => _onMessage?.(coerceNotification(raw)))
421
504
  }
422
505
  for (const raw of taps) {
423
- autoCorrelate(raw, 'opened')
424
- _onTap?.(raw.userInfo ?? raw)
506
+ safely('autoCorrelate', () => autoCorrelate(raw, 'opened'))
507
+ safely('onTap', () => _onTap?.(raw.userInfo ?? raw))
425
508
  }
426
509
  }
427
510
 
@@ -531,6 +614,51 @@ function startAppStateWatch(): void {
531
614
  }
532
615
  }
533
616
 
617
+ /// This installation's id, minted on first use and kept after.
618
+ ///
619
+ /// Falls back to a module-scoped value when there is no AsyncStorage,
620
+ /// which is the same fallback the handle uses: worse than persistent,
621
+ /// better than nothing, and it still keeps one launch's rotations
622
+ /// pointed at one row.
623
+ async function installId(): Promise<string> {
624
+ if (_installId != null) return _installId
625
+ const storage = await tryAsyncStorage()
626
+ if (storage) {
627
+ try {
628
+ const seen = await storage.getItem(INSTALL_KEY)
629
+ if (typeof seen === 'string' && seen.length > 0) {
630
+ _installId = seen
631
+ return seen
632
+ }
633
+ } catch {
634
+ // An unreadable store is a store with nothing in it.
635
+ }
636
+ }
637
+ const fresh = randomId()
638
+ _installId = fresh
639
+ if (storage) {
640
+ try {
641
+ await storage.setItem(INSTALL_KEY, fresh)
642
+ } catch (e) {
643
+ logger.warn('push', 'AsyncStorage.setItem failed', e)
644
+ }
645
+ }
646
+ return fresh
647
+ }
648
+
649
+ /// A uuid, without requiring one to exist.
650
+ ///
651
+ /// `crypto.randomUUID` is present on newer Hermes and absent on older
652
+ /// ones, and this must not be the line that decides whether push
653
+ /// works on an older engine.
654
+ function randomId(): string {
655
+ const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto
656
+ if (typeof c?.randomUUID === 'function') return c.randomUUID()
657
+ let out = ''
658
+ for (let i = 0; i < 32; i++) out += Math.floor(Math.random() * 16).toString(16)
659
+ return out
660
+ }
661
+
534
662
  async function persistIpt(ipt: string): Promise<void> {
535
663
  const storage = await tryAsyncStorage()
536
664
  if (!storage) return
@@ -586,6 +714,7 @@ function joinUrl(base: string, path: string): string {
586
714
  * into the next. Production code paths must not call this. */
587
715
  export function __resetForTests(): void {
588
716
  _cachedIpt = null
717
+ _installId = null
589
718
  _onMessage = undefined
590
719
  _onTap = undefined
591
720
  _ackQueue = []
package/src/transport.ts CHANGED
@@ -20,7 +20,7 @@ const STORAGE_KEY = '@sentori/pending';
20
20
  const MAX_PERSISTED = 1000;
21
21
 
22
22
  // Pinned to package.json by a test — bump both together.
23
- const SDK_VERSION = '6.3.0';
23
+ const SDK_VERSION = '6.4.1';
24
24
 
25
25
  let _queue: WireEvent[] = [];
26
26
  let _assertStats = new Map<string, AssertStat>();