@sendoracloud/sdk-react-native 0.17.0 → 0.17.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/README.md +34 -1
- package/dist/index.cjs +78 -9
- package/dist/index.d.cts +32 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +83 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -88,7 +88,7 @@ await SendoraCloud.reset();
|
|
|
88
88
|
| `track(event, props?)` | Fire a custom product event. |
|
|
89
89
|
| `screen(name, props?)` | Track a screen view. |
|
|
90
90
|
| `registerPushToken(reg)` | Register an APNs or FCM push token. |
|
|
91
|
-
| `auth.*` | Sign-in / sign-up / MFA / passkeys / OIDC / SSO. See `auth.ts`. |
|
|
91
|
+
| `auth.*` | Sign-in / sign-up / MFA / passkeys / OIDC / SSO + `auth.getRefreshToken()` + `auth.merge(anonRefresh, targetRefresh?)`. See `auth.ts`. |
|
|
92
92
|
| `links.create<T>(input, opts?)` | Mint a Sendora deep link. Generic `T` for typed `linkData`. |
|
|
93
93
|
| `links.prewarm<T>(input, opts?)` | Background-mint + cache so share-tap is instant. |
|
|
94
94
|
| `links.handleUniversalLink(url)` | Resolve a warm-path Universal Link / App Link delivery. |
|
|
@@ -183,6 +183,39 @@ Full paste-ready recipes live in [`examples/links-share.tsx`](./examples/links-s
|
|
|
183
183
|
|
|
184
184
|
`SendoraCloud.auth` covers anonymous sign-in, email + password, magic link, email OTP, TOTP MFA, recovery codes, OIDC / SAML SSO, Sign in with Apple, Google, Microsoft, LinkedIn, Facebook, Discord. See [auth quickstart](https://sendoracloud.com/docs/quickstart-react-native#auth).
|
|
185
185
|
|
|
186
|
+
### Anonymous → identified lifecycle
|
|
187
|
+
|
|
188
|
+
The SDK creates an anonymous user on first `init()`. Every `track()` / `identify()` attaches to that `user_id` until the user signs up or signs in. Two transitions exist; both preserve analytics continuity, but the second one requires one extra line of code from your app:
|
|
189
|
+
|
|
190
|
+
**Case 1 — fresh signup.** `auth.signUp(email, password)` detects the anonymous refresh token and calls `/auth-service/upgrade` first, which flips `is_anonymous=false` + sets the password on the SAME row. The `user_id` is preserved — no funnel break. Falls back to a fresh `/signup` if the bound user is already identified (rare race).
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
const { user } = await SendoraCloud.auth.signUp(email, password);
|
|
194
|
+
// user.id === the same id the anon user had. Done.
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Case 2 — email already exists.** `signUp()` throws `EmailAlreadyTakenError` without consuming the anon refresh. You fall back to `signIn()` — but `signIn()` wipes the local anon identity, so anon events stay orphaned on the backend unless you transfer them with `auth.merge(anonRefresh)`. Capture the anon refresh BEFORE the signIn:
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
try {
|
|
201
|
+
const { user } = await SendoraCloud.auth.signUp(email, password);
|
|
202
|
+
} catch (err) {
|
|
203
|
+
if (!(err instanceof EmailAlreadyTakenError)) throw err;
|
|
204
|
+
|
|
205
|
+
const anonRefresh = await SendoraCloud.auth.getRefreshToken();
|
|
206
|
+
const { user } = await SendoraCloud.auth.signIn(email, password);
|
|
207
|
+
|
|
208
|
+
// Best-effort, silent-fail-tolerant — anon row may be already drained.
|
|
209
|
+
if (anonRefresh) {
|
|
210
|
+
SendoraCloud.auth.merge(anonRefresh).catch(() => undefined);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**Why merge isn't automatic on signIn.** Two people sharing a device (browser kiosk, family iPad) would silently get their anon-session events combined with a third party's real account. Forcing the merge call into your app keeps the data-mixing decision explicit. Wrap `signIn()` with the snippet above if you want auto behaviour.
|
|
216
|
+
|
|
217
|
+
Full reference + edge-case writeup: [/docs/quickstart-react-native#anon-existing-email](https://sendoracloud.com/docs/quickstart-react-native#anon-existing-email).
|
|
218
|
+
|
|
186
219
|
## Config
|
|
187
220
|
|
|
188
221
|
```ts
|
package/dist/index.cjs
CHANGED
|
@@ -785,6 +785,75 @@ var Auth = class {
|
|
|
785
785
|
})();
|
|
786
786
|
return this.refreshInflight;
|
|
787
787
|
}
|
|
788
|
+
/**
|
|
789
|
+
* Returns the currently-stored refresh token (or null when signed out).
|
|
790
|
+
* Primary use: capture the anonymous refresh token BEFORE calling
|
|
791
|
+
* `signIn()` so it can be transferred via `merge()` after the existing
|
|
792
|
+
* account is authenticated. See the "Anonymous → identified" section
|
|
793
|
+
* of the SDK README + /docs/quickstart-react-native#anon-existing-email.
|
|
794
|
+
*/
|
|
795
|
+
getRefreshToken() {
|
|
796
|
+
return this.hooks.storage.get(REFRESH_KEY) ?? null;
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Transfer every event + trait from an anonymous user to the
|
|
800
|
+
* currently-signed-in (target) user, then delete the anonymous row.
|
|
801
|
+
* `targetRefreshToken` defaults to the SDK's currently-stored refresh
|
|
802
|
+
* token — call this AFTER a successful `signIn()` so the storage
|
|
803
|
+
* already holds the target's refresh.
|
|
804
|
+
*
|
|
805
|
+
* Throws AuthError when:
|
|
806
|
+
* - `anonymousRefreshToken` is invalid / expired / revoked,
|
|
807
|
+
* - the bound source row is not anonymous (CONFLICT — use upgrade),
|
|
808
|
+
* - the target is anonymous (CONFLICT — sign in first),
|
|
809
|
+
* - both tokens resolve to the same user (CONFLICT — no-op).
|
|
810
|
+
*
|
|
811
|
+
* Mirrors Auth0 `accounts.link` + Firebase manual transfer semantics.
|
|
812
|
+
*/
|
|
813
|
+
async merge(anonymousRefreshToken, targetRefreshToken) {
|
|
814
|
+
const target = targetRefreshToken ?? this.getRefreshToken();
|
|
815
|
+
if (!target) {
|
|
816
|
+
throw new AuthError(
|
|
817
|
+
"NO_TARGET_REFRESH",
|
|
818
|
+
"merge() needs a target refresh token. Call signIn() first or pass targetRefreshToken explicitly."
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
const res = await post(
|
|
822
|
+
{ apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
|
|
823
|
+
"/api/v1/auth-service/merge",
|
|
824
|
+
{ anonymousRefreshToken, targetRefreshToken: target }
|
|
825
|
+
);
|
|
826
|
+
if (!res) throw new AuthError("NETWORK_ERROR", "Network request failed");
|
|
827
|
+
let parsed;
|
|
828
|
+
try {
|
|
829
|
+
parsed = await res.json();
|
|
830
|
+
} catch {
|
|
831
|
+
throw new AuthError("PARSE_ERROR", `HTTP ${res.status}`);
|
|
832
|
+
}
|
|
833
|
+
if (!res.ok || !parsed.success || !parsed.data) {
|
|
834
|
+
throw new AuthError(
|
|
835
|
+
parsed.error?.code ?? `HTTP_${res.status}`,
|
|
836
|
+
parsed.error?.message ?? `HTTP ${res.status}`
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
const t = parsed.data.tokens;
|
|
840
|
+
if (t?.accessToken && t.refreshToken && typeof t.expiresIn === "number") {
|
|
841
|
+
this.accessToken = t.accessToken;
|
|
842
|
+
this.accessExpiresAt = Date.now() + t.expiresIn * 1e3;
|
|
843
|
+
this.hooks.storage.set(TOKEN_KEY, t.accessToken);
|
|
844
|
+
this.hooks.storage.set(TOKEN_EXPIRES_KEY, String(this.accessExpiresAt));
|
|
845
|
+
this.hooks.storage.set(REFRESH_KEY, t.refreshToken);
|
|
846
|
+
}
|
|
847
|
+
return {
|
|
848
|
+
mergedUserId: parsed.data.mergedUserId ?? "",
|
|
849
|
+
deletedAnonymousId: parsed.data.deletedAnonymousId ?? "",
|
|
850
|
+
eventsReassigned: parsed.data.eventsReassigned ?? 0,
|
|
851
|
+
profilesReassigned: parsed.data.profilesReassigned ?? 0,
|
|
852
|
+
profilesDropped: parsed.data.profilesDropped ?? 0,
|
|
853
|
+
entitiesReassigned: parsed.data.entitiesReassigned ?? 0,
|
|
854
|
+
entitiesDropped: parsed.data.entitiesDropped ?? 0
|
|
855
|
+
};
|
|
856
|
+
}
|
|
788
857
|
async callAuth(path, body) {
|
|
789
858
|
const res = await post(
|
|
790
859
|
{ apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
|
|
@@ -848,6 +917,7 @@ function decodeJwtPayload(token) {
|
|
|
848
917
|
}
|
|
849
918
|
|
|
850
919
|
// src/links.ts
|
|
920
|
+
var import_react_native = require("react-native");
|
|
851
921
|
var LinkError = class extends Error {
|
|
852
922
|
constructor(code, message, statusCode = 0, details) {
|
|
853
923
|
super(message);
|
|
@@ -925,7 +995,12 @@ function dynRequire(id) {
|
|
|
925
995
|
}
|
|
926
996
|
}
|
|
927
997
|
function loadRn() {
|
|
928
|
-
return
|
|
998
|
+
return {
|
|
999
|
+
Platform: import_react_native.Platform,
|
|
1000
|
+
Dimensions: import_react_native.Dimensions,
|
|
1001
|
+
NativeModules: import_react_native.NativeModules,
|
|
1002
|
+
Linking: import_react_native.Linking
|
|
1003
|
+
};
|
|
929
1004
|
}
|
|
930
1005
|
function readTimezone() {
|
|
931
1006
|
try {
|
|
@@ -1406,6 +1481,7 @@ var Links = class {
|
|
|
1406
1481
|
};
|
|
1407
1482
|
|
|
1408
1483
|
// src/auto-track.ts
|
|
1484
|
+
var import_react_native2 = require("react-native");
|
|
1409
1485
|
var DEFAULT_IDLE_MS = 30 * 60 * 1e3;
|
|
1410
1486
|
function resolveFlags(cfg) {
|
|
1411
1487
|
if (cfg === false) {
|
|
@@ -1449,14 +1525,7 @@ function installAutoTrack(args) {
|
|
|
1449
1525
|
args.fire("app.opened", { sessionId });
|
|
1450
1526
|
}
|
|
1451
1527
|
if (flags.appBackground) {
|
|
1452
|
-
|
|
1453
|
-
try {
|
|
1454
|
-
const dyn = globalThis.require;
|
|
1455
|
-
const mod = dyn ? dyn("react-native") : null;
|
|
1456
|
-
AppState = mod?.AppState ?? null;
|
|
1457
|
-
} catch {
|
|
1458
|
-
AppState = null;
|
|
1459
|
-
}
|
|
1528
|
+
const AppState = import_react_native2.AppState;
|
|
1460
1529
|
if (AppState && typeof AppState.addEventListener === "function") {
|
|
1461
1530
|
const handler = (state) => {
|
|
1462
1531
|
ensureSession(true);
|
package/dist/index.d.cts
CHANGED
|
@@ -299,6 +299,38 @@ declare class Auth {
|
|
|
299
299
|
* surface NOT_SIGNED_IN to the host app instead of looping.
|
|
300
300
|
*/
|
|
301
301
|
private refreshAccessToken;
|
|
302
|
+
/**
|
|
303
|
+
* Returns the currently-stored refresh token (or null when signed out).
|
|
304
|
+
* Primary use: capture the anonymous refresh token BEFORE calling
|
|
305
|
+
* `signIn()` so it can be transferred via `merge()` after the existing
|
|
306
|
+
* account is authenticated. See the "Anonymous → identified" section
|
|
307
|
+
* of the SDK README + /docs/quickstart-react-native#anon-existing-email.
|
|
308
|
+
*/
|
|
309
|
+
getRefreshToken(): string | null;
|
|
310
|
+
/**
|
|
311
|
+
* Transfer every event + trait from an anonymous user to the
|
|
312
|
+
* currently-signed-in (target) user, then delete the anonymous row.
|
|
313
|
+
* `targetRefreshToken` defaults to the SDK's currently-stored refresh
|
|
314
|
+
* token — call this AFTER a successful `signIn()` so the storage
|
|
315
|
+
* already holds the target's refresh.
|
|
316
|
+
*
|
|
317
|
+
* Throws AuthError when:
|
|
318
|
+
* - `anonymousRefreshToken` is invalid / expired / revoked,
|
|
319
|
+
* - the bound source row is not anonymous (CONFLICT — use upgrade),
|
|
320
|
+
* - the target is anonymous (CONFLICT — sign in first),
|
|
321
|
+
* - both tokens resolve to the same user (CONFLICT — no-op).
|
|
322
|
+
*
|
|
323
|
+
* Mirrors Auth0 `accounts.link` + Firebase manual transfer semantics.
|
|
324
|
+
*/
|
|
325
|
+
merge(anonymousRefreshToken: string, targetRefreshToken?: string): Promise<{
|
|
326
|
+
mergedUserId: string;
|
|
327
|
+
deletedAnonymousId: string;
|
|
328
|
+
eventsReassigned: number;
|
|
329
|
+
profilesReassigned: number;
|
|
330
|
+
profilesDropped: number;
|
|
331
|
+
entitiesReassigned: number;
|
|
332
|
+
entitiesDropped: number;
|
|
333
|
+
}>;
|
|
302
334
|
private callAuth;
|
|
303
335
|
private persist;
|
|
304
336
|
private wipeLocalIdentity;
|
package/dist/index.d.ts
CHANGED
|
@@ -299,6 +299,38 @@ declare class Auth {
|
|
|
299
299
|
* surface NOT_SIGNED_IN to the host app instead of looping.
|
|
300
300
|
*/
|
|
301
301
|
private refreshAccessToken;
|
|
302
|
+
/**
|
|
303
|
+
* Returns the currently-stored refresh token (or null when signed out).
|
|
304
|
+
* Primary use: capture the anonymous refresh token BEFORE calling
|
|
305
|
+
* `signIn()` so it can be transferred via `merge()` after the existing
|
|
306
|
+
* account is authenticated. See the "Anonymous → identified" section
|
|
307
|
+
* of the SDK README + /docs/quickstart-react-native#anon-existing-email.
|
|
308
|
+
*/
|
|
309
|
+
getRefreshToken(): string | null;
|
|
310
|
+
/**
|
|
311
|
+
* Transfer every event + trait from an anonymous user to the
|
|
312
|
+
* currently-signed-in (target) user, then delete the anonymous row.
|
|
313
|
+
* `targetRefreshToken` defaults to the SDK's currently-stored refresh
|
|
314
|
+
* token — call this AFTER a successful `signIn()` so the storage
|
|
315
|
+
* already holds the target's refresh.
|
|
316
|
+
*
|
|
317
|
+
* Throws AuthError when:
|
|
318
|
+
* - `anonymousRefreshToken` is invalid / expired / revoked,
|
|
319
|
+
* - the bound source row is not anonymous (CONFLICT — use upgrade),
|
|
320
|
+
* - the target is anonymous (CONFLICT — sign in first),
|
|
321
|
+
* - both tokens resolve to the same user (CONFLICT — no-op).
|
|
322
|
+
*
|
|
323
|
+
* Mirrors Auth0 `accounts.link` + Firebase manual transfer semantics.
|
|
324
|
+
*/
|
|
325
|
+
merge(anonymousRefreshToken: string, targetRefreshToken?: string): Promise<{
|
|
326
|
+
mergedUserId: string;
|
|
327
|
+
deletedAnonymousId: string;
|
|
328
|
+
eventsReassigned: number;
|
|
329
|
+
profilesReassigned: number;
|
|
330
|
+
profilesDropped: number;
|
|
331
|
+
entitiesReassigned: number;
|
|
332
|
+
entitiesDropped: number;
|
|
333
|
+
}>;
|
|
302
334
|
private callAuth;
|
|
303
335
|
private persist;
|
|
304
336
|
private wipeLocalIdentity;
|
package/dist/index.js
CHANGED
|
@@ -742,6 +742,75 @@ var Auth = class {
|
|
|
742
742
|
})();
|
|
743
743
|
return this.refreshInflight;
|
|
744
744
|
}
|
|
745
|
+
/**
|
|
746
|
+
* Returns the currently-stored refresh token (or null when signed out).
|
|
747
|
+
* Primary use: capture the anonymous refresh token BEFORE calling
|
|
748
|
+
* `signIn()` so it can be transferred via `merge()` after the existing
|
|
749
|
+
* account is authenticated. See the "Anonymous → identified" section
|
|
750
|
+
* of the SDK README + /docs/quickstart-react-native#anon-existing-email.
|
|
751
|
+
*/
|
|
752
|
+
getRefreshToken() {
|
|
753
|
+
return this.hooks.storage.get(REFRESH_KEY) ?? null;
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* Transfer every event + trait from an anonymous user to the
|
|
757
|
+
* currently-signed-in (target) user, then delete the anonymous row.
|
|
758
|
+
* `targetRefreshToken` defaults to the SDK's currently-stored refresh
|
|
759
|
+
* token — call this AFTER a successful `signIn()` so the storage
|
|
760
|
+
* already holds the target's refresh.
|
|
761
|
+
*
|
|
762
|
+
* Throws AuthError when:
|
|
763
|
+
* - `anonymousRefreshToken` is invalid / expired / revoked,
|
|
764
|
+
* - the bound source row is not anonymous (CONFLICT — use upgrade),
|
|
765
|
+
* - the target is anonymous (CONFLICT — sign in first),
|
|
766
|
+
* - both tokens resolve to the same user (CONFLICT — no-op).
|
|
767
|
+
*
|
|
768
|
+
* Mirrors Auth0 `accounts.link` + Firebase manual transfer semantics.
|
|
769
|
+
*/
|
|
770
|
+
async merge(anonymousRefreshToken, targetRefreshToken) {
|
|
771
|
+
const target = targetRefreshToken ?? this.getRefreshToken();
|
|
772
|
+
if (!target) {
|
|
773
|
+
throw new AuthError(
|
|
774
|
+
"NO_TARGET_REFRESH",
|
|
775
|
+
"merge() needs a target refresh token. Call signIn() first or pass targetRefreshToken explicitly."
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
const res = await post(
|
|
779
|
+
{ apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
|
|
780
|
+
"/api/v1/auth-service/merge",
|
|
781
|
+
{ anonymousRefreshToken, targetRefreshToken: target }
|
|
782
|
+
);
|
|
783
|
+
if (!res) throw new AuthError("NETWORK_ERROR", "Network request failed");
|
|
784
|
+
let parsed;
|
|
785
|
+
try {
|
|
786
|
+
parsed = await res.json();
|
|
787
|
+
} catch {
|
|
788
|
+
throw new AuthError("PARSE_ERROR", `HTTP ${res.status}`);
|
|
789
|
+
}
|
|
790
|
+
if (!res.ok || !parsed.success || !parsed.data) {
|
|
791
|
+
throw new AuthError(
|
|
792
|
+
parsed.error?.code ?? `HTTP_${res.status}`,
|
|
793
|
+
parsed.error?.message ?? `HTTP ${res.status}`
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
const t = parsed.data.tokens;
|
|
797
|
+
if (t?.accessToken && t.refreshToken && typeof t.expiresIn === "number") {
|
|
798
|
+
this.accessToken = t.accessToken;
|
|
799
|
+
this.accessExpiresAt = Date.now() + t.expiresIn * 1e3;
|
|
800
|
+
this.hooks.storage.set(TOKEN_KEY, t.accessToken);
|
|
801
|
+
this.hooks.storage.set(TOKEN_EXPIRES_KEY, String(this.accessExpiresAt));
|
|
802
|
+
this.hooks.storage.set(REFRESH_KEY, t.refreshToken);
|
|
803
|
+
}
|
|
804
|
+
return {
|
|
805
|
+
mergedUserId: parsed.data.mergedUserId ?? "",
|
|
806
|
+
deletedAnonymousId: parsed.data.deletedAnonymousId ?? "",
|
|
807
|
+
eventsReassigned: parsed.data.eventsReassigned ?? 0,
|
|
808
|
+
profilesReassigned: parsed.data.profilesReassigned ?? 0,
|
|
809
|
+
profilesDropped: parsed.data.profilesDropped ?? 0,
|
|
810
|
+
entitiesReassigned: parsed.data.entitiesReassigned ?? 0,
|
|
811
|
+
entitiesDropped: parsed.data.entitiesDropped ?? 0
|
|
812
|
+
};
|
|
813
|
+
}
|
|
745
814
|
async callAuth(path, body) {
|
|
746
815
|
const res = await post(
|
|
747
816
|
{ apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
|
|
@@ -805,6 +874,12 @@ function decodeJwtPayload(token) {
|
|
|
805
874
|
}
|
|
806
875
|
|
|
807
876
|
// src/links.ts
|
|
877
|
+
import {
|
|
878
|
+
Dimensions as RnDimensions,
|
|
879
|
+
Linking as RnLinking,
|
|
880
|
+
NativeModules as RnNativeModules,
|
|
881
|
+
Platform as RnPlatform
|
|
882
|
+
} from "react-native";
|
|
808
883
|
var LinkError = class extends Error {
|
|
809
884
|
constructor(code, message, statusCode = 0, details) {
|
|
810
885
|
super(message);
|
|
@@ -882,7 +957,12 @@ function dynRequire(id) {
|
|
|
882
957
|
}
|
|
883
958
|
}
|
|
884
959
|
function loadRn() {
|
|
885
|
-
return
|
|
960
|
+
return {
|
|
961
|
+
Platform: RnPlatform,
|
|
962
|
+
Dimensions: RnDimensions,
|
|
963
|
+
NativeModules: RnNativeModules,
|
|
964
|
+
Linking: RnLinking
|
|
965
|
+
};
|
|
886
966
|
}
|
|
887
967
|
function readTimezone() {
|
|
888
968
|
try {
|
|
@@ -1363,6 +1443,7 @@ var Links = class {
|
|
|
1363
1443
|
};
|
|
1364
1444
|
|
|
1365
1445
|
// src/auto-track.ts
|
|
1446
|
+
import { AppState as RnAppState } from "react-native";
|
|
1366
1447
|
var DEFAULT_IDLE_MS = 30 * 60 * 1e3;
|
|
1367
1448
|
function resolveFlags(cfg) {
|
|
1368
1449
|
if (cfg === false) {
|
|
@@ -1406,14 +1487,7 @@ function installAutoTrack(args) {
|
|
|
1406
1487
|
args.fire("app.opened", { sessionId });
|
|
1407
1488
|
}
|
|
1408
1489
|
if (flags.appBackground) {
|
|
1409
|
-
|
|
1410
|
-
try {
|
|
1411
|
-
const dyn = globalThis.require;
|
|
1412
|
-
const mod = dyn ? dyn("react-native") : null;
|
|
1413
|
-
AppState = mod?.AppState ?? null;
|
|
1414
|
-
} catch {
|
|
1415
|
-
AppState = null;
|
|
1416
|
-
}
|
|
1490
|
+
const AppState = RnAppState;
|
|
1417
1491
|
if (AppState && typeof AppState.addEventListener === "function") {
|
|
1418
1492
|
const handler = (state) => {
|
|
1419
1493
|
ensureSession(true);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sendoracloud/sdk-react-native",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.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",
|