@sendoracloud/sdk-react-native 1.8.0 → 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 +44 -28
- package/dist/index.d.cts +34 -18
- package/dist/index.d.ts +34 -18
- package/dist/index.js +44 -28
- package/package.json +1 -1
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.
|
|
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
|
|
468
|
+
* Returns a VALID (non-expired) access token, or null.
|
|
469
469
|
*
|
|
470
|
-
*
|
|
471
|
-
*
|
|
472
|
-
*
|
|
473
|
-
*
|
|
474
|
-
*
|
|
475
|
-
*
|
|
476
|
-
*
|
|
477
|
-
*
|
|
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
|
-
*
|
|
487
|
-
*
|
|
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 (
|
|
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. */
|
|
@@ -865,12 +871,22 @@ var Auth = class {
|
|
|
865
871
|
* `scheduledPurgeAt`. The user can CANCEL by signing back in before then.
|
|
866
872
|
* Either outcome wipes local identity (the server has already revoked the
|
|
867
873
|
* session). Requires a signed-in user. Throws on a non-2xx response.
|
|
874
|
+
*
|
|
875
|
+
* Resolves a FRESH access token via `getAccessToken()` first (refreshing if
|
|
876
|
+
* the cached one expired) — unlike the other Bearer calls this is a one-shot
|
|
877
|
+
* destructive action, so a 401 from a stale token would silently strand the
|
|
878
|
+
* user with an undeleted account. A stale `this.accessToken` was the cause of
|
|
879
|
+
* the 401s seen in prod when a user tapped "delete" after the app sat idle.
|
|
868
880
|
*/
|
|
869
881
|
async deleteAccount() {
|
|
882
|
+
const token = await this.getAccessToken();
|
|
883
|
+
if (!token) {
|
|
884
|
+
throw new AuthError("NOT_SIGNED_IN", "Sign in before deleting your account");
|
|
885
|
+
}
|
|
870
886
|
const res = await del(
|
|
871
887
|
{ apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
|
|
872
888
|
"/api/v1/auth-service/me",
|
|
873
|
-
|
|
889
|
+
{ Authorization: `Bearer ${token}` }
|
|
874
890
|
);
|
|
875
891
|
if (!res || !res.ok) {
|
|
876
892
|
throw new Error(`deleteAccount failed${res ? ` (HTTP ${res.status})` : " (network error)"}`);
|
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
|
|
213
|
+
* Returns a VALID (non-expired) access token, or null.
|
|
214
214
|
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
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
|
-
*
|
|
232
|
-
*
|
|
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. */
|
|
@@ -353,6 +363,12 @@ declare class Auth {
|
|
|
353
363
|
* `scheduledPurgeAt`. The user can CANCEL by signing back in before then.
|
|
354
364
|
* Either outcome wipes local identity (the server has already revoked the
|
|
355
365
|
* session). Requires a signed-in user. Throws on a non-2xx response.
|
|
366
|
+
*
|
|
367
|
+
* Resolves a FRESH access token via `getAccessToken()` first (refreshing if
|
|
368
|
+
* the cached one expired) — unlike the other Bearer calls this is a one-shot
|
|
369
|
+
* destructive action, so a 401 from a stale token would silently strand the
|
|
370
|
+
* user with an undeleted account. A stale `this.accessToken` was the cause of
|
|
371
|
+
* the 401s seen in prod when a user tapped "delete" after the app sat idle.
|
|
356
372
|
*/
|
|
357
373
|
deleteAccount(): Promise<{
|
|
358
374
|
status: "purged" | "pending";
|
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
|
|
213
|
+
* Returns a VALID (non-expired) access token, or null.
|
|
214
214
|
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
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
|
-
*
|
|
232
|
-
*
|
|
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. */
|
|
@@ -353,6 +363,12 @@ declare class Auth {
|
|
|
353
363
|
* `scheduledPurgeAt`. The user can CANCEL by signing back in before then.
|
|
354
364
|
* Either outcome wipes local identity (the server has already revoked the
|
|
355
365
|
* session). Requires a signed-in user. Throws on a non-2xx response.
|
|
366
|
+
*
|
|
367
|
+
* Resolves a FRESH access token via `getAccessToken()` first (refreshing if
|
|
368
|
+
* the cached one expired) — unlike the other Bearer calls this is a one-shot
|
|
369
|
+
* destructive action, so a 401 from a stale token would silently strand the
|
|
370
|
+
* user with an undeleted account. A stale `this.accessToken` was the cause of
|
|
371
|
+
* the 401s seen in prod when a user tapped "delete" after the app sat idle.
|
|
356
372
|
*/
|
|
357
373
|
deleteAccount(): Promise<{
|
|
358
374
|
status: "purged" | "pending";
|
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.
|
|
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
|
|
425
|
+
* Returns a VALID (non-expired) access token, or null.
|
|
426
426
|
*
|
|
427
|
-
*
|
|
428
|
-
*
|
|
429
|
-
*
|
|
430
|
-
*
|
|
431
|
-
*
|
|
432
|
-
*
|
|
433
|
-
*
|
|
434
|
-
*
|
|
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
|
-
*
|
|
444
|
-
*
|
|
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 (
|
|
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. */
|
|
@@ -822,12 +828,22 @@ var Auth = class {
|
|
|
822
828
|
* `scheduledPurgeAt`. The user can CANCEL by signing back in before then.
|
|
823
829
|
* Either outcome wipes local identity (the server has already revoked the
|
|
824
830
|
* session). Requires a signed-in user. Throws on a non-2xx response.
|
|
831
|
+
*
|
|
832
|
+
* Resolves a FRESH access token via `getAccessToken()` first (refreshing if
|
|
833
|
+
* the cached one expired) — unlike the other Bearer calls this is a one-shot
|
|
834
|
+
* destructive action, so a 401 from a stale token would silently strand the
|
|
835
|
+
* user with an undeleted account. A stale `this.accessToken` was the cause of
|
|
836
|
+
* the 401s seen in prod when a user tapped "delete" after the app sat idle.
|
|
825
837
|
*/
|
|
826
838
|
async deleteAccount() {
|
|
839
|
+
const token = await this.getAccessToken();
|
|
840
|
+
if (!token) {
|
|
841
|
+
throw new AuthError("NOT_SIGNED_IN", "Sign in before deleting your account");
|
|
842
|
+
}
|
|
827
843
|
const res = await del(
|
|
828
844
|
{ apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
|
|
829
845
|
"/api/v1/auth-service/me",
|
|
830
|
-
|
|
846
|
+
{ Authorization: `Bearer ${token}` }
|
|
831
847
|
);
|
|
832
848
|
if (!res || !res.ok) {
|
|
833
849
|
throw new Error(`deleteAccount failed${res ? ` (HTTP ${res.status})` : " (network error)"}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sendoracloud/sdk-react-native",
|
|
3
|
-
"version": "1.8.
|
|
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",
|