@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 +8 -0
- package/dist/index.cjs +22 -3
- package/dist/index.js +22 -3
- package/package.json +2 -2
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.
|
|
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.
|
|
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.
|
|
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",
|