@sendoracloud/sdk-react-native 0.11.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 +49 -3
- package/dist/index.d.cts +24 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +49 -10
- 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(".");
|
|
@@ -845,7 +867,9 @@ function installAutoTrack(args) {
|
|
|
845
867
|
if (flags.appBackground) {
|
|
846
868
|
let AppState = null;
|
|
847
869
|
try {
|
|
848
|
-
|
|
870
|
+
const dyn = globalThis.require;
|
|
871
|
+
const mod = dyn ? dyn("react-native") : null;
|
|
872
|
+
AppState = mod?.AppState ?? null;
|
|
849
873
|
} catch {
|
|
850
874
|
AppState = null;
|
|
851
875
|
}
|
|
@@ -1133,6 +1157,28 @@ var SendoraSDK = class {
|
|
|
1133
1157
|
}
|
|
1134
1158
|
return { tokenId: data.tokenId, created: data.created === true };
|
|
1135
1159
|
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Push namespace — mirrors the iOS / Android / Web SDK shape so
|
|
1162
|
+
* `sendora.push.registerToken(...)` works consistently across all 5 SDKs.
|
|
1163
|
+
* Backed by the same registerPushToken() implementation; kept top-level
|
|
1164
|
+
* too for backwards compatibility with the s47 release.
|
|
1165
|
+
*/
|
|
1166
|
+
get push() {
|
|
1167
|
+
const self = this;
|
|
1168
|
+
return {
|
|
1169
|
+
registerToken: (reg) => self.registerPushToken(reg),
|
|
1170
|
+
trackOpen: async (sendId, clickAction) => {
|
|
1171
|
+
if (!self.config) throw new Error("[sendoracloud] init() must complete before push.trackOpen().");
|
|
1172
|
+
const body = { sendId };
|
|
1173
|
+
if (clickAction !== void 0) body.clickAction = clickAction;
|
|
1174
|
+
await post(
|
|
1175
|
+
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
1176
|
+
"/push/track-open",
|
|
1177
|
+
body
|
|
1178
|
+
);
|
|
1179
|
+
}
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1136
1182
|
/**
|
|
1137
1183
|
* Clear identity + rotate the anonymous id. Call on logout. Awaits
|
|
1138
1184
|
* the AsyncStorage writes so a caller who kills the app immediately
|
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;
|
|
@@ -425,6 +439,16 @@ declare class SendoraSDK {
|
|
|
425
439
|
* `@react-native-firebase/messaging`.
|
|
426
440
|
*/
|
|
427
441
|
registerPushToken(reg: PushTokenRegistration): Promise<PushTokenReceipt>;
|
|
442
|
+
/**
|
|
443
|
+
* Push namespace — mirrors the iOS / Android / Web SDK shape so
|
|
444
|
+
* `sendora.push.registerToken(...)` works consistently across all 5 SDKs.
|
|
445
|
+
* Backed by the same registerPushToken() implementation; kept top-level
|
|
446
|
+
* too for backwards compatibility with the s47 release.
|
|
447
|
+
*/
|
|
448
|
+
get push(): {
|
|
449
|
+
registerToken: (reg: PushTokenRegistration) => Promise<PushTokenReceipt>;
|
|
450
|
+
trackOpen: (sendId: string, clickAction?: string) => Promise<void>;
|
|
451
|
+
};
|
|
428
452
|
/**
|
|
429
453
|
* Clear identity + rotate the anonymous id. Call on logout. Awaits
|
|
430
454
|
* the AsyncStorage writes so a caller who kills the app immediately
|
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;
|
|
@@ -425,6 +439,16 @@ declare class SendoraSDK {
|
|
|
425
439
|
* `@react-native-firebase/messaging`.
|
|
426
440
|
*/
|
|
427
441
|
registerPushToken(reg: PushTokenRegistration): Promise<PushTokenReceipt>;
|
|
442
|
+
/**
|
|
443
|
+
* Push namespace — mirrors the iOS / Android / Web SDK shape so
|
|
444
|
+
* `sendora.push.registerToken(...)` works consistently across all 5 SDKs.
|
|
445
|
+
* Backed by the same registerPushToken() implementation; kept top-level
|
|
446
|
+
* too for backwards compatibility with the s47 release.
|
|
447
|
+
*/
|
|
448
|
+
get push(): {
|
|
449
|
+
registerToken: (reg: PushTokenRegistration) => Promise<PushTokenReceipt>;
|
|
450
|
+
trackOpen: (sendId: string, clickAction?: string) => Promise<void>;
|
|
451
|
+
};
|
|
428
452
|
/**
|
|
429
453
|
* Clear identity + rotate the anonymous id. Call on logout. Awaits
|
|
430
454
|
* the AsyncStorage writes so a caller who kills the app immediately
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
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
|
-
|
|
8
1
|
// src/storage.ts
|
|
9
2
|
var PREFIX = "sendora_";
|
|
10
3
|
var asyncStoragePromise = null;
|
|
@@ -686,6 +679,20 @@ var Auth = class {
|
|
|
686
679
|
this.inflight = next.catch(() => void 0);
|
|
687
680
|
return next;
|
|
688
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
|
+
*/
|
|
689
696
|
async refreshAccessToken() {
|
|
690
697
|
if (this.refreshInflight) return this.refreshInflight;
|
|
691
698
|
const refresh = this.hooks.storage.get(REFRESH_KEY);
|
|
@@ -694,14 +701,18 @@ var Auth = class {
|
|
|
694
701
|
try {
|
|
695
702
|
const data = await this.callAuth("/api/v1/auth-service/token/refresh", {
|
|
696
703
|
refreshToken: refresh
|
|
697
|
-
})
|
|
698
|
-
if (!data) return null;
|
|
704
|
+
});
|
|
699
705
|
this.accessToken = data.tokens.accessToken;
|
|
700
706
|
this.accessExpiresAt = Date.now() + data.tokens.expiresIn * 1e3;
|
|
701
707
|
this.hooks.storage.set(TOKEN_KEY, data.tokens.accessToken);
|
|
702
708
|
this.hooks.storage.set(TOKEN_EXPIRES_KEY, String(this.accessExpiresAt));
|
|
703
709
|
this.hooks.storage.set(REFRESH_KEY, data.tokens.refreshToken);
|
|
704
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;
|
|
705
716
|
} finally {
|
|
706
717
|
this.refreshInflight = null;
|
|
707
718
|
}
|
|
@@ -752,6 +763,10 @@ var Auth = class {
|
|
|
752
763
|
await this.hooks.onAnonymousWipe();
|
|
753
764
|
}
|
|
754
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
|
+
}
|
|
755
770
|
function decodeJwtPayload(token) {
|
|
756
771
|
try {
|
|
757
772
|
const parts = token.split(".");
|
|
@@ -812,7 +827,9 @@ function installAutoTrack(args) {
|
|
|
812
827
|
if (flags.appBackground) {
|
|
813
828
|
let AppState = null;
|
|
814
829
|
try {
|
|
815
|
-
|
|
830
|
+
const dyn = globalThis.require;
|
|
831
|
+
const mod = dyn ? dyn("react-native") : null;
|
|
832
|
+
AppState = mod?.AppState ?? null;
|
|
816
833
|
} catch {
|
|
817
834
|
AppState = null;
|
|
818
835
|
}
|
|
@@ -1100,6 +1117,28 @@ var SendoraSDK = class {
|
|
|
1100
1117
|
}
|
|
1101
1118
|
return { tokenId: data.tokenId, created: data.created === true };
|
|
1102
1119
|
}
|
|
1120
|
+
/**
|
|
1121
|
+
* Push namespace — mirrors the iOS / Android / Web SDK shape so
|
|
1122
|
+
* `sendora.push.registerToken(...)` works consistently across all 5 SDKs.
|
|
1123
|
+
* Backed by the same registerPushToken() implementation; kept top-level
|
|
1124
|
+
* too for backwards compatibility with the s47 release.
|
|
1125
|
+
*/
|
|
1126
|
+
get push() {
|
|
1127
|
+
const self = this;
|
|
1128
|
+
return {
|
|
1129
|
+
registerToken: (reg) => self.registerPushToken(reg),
|
|
1130
|
+
trackOpen: async (sendId, clickAction) => {
|
|
1131
|
+
if (!self.config) throw new Error("[sendoracloud] init() must complete before push.trackOpen().");
|
|
1132
|
+
const body = { sendId };
|
|
1133
|
+
if (clickAction !== void 0) body.clickAction = clickAction;
|
|
1134
|
+
await post(
|
|
1135
|
+
{ apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
|
|
1136
|
+
"/push/track-open",
|
|
1137
|
+
body
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1103
1142
|
/**
|
|
1104
1143
|
* Clear identity + rotate the anonymous id. Call on logout. Awaits
|
|
1105
1144
|
* the AsyncStorage writes so a caller who kills the app immediately
|
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",
|