@sendoracloud/sdk-react-native 1.18.0 → 1.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 CHANGED
@@ -200,7 +200,7 @@ var Storage = class {
200
200
  // package.json
201
201
  var package_default = {
202
202
  name: "@sendoracloud/sdk-react-native",
203
- version: "1.18.0",
203
+ version: "1.20.0",
204
204
  description: "Sendora Cloud React Native + Expo SDK \u2014 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`).",
205
205
  type: "module",
206
206
  main: "./dist/index.cjs",
@@ -705,8 +705,10 @@ var Auth = class {
705
705
  if (stashed) prevAnonRefreshToken = stashed;
706
706
  }
707
707
  if (this.user !== null) await this.wipeLocalIdentity();
708
- const payload = { ...input };
708
+ const { link, ...socialInput } = input;
709
+ const payload = { ...socialInput };
709
710
  if (prevAnonRefreshToken) payload.prevAnonRefreshToken = prevAnonRefreshToken;
711
+ if (link) payload.linkAnonymous = true;
710
712
  const res = await post(
711
713
  { apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
712
714
  "/api/v1/auth-service/login/social",
@@ -729,8 +731,8 @@ var Auth = class {
729
731
  return parsed.data.user;
730
732
  });
731
733
  }
732
- signInWithGoogle(code, redirectUri) {
733
- return this.loginSocial({ provider: "google", code, redirectUri });
734
+ signInWithGoogle(code, redirectUri, link) {
735
+ return this.loginSocial({ provider: "google", code, redirectUri, link });
734
736
  }
735
737
  signInWithGitHub(code, redirectUri) {
736
738
  return this.loginSocial({ provider: "github", code, redirectUri });
@@ -2358,6 +2360,37 @@ var SendoraSDK = class {
2358
2360
  getUserId() {
2359
2361
  return this.userId;
2360
2362
  }
2363
+ /**
2364
+ * Non-throwing, synchronous read of the verified auth principal. Returns
2365
+ * the persisted `AuthUser` (`{ id, email, emailVerified, name, isAnonymous }`)
2366
+ * or `null` when no session is hydrated yet. Zero I/O — a pure in-memory read
2367
+ * of the session restored during `init()` — so it is safe on the very first
2368
+ * API call at cold start: await `init()` once (disk-only, no network), then
2369
+ * read this synchronously, even offline.
2370
+ *
2371
+ * `id` is the JWT `sub` — the durable, server-verified identity to key your
2372
+ * backend data on. Do NOT use `getAnonymousIdSync()` for that (it's a
2373
+ * rotating analytics id). Pair with `isReady()` to tell "still restoring"
2374
+ * apart from "signed out": `null` here while `isReady()` is `false` = init
2375
+ * hasn't finished; `null` while `isReady()` is `true` = genuinely no session.
2376
+ * `isAnonymous` is optional on the wire — test `=== false`, not truthiness.
2377
+ * The access token is re-verified server-side, so reading identity here
2378
+ * (client-untrusted) for UI/routing is safe; never use it as your own
2379
+ * authorization decision.
2380
+ */
2381
+ getUserSync() {
2382
+ return this.ready ? this.auth.getCurrentUser() : null;
2383
+ }
2384
+ /**
2385
+ * Non-throwing, synchronous readiness flag. `true` once `init()` has
2386
+ * resolved (storage hydrated + auth session restored). Gate a cold-start
2387
+ * identity read on this so you never mistake "still restoring the session"
2388
+ * for "signed out" — the loading gate every mobile auth SDK expects
2389
+ * (Firebase's init-null caveat, Clerk's `isLoaded`).
2390
+ */
2391
+ isReady() {
2392
+ return this.ready;
2393
+ }
2361
2394
  /** Toggle consent at runtime — when false, events drop instead of fire. */
2362
2395
  setConsent(granted) {
2363
2396
  this.consentGranted = granted;
package/dist/index.d.cts CHANGED
@@ -230,16 +230,15 @@ declare class Storage {
230
230
  * so a UI double-submit can't mint two anonymous users or interleave
231
231
  * a signIn + signOut.
232
232
  *
233
- * Tokens (including the refresh token) persist in AsyncStorage, not
234
- * the iOS Keychain / Android Keystore. AsyncStorage is unencrypted
235
- * app-sandbox storage: readable by anyone with filesystem/backup
236
- * access to a jailbroken/rooted or unlocked device, but isolated from
237
- * other apps by the OS sandbox. There is NO `secureStorage` adapter
238
- * option today earlier docs claimed a Keychain/SecureStore-backed
239
- * adapter that was never implemented. If you ship a follow-up that
240
- * moves the refresh token to expo-secure-store / react-native-keychain
241
- * (optional peer dep, Metro-resolvable static import never an inline
242
- * require), keep the `auth_refresh_token` storage key for back-compat.
233
+ * Tokens (including the refresh token) persist in AsyncStorage BY
234
+ * DEFAULT unencrypted app-sandbox storage: isolated from other apps
235
+ * by the OS sandbox, but readable with filesystem/backup access to a
236
+ * jailbroken/rooted or unlocked device (OWASP MASVS-L1). Since 1.18.0 an
237
+ * OPTIONAL `secureStorage` adapter (`Sendora.init({ secureStorage })`,
238
+ * wired in index.ts) routes the SECURE_KEYS (refresh/access/user) through
239
+ * an app-supplied Keychain / Keystore adapter, with transparent
240
+ * plaintext→secure migration; omit it and behaviour is unchanged. The
241
+ * `auth_refresh_token` storage key stays frozen for back-compat either way.
243
242
  *
244
243
  * Response payloads are validated non-empty before persisting so a
245
244
  * malformed/MITM'd response can't install an `id = ""` user.
@@ -478,8 +477,17 @@ declare class Auth {
478
477
  firstName?: string;
479
478
  lastName?: string;
480
479
  };
480
+ /**
481
+ * ADR-025 link-in-place opt-in. When this device is anonymous and you set
482
+ * `link: true`, an anon→social upgrade KEEPS the same user id (sub) — the
483
+ * anon account is promoted in place (like Firebase linkWithCredential)
484
+ * instead of a device-takeover that mints a new id. No effect when the
485
+ * device isn't anonymous, or when the social identity already belongs to
486
+ * another account (collision → falls back to takeover/merge).
487
+ */
488
+ link?: boolean;
481
489
  }): Promise<AuthUser>;
482
- signInWithGoogle(code: string, redirectUri: string): Promise<AuthUser>;
490
+ signInWithGoogle(code: string, redirectUri: string, link?: boolean): Promise<AuthUser>;
483
491
  signInWithGitHub(code: string, redirectUri: string): Promise<AuthUser>;
484
492
  signInWithApple(input: {
485
493
  idToken?: string;
@@ -489,6 +497,8 @@ declare class Auth {
489
497
  firstName?: string;
490
498
  lastName?: string;
491
499
  };
500
+ /** ADR-025: keep the same user id on an anon→Apple upgrade. See `loginSocial`. */
501
+ link?: boolean;
492
502
  }): Promise<AuthUser>;
493
503
  signInWithMicrosoft(code: string, redirectUri: string): Promise<AuthUser>;
494
504
  signInWithLinkedIn(code: string, redirectUri: string): Promise<AuthUser>;
@@ -985,6 +995,33 @@ declare class SendoraSDK {
985
995
  getAnonymousIdSync(): string | null;
986
996
  /** Current identified user id, if any. */
987
997
  getUserId(): string | null;
998
+ /**
999
+ * Non-throwing, synchronous read of the verified auth principal. Returns
1000
+ * the persisted `AuthUser` (`{ id, email, emailVerified, name, isAnonymous }`)
1001
+ * or `null` when no session is hydrated yet. Zero I/O — a pure in-memory read
1002
+ * of the session restored during `init()` — so it is safe on the very first
1003
+ * API call at cold start: await `init()` once (disk-only, no network), then
1004
+ * read this synchronously, even offline.
1005
+ *
1006
+ * `id` is the JWT `sub` — the durable, server-verified identity to key your
1007
+ * backend data on. Do NOT use `getAnonymousIdSync()` for that (it's a
1008
+ * rotating analytics id). Pair with `isReady()` to tell "still restoring"
1009
+ * apart from "signed out": `null` here while `isReady()` is `false` = init
1010
+ * hasn't finished; `null` while `isReady()` is `true` = genuinely no session.
1011
+ * `isAnonymous` is optional on the wire — test `=== false`, not truthiness.
1012
+ * The access token is re-verified server-side, so reading identity here
1013
+ * (client-untrusted) for UI/routing is safe; never use it as your own
1014
+ * authorization decision.
1015
+ */
1016
+ getUserSync(): AuthUser | null;
1017
+ /**
1018
+ * Non-throwing, synchronous readiness flag. `true` once `init()` has
1019
+ * resolved (storage hydrated + auth session restored). Gate a cold-start
1020
+ * identity read on this so you never mistake "still restoring the session"
1021
+ * for "signed out" — the loading gate every mobile auth SDK expects
1022
+ * (Firebase's init-null caveat, Clerk's `isLoaded`).
1023
+ */
1024
+ isReady(): boolean;
988
1025
  /** Toggle consent at runtime — when false, events drop instead of fire. */
989
1026
  setConsent(granted: boolean): void;
990
1027
  /** Associate a user id + traits with the current anonymous install. */
package/dist/index.d.ts CHANGED
@@ -230,16 +230,15 @@ declare class Storage {
230
230
  * so a UI double-submit can't mint two anonymous users or interleave
231
231
  * a signIn + signOut.
232
232
  *
233
- * Tokens (including the refresh token) persist in AsyncStorage, not
234
- * the iOS Keychain / Android Keystore. AsyncStorage is unencrypted
235
- * app-sandbox storage: readable by anyone with filesystem/backup
236
- * access to a jailbroken/rooted or unlocked device, but isolated from
237
- * other apps by the OS sandbox. There is NO `secureStorage` adapter
238
- * option today earlier docs claimed a Keychain/SecureStore-backed
239
- * adapter that was never implemented. If you ship a follow-up that
240
- * moves the refresh token to expo-secure-store / react-native-keychain
241
- * (optional peer dep, Metro-resolvable static import never an inline
242
- * require), keep the `auth_refresh_token` storage key for back-compat.
233
+ * Tokens (including the refresh token) persist in AsyncStorage BY
234
+ * DEFAULT unencrypted app-sandbox storage: isolated from other apps
235
+ * by the OS sandbox, but readable with filesystem/backup access to a
236
+ * jailbroken/rooted or unlocked device (OWASP MASVS-L1). Since 1.18.0 an
237
+ * OPTIONAL `secureStorage` adapter (`Sendora.init({ secureStorage })`,
238
+ * wired in index.ts) routes the SECURE_KEYS (refresh/access/user) through
239
+ * an app-supplied Keychain / Keystore adapter, with transparent
240
+ * plaintext→secure migration; omit it and behaviour is unchanged. The
241
+ * `auth_refresh_token` storage key stays frozen for back-compat either way.
243
242
  *
244
243
  * Response payloads are validated non-empty before persisting so a
245
244
  * malformed/MITM'd response can't install an `id = ""` user.
@@ -478,8 +477,17 @@ declare class Auth {
478
477
  firstName?: string;
479
478
  lastName?: string;
480
479
  };
480
+ /**
481
+ * ADR-025 link-in-place opt-in. When this device is anonymous and you set
482
+ * `link: true`, an anon→social upgrade KEEPS the same user id (sub) — the
483
+ * anon account is promoted in place (like Firebase linkWithCredential)
484
+ * instead of a device-takeover that mints a new id. No effect when the
485
+ * device isn't anonymous, or when the social identity already belongs to
486
+ * another account (collision → falls back to takeover/merge).
487
+ */
488
+ link?: boolean;
481
489
  }): Promise<AuthUser>;
482
- signInWithGoogle(code: string, redirectUri: string): Promise<AuthUser>;
490
+ signInWithGoogle(code: string, redirectUri: string, link?: boolean): Promise<AuthUser>;
483
491
  signInWithGitHub(code: string, redirectUri: string): Promise<AuthUser>;
484
492
  signInWithApple(input: {
485
493
  idToken?: string;
@@ -489,6 +497,8 @@ declare class Auth {
489
497
  firstName?: string;
490
498
  lastName?: string;
491
499
  };
500
+ /** ADR-025: keep the same user id on an anon→Apple upgrade. See `loginSocial`. */
501
+ link?: boolean;
492
502
  }): Promise<AuthUser>;
493
503
  signInWithMicrosoft(code: string, redirectUri: string): Promise<AuthUser>;
494
504
  signInWithLinkedIn(code: string, redirectUri: string): Promise<AuthUser>;
@@ -985,6 +995,33 @@ declare class SendoraSDK {
985
995
  getAnonymousIdSync(): string | null;
986
996
  /** Current identified user id, if any. */
987
997
  getUserId(): string | null;
998
+ /**
999
+ * Non-throwing, synchronous read of the verified auth principal. Returns
1000
+ * the persisted `AuthUser` (`{ id, email, emailVerified, name, isAnonymous }`)
1001
+ * or `null` when no session is hydrated yet. Zero I/O — a pure in-memory read
1002
+ * of the session restored during `init()` — so it is safe on the very first
1003
+ * API call at cold start: await `init()` once (disk-only, no network), then
1004
+ * read this synchronously, even offline.
1005
+ *
1006
+ * `id` is the JWT `sub` — the durable, server-verified identity to key your
1007
+ * backend data on. Do NOT use `getAnonymousIdSync()` for that (it's a
1008
+ * rotating analytics id). Pair with `isReady()` to tell "still restoring"
1009
+ * apart from "signed out": `null` here while `isReady()` is `false` = init
1010
+ * hasn't finished; `null` while `isReady()` is `true` = genuinely no session.
1011
+ * `isAnonymous` is optional on the wire — test `=== false`, not truthiness.
1012
+ * The access token is re-verified server-side, so reading identity here
1013
+ * (client-untrusted) for UI/routing is safe; never use it as your own
1014
+ * authorization decision.
1015
+ */
1016
+ getUserSync(): AuthUser | null;
1017
+ /**
1018
+ * Non-throwing, synchronous readiness flag. `true` once `init()` has
1019
+ * resolved (storage hydrated + auth session restored). Gate a cold-start
1020
+ * identity read on this so you never mistake "still restoring the session"
1021
+ * for "signed out" — the loading gate every mobile auth SDK expects
1022
+ * (Firebase's init-null caveat, Clerk's `isLoaded`).
1023
+ */
1024
+ isReady(): boolean;
988
1025
  /** Toggle consent at runtime — when false, events drop instead of fire. */
989
1026
  setConsent(granted: boolean): void;
990
1027
  /** Associate a user id + traits with the current anonymous install. */
package/dist/index.js CHANGED
@@ -157,7 +157,7 @@ var Storage = class {
157
157
  // package.json
158
158
  var package_default = {
159
159
  name: "@sendoracloud/sdk-react-native",
160
- version: "1.18.0",
160
+ version: "1.20.0",
161
161
  description: "Sendora Cloud React Native + Expo SDK \u2014 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`).",
162
162
  type: "module",
163
163
  main: "./dist/index.cjs",
@@ -662,8 +662,10 @@ var Auth = class {
662
662
  if (stashed) prevAnonRefreshToken = stashed;
663
663
  }
664
664
  if (this.user !== null) await this.wipeLocalIdentity();
665
- const payload = { ...input };
665
+ const { link, ...socialInput } = input;
666
+ const payload = { ...socialInput };
666
667
  if (prevAnonRefreshToken) payload.prevAnonRefreshToken = prevAnonRefreshToken;
668
+ if (link) payload.linkAnonymous = true;
667
669
  const res = await post(
668
670
  { apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
669
671
  "/api/v1/auth-service/login/social",
@@ -686,8 +688,8 @@ var Auth = class {
686
688
  return parsed.data.user;
687
689
  });
688
690
  }
689
- signInWithGoogle(code, redirectUri) {
690
- return this.loginSocial({ provider: "google", code, redirectUri });
691
+ signInWithGoogle(code, redirectUri, link) {
692
+ return this.loginSocial({ provider: "google", code, redirectUri, link });
691
693
  }
692
694
  signInWithGitHub(code, redirectUri) {
693
695
  return this.loginSocial({ provider: "github", code, redirectUri });
@@ -2320,6 +2322,37 @@ var SendoraSDK = class {
2320
2322
  getUserId() {
2321
2323
  return this.userId;
2322
2324
  }
2325
+ /**
2326
+ * Non-throwing, synchronous read of the verified auth principal. Returns
2327
+ * the persisted `AuthUser` (`{ id, email, emailVerified, name, isAnonymous }`)
2328
+ * or `null` when no session is hydrated yet. Zero I/O — a pure in-memory read
2329
+ * of the session restored during `init()` — so it is safe on the very first
2330
+ * API call at cold start: await `init()` once (disk-only, no network), then
2331
+ * read this synchronously, even offline.
2332
+ *
2333
+ * `id` is the JWT `sub` — the durable, server-verified identity to key your
2334
+ * backend data on. Do NOT use `getAnonymousIdSync()` for that (it's a
2335
+ * rotating analytics id). Pair with `isReady()` to tell "still restoring"
2336
+ * apart from "signed out": `null` here while `isReady()` is `false` = init
2337
+ * hasn't finished; `null` while `isReady()` is `true` = genuinely no session.
2338
+ * `isAnonymous` is optional on the wire — test `=== false`, not truthiness.
2339
+ * The access token is re-verified server-side, so reading identity here
2340
+ * (client-untrusted) for UI/routing is safe; never use it as your own
2341
+ * authorization decision.
2342
+ */
2343
+ getUserSync() {
2344
+ return this.ready ? this.auth.getCurrentUser() : null;
2345
+ }
2346
+ /**
2347
+ * Non-throwing, synchronous readiness flag. `true` once `init()` has
2348
+ * resolved (storage hydrated + auth session restored). Gate a cold-start
2349
+ * identity read on this so you never mistake "still restoring the session"
2350
+ * for "signed out" — the loading gate every mobile auth SDK expects
2351
+ * (Firebase's init-null caveat, Clerk's `isLoaded`).
2352
+ */
2353
+ isReady() {
2354
+ return this.ready;
2355
+ }
2323
2356
  /** Toggle consent at runtime — when false, events drop instead of fire. */
2324
2357
  setConsent(granted) {
2325
2358
  this.consentGranted = granted;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sendoracloud/sdk-react-native",
3
- "version": "1.18.0",
3
+ "version": "1.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",