@sendoracloud/sdk-react-native 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +24 -2
- package/dist/index.d.cts +14 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +24 -2
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -719,6 +719,20 @@ var Auth = class {
|
|
|
719
719
|
this.inflight = next.catch(() => void 0);
|
|
720
720
|
return next;
|
|
721
721
|
}
|
|
722
|
+
/**
|
|
723
|
+
* Hot-path token refresh. Single-flight via `refreshInflight` so a
|
|
724
|
+
* UI batch firing 10 simultaneous reads never produces 10 /refresh
|
|
725
|
+
* round-trips. Returns null on any failure so callers can decide
|
|
726
|
+
* whether to surface a sign-in prompt or proceed anonymously.
|
|
727
|
+
*
|
|
728
|
+
* s58.46 — on a `INVALID_REFRESH_TOKEN` (new backend error code) or
|
|
729
|
+
* HTTP 401 we WIPE the stored refresh token. Pre-s58.46 we caught
|
|
730
|
+
* the error and returned null but kept the stored token intact, so
|
|
731
|
+
* the very next caller would re-attempt the same dead token in an
|
|
732
|
+
* infinite loop (Pulse News iOS hammered /refresh ~1×/s for hours).
|
|
733
|
+
* The wipe forces the next op to fall through to /anonymous or
|
|
734
|
+
* surface NOT_SIGNED_IN to the host app instead of looping.
|
|
735
|
+
*/
|
|
722
736
|
async refreshAccessToken() {
|
|
723
737
|
if (this.refreshInflight) return this.refreshInflight;
|
|
724
738
|
const refresh = this.hooks.storage.get(REFRESH_KEY);
|
|
@@ -727,14 +741,18 @@ var Auth = class {
|
|
|
727
741
|
try {
|
|
728
742
|
const data = await this.callAuth("/api/v1/auth-service/token/refresh", {
|
|
729
743
|
refreshToken: refresh
|
|
730
|
-
})
|
|
731
|
-
if (!data) return null;
|
|
744
|
+
});
|
|
732
745
|
this.accessToken = data.tokens.accessToken;
|
|
733
746
|
this.accessExpiresAt = Date.now() + data.tokens.expiresIn * 1e3;
|
|
734
747
|
this.hooks.storage.set(TOKEN_KEY, data.tokens.accessToken);
|
|
735
748
|
this.hooks.storage.set(TOKEN_EXPIRES_KEY, String(this.accessExpiresAt));
|
|
736
749
|
this.hooks.storage.set(REFRESH_KEY, data.tokens.refreshToken);
|
|
737
750
|
return data.tokens.accessToken;
|
|
751
|
+
} catch (err) {
|
|
752
|
+
if (err instanceof AuthError && isDeadRefreshError(err.code)) {
|
|
753
|
+
await this.wipeLocalIdentity().catch(() => void 0);
|
|
754
|
+
}
|
|
755
|
+
return null;
|
|
738
756
|
} finally {
|
|
739
757
|
this.refreshInflight = null;
|
|
740
758
|
}
|
|
@@ -785,6 +803,10 @@ var Auth = class {
|
|
|
785
803
|
await this.hooks.onAnonymousWipe();
|
|
786
804
|
}
|
|
787
805
|
};
|
|
806
|
+
function isDeadRefreshError(code) {
|
|
807
|
+
if (!code) return false;
|
|
808
|
+
return code === "INVALID_REFRESH_TOKEN" || code === "HTTP_401" || code === "UNAUTHORIZED" || code === "RATE_LIMIT_EXCEEDED" || code === "RATE_LIMIT";
|
|
809
|
+
}
|
|
788
810
|
function decodeJwtPayload(token) {
|
|
789
811
|
try {
|
|
790
812
|
const parts = token.split(".");
|
package/dist/index.d.cts
CHANGED
|
@@ -270,6 +270,20 @@ declare class Auth {
|
|
|
270
270
|
*/
|
|
271
271
|
signOut(): Promise<void>;
|
|
272
272
|
private serialize;
|
|
273
|
+
/**
|
|
274
|
+
* Hot-path token refresh. Single-flight via `refreshInflight` so a
|
|
275
|
+
* UI batch firing 10 simultaneous reads never produces 10 /refresh
|
|
276
|
+
* round-trips. Returns null on any failure so callers can decide
|
|
277
|
+
* whether to surface a sign-in prompt or proceed anonymously.
|
|
278
|
+
*
|
|
279
|
+
* s58.46 — on a `INVALID_REFRESH_TOKEN` (new backend error code) or
|
|
280
|
+
* HTTP 401 we WIPE the stored refresh token. Pre-s58.46 we caught
|
|
281
|
+
* the error and returned null but kept the stored token intact, so
|
|
282
|
+
* the very next caller would re-attempt the same dead token in an
|
|
283
|
+
* infinite loop (Pulse News iOS hammered /refresh ~1×/s for hours).
|
|
284
|
+
* The wipe forces the next op to fall through to /anonymous or
|
|
285
|
+
* surface NOT_SIGNED_IN to the host app instead of looping.
|
|
286
|
+
*/
|
|
273
287
|
private refreshAccessToken;
|
|
274
288
|
private callAuth;
|
|
275
289
|
private persist;
|
package/dist/index.d.ts
CHANGED
|
@@ -270,6 +270,20 @@ declare class Auth {
|
|
|
270
270
|
*/
|
|
271
271
|
signOut(): Promise<void>;
|
|
272
272
|
private serialize;
|
|
273
|
+
/**
|
|
274
|
+
* Hot-path token refresh. Single-flight via `refreshInflight` so a
|
|
275
|
+
* UI batch firing 10 simultaneous reads never produces 10 /refresh
|
|
276
|
+
* round-trips. Returns null on any failure so callers can decide
|
|
277
|
+
* whether to surface a sign-in prompt or proceed anonymously.
|
|
278
|
+
*
|
|
279
|
+
* s58.46 — on a `INVALID_REFRESH_TOKEN` (new backend error code) or
|
|
280
|
+
* HTTP 401 we WIPE the stored refresh token. Pre-s58.46 we caught
|
|
281
|
+
* the error and returned null but kept the stored token intact, so
|
|
282
|
+
* the very next caller would re-attempt the same dead token in an
|
|
283
|
+
* infinite loop (Pulse News iOS hammered /refresh ~1×/s for hours).
|
|
284
|
+
* The wipe forces the next op to fall through to /anonymous or
|
|
285
|
+
* surface NOT_SIGNED_IN to the host app instead of looping.
|
|
286
|
+
*/
|
|
273
287
|
private refreshAccessToken;
|
|
274
288
|
private callAuth;
|
|
275
289
|
private persist;
|
package/dist/index.js
CHANGED
|
@@ -679,6 +679,20 @@ var Auth = class {
|
|
|
679
679
|
this.inflight = next.catch(() => void 0);
|
|
680
680
|
return next;
|
|
681
681
|
}
|
|
682
|
+
/**
|
|
683
|
+
* Hot-path token refresh. Single-flight via `refreshInflight` so a
|
|
684
|
+
* UI batch firing 10 simultaneous reads never produces 10 /refresh
|
|
685
|
+
* round-trips. Returns null on any failure so callers can decide
|
|
686
|
+
* whether to surface a sign-in prompt or proceed anonymously.
|
|
687
|
+
*
|
|
688
|
+
* s58.46 — on a `INVALID_REFRESH_TOKEN` (new backend error code) or
|
|
689
|
+
* HTTP 401 we WIPE the stored refresh token. Pre-s58.46 we caught
|
|
690
|
+
* the error and returned null but kept the stored token intact, so
|
|
691
|
+
* the very next caller would re-attempt the same dead token in an
|
|
692
|
+
* infinite loop (Pulse News iOS hammered /refresh ~1×/s for hours).
|
|
693
|
+
* The wipe forces the next op to fall through to /anonymous or
|
|
694
|
+
* surface NOT_SIGNED_IN to the host app instead of looping.
|
|
695
|
+
*/
|
|
682
696
|
async refreshAccessToken() {
|
|
683
697
|
if (this.refreshInflight) return this.refreshInflight;
|
|
684
698
|
const refresh = this.hooks.storage.get(REFRESH_KEY);
|
|
@@ -687,14 +701,18 @@ var Auth = class {
|
|
|
687
701
|
try {
|
|
688
702
|
const data = await this.callAuth("/api/v1/auth-service/token/refresh", {
|
|
689
703
|
refreshToken: refresh
|
|
690
|
-
})
|
|
691
|
-
if (!data) return null;
|
|
704
|
+
});
|
|
692
705
|
this.accessToken = data.tokens.accessToken;
|
|
693
706
|
this.accessExpiresAt = Date.now() + data.tokens.expiresIn * 1e3;
|
|
694
707
|
this.hooks.storage.set(TOKEN_KEY, data.tokens.accessToken);
|
|
695
708
|
this.hooks.storage.set(TOKEN_EXPIRES_KEY, String(this.accessExpiresAt));
|
|
696
709
|
this.hooks.storage.set(REFRESH_KEY, data.tokens.refreshToken);
|
|
697
710
|
return data.tokens.accessToken;
|
|
711
|
+
} catch (err) {
|
|
712
|
+
if (err instanceof AuthError && isDeadRefreshError(err.code)) {
|
|
713
|
+
await this.wipeLocalIdentity().catch(() => void 0);
|
|
714
|
+
}
|
|
715
|
+
return null;
|
|
698
716
|
} finally {
|
|
699
717
|
this.refreshInflight = null;
|
|
700
718
|
}
|
|
@@ -745,6 +763,10 @@ var Auth = class {
|
|
|
745
763
|
await this.hooks.onAnonymousWipe();
|
|
746
764
|
}
|
|
747
765
|
};
|
|
766
|
+
function isDeadRefreshError(code) {
|
|
767
|
+
if (!code) return false;
|
|
768
|
+
return code === "INVALID_REFRESH_TOKEN" || code === "HTTP_401" || code === "UNAUTHORIZED" || code === "RATE_LIMIT_EXCEEDED" || code === "RATE_LIMIT";
|
|
769
|
+
}
|
|
748
770
|
function decodeJwtPayload(token) {
|
|
749
771
|
try {
|
|
750
772
|
const parts = token.split(".");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sendoracloud/sdk-react-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Sendora Cloud React Native + Expo SDK — analytics, identity, push-token registration, auth. Expo Go compatible, no native modules beyond AsyncStorage.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|