@sendoracloud/sdk-react-native 1.8.1 → 1.8.2

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.cjs CHANGED
@@ -222,7 +222,7 @@ async function request(opts, method, path, body, extraHeaders, reqOpts) {
222
222
  // package.json
223
223
  var package_default = {
224
224
  name: "@sendoracloud/sdk-react-native",
225
- version: "1.8.1",
225
+ version: "1.8.2",
226
226
  description: "Sendora Cloud React Native + Expo SDK \u2014 analytics, identity, push-token registration, auth, deep links (Branch / Firebase Dynamic Links parity). Auth + analytics + deep links work in Expo Go; push token registration requires a Dev Client (EAS Build or `npx expo prebuild`).",
227
227
  type: "module",
228
228
  main: "./dist/index.cjs",
@@ -465,38 +465,44 @@ var Auth = class {
465
465
  }
466
466
  }
467
467
  /**
468
- * Returns an access token or null. Cold-launch hardening (Wave 62):
468
+ * Returns a VALID (non-expired) access token, or null.
469
469
  *
470
- * 1. Non-expired cached return it. (Fast path.)
471
- * 2. Expired AND refresh inflight return stale cached anyway.
472
- * Backend grace window (60s see auth-service refresh-rotation
473
- * middleware) accepts slightly-stale access tokens, so the
474
- * caller's downstream RPC will succeed against grace while
475
- * the in-flight refresh resolves out of band. Distinct from
476
- * pre-Wave-62 behaviour where 5 concurrent callers all shared
477
- * the same in-flight promise when that promise hung (iOS
478
- * NSURLSession cold-launch contention) every caller hung too.
479
- * 3. Expired + no inflight + refresh available + not in cooldown →
480
- * fire refresh, await it.
481
- * 4. Expired + in cooldown → return stale cached. Caller's RPC
482
- * either succeeds against grace or fails fast on 401, which is
483
- * a better UX than blocking the whole bridge waiting on a
484
- * network that we already know is stalled.
470
+ * Hard contract (matches sdk-ios / sdk-android, and the industry
471
+ * token-client pattern Supabase `_callRefreshToken`, Auth0
472
+ * `getTokenSilently`, Firebase `getIdToken`): this method NEVER
473
+ * returns a token past its `exp`. Callers attach the result as a
474
+ * Bearer to arbitrary resource servers including third-party
475
+ * backends (PostgREST/Supabase) that have NO knowledge of Sendora's
476
+ * own refresh grace window so a token must be either fresh or
477
+ * absent. A token expired by hours is useless and yields a 401.
485
478
  *
486
- * Returns null only when there's NO usable token at all (no cached
487
- * + no refresh available).
479
+ * Resolution order:
480
+ * 1. Cached token with comfortable life left (>= REFRESH_SAFETY_MS
481
+ * skew before exp) → return it. Fast path, no I/O.
482
+ * 2. No cached token AND no refresh token in storage → genuinely
483
+ * signed out → null.
484
+ * 3. Expired/near-expiry but a refresh chain exists:
485
+ * a. A refresh is already inflight → AWAIT it and return the
486
+ * fresh token. Concurrent callers coalesce onto the single
487
+ * in-flight `/token/refresh` (single-flight) — nobody is
488
+ * handed a stale token. The inflight fetch is
489
+ * AbortController-bounded (http.ts caps `/auth-service/*` at
490
+ * 10s), so awaiting can NEVER hang the JS bridge. That
491
+ * bounded-fetch guarantee — NOT the old stale-token return —
492
+ * is what actually fixed the Wave-62 cold-launch hang.
493
+ * b. A recent refresh failed and we're in backoff cooldown →
494
+ * null. Honest "no usable token right now" rather than a
495
+ * provably-expired one; the AppState foreground hook clears
496
+ * the cooldown for an immediate retry on resume.
497
+ * c. Otherwise → fire the refresh and await it.
488
498
  */
489
499
  async getAccessToken() {
490
- if (!this.accessToken) return null;
491
- if (this.accessExpiresAt && Date.now() < this.accessExpiresAt - REFRESH_SAFETY_MS) {
492
- return this.accessToken;
493
- }
494
- if (this.refreshInflight) {
495
- return this.accessToken;
496
- }
497
- if (Date.now() < this.refreshCooldownUntil) {
500
+ if (this.accessToken && this.accessExpiresAt && Date.now() < this.accessExpiresAt - REFRESH_SAFETY_MS) {
498
501
  return this.accessToken;
499
502
  }
503
+ if (!this.hooks.storage.get(REFRESH_KEY)) return null;
504
+ if (this.refreshInflight) return this.refreshInflight;
505
+ if (Date.now() < this.refreshCooldownUntil) return null;
500
506
  return this.refreshAccessToken();
501
507
  }
502
508
  /** Sync read — returns whatever's cached even if expired. Prefer the async variant. */
package/dist/index.d.cts CHANGED
@@ -210,26 +210,36 @@ declare class Auth {
210
210
  /** Internal — fan out to subscribers + cache the latest. */
211
211
  private fireDeviceTakeover;
212
212
  /**
213
- * Returns an access token or null. Cold-launch hardening (Wave 62):
213
+ * Returns a VALID (non-expired) access token, or null.
214
214
  *
215
- * 1. Non-expired cached return it. (Fast path.)
216
- * 2. Expired AND refresh inflight return stale cached anyway.
217
- * Backend grace window (60s see auth-service refresh-rotation
218
- * middleware) accepts slightly-stale access tokens, so the
219
- * caller's downstream RPC will succeed against grace while
220
- * the in-flight refresh resolves out of band. Distinct from
221
- * pre-Wave-62 behaviour where 5 concurrent callers all shared
222
- * the same in-flight promise when that promise hung (iOS
223
- * NSURLSession cold-launch contention) every caller hung too.
224
- * 3. Expired + no inflight + refresh available + not in cooldown →
225
- * fire refresh, await it.
226
- * 4. Expired + in cooldown → return stale cached. Caller's RPC
227
- * either succeeds against grace or fails fast on 401, which is
228
- * a better UX than blocking the whole bridge waiting on a
229
- * network that we already know is stalled.
215
+ * Hard contract (matches sdk-ios / sdk-android, and the industry
216
+ * token-client pattern Supabase `_callRefreshToken`, Auth0
217
+ * `getTokenSilently`, Firebase `getIdToken`): this method NEVER
218
+ * returns a token past its `exp`. Callers attach the result as a
219
+ * Bearer to arbitrary resource servers including third-party
220
+ * backends (PostgREST/Supabase) that have NO knowledge of Sendora's
221
+ * own refresh grace window so a token must be either fresh or
222
+ * absent. A token expired by hours is useless and yields a 401.
230
223
  *
231
- * Returns null only when there's NO usable token at all (no cached
232
- * + no refresh available).
224
+ * Resolution order:
225
+ * 1. Cached token with comfortable life left (>= REFRESH_SAFETY_MS
226
+ * skew before exp) → return it. Fast path, no I/O.
227
+ * 2. No cached token AND no refresh token in storage → genuinely
228
+ * signed out → null.
229
+ * 3. Expired/near-expiry but a refresh chain exists:
230
+ * a. A refresh is already inflight → AWAIT it and return the
231
+ * fresh token. Concurrent callers coalesce onto the single
232
+ * in-flight `/token/refresh` (single-flight) — nobody is
233
+ * handed a stale token. The inflight fetch is
234
+ * AbortController-bounded (http.ts caps `/auth-service/*` at
235
+ * 10s), so awaiting can NEVER hang the JS bridge. That
236
+ * bounded-fetch guarantee — NOT the old stale-token return —
237
+ * is what actually fixed the Wave-62 cold-launch hang.
238
+ * b. A recent refresh failed and we're in backoff cooldown →
239
+ * null. Honest "no usable token right now" rather than a
240
+ * provably-expired one; the AppState foreground hook clears
241
+ * the cooldown for an immediate retry on resume.
242
+ * c. Otherwise → fire the refresh and await it.
233
243
  */
234
244
  getAccessToken(): Promise<string | null>;
235
245
  /** Sync read — returns whatever's cached even if expired. Prefer the async variant. */
package/dist/index.d.ts CHANGED
@@ -210,26 +210,36 @@ declare class Auth {
210
210
  /** Internal — fan out to subscribers + cache the latest. */
211
211
  private fireDeviceTakeover;
212
212
  /**
213
- * Returns an access token or null. Cold-launch hardening (Wave 62):
213
+ * Returns a VALID (non-expired) access token, or null.
214
214
  *
215
- * 1. Non-expired cached return it. (Fast path.)
216
- * 2. Expired AND refresh inflight return stale cached anyway.
217
- * Backend grace window (60s see auth-service refresh-rotation
218
- * middleware) accepts slightly-stale access tokens, so the
219
- * caller's downstream RPC will succeed against grace while
220
- * the in-flight refresh resolves out of band. Distinct from
221
- * pre-Wave-62 behaviour where 5 concurrent callers all shared
222
- * the same in-flight promise when that promise hung (iOS
223
- * NSURLSession cold-launch contention) every caller hung too.
224
- * 3. Expired + no inflight + refresh available + not in cooldown →
225
- * fire refresh, await it.
226
- * 4. Expired + in cooldown → return stale cached. Caller's RPC
227
- * either succeeds against grace or fails fast on 401, which is
228
- * a better UX than blocking the whole bridge waiting on a
229
- * network that we already know is stalled.
215
+ * Hard contract (matches sdk-ios / sdk-android, and the industry
216
+ * token-client pattern Supabase `_callRefreshToken`, Auth0
217
+ * `getTokenSilently`, Firebase `getIdToken`): this method NEVER
218
+ * returns a token past its `exp`. Callers attach the result as a
219
+ * Bearer to arbitrary resource servers including third-party
220
+ * backends (PostgREST/Supabase) that have NO knowledge of Sendora's
221
+ * own refresh grace window so a token must be either fresh or
222
+ * absent. A token expired by hours is useless and yields a 401.
230
223
  *
231
- * Returns null only when there's NO usable token at all (no cached
232
- * + no refresh available).
224
+ * Resolution order:
225
+ * 1. Cached token with comfortable life left (>= REFRESH_SAFETY_MS
226
+ * skew before exp) → return it. Fast path, no I/O.
227
+ * 2. No cached token AND no refresh token in storage → genuinely
228
+ * signed out → null.
229
+ * 3. Expired/near-expiry but a refresh chain exists:
230
+ * a. A refresh is already inflight → AWAIT it and return the
231
+ * fresh token. Concurrent callers coalesce onto the single
232
+ * in-flight `/token/refresh` (single-flight) — nobody is
233
+ * handed a stale token. The inflight fetch is
234
+ * AbortController-bounded (http.ts caps `/auth-service/*` at
235
+ * 10s), so awaiting can NEVER hang the JS bridge. That
236
+ * bounded-fetch guarantee — NOT the old stale-token return —
237
+ * is what actually fixed the Wave-62 cold-launch hang.
238
+ * b. A recent refresh failed and we're in backoff cooldown →
239
+ * null. Honest "no usable token right now" rather than a
240
+ * provably-expired one; the AppState foreground hook clears
241
+ * the cooldown for an immediate retry on resume.
242
+ * c. Otherwise → fire the refresh and await it.
233
243
  */
234
244
  getAccessToken(): Promise<string | null>;
235
245
  /** Sync read — returns whatever's cached even if expired. Prefer the async variant. */
package/dist/index.js CHANGED
@@ -179,7 +179,7 @@ async function request(opts, method, path, body, extraHeaders, reqOpts) {
179
179
  // package.json
180
180
  var package_default = {
181
181
  name: "@sendoracloud/sdk-react-native",
182
- version: "1.8.1",
182
+ version: "1.8.2",
183
183
  description: "Sendora Cloud React Native + Expo SDK \u2014 analytics, identity, push-token registration, auth, deep links (Branch / Firebase Dynamic Links parity). Auth + analytics + deep links work in Expo Go; push token registration requires a Dev Client (EAS Build or `npx expo prebuild`).",
184
184
  type: "module",
185
185
  main: "./dist/index.cjs",
@@ -422,38 +422,44 @@ var Auth = class {
422
422
  }
423
423
  }
424
424
  /**
425
- * Returns an access token or null. Cold-launch hardening (Wave 62):
425
+ * Returns a VALID (non-expired) access token, or null.
426
426
  *
427
- * 1. Non-expired cached return it. (Fast path.)
428
- * 2. Expired AND refresh inflight return stale cached anyway.
429
- * Backend grace window (60s see auth-service refresh-rotation
430
- * middleware) accepts slightly-stale access tokens, so the
431
- * caller's downstream RPC will succeed against grace while
432
- * the in-flight refresh resolves out of band. Distinct from
433
- * pre-Wave-62 behaviour where 5 concurrent callers all shared
434
- * the same in-flight promise when that promise hung (iOS
435
- * NSURLSession cold-launch contention) every caller hung too.
436
- * 3. Expired + no inflight + refresh available + not in cooldown →
437
- * fire refresh, await it.
438
- * 4. Expired + in cooldown → return stale cached. Caller's RPC
439
- * either succeeds against grace or fails fast on 401, which is
440
- * a better UX than blocking the whole bridge waiting on a
441
- * network that we already know is stalled.
427
+ * Hard contract (matches sdk-ios / sdk-android, and the industry
428
+ * token-client pattern Supabase `_callRefreshToken`, Auth0
429
+ * `getTokenSilently`, Firebase `getIdToken`): this method NEVER
430
+ * returns a token past its `exp`. Callers attach the result as a
431
+ * Bearer to arbitrary resource servers including third-party
432
+ * backends (PostgREST/Supabase) that have NO knowledge of Sendora's
433
+ * own refresh grace window so a token must be either fresh or
434
+ * absent. A token expired by hours is useless and yields a 401.
442
435
  *
443
- * Returns null only when there's NO usable token at all (no cached
444
- * + no refresh available).
436
+ * Resolution order:
437
+ * 1. Cached token with comfortable life left (>= REFRESH_SAFETY_MS
438
+ * skew before exp) → return it. Fast path, no I/O.
439
+ * 2. No cached token AND no refresh token in storage → genuinely
440
+ * signed out → null.
441
+ * 3. Expired/near-expiry but a refresh chain exists:
442
+ * a. A refresh is already inflight → AWAIT it and return the
443
+ * fresh token. Concurrent callers coalesce onto the single
444
+ * in-flight `/token/refresh` (single-flight) — nobody is
445
+ * handed a stale token. The inflight fetch is
446
+ * AbortController-bounded (http.ts caps `/auth-service/*` at
447
+ * 10s), so awaiting can NEVER hang the JS bridge. That
448
+ * bounded-fetch guarantee — NOT the old stale-token return —
449
+ * is what actually fixed the Wave-62 cold-launch hang.
450
+ * b. A recent refresh failed and we're in backoff cooldown →
451
+ * null. Honest "no usable token right now" rather than a
452
+ * provably-expired one; the AppState foreground hook clears
453
+ * the cooldown for an immediate retry on resume.
454
+ * c. Otherwise → fire the refresh and await it.
445
455
  */
446
456
  async getAccessToken() {
447
- if (!this.accessToken) return null;
448
- if (this.accessExpiresAt && Date.now() < this.accessExpiresAt - REFRESH_SAFETY_MS) {
449
- return this.accessToken;
450
- }
451
- if (this.refreshInflight) {
452
- return this.accessToken;
453
- }
454
- if (Date.now() < this.refreshCooldownUntil) {
457
+ if (this.accessToken && this.accessExpiresAt && Date.now() < this.accessExpiresAt - REFRESH_SAFETY_MS) {
455
458
  return this.accessToken;
456
459
  }
460
+ if (!this.hooks.storage.get(REFRESH_KEY)) return null;
461
+ if (this.refreshInflight) return this.refreshInflight;
462
+ if (Date.now() < this.refreshCooldownUntil) return null;
457
463
  return this.refreshAccessToken();
458
464
  }
459
465
  /** Sync read — returns whatever's cached even if expired. Prefer the async variant. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sendoracloud/sdk-react-native",
3
- "version": "1.8.1",
3
+ "version": "1.8.2",
4
4
  "description": "Sendora Cloud React Native + Expo SDK — analytics, identity, push-token registration, auth, deep links (Branch / Firebase Dynamic Links parity). Auth + analytics + deep links work in Expo Go; push token registration requires a Dev Client (EAS Build or `npx expo prebuild`).",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",