@sendoracloud/sdk-react-native 0.19.0 → 0.20.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 +67 -3
- package/dist/index.d.cts +11 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +74 -3
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -236,6 +236,15 @@ var Auth = class {
|
|
|
236
236
|
this.accessExpiresAt = 0;
|
|
237
237
|
this.inflight = Promise.resolve();
|
|
238
238
|
this.refreshInflight = null;
|
|
239
|
+
/**
|
|
240
|
+
* Proactive refresh (s58.72). Fires when access-token age crosses
|
|
241
|
+
* ~80% of TTL. Pairs with the AppState foreground listener so a
|
|
242
|
+
* backgrounded app coming back to active state immediately runs
|
|
243
|
+
* a tick — much better UX than waiting 60s for the next interval.
|
|
244
|
+
* Stopped on signOut() / wipeLocalIdentity().
|
|
245
|
+
*/
|
|
246
|
+
this.proactiveRefreshTimer = null;
|
|
247
|
+
this.proactiveRefreshAppStateUnsub = null;
|
|
239
248
|
this.hooks = hooks;
|
|
240
249
|
this.readyPromise = new Promise((res) => {
|
|
241
250
|
this.resolveReady = res;
|
|
@@ -784,12 +793,25 @@ var Auth = class {
|
|
|
784
793
|
*/
|
|
785
794
|
async refreshAccessToken() {
|
|
786
795
|
if (this.refreshInflight) return this.refreshInflight;
|
|
787
|
-
const
|
|
788
|
-
if (!
|
|
796
|
+
const refreshSnapshot = this.hooks.storage.get(REFRESH_KEY);
|
|
797
|
+
if (!refreshSnapshot) return null;
|
|
789
798
|
this.refreshInflight = (async () => {
|
|
790
799
|
try {
|
|
800
|
+
const current = this.hooks.storage.get(REFRESH_KEY);
|
|
801
|
+
if (current && current !== refreshSnapshot) {
|
|
802
|
+
const freshAccess = this.hooks.storage.get(TOKEN_KEY);
|
|
803
|
+
const freshExpiresStr = this.hooks.storage.get(TOKEN_EXPIRES_KEY);
|
|
804
|
+
if (freshAccess && freshExpiresStr) {
|
|
805
|
+
const freshExpires = Number(freshExpiresStr);
|
|
806
|
+
if (Number.isFinite(freshExpires) && freshExpires > Date.now()) {
|
|
807
|
+
this.accessToken = freshAccess;
|
|
808
|
+
this.accessExpiresAt = freshExpires;
|
|
809
|
+
return freshAccess;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
791
813
|
const data = await this.callAuth("/api/v1/auth-service/token/refresh", {
|
|
792
|
-
refreshToken:
|
|
814
|
+
refreshToken: current ?? refreshSnapshot
|
|
793
815
|
});
|
|
794
816
|
this.accessToken = data.tokens.accessToken;
|
|
795
817
|
this.accessExpiresAt = Date.now() + data.tokens.expiresIn * 1e3;
|
|
@@ -808,6 +830,46 @@ var Auth = class {
|
|
|
808
830
|
})();
|
|
809
831
|
return this.refreshInflight;
|
|
810
832
|
}
|
|
833
|
+
startProactiveRefreshCron() {
|
|
834
|
+
if (this.proactiveRefreshTimer) return;
|
|
835
|
+
const PROACTIVE_TARGET_PCT = 0.8;
|
|
836
|
+
const TICK_MS = 6e4;
|
|
837
|
+
const JITTER_MS = 3e4;
|
|
838
|
+
const tick = async () => {
|
|
839
|
+
if (!this.accessToken || !this.accessExpiresAt) return;
|
|
840
|
+
const now = Date.now();
|
|
841
|
+
const lifetimeMs = this.accessExpiresAt - now;
|
|
842
|
+
if (lifetimeMs <= 0) return;
|
|
843
|
+
const guessOriginalMs = Math.max(lifetimeMs, 5 * 6e4);
|
|
844
|
+
const fireWhenRemainingMs = guessOriginalMs * (1 - PROACTIVE_TARGET_PCT) + (Math.random() * JITTER_MS * 2 - JITTER_MS);
|
|
845
|
+
if (lifetimeMs <= fireWhenRemainingMs) {
|
|
846
|
+
await this.refreshAccessToken();
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
this.proactiveRefreshTimer = setInterval(() => {
|
|
850
|
+
void tick();
|
|
851
|
+
}, TICK_MS);
|
|
852
|
+
try {
|
|
853
|
+
const RN = require("react-native");
|
|
854
|
+
const subscription = RN.AppState?.addEventListener?.("change", (state) => {
|
|
855
|
+
if (state === "active") void tick();
|
|
856
|
+
});
|
|
857
|
+
if (subscription) {
|
|
858
|
+
this.proactiveRefreshAppStateUnsub = () => subscription.remove();
|
|
859
|
+
}
|
|
860
|
+
} catch {
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
stopProactiveRefreshCron() {
|
|
864
|
+
if (this.proactiveRefreshTimer) {
|
|
865
|
+
clearInterval(this.proactiveRefreshTimer);
|
|
866
|
+
this.proactiveRefreshTimer = null;
|
|
867
|
+
}
|
|
868
|
+
if (this.proactiveRefreshAppStateUnsub) {
|
|
869
|
+
this.proactiveRefreshAppStateUnsub();
|
|
870
|
+
this.proactiveRefreshAppStateUnsub = null;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
811
873
|
/**
|
|
812
874
|
* Returns the currently-stored refresh token (or null when signed out).
|
|
813
875
|
* Primary use: capture the anonymous refresh token BEFORE calling
|
|
@@ -909,6 +971,7 @@ var Auth = class {
|
|
|
909
971
|
this.hooks.storage.set(TOKEN_EXPIRES_KEY, String(this.accessExpiresAt));
|
|
910
972
|
this.hooks.storage.set(REFRESH_KEY, data.tokens.refreshToken);
|
|
911
973
|
this.hooks.onIdentityChange(data.user.id);
|
|
974
|
+
this.startProactiveRefreshCron();
|
|
912
975
|
}
|
|
913
976
|
async wipeLocalIdentity() {
|
|
914
977
|
this.user = null;
|
|
@@ -918,6 +981,7 @@ var Auth = class {
|
|
|
918
981
|
this.hooks.storage.remove(TOKEN_KEY);
|
|
919
982
|
this.hooks.storage.remove(TOKEN_EXPIRES_KEY);
|
|
920
983
|
this.hooks.storage.remove(REFRESH_KEY);
|
|
984
|
+
this.stopProactiveRefreshCron();
|
|
921
985
|
await this.hooks.onAnonymousWipe();
|
|
922
986
|
}
|
|
923
987
|
};
|
package/dist/index.d.cts
CHANGED
|
@@ -312,6 +312,17 @@ declare class Auth {
|
|
|
312
312
|
* surface NOT_SIGNED_IN to the host app instead of looping.
|
|
313
313
|
*/
|
|
314
314
|
private refreshAccessToken;
|
|
315
|
+
/**
|
|
316
|
+
* Proactive refresh (s58.72). Fires when access-token age crosses
|
|
317
|
+
* ~80% of TTL. Pairs with the AppState foreground listener so a
|
|
318
|
+
* backgrounded app coming back to active state immediately runs
|
|
319
|
+
* a tick — much better UX than waiting 60s for the next interval.
|
|
320
|
+
* Stopped on signOut() / wipeLocalIdentity().
|
|
321
|
+
*/
|
|
322
|
+
private proactiveRefreshTimer;
|
|
323
|
+
private proactiveRefreshAppStateUnsub;
|
|
324
|
+
private startProactiveRefreshCron;
|
|
325
|
+
private stopProactiveRefreshCron;
|
|
315
326
|
/**
|
|
316
327
|
* Returns the currently-stored refresh token (or null when signed out).
|
|
317
328
|
* Primary use: capture the anonymous refresh token BEFORE calling
|
package/dist/index.d.ts
CHANGED
|
@@ -312,6 +312,17 @@ declare class Auth {
|
|
|
312
312
|
* surface NOT_SIGNED_IN to the host app instead of looping.
|
|
313
313
|
*/
|
|
314
314
|
private refreshAccessToken;
|
|
315
|
+
/**
|
|
316
|
+
* Proactive refresh (s58.72). Fires when access-token age crosses
|
|
317
|
+
* ~80% of TTL. Pairs with the AppState foreground listener so a
|
|
318
|
+
* backgrounded app coming back to active state immediately runs
|
|
319
|
+
* a tick — much better UX than waiting 60s for the next interval.
|
|
320
|
+
* Stopped on signOut() / wipeLocalIdentity().
|
|
321
|
+
*/
|
|
322
|
+
private proactiveRefreshTimer;
|
|
323
|
+
private proactiveRefreshAppStateUnsub;
|
|
324
|
+
private startProactiveRefreshCron;
|
|
325
|
+
private stopProactiveRefreshCron;
|
|
315
326
|
/**
|
|
316
327
|
* Returns the currently-stored refresh token (or null when signed out).
|
|
317
328
|
* Primary use: capture the anonymous refresh token BEFORE calling
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
1
8
|
// src/index.ts
|
|
2
9
|
import "react-native-get-random-values";
|
|
3
10
|
|
|
@@ -193,6 +200,15 @@ var Auth = class {
|
|
|
193
200
|
this.accessExpiresAt = 0;
|
|
194
201
|
this.inflight = Promise.resolve();
|
|
195
202
|
this.refreshInflight = null;
|
|
203
|
+
/**
|
|
204
|
+
* Proactive refresh (s58.72). Fires when access-token age crosses
|
|
205
|
+
* ~80% of TTL. Pairs with the AppState foreground listener so a
|
|
206
|
+
* backgrounded app coming back to active state immediately runs
|
|
207
|
+
* a tick — much better UX than waiting 60s for the next interval.
|
|
208
|
+
* Stopped on signOut() / wipeLocalIdentity().
|
|
209
|
+
*/
|
|
210
|
+
this.proactiveRefreshTimer = null;
|
|
211
|
+
this.proactiveRefreshAppStateUnsub = null;
|
|
196
212
|
this.hooks = hooks;
|
|
197
213
|
this.readyPromise = new Promise((res) => {
|
|
198
214
|
this.resolveReady = res;
|
|
@@ -741,12 +757,25 @@ var Auth = class {
|
|
|
741
757
|
*/
|
|
742
758
|
async refreshAccessToken() {
|
|
743
759
|
if (this.refreshInflight) return this.refreshInflight;
|
|
744
|
-
const
|
|
745
|
-
if (!
|
|
760
|
+
const refreshSnapshot = this.hooks.storage.get(REFRESH_KEY);
|
|
761
|
+
if (!refreshSnapshot) return null;
|
|
746
762
|
this.refreshInflight = (async () => {
|
|
747
763
|
try {
|
|
764
|
+
const current = this.hooks.storage.get(REFRESH_KEY);
|
|
765
|
+
if (current && current !== refreshSnapshot) {
|
|
766
|
+
const freshAccess = this.hooks.storage.get(TOKEN_KEY);
|
|
767
|
+
const freshExpiresStr = this.hooks.storage.get(TOKEN_EXPIRES_KEY);
|
|
768
|
+
if (freshAccess && freshExpiresStr) {
|
|
769
|
+
const freshExpires = Number(freshExpiresStr);
|
|
770
|
+
if (Number.isFinite(freshExpires) && freshExpires > Date.now()) {
|
|
771
|
+
this.accessToken = freshAccess;
|
|
772
|
+
this.accessExpiresAt = freshExpires;
|
|
773
|
+
return freshAccess;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
748
777
|
const data = await this.callAuth("/api/v1/auth-service/token/refresh", {
|
|
749
|
-
refreshToken:
|
|
778
|
+
refreshToken: current ?? refreshSnapshot
|
|
750
779
|
});
|
|
751
780
|
this.accessToken = data.tokens.accessToken;
|
|
752
781
|
this.accessExpiresAt = Date.now() + data.tokens.expiresIn * 1e3;
|
|
@@ -765,6 +794,46 @@ var Auth = class {
|
|
|
765
794
|
})();
|
|
766
795
|
return this.refreshInflight;
|
|
767
796
|
}
|
|
797
|
+
startProactiveRefreshCron() {
|
|
798
|
+
if (this.proactiveRefreshTimer) return;
|
|
799
|
+
const PROACTIVE_TARGET_PCT = 0.8;
|
|
800
|
+
const TICK_MS = 6e4;
|
|
801
|
+
const JITTER_MS = 3e4;
|
|
802
|
+
const tick = async () => {
|
|
803
|
+
if (!this.accessToken || !this.accessExpiresAt) return;
|
|
804
|
+
const now = Date.now();
|
|
805
|
+
const lifetimeMs = this.accessExpiresAt - now;
|
|
806
|
+
if (lifetimeMs <= 0) return;
|
|
807
|
+
const guessOriginalMs = Math.max(lifetimeMs, 5 * 6e4);
|
|
808
|
+
const fireWhenRemainingMs = guessOriginalMs * (1 - PROACTIVE_TARGET_PCT) + (Math.random() * JITTER_MS * 2 - JITTER_MS);
|
|
809
|
+
if (lifetimeMs <= fireWhenRemainingMs) {
|
|
810
|
+
await this.refreshAccessToken();
|
|
811
|
+
}
|
|
812
|
+
};
|
|
813
|
+
this.proactiveRefreshTimer = setInterval(() => {
|
|
814
|
+
void tick();
|
|
815
|
+
}, TICK_MS);
|
|
816
|
+
try {
|
|
817
|
+
const RN = __require("react-native");
|
|
818
|
+
const subscription = RN.AppState?.addEventListener?.("change", (state) => {
|
|
819
|
+
if (state === "active") void tick();
|
|
820
|
+
});
|
|
821
|
+
if (subscription) {
|
|
822
|
+
this.proactiveRefreshAppStateUnsub = () => subscription.remove();
|
|
823
|
+
}
|
|
824
|
+
} catch {
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
stopProactiveRefreshCron() {
|
|
828
|
+
if (this.proactiveRefreshTimer) {
|
|
829
|
+
clearInterval(this.proactiveRefreshTimer);
|
|
830
|
+
this.proactiveRefreshTimer = null;
|
|
831
|
+
}
|
|
832
|
+
if (this.proactiveRefreshAppStateUnsub) {
|
|
833
|
+
this.proactiveRefreshAppStateUnsub();
|
|
834
|
+
this.proactiveRefreshAppStateUnsub = null;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
768
837
|
/**
|
|
769
838
|
* Returns the currently-stored refresh token (or null when signed out).
|
|
770
839
|
* Primary use: capture the anonymous refresh token BEFORE calling
|
|
@@ -866,6 +935,7 @@ var Auth = class {
|
|
|
866
935
|
this.hooks.storage.set(TOKEN_EXPIRES_KEY, String(this.accessExpiresAt));
|
|
867
936
|
this.hooks.storage.set(REFRESH_KEY, data.tokens.refreshToken);
|
|
868
937
|
this.hooks.onIdentityChange(data.user.id);
|
|
938
|
+
this.startProactiveRefreshCron();
|
|
869
939
|
}
|
|
870
940
|
async wipeLocalIdentity() {
|
|
871
941
|
this.user = null;
|
|
@@ -875,6 +945,7 @@ var Auth = class {
|
|
|
875
945
|
this.hooks.storage.remove(TOKEN_KEY);
|
|
876
946
|
this.hooks.storage.remove(TOKEN_EXPIRES_KEY);
|
|
877
947
|
this.hooks.storage.remove(REFRESH_KEY);
|
|
948
|
+
this.stopProactiveRefreshCron();
|
|
878
949
|
await this.hooks.onAnonymousWipe();
|
|
879
950
|
}
|
|
880
951
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sendoracloud/sdk-react-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
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",
|