@sendoracloud/sdk-react-native 0.19.0 → 0.20.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.
- package/dist/index.cjs +74 -10
- package/dist/index.d.cts +11 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +69 -5
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -195,6 +195,7 @@ async function request(opts, method, path, body, extraHeaders) {
|
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
// src/auth.ts
|
|
198
|
+
var import_react_native = require("react-native");
|
|
198
199
|
var TOKEN_KEY = "auth_access_token";
|
|
199
200
|
var TOKEN_EXPIRES_KEY = "auth_access_expires";
|
|
200
201
|
var REFRESH_KEY = "auth_refresh_token";
|
|
@@ -236,6 +237,15 @@ var Auth = class {
|
|
|
236
237
|
this.accessExpiresAt = 0;
|
|
237
238
|
this.inflight = Promise.resolve();
|
|
238
239
|
this.refreshInflight = null;
|
|
240
|
+
/**
|
|
241
|
+
* Proactive refresh (s58.72). Fires when access-token age crosses
|
|
242
|
+
* ~80% of TTL. Pairs with the AppState foreground listener so a
|
|
243
|
+
* backgrounded app coming back to active state immediately runs
|
|
244
|
+
* a tick — much better UX than waiting 60s for the next interval.
|
|
245
|
+
* Stopped on signOut() / wipeLocalIdentity().
|
|
246
|
+
*/
|
|
247
|
+
this.proactiveRefreshTimer = null;
|
|
248
|
+
this.proactiveRefreshAppStateUnsub = null;
|
|
239
249
|
this.hooks = hooks;
|
|
240
250
|
this.readyPromise = new Promise((res) => {
|
|
241
251
|
this.resolveReady = res;
|
|
@@ -784,12 +794,25 @@ var Auth = class {
|
|
|
784
794
|
*/
|
|
785
795
|
async refreshAccessToken() {
|
|
786
796
|
if (this.refreshInflight) return this.refreshInflight;
|
|
787
|
-
const
|
|
788
|
-
if (!
|
|
797
|
+
const refreshSnapshot = this.hooks.storage.get(REFRESH_KEY);
|
|
798
|
+
if (!refreshSnapshot) return null;
|
|
789
799
|
this.refreshInflight = (async () => {
|
|
790
800
|
try {
|
|
801
|
+
const current = this.hooks.storage.get(REFRESH_KEY);
|
|
802
|
+
if (current && current !== refreshSnapshot) {
|
|
803
|
+
const freshAccess = this.hooks.storage.get(TOKEN_KEY);
|
|
804
|
+
const freshExpiresStr = this.hooks.storage.get(TOKEN_EXPIRES_KEY);
|
|
805
|
+
if (freshAccess && freshExpiresStr) {
|
|
806
|
+
const freshExpires = Number(freshExpiresStr);
|
|
807
|
+
if (Number.isFinite(freshExpires) && freshExpires > Date.now()) {
|
|
808
|
+
this.accessToken = freshAccess;
|
|
809
|
+
this.accessExpiresAt = freshExpires;
|
|
810
|
+
return freshAccess;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
791
814
|
const data = await this.callAuth("/api/v1/auth-service/token/refresh", {
|
|
792
|
-
refreshToken:
|
|
815
|
+
refreshToken: current ?? refreshSnapshot
|
|
793
816
|
});
|
|
794
817
|
this.accessToken = data.tokens.accessToken;
|
|
795
818
|
this.accessExpiresAt = Date.now() + data.tokens.expiresIn * 1e3;
|
|
@@ -808,6 +831,45 @@ var Auth = class {
|
|
|
808
831
|
})();
|
|
809
832
|
return this.refreshInflight;
|
|
810
833
|
}
|
|
834
|
+
startProactiveRefreshCron() {
|
|
835
|
+
if (this.proactiveRefreshTimer) return;
|
|
836
|
+
const PROACTIVE_TARGET_PCT = 0.8;
|
|
837
|
+
const TICK_MS = 6e4;
|
|
838
|
+
const JITTER_MS = 3e4;
|
|
839
|
+
const tick = async () => {
|
|
840
|
+
if (!this.accessToken || !this.accessExpiresAt) return;
|
|
841
|
+
const now = Date.now();
|
|
842
|
+
const lifetimeMs = this.accessExpiresAt - now;
|
|
843
|
+
if (lifetimeMs <= 0) return;
|
|
844
|
+
const guessOriginalMs = Math.max(lifetimeMs, 5 * 6e4);
|
|
845
|
+
const fireWhenRemainingMs = guessOriginalMs * (1 - PROACTIVE_TARGET_PCT) + (Math.random() * JITTER_MS * 2 - JITTER_MS);
|
|
846
|
+
if (lifetimeMs <= fireWhenRemainingMs) {
|
|
847
|
+
await this.refreshAccessToken();
|
|
848
|
+
}
|
|
849
|
+
};
|
|
850
|
+
this.proactiveRefreshTimer = setInterval(() => {
|
|
851
|
+
void tick();
|
|
852
|
+
}, TICK_MS);
|
|
853
|
+
try {
|
|
854
|
+
const subscription = import_react_native.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
|
};
|
|
@@ -940,7 +1004,7 @@ function decodeJwtPayload(token) {
|
|
|
940
1004
|
}
|
|
941
1005
|
|
|
942
1006
|
// src/links.ts
|
|
943
|
-
var
|
|
1007
|
+
var import_react_native2 = require("react-native");
|
|
944
1008
|
var LinkError = class extends Error {
|
|
945
1009
|
constructor(code, message, statusCode = 0, details) {
|
|
946
1010
|
super(message);
|
|
@@ -1019,10 +1083,10 @@ function dynRequire(id) {
|
|
|
1019
1083
|
}
|
|
1020
1084
|
function loadRn() {
|
|
1021
1085
|
return {
|
|
1022
|
-
Platform:
|
|
1023
|
-
Dimensions:
|
|
1024
|
-
NativeModules:
|
|
1025
|
-
Linking:
|
|
1086
|
+
Platform: import_react_native2.Platform,
|
|
1087
|
+
Dimensions: import_react_native2.Dimensions,
|
|
1088
|
+
NativeModules: import_react_native2.NativeModules,
|
|
1089
|
+
Linking: import_react_native2.Linking
|
|
1026
1090
|
};
|
|
1027
1091
|
}
|
|
1028
1092
|
function readTimezone() {
|
|
@@ -1504,7 +1568,7 @@ var Links = class {
|
|
|
1504
1568
|
};
|
|
1505
1569
|
|
|
1506
1570
|
// src/auto-track.ts
|
|
1507
|
-
var
|
|
1571
|
+
var import_react_native3 = require("react-native");
|
|
1508
1572
|
var DEFAULT_IDLE_MS = 30 * 60 * 1e3;
|
|
1509
1573
|
function resolveFlags(cfg) {
|
|
1510
1574
|
if (cfg === false) {
|
|
@@ -1548,7 +1612,7 @@ function installAutoTrack(args) {
|
|
|
1548
1612
|
args.fire("app.opened", { sessionId });
|
|
1549
1613
|
}
|
|
1550
1614
|
if (flags.appBackground) {
|
|
1551
|
-
const AppState =
|
|
1615
|
+
const AppState = import_react_native3.AppState;
|
|
1552
1616
|
if (AppState && typeof AppState.addEventListener === "function") {
|
|
1553
1617
|
const handler = (state) => {
|
|
1554
1618
|
ensureSession(true);
|
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
|
@@ -152,6 +152,7 @@ async function request(opts, method, path, body, extraHeaders) {
|
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
// src/auth.ts
|
|
155
|
+
import { AppState as RnAppState } from "react-native";
|
|
155
156
|
var TOKEN_KEY = "auth_access_token";
|
|
156
157
|
var TOKEN_EXPIRES_KEY = "auth_access_expires";
|
|
157
158
|
var REFRESH_KEY = "auth_refresh_token";
|
|
@@ -193,6 +194,15 @@ var Auth = class {
|
|
|
193
194
|
this.accessExpiresAt = 0;
|
|
194
195
|
this.inflight = Promise.resolve();
|
|
195
196
|
this.refreshInflight = null;
|
|
197
|
+
/**
|
|
198
|
+
* Proactive refresh (s58.72). Fires when access-token age crosses
|
|
199
|
+
* ~80% of TTL. Pairs with the AppState foreground listener so a
|
|
200
|
+
* backgrounded app coming back to active state immediately runs
|
|
201
|
+
* a tick — much better UX than waiting 60s for the next interval.
|
|
202
|
+
* Stopped on signOut() / wipeLocalIdentity().
|
|
203
|
+
*/
|
|
204
|
+
this.proactiveRefreshTimer = null;
|
|
205
|
+
this.proactiveRefreshAppStateUnsub = null;
|
|
196
206
|
this.hooks = hooks;
|
|
197
207
|
this.readyPromise = new Promise((res) => {
|
|
198
208
|
this.resolveReady = res;
|
|
@@ -741,12 +751,25 @@ var Auth = class {
|
|
|
741
751
|
*/
|
|
742
752
|
async refreshAccessToken() {
|
|
743
753
|
if (this.refreshInflight) return this.refreshInflight;
|
|
744
|
-
const
|
|
745
|
-
if (!
|
|
754
|
+
const refreshSnapshot = this.hooks.storage.get(REFRESH_KEY);
|
|
755
|
+
if (!refreshSnapshot) return null;
|
|
746
756
|
this.refreshInflight = (async () => {
|
|
747
757
|
try {
|
|
758
|
+
const current = this.hooks.storage.get(REFRESH_KEY);
|
|
759
|
+
if (current && current !== refreshSnapshot) {
|
|
760
|
+
const freshAccess = this.hooks.storage.get(TOKEN_KEY);
|
|
761
|
+
const freshExpiresStr = this.hooks.storage.get(TOKEN_EXPIRES_KEY);
|
|
762
|
+
if (freshAccess && freshExpiresStr) {
|
|
763
|
+
const freshExpires = Number(freshExpiresStr);
|
|
764
|
+
if (Number.isFinite(freshExpires) && freshExpires > Date.now()) {
|
|
765
|
+
this.accessToken = freshAccess;
|
|
766
|
+
this.accessExpiresAt = freshExpires;
|
|
767
|
+
return freshAccess;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
}
|
|
748
771
|
const data = await this.callAuth("/api/v1/auth-service/token/refresh", {
|
|
749
|
-
refreshToken:
|
|
772
|
+
refreshToken: current ?? refreshSnapshot
|
|
750
773
|
});
|
|
751
774
|
this.accessToken = data.tokens.accessToken;
|
|
752
775
|
this.accessExpiresAt = Date.now() + data.tokens.expiresIn * 1e3;
|
|
@@ -765,6 +788,45 @@ var Auth = class {
|
|
|
765
788
|
})();
|
|
766
789
|
return this.refreshInflight;
|
|
767
790
|
}
|
|
791
|
+
startProactiveRefreshCron() {
|
|
792
|
+
if (this.proactiveRefreshTimer) return;
|
|
793
|
+
const PROACTIVE_TARGET_PCT = 0.8;
|
|
794
|
+
const TICK_MS = 6e4;
|
|
795
|
+
const JITTER_MS = 3e4;
|
|
796
|
+
const tick = async () => {
|
|
797
|
+
if (!this.accessToken || !this.accessExpiresAt) return;
|
|
798
|
+
const now = Date.now();
|
|
799
|
+
const lifetimeMs = this.accessExpiresAt - now;
|
|
800
|
+
if (lifetimeMs <= 0) return;
|
|
801
|
+
const guessOriginalMs = Math.max(lifetimeMs, 5 * 6e4);
|
|
802
|
+
const fireWhenRemainingMs = guessOriginalMs * (1 - PROACTIVE_TARGET_PCT) + (Math.random() * JITTER_MS * 2 - JITTER_MS);
|
|
803
|
+
if (lifetimeMs <= fireWhenRemainingMs) {
|
|
804
|
+
await this.refreshAccessToken();
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
this.proactiveRefreshTimer = setInterval(() => {
|
|
808
|
+
void tick();
|
|
809
|
+
}, TICK_MS);
|
|
810
|
+
try {
|
|
811
|
+
const subscription = RnAppState?.addEventListener?.("change", (state) => {
|
|
812
|
+
if (state === "active") void tick();
|
|
813
|
+
});
|
|
814
|
+
if (subscription) {
|
|
815
|
+
this.proactiveRefreshAppStateUnsub = () => subscription.remove();
|
|
816
|
+
}
|
|
817
|
+
} catch {
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
stopProactiveRefreshCron() {
|
|
821
|
+
if (this.proactiveRefreshTimer) {
|
|
822
|
+
clearInterval(this.proactiveRefreshTimer);
|
|
823
|
+
this.proactiveRefreshTimer = null;
|
|
824
|
+
}
|
|
825
|
+
if (this.proactiveRefreshAppStateUnsub) {
|
|
826
|
+
this.proactiveRefreshAppStateUnsub();
|
|
827
|
+
this.proactiveRefreshAppStateUnsub = null;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
768
830
|
/**
|
|
769
831
|
* Returns the currently-stored refresh token (or null when signed out).
|
|
770
832
|
* Primary use: capture the anonymous refresh token BEFORE calling
|
|
@@ -866,6 +928,7 @@ var Auth = class {
|
|
|
866
928
|
this.hooks.storage.set(TOKEN_EXPIRES_KEY, String(this.accessExpiresAt));
|
|
867
929
|
this.hooks.storage.set(REFRESH_KEY, data.tokens.refreshToken);
|
|
868
930
|
this.hooks.onIdentityChange(data.user.id);
|
|
931
|
+
this.startProactiveRefreshCron();
|
|
869
932
|
}
|
|
870
933
|
async wipeLocalIdentity() {
|
|
871
934
|
this.user = null;
|
|
@@ -875,6 +938,7 @@ var Auth = class {
|
|
|
875
938
|
this.hooks.storage.remove(TOKEN_KEY);
|
|
876
939
|
this.hooks.storage.remove(TOKEN_EXPIRES_KEY);
|
|
877
940
|
this.hooks.storage.remove(REFRESH_KEY);
|
|
941
|
+
this.stopProactiveRefreshCron();
|
|
878
942
|
await this.hooks.onAnonymousWipe();
|
|
879
943
|
}
|
|
880
944
|
};
|
|
@@ -1466,7 +1530,7 @@ var Links = class {
|
|
|
1466
1530
|
};
|
|
1467
1531
|
|
|
1468
1532
|
// src/auto-track.ts
|
|
1469
|
-
import { AppState as
|
|
1533
|
+
import { AppState as RnAppState2 } from "react-native";
|
|
1470
1534
|
var DEFAULT_IDLE_MS = 30 * 60 * 1e3;
|
|
1471
1535
|
function resolveFlags(cfg) {
|
|
1472
1536
|
if (cfg === false) {
|
|
@@ -1510,7 +1574,7 @@ function installAutoTrack(args) {
|
|
|
1510
1574
|
args.fire("app.opened", { sessionId });
|
|
1511
1575
|
}
|
|
1512
1576
|
if (flags.appBackground) {
|
|
1513
|
-
const AppState =
|
|
1577
|
+
const AppState = RnAppState2;
|
|
1514
1578
|
if (AppState && typeof AppState.addEventListener === "function") {
|
|
1515
1579
|
const handler = (state) => {
|
|
1516
1580
|
ensureSession(true);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sendoracloud/sdk-react-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.1",
|
|
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",
|