@sendoracloud/sdk-react-native 1.41.1 → 1.41.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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.41.2 — claiming or restoring a held guest works on a real phone
4
+
5
+ **Fix (reported by TapMap: `merge()` / `claimHeldGuest()` returned 401 on an iPhone after every sign-in).** Before the server fix (e09c86dc, already live), `/token/refresh` preferred the `sd_rt` refresh cookie over the token in the request body. A phone keeps a native cookie jar, and after a sign-in that jar holds the ACCOUNT's cookie — so refreshing a HELD guest's token was answered for the account, and the SDK wrote the account's token into the guest's slot. The device lost its only copy of the guest's credential; `claimHeldGuest()` / `merge()` then failed with 401 and `restoreHeldGuest()` released the guest. The guest's account was never touched on the server.
6
+
7
+ - **No cookies.** Every SDK request now sends `credentials: "omit"`, so React Native's native cookie jar is neither read nor written (iOS `HTTPShouldHandleCookies = NO`, Android `CookieJar.NO_COOKIES`). The SDK never used cookies — it authenticates with the API key header and tokens in the body.
8
+ - **Identity check on a held refresh.** A held guest's refresh must come back for that guest. A response for anyone else now throws `HELD_GUEST_IDENTITY_MISMATCH` and changes nothing: the slot keeps the guest's own token, no `/merge`, nothing installed, nothing released.
9
+ - No action needed for apps on 1.41.0 / 1.41.1: the server fix already covers them. Upgrade for the belt-and-braces. Same fix in web 3.24.2 / iOS 5.4.2 / Android 4.26.2.
10
+
3
11
  ## 1.41.1 — a dead session is `session_expired` again; the server's detail moves to `endReason`
4
12
 
5
13
  **Fix to a 1.41.0 behaviour change (ADR-023: a new value is additive only for a NEW situation).**
package/dist/index.cjs CHANGED
@@ -263,7 +263,7 @@ var Storage = class {
263
263
  // package.json
264
264
  var package_default = {
265
265
  name: "@sendoracloud/sdk-react-native",
266
- version: "1.41.1",
266
+ version: "1.41.2",
267
267
  description: "Sendora Cloud React Native + Expo SDK \u2014 analytics, identity, push-token registration, auth (incl. Game Center / Play Games), deep links (Branch / Firebase Dynamic Links parity). Auth + analytics + deep links work in Expo Go; push-token registration and the no-arg Game Center / Play Games sign-in require a Dev Client (EAS Build or `npx expo prebuild`).",
268
268
  type: "module",
269
269
  main: "./dist/index.cjs",
@@ -320,7 +320,7 @@ var package_default = {
320
320
  scripts: {
321
321
  build: "tsup src/index.ts src/contact-widget.tsx --format esm,cjs --dts --clean --external react --external react-native --external react-native-webview",
322
322
  typecheck: "tsc --noEmit",
323
- test: "tsx test/secure-storage.test.ts && tsx test/integration-storage.test.ts && tsx test/plugin.test.ts && tsx test/game-auth.test.ts && tsx test/link.test.ts && tsx test/session-preservation.test.ts && tsx test/credential-collision.test.ts && tsx test/token-expiry.test.ts && tsx test/unlink.test.ts && tsx test/deferred-match.test.ts && tsx test/anon-id-stability.test.ts && tsx test/event-queue.test.ts && tsx test/anonymous-over-account.test.ts && tsx test/auth-machine-conformance.test.ts && tsx test/held-guests.test.ts"
323
+ test: "tsx test/secure-storage.test.ts && tsx test/integration-storage.test.ts && tsx test/plugin.test.ts && tsx test/game-auth.test.ts && tsx test/link.test.ts && tsx test/session-preservation.test.ts && tsx test/credential-collision.test.ts && tsx test/token-expiry.test.ts && tsx test/unlink.test.ts && tsx test/deferred-match.test.ts && tsx test/anon-id-stability.test.ts && tsx test/event-queue.test.ts && tsx test/anonymous-over-account.test.ts && tsx test/auth-machine-conformance.test.ts && tsx test/held-guests.test.ts && tsx test/held-identity.test.ts"
324
324
  },
325
325
  devDependencies: {
326
326
  "@types/react": "^19.2.16",
@@ -391,7 +391,18 @@ async function request(opts, method, path, body, extraHeaders, reqOpts) {
391
391
  ...extraHeaders ?? {}
392
392
  },
393
393
  body: body !== void 0 ? JSON.stringify(body) : void 0,
394
- signal: controller.signal
394
+ signal: controller.signal,
395
+ // ⚠ NO COOKIES (s58.360). The SDK authenticates with the x-api-key header
396
+ // and tokens in the body — never a cookie. But RN's fetch keeps a NATIVE
397
+ // cookie jar unless told otherwise (`XMLHttpRequest.withCredentials`
398
+ // defaults to true → iOS `HTTPShouldHandleCookies`; Android's OkHttp jar),
399
+ // so every `sd_rt` the server set was replayed on the next
400
+ // /token/refresh. After a sign-in that cookie is the ACCOUNT's, and the
401
+ // server preferred it over the HELD GUEST's token in the body: a claim
402
+ // refreshed the account and wrote its token into the guest's slot. The
403
+ // server now answers for the body (e09c86dc); this keeps the SDK out of
404
+ // that class regardless of what any endpoint prefers.
405
+ credentials: "omit"
395
406
  });
396
407
  } catch (err) {
397
408
  clearTimeout(timer);
@@ -2810,6 +2821,14 @@ var Auth = class {
2810
2821
  const rotated = readRotatedTokens(parsed.data);
2811
2822
  if (!rotated) throw new AuthError("PARSE_ERROR", "refresh response carried no token pair", { status: res.status });
2812
2823
  const d = parsed.data;
2824
+ const answeredFor = isAuthUserShape(d?.user) ? d.user.id : this.userFromClaims(rotated.accessToken).id;
2825
+ if (answeredFor && answeredFor !== slot.userId) {
2826
+ throw new AuthError(
2827
+ "HELD_GUEST_IDENTITY_MISMATCH",
2828
+ "The held guest's refresh was answered for a different account; nothing was changed",
2829
+ { kind: "unknown", status: res.status }
2830
+ );
2831
+ }
2813
2832
  const user = isAuthUserShape(d?.user) ? d.user : { ...this.userFromClaims(rotated.accessToken), id: slot.userId, isAnonymous: true };
2814
2833
  const persisted = this.commit({ kind: "slot_tokens", slot: i, sent, refreshToken: rotated.refreshToken });
2815
2834
  if (persisted.status !== "applied") return { outcome: "kept" };
package/dist/index.js CHANGED
@@ -212,7 +212,7 @@ var Storage = class {
212
212
  // package.json
213
213
  var package_default = {
214
214
  name: "@sendoracloud/sdk-react-native",
215
- version: "1.41.1",
215
+ version: "1.41.2",
216
216
  description: "Sendora Cloud React Native + Expo SDK \u2014 analytics, identity, push-token registration, auth (incl. Game Center / Play Games), deep links (Branch / Firebase Dynamic Links parity). Auth + analytics + deep links work in Expo Go; push-token registration and the no-arg Game Center / Play Games sign-in require a Dev Client (EAS Build or `npx expo prebuild`).",
217
217
  type: "module",
218
218
  main: "./dist/index.cjs",
@@ -269,7 +269,7 @@ var package_default = {
269
269
  scripts: {
270
270
  build: "tsup src/index.ts src/contact-widget.tsx --format esm,cjs --dts --clean --external react --external react-native --external react-native-webview",
271
271
  typecheck: "tsc --noEmit",
272
- test: "tsx test/secure-storage.test.ts && tsx test/integration-storage.test.ts && tsx test/plugin.test.ts && tsx test/game-auth.test.ts && tsx test/link.test.ts && tsx test/session-preservation.test.ts && tsx test/credential-collision.test.ts && tsx test/token-expiry.test.ts && tsx test/unlink.test.ts && tsx test/deferred-match.test.ts && tsx test/anon-id-stability.test.ts && tsx test/event-queue.test.ts && tsx test/anonymous-over-account.test.ts && tsx test/auth-machine-conformance.test.ts && tsx test/held-guests.test.ts"
272
+ test: "tsx test/secure-storage.test.ts && tsx test/integration-storage.test.ts && tsx test/plugin.test.ts && tsx test/game-auth.test.ts && tsx test/link.test.ts && tsx test/session-preservation.test.ts && tsx test/credential-collision.test.ts && tsx test/token-expiry.test.ts && tsx test/unlink.test.ts && tsx test/deferred-match.test.ts && tsx test/anon-id-stability.test.ts && tsx test/event-queue.test.ts && tsx test/anonymous-over-account.test.ts && tsx test/auth-machine-conformance.test.ts && tsx test/held-guests.test.ts && tsx test/held-identity.test.ts"
273
273
  },
274
274
  devDependencies: {
275
275
  "@types/react": "^19.2.16",
@@ -340,7 +340,18 @@ async function request(opts, method, path, body, extraHeaders, reqOpts) {
340
340
  ...extraHeaders ?? {}
341
341
  },
342
342
  body: body !== void 0 ? JSON.stringify(body) : void 0,
343
- signal: controller.signal
343
+ signal: controller.signal,
344
+ // ⚠ NO COOKIES (s58.360). The SDK authenticates with the x-api-key header
345
+ // and tokens in the body — never a cookie. But RN's fetch keeps a NATIVE
346
+ // cookie jar unless told otherwise (`XMLHttpRequest.withCredentials`
347
+ // defaults to true → iOS `HTTPShouldHandleCookies`; Android's OkHttp jar),
348
+ // so every `sd_rt` the server set was replayed on the next
349
+ // /token/refresh. After a sign-in that cookie is the ACCOUNT's, and the
350
+ // server preferred it over the HELD GUEST's token in the body: a claim
351
+ // refreshed the account and wrote its token into the guest's slot. The
352
+ // server now answers for the body (e09c86dc); this keeps the SDK out of
353
+ // that class regardless of what any endpoint prefers.
354
+ credentials: "omit"
344
355
  });
345
356
  } catch (err) {
346
357
  clearTimeout(timer);
@@ -2759,6 +2770,14 @@ var Auth = class {
2759
2770
  const rotated = readRotatedTokens(parsed.data);
2760
2771
  if (!rotated) throw new AuthError("PARSE_ERROR", "refresh response carried no token pair", { status: res.status });
2761
2772
  const d = parsed.data;
2773
+ const answeredFor = isAuthUserShape(d?.user) ? d.user.id : this.userFromClaims(rotated.accessToken).id;
2774
+ if (answeredFor && answeredFor !== slot.userId) {
2775
+ throw new AuthError(
2776
+ "HELD_GUEST_IDENTITY_MISMATCH",
2777
+ "The held guest's refresh was answered for a different account; nothing was changed",
2778
+ { kind: "unknown", status: res.status }
2779
+ );
2780
+ }
2762
2781
  const user = isAuthUserShape(d?.user) ? d.user : { ...this.userFromClaims(rotated.accessToken), id: slot.userId, isAnonymous: true };
2763
2782
  const persisted = this.commit({ kind: "slot_tokens", slot: i, sent, refreshToken: rotated.refreshToken });
2764
2783
  if (persisted.status !== "applied") return { outcome: "kept" };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sendoracloud/sdk-react-native",
3
- "version": "1.41.1",
3
+ "version": "1.41.2",
4
4
  "description": "Sendora Cloud React Native + Expo SDK — analytics, identity, push-token registration, auth (incl. Game Center / Play Games), deep links (Branch / Firebase Dynamic Links parity). Auth + analytics + deep links work in Expo Go; push-token registration and the no-arg Game Center / Play Games sign-in require a Dev Client (EAS Build or `npx expo prebuild`).",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -57,7 +57,7 @@
57
57
  "scripts": {
58
58
  "build": "tsup src/index.ts src/contact-widget.tsx --format esm,cjs --dts --clean --external react --external react-native --external react-native-webview",
59
59
  "typecheck": "tsc --noEmit",
60
- "test": "tsx test/secure-storage.test.ts && tsx test/integration-storage.test.ts && tsx test/plugin.test.ts && tsx test/game-auth.test.ts && tsx test/link.test.ts && tsx test/session-preservation.test.ts && tsx test/credential-collision.test.ts && tsx test/token-expiry.test.ts && tsx test/unlink.test.ts && tsx test/deferred-match.test.ts && tsx test/anon-id-stability.test.ts && tsx test/event-queue.test.ts && tsx test/anonymous-over-account.test.ts && tsx test/auth-machine-conformance.test.ts && tsx test/held-guests.test.ts"
60
+ "test": "tsx test/secure-storage.test.ts && tsx test/integration-storage.test.ts && tsx test/plugin.test.ts && tsx test/game-auth.test.ts && tsx test/link.test.ts && tsx test/session-preservation.test.ts && tsx test/credential-collision.test.ts && tsx test/token-expiry.test.ts && tsx test/unlink.test.ts && tsx test/deferred-match.test.ts && tsx test/anon-id-stability.test.ts && tsx test/event-queue.test.ts && tsx test/anonymous-over-account.test.ts && tsx test/auth-machine-conformance.test.ts && tsx test/held-guests.test.ts && tsx test/held-identity.test.ts"
61
61
  },
62
62
  "devDependencies": {
63
63
  "@types/react": "^19.2.16",