@sendoracloud/sdk-react-native 1.36.0 → 1.38.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 +25 -25
- package/dist/index.d.cts +59 -17
- package/dist/index.d.ts +59 -17
- package/dist/index.js +25 -25
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -228,7 +228,7 @@ var Storage = class {
|
|
|
228
228
|
// package.json
|
|
229
229
|
var package_default = {
|
|
230
230
|
name: "@sendoracloud/sdk-react-native",
|
|
231
|
-
version: "1.
|
|
231
|
+
version: "1.38.0",
|
|
232
232
|
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`).",
|
|
233
233
|
type: "module",
|
|
234
234
|
main: "./dist/index.cjs",
|
|
@@ -284,7 +284,7 @@ var package_default = {
|
|
|
284
284
|
scripts: {
|
|
285
285
|
build: "tsup src/index.ts src/contact-widget.tsx --format esm,cjs --dts --clean --external react --external react-native --external react-native-webview",
|
|
286
286
|
typecheck: "tsc --noEmit",
|
|
287
|
-
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"
|
|
287
|
+
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"
|
|
288
288
|
},
|
|
289
289
|
devDependencies: {
|
|
290
290
|
"@types/react": "^19.2.16",
|
|
@@ -2156,7 +2156,7 @@ var Auth = class {
|
|
|
2156
2156
|
this.hooks.storage.remove(TOKEN_EXPIRES_KEY);
|
|
2157
2157
|
this.hooks.storage.remove(REFRESH_KEY);
|
|
2158
2158
|
this.stopProactiveRefreshCron();
|
|
2159
|
-
await this.hooks.onAnonymousWipe();
|
|
2159
|
+
await this.hooks.onAnonymousWipe(reason);
|
|
2160
2160
|
if (reason !== "replaced") {
|
|
2161
2161
|
this.emitAuthState({ event: "signed_out", user: null, reason });
|
|
2162
2162
|
}
|
|
@@ -2627,33 +2627,31 @@ var Links = class {
|
|
|
2627
2627
|
}
|
|
2628
2628
|
}
|
|
2629
2629
|
/**
|
|
2630
|
-
* Cold-launch deferred deep
|
|
2631
|
-
*
|
|
2632
|
-
*
|
|
2633
|
-
*
|
|
2634
|
-
*
|
|
2630
|
+
* Cold-launch deferred deep-link match.
|
|
2631
|
+
*
|
|
2632
|
+
* ⚠ NO IMPLICIT FINGERPRINTING (compliance). This used to compute a device
|
|
2633
|
+
* fingerprint on iOS when you passed no signal; it no longer does — Apple
|
|
2634
|
+
* prohibits deriving a device identifier from device signals for attribution
|
|
2635
|
+
* (App Store 5.1.2 / DPLA §3.3.9), regardless of ATT, and doing it on-device is
|
|
2636
|
+
* exactly that. Signal resolution now:
|
|
2637
|
+
* - Android: probes `react-native-play-install-referrer` — deterministic, the
|
|
2638
|
+
* preferred and compliant path.
|
|
2639
|
+
* - iOS: no deterministic signal exists (the App Store has no install referrer).
|
|
2640
|
+
* With `allowProbabilisticMatch: true` the server matches on a coarse
|
|
2641
|
+
* IP + time window (OFF by default — see `LinkMatchInput`). Otherwise this
|
|
2642
|
+
* returns `null` and you route the user through your normal onboarding UX —
|
|
2643
|
+
* which is the expected iOS cold-launch outcome, not an error.
|
|
2635
2644
|
*
|
|
2636
2645
|
* Fires `onLinkOpened` with `isDeferred: true` on success.
|
|
2637
2646
|
*/
|
|
2638
2647
|
async matchDeferred(input = {}) {
|
|
2639
|
-
|
|
2648
|
+
const fingerprintHash = input.fingerprintHash;
|
|
2640
2649
|
let installReferrer = input.installReferrer;
|
|
2641
2650
|
if (!fingerprintHash && !installReferrer) {
|
|
2642
2651
|
installReferrer = await getPlayInstallReferrer() ?? void 0;
|
|
2643
|
-
if (!installReferrer) {
|
|
2644
|
-
try {
|
|
2645
|
-
fingerprintHash = await computeDeviceFingerprint();
|
|
2646
|
-
} catch (err) {
|
|
2647
|
-
if (this.deps.debug) console.warn("[sendoracloud] fingerprint compute failed:", err);
|
|
2648
|
-
}
|
|
2649
|
-
}
|
|
2650
|
-
}
|
|
2651
|
-
if (!fingerprintHash && !installReferrer) {
|
|
2652
|
-
throw new LinkError(
|
|
2653
|
-
"INVALID_INPUT",
|
|
2654
|
-
"links.matchDeferred: install `react-native-play-install-referrer` for Android, or ensure web crypto / expo-crypto is available for iOS fingerprint compute."
|
|
2655
|
-
);
|
|
2656
2652
|
}
|
|
2653
|
+
const canMatch = !!fingerprintHash || !!installReferrer || input.allowProbabilisticMatch === true;
|
|
2654
|
+
if (!canMatch) return null;
|
|
2657
2655
|
const body = {};
|
|
2658
2656
|
if (fingerprintHash) body.fingerprintHash = fingerprintHash;
|
|
2659
2657
|
if (installReferrer) body.installReferrer = installReferrer;
|
|
@@ -3101,9 +3099,11 @@ var SendoraSDK = class {
|
|
|
3101
3099
|
if (userId) this.storage.set(USER_ID_KEY, userId);
|
|
3102
3100
|
else this.storage.remove(USER_ID_KEY);
|
|
3103
3101
|
},
|
|
3104
|
-
onAnonymousWipe: async () => {
|
|
3105
|
-
|
|
3106
|
-
|
|
3102
|
+
onAnonymousWipe: async (reason) => {
|
|
3103
|
+
if (reason === "user" || reason === "account_deleted") {
|
|
3104
|
+
this.anonId = uuid();
|
|
3105
|
+
this.storage.set(ANON_KEY, this.anonId);
|
|
3106
|
+
}
|
|
3107
3107
|
this.userId = null;
|
|
3108
3108
|
this.storage.remove(USER_ID_KEY);
|
|
3109
3109
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -497,12 +497,15 @@ interface AuthHooks {
|
|
|
497
497
|
* identity (analytics userId, push token registration, etc). */
|
|
498
498
|
onIdentityChange: (userId: string | null) => void;
|
|
499
499
|
/**
|
|
500
|
-
* Called
|
|
501
|
-
*
|
|
502
|
-
*
|
|
503
|
-
*
|
|
504
|
-
|
|
505
|
-
|
|
500
|
+
* Called from `wipeLocalIdentity` whenever the local session is cleared. The
|
|
501
|
+
* `reason` lets the parent decide whether to rotate the anonymous analytics id:
|
|
502
|
+
* it rotates ONLY at a person-boundary ("user" = explicit signOut,
|
|
503
|
+
* "account_deleted"), never on a successful sign-in/upgrade ("replaced") or an
|
|
504
|
+
* involuntary session death ("session_expired") — those are the same person, and
|
|
505
|
+
* the anon id must survive so pre-auth analytics link to them (Firebase-FID
|
|
506
|
+
* semantics; the industry norm).
|
|
507
|
+
*/
|
|
508
|
+
onAnonymousWipe: (reason: WipeReason) => Promise<void>;
|
|
506
509
|
apiUrl: string;
|
|
507
510
|
publicKey: string;
|
|
508
511
|
debug: boolean;
|
|
@@ -565,6 +568,12 @@ type AuthStateChange = {
|
|
|
565
568
|
user: AuthUser | null;
|
|
566
569
|
};
|
|
567
570
|
type AuthStateListener = (change: AuthStateChange) => void;
|
|
571
|
+
/**
|
|
572
|
+
* Internal marker for a wipe that is immediately followed by a `persist()` of
|
|
573
|
+
* a NEW session (the sign-in success path). Not a sign-out — emitting one
|
|
574
|
+
* would make every successful sign-in look like a logout to subscribers.
|
|
575
|
+
*/
|
|
576
|
+
type WipeReason = AuthSignedOutReason | "replaced";
|
|
568
577
|
declare class Auth {
|
|
569
578
|
private hooks;
|
|
570
579
|
private user;
|
|
@@ -1400,11 +1409,33 @@ interface LinkOpenedEvent<T extends LinkData = LinkData> {
|
|
|
1400
1409
|
}
|
|
1401
1410
|
type LinkOpenedHandler<T extends LinkData = LinkData> = (event: LinkOpenedEvent<T>) => void;
|
|
1402
1411
|
interface LinkMatchInput {
|
|
1403
|
-
/**
|
|
1412
|
+
/**
|
|
1413
|
+
* Hex-encoded SHA-256 device fingerprint.
|
|
1414
|
+
*
|
|
1415
|
+
* ⚠ **Do not supply this on iOS.** Apple prohibits deriving a device identifier
|
|
1416
|
+
* from device signals for attribution (App Store 5.1.2 / DPLA §3.3.9), and it
|
|
1417
|
+
* does not care which side of the network computed it — passing a hash you built
|
|
1418
|
+
* on-device is the same practice. The SDK no longer computes one for you (it used
|
|
1419
|
+
* to, implicitly, on iOS). Left here for the rare Android/self-hosted caller that
|
|
1420
|
+
* has a legitimate deterministic hash; on iOS, omit it.
|
|
1421
|
+
*/
|
|
1404
1422
|
fingerprintHash?: string;
|
|
1405
1423
|
/** Raw Play Install Referrer. Sendora probes
|
|
1406
1424
|
* `react-native-play-install-referrer` automatically when this is omitted. */
|
|
1407
1425
|
installReferrer?: string;
|
|
1426
|
+
/**
|
|
1427
|
+
* Opt in to probabilistic matching on iOS.
|
|
1428
|
+
*
|
|
1429
|
+
* iOS has no deterministic deferred-install signal (no install referrer), and the
|
|
1430
|
+
* SDK will not fingerprint the device. The only remaining matcher is the server's
|
|
1431
|
+
* coarse IP + timestamp window — the same class of practice Apple scrutinises, and
|
|
1432
|
+
* it collides across a shared NAT / iCloud Private Relay. So it is **off by
|
|
1433
|
+
* default**: without this flag, `matchDeferred()` on iOS matches nothing and
|
|
1434
|
+
* returns `null` (route the user through your normal onboarding UX). Set it only
|
|
1435
|
+
* if you accept the accuracy and compliance trade-off. No effect on Android, where
|
|
1436
|
+
* the Play Install Referrer is deterministic.
|
|
1437
|
+
*/
|
|
1438
|
+
allowProbabilisticMatch?: boolean;
|
|
1408
1439
|
iosBundleId?: string;
|
|
1409
1440
|
androidPackageName?: string;
|
|
1410
1441
|
}
|
|
@@ -1437,12 +1468,14 @@ interface LinkStats {
|
|
|
1437
1468
|
*/
|
|
1438
1469
|
declare function extractShortcodeFromUrl(url: string, linkDomain?: string): string | null;
|
|
1439
1470
|
/**
|
|
1440
|
-
* Canonical device-fingerprint recipe
|
|
1441
|
-
*
|
|
1442
|
-
* install completing on another won't accidentally match.
|
|
1471
|
+
* Canonical device-fingerprint recipe (`${platform}|${screenW}x${screenH}|${timezone}|${locale}`
|
|
1472
|
+
* → lowercase hex SHA-256), identical across the RN / iOS / Android SDKs.
|
|
1443
1473
|
*
|
|
1444
|
-
*
|
|
1445
|
-
*
|
|
1474
|
+
* @deprecated ⚠ Do not use on iOS. Apple prohibits deriving a device identifier
|
|
1475
|
+
* from device signals for attribution (App Store 5.1.2 / DPLA §3.3.9), regardless
|
|
1476
|
+
* of ATT — and this is exactly that. `matchDeferred()` no longer calls it
|
|
1477
|
+
* implicitly. It is retained only for an Android/self-hosted caller with a
|
|
1478
|
+
* legitimate deterministic use, and is slated for removal in a future major.
|
|
1446
1479
|
*/
|
|
1447
1480
|
declare function computeDeviceFingerprint(): Promise<string>;
|
|
1448
1481
|
/** Probe optional `react-native-play-install-referrer` peer. Returns the
|
|
@@ -1511,11 +1544,20 @@ declare class Links {
|
|
|
1511
1544
|
*/
|
|
1512
1545
|
handleUniversalLink(url: string): Promise<boolean>;
|
|
1513
1546
|
/**
|
|
1514
|
-
* Cold-launch deferred deep
|
|
1515
|
-
*
|
|
1516
|
-
*
|
|
1517
|
-
*
|
|
1518
|
-
*
|
|
1547
|
+
* Cold-launch deferred deep-link match.
|
|
1548
|
+
*
|
|
1549
|
+
* ⚠ NO IMPLICIT FINGERPRINTING (compliance). This used to compute a device
|
|
1550
|
+
* fingerprint on iOS when you passed no signal; it no longer does — Apple
|
|
1551
|
+
* prohibits deriving a device identifier from device signals for attribution
|
|
1552
|
+
* (App Store 5.1.2 / DPLA §3.3.9), regardless of ATT, and doing it on-device is
|
|
1553
|
+
* exactly that. Signal resolution now:
|
|
1554
|
+
* - Android: probes `react-native-play-install-referrer` — deterministic, the
|
|
1555
|
+
* preferred and compliant path.
|
|
1556
|
+
* - iOS: no deterministic signal exists (the App Store has no install referrer).
|
|
1557
|
+
* With `allowProbabilisticMatch: true` the server matches on a coarse
|
|
1558
|
+
* IP + time window (OFF by default — see `LinkMatchInput`). Otherwise this
|
|
1559
|
+
* returns `null` and you route the user through your normal onboarding UX —
|
|
1560
|
+
* which is the expected iOS cold-launch outcome, not an error.
|
|
1519
1561
|
*
|
|
1520
1562
|
* Fires `onLinkOpened` with `isDeferred: true` on success.
|
|
1521
1563
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -497,12 +497,15 @@ interface AuthHooks {
|
|
|
497
497
|
* identity (analytics userId, push token registration, etc). */
|
|
498
498
|
onIdentityChange: (userId: string | null) => void;
|
|
499
499
|
/**
|
|
500
|
-
* Called
|
|
501
|
-
*
|
|
502
|
-
*
|
|
503
|
-
*
|
|
504
|
-
|
|
505
|
-
|
|
500
|
+
* Called from `wipeLocalIdentity` whenever the local session is cleared. The
|
|
501
|
+
* `reason` lets the parent decide whether to rotate the anonymous analytics id:
|
|
502
|
+
* it rotates ONLY at a person-boundary ("user" = explicit signOut,
|
|
503
|
+
* "account_deleted"), never on a successful sign-in/upgrade ("replaced") or an
|
|
504
|
+
* involuntary session death ("session_expired") — those are the same person, and
|
|
505
|
+
* the anon id must survive so pre-auth analytics link to them (Firebase-FID
|
|
506
|
+
* semantics; the industry norm).
|
|
507
|
+
*/
|
|
508
|
+
onAnonymousWipe: (reason: WipeReason) => Promise<void>;
|
|
506
509
|
apiUrl: string;
|
|
507
510
|
publicKey: string;
|
|
508
511
|
debug: boolean;
|
|
@@ -565,6 +568,12 @@ type AuthStateChange = {
|
|
|
565
568
|
user: AuthUser | null;
|
|
566
569
|
};
|
|
567
570
|
type AuthStateListener = (change: AuthStateChange) => void;
|
|
571
|
+
/**
|
|
572
|
+
* Internal marker for a wipe that is immediately followed by a `persist()` of
|
|
573
|
+
* a NEW session (the sign-in success path). Not a sign-out — emitting one
|
|
574
|
+
* would make every successful sign-in look like a logout to subscribers.
|
|
575
|
+
*/
|
|
576
|
+
type WipeReason = AuthSignedOutReason | "replaced";
|
|
568
577
|
declare class Auth {
|
|
569
578
|
private hooks;
|
|
570
579
|
private user;
|
|
@@ -1400,11 +1409,33 @@ interface LinkOpenedEvent<T extends LinkData = LinkData> {
|
|
|
1400
1409
|
}
|
|
1401
1410
|
type LinkOpenedHandler<T extends LinkData = LinkData> = (event: LinkOpenedEvent<T>) => void;
|
|
1402
1411
|
interface LinkMatchInput {
|
|
1403
|
-
/**
|
|
1412
|
+
/**
|
|
1413
|
+
* Hex-encoded SHA-256 device fingerprint.
|
|
1414
|
+
*
|
|
1415
|
+
* ⚠ **Do not supply this on iOS.** Apple prohibits deriving a device identifier
|
|
1416
|
+
* from device signals for attribution (App Store 5.1.2 / DPLA §3.3.9), and it
|
|
1417
|
+
* does not care which side of the network computed it — passing a hash you built
|
|
1418
|
+
* on-device is the same practice. The SDK no longer computes one for you (it used
|
|
1419
|
+
* to, implicitly, on iOS). Left here for the rare Android/self-hosted caller that
|
|
1420
|
+
* has a legitimate deterministic hash; on iOS, omit it.
|
|
1421
|
+
*/
|
|
1404
1422
|
fingerprintHash?: string;
|
|
1405
1423
|
/** Raw Play Install Referrer. Sendora probes
|
|
1406
1424
|
* `react-native-play-install-referrer` automatically when this is omitted. */
|
|
1407
1425
|
installReferrer?: string;
|
|
1426
|
+
/**
|
|
1427
|
+
* Opt in to probabilistic matching on iOS.
|
|
1428
|
+
*
|
|
1429
|
+
* iOS has no deterministic deferred-install signal (no install referrer), and the
|
|
1430
|
+
* SDK will not fingerprint the device. The only remaining matcher is the server's
|
|
1431
|
+
* coarse IP + timestamp window — the same class of practice Apple scrutinises, and
|
|
1432
|
+
* it collides across a shared NAT / iCloud Private Relay. So it is **off by
|
|
1433
|
+
* default**: without this flag, `matchDeferred()` on iOS matches nothing and
|
|
1434
|
+
* returns `null` (route the user through your normal onboarding UX). Set it only
|
|
1435
|
+
* if you accept the accuracy and compliance trade-off. No effect on Android, where
|
|
1436
|
+
* the Play Install Referrer is deterministic.
|
|
1437
|
+
*/
|
|
1438
|
+
allowProbabilisticMatch?: boolean;
|
|
1408
1439
|
iosBundleId?: string;
|
|
1409
1440
|
androidPackageName?: string;
|
|
1410
1441
|
}
|
|
@@ -1437,12 +1468,14 @@ interface LinkStats {
|
|
|
1437
1468
|
*/
|
|
1438
1469
|
declare function extractShortcodeFromUrl(url: string, linkDomain?: string): string | null;
|
|
1439
1470
|
/**
|
|
1440
|
-
* Canonical device-fingerprint recipe
|
|
1441
|
-
*
|
|
1442
|
-
* install completing on another won't accidentally match.
|
|
1471
|
+
* Canonical device-fingerprint recipe (`${platform}|${screenW}x${screenH}|${timezone}|${locale}`
|
|
1472
|
+
* → lowercase hex SHA-256), identical across the RN / iOS / Android SDKs.
|
|
1443
1473
|
*
|
|
1444
|
-
*
|
|
1445
|
-
*
|
|
1474
|
+
* @deprecated ⚠ Do not use on iOS. Apple prohibits deriving a device identifier
|
|
1475
|
+
* from device signals for attribution (App Store 5.1.2 / DPLA §3.3.9), regardless
|
|
1476
|
+
* of ATT — and this is exactly that. `matchDeferred()` no longer calls it
|
|
1477
|
+
* implicitly. It is retained only for an Android/self-hosted caller with a
|
|
1478
|
+
* legitimate deterministic use, and is slated for removal in a future major.
|
|
1446
1479
|
*/
|
|
1447
1480
|
declare function computeDeviceFingerprint(): Promise<string>;
|
|
1448
1481
|
/** Probe optional `react-native-play-install-referrer` peer. Returns the
|
|
@@ -1511,11 +1544,20 @@ declare class Links {
|
|
|
1511
1544
|
*/
|
|
1512
1545
|
handleUniversalLink(url: string): Promise<boolean>;
|
|
1513
1546
|
/**
|
|
1514
|
-
* Cold-launch deferred deep
|
|
1515
|
-
*
|
|
1516
|
-
*
|
|
1517
|
-
*
|
|
1518
|
-
*
|
|
1547
|
+
* Cold-launch deferred deep-link match.
|
|
1548
|
+
*
|
|
1549
|
+
* ⚠ NO IMPLICIT FINGERPRINTING (compliance). This used to compute a device
|
|
1550
|
+
* fingerprint on iOS when you passed no signal; it no longer does — Apple
|
|
1551
|
+
* prohibits deriving a device identifier from device signals for attribution
|
|
1552
|
+
* (App Store 5.1.2 / DPLA §3.3.9), regardless of ATT, and doing it on-device is
|
|
1553
|
+
* exactly that. Signal resolution now:
|
|
1554
|
+
* - Android: probes `react-native-play-install-referrer` — deterministic, the
|
|
1555
|
+
* preferred and compliant path.
|
|
1556
|
+
* - iOS: no deterministic signal exists (the App Store has no install referrer).
|
|
1557
|
+
* With `allowProbabilisticMatch: true` the server matches on a coarse
|
|
1558
|
+
* IP + time window (OFF by default — see `LinkMatchInput`). Otherwise this
|
|
1559
|
+
* returns `null` and you route the user through your normal onboarding UX —
|
|
1560
|
+
* which is the expected iOS cold-launch outcome, not an error.
|
|
1519
1561
|
*
|
|
1520
1562
|
* Fires `onLinkOpened` with `isDeferred: true` on success.
|
|
1521
1563
|
*/
|
package/dist/index.js
CHANGED
|
@@ -183,7 +183,7 @@ var Storage = class {
|
|
|
183
183
|
// package.json
|
|
184
184
|
var package_default = {
|
|
185
185
|
name: "@sendoracloud/sdk-react-native",
|
|
186
|
-
version: "1.
|
|
186
|
+
version: "1.38.0",
|
|
187
187
|
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`).",
|
|
188
188
|
type: "module",
|
|
189
189
|
main: "./dist/index.cjs",
|
|
@@ -239,7 +239,7 @@ var package_default = {
|
|
|
239
239
|
scripts: {
|
|
240
240
|
build: "tsup src/index.ts src/contact-widget.tsx --format esm,cjs --dts --clean --external react --external react-native --external react-native-webview",
|
|
241
241
|
typecheck: "tsc --noEmit",
|
|
242
|
-
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"
|
|
242
|
+
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"
|
|
243
243
|
},
|
|
244
244
|
devDependencies: {
|
|
245
245
|
"@types/react": "^19.2.16",
|
|
@@ -2111,7 +2111,7 @@ var Auth = class {
|
|
|
2111
2111
|
this.hooks.storage.remove(TOKEN_EXPIRES_KEY);
|
|
2112
2112
|
this.hooks.storage.remove(REFRESH_KEY);
|
|
2113
2113
|
this.stopProactiveRefreshCron();
|
|
2114
|
-
await this.hooks.onAnonymousWipe();
|
|
2114
|
+
await this.hooks.onAnonymousWipe(reason);
|
|
2115
2115
|
if (reason !== "replaced") {
|
|
2116
2116
|
this.emitAuthState({ event: "signed_out", user: null, reason });
|
|
2117
2117
|
}
|
|
@@ -2587,33 +2587,31 @@ var Links = class {
|
|
|
2587
2587
|
}
|
|
2588
2588
|
}
|
|
2589
2589
|
/**
|
|
2590
|
-
* Cold-launch deferred deep
|
|
2591
|
-
*
|
|
2592
|
-
*
|
|
2593
|
-
*
|
|
2594
|
-
*
|
|
2590
|
+
* Cold-launch deferred deep-link match.
|
|
2591
|
+
*
|
|
2592
|
+
* ⚠ NO IMPLICIT FINGERPRINTING (compliance). This used to compute a device
|
|
2593
|
+
* fingerprint on iOS when you passed no signal; it no longer does — Apple
|
|
2594
|
+
* prohibits deriving a device identifier from device signals for attribution
|
|
2595
|
+
* (App Store 5.1.2 / DPLA §3.3.9), regardless of ATT, and doing it on-device is
|
|
2596
|
+
* exactly that. Signal resolution now:
|
|
2597
|
+
* - Android: probes `react-native-play-install-referrer` — deterministic, the
|
|
2598
|
+
* preferred and compliant path.
|
|
2599
|
+
* - iOS: no deterministic signal exists (the App Store has no install referrer).
|
|
2600
|
+
* With `allowProbabilisticMatch: true` the server matches on a coarse
|
|
2601
|
+
* IP + time window (OFF by default — see `LinkMatchInput`). Otherwise this
|
|
2602
|
+
* returns `null` and you route the user through your normal onboarding UX —
|
|
2603
|
+
* which is the expected iOS cold-launch outcome, not an error.
|
|
2595
2604
|
*
|
|
2596
2605
|
* Fires `onLinkOpened` with `isDeferred: true` on success.
|
|
2597
2606
|
*/
|
|
2598
2607
|
async matchDeferred(input = {}) {
|
|
2599
|
-
|
|
2608
|
+
const fingerprintHash = input.fingerprintHash;
|
|
2600
2609
|
let installReferrer = input.installReferrer;
|
|
2601
2610
|
if (!fingerprintHash && !installReferrer) {
|
|
2602
2611
|
installReferrer = await getPlayInstallReferrer() ?? void 0;
|
|
2603
|
-
if (!installReferrer) {
|
|
2604
|
-
try {
|
|
2605
|
-
fingerprintHash = await computeDeviceFingerprint();
|
|
2606
|
-
} catch (err) {
|
|
2607
|
-
if (this.deps.debug) console.warn("[sendoracloud] fingerprint compute failed:", err);
|
|
2608
|
-
}
|
|
2609
|
-
}
|
|
2610
|
-
}
|
|
2611
|
-
if (!fingerprintHash && !installReferrer) {
|
|
2612
|
-
throw new LinkError(
|
|
2613
|
-
"INVALID_INPUT",
|
|
2614
|
-
"links.matchDeferred: install `react-native-play-install-referrer` for Android, or ensure web crypto / expo-crypto is available for iOS fingerprint compute."
|
|
2615
|
-
);
|
|
2616
2612
|
}
|
|
2613
|
+
const canMatch = !!fingerprintHash || !!installReferrer || input.allowProbabilisticMatch === true;
|
|
2614
|
+
if (!canMatch) return null;
|
|
2617
2615
|
const body = {};
|
|
2618
2616
|
if (fingerprintHash) body.fingerprintHash = fingerprintHash;
|
|
2619
2617
|
if (installReferrer) body.installReferrer = installReferrer;
|
|
@@ -3061,9 +3059,11 @@ var SendoraSDK = class {
|
|
|
3061
3059
|
if (userId) this.storage.set(USER_ID_KEY, userId);
|
|
3062
3060
|
else this.storage.remove(USER_ID_KEY);
|
|
3063
3061
|
},
|
|
3064
|
-
onAnonymousWipe: async () => {
|
|
3065
|
-
|
|
3066
|
-
|
|
3062
|
+
onAnonymousWipe: async (reason) => {
|
|
3063
|
+
if (reason === "user" || reason === "account_deleted") {
|
|
3064
|
+
this.anonId = uuid();
|
|
3065
|
+
this.storage.set(ANON_KEY, this.anonId);
|
|
3066
|
+
}
|
|
3067
3067
|
this.userId = null;
|
|
3068
3068
|
this.storage.remove(USER_ID_KEY);
|
|
3069
3069
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sendoracloud/sdk-react-native",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.38.0",
|
|
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",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"scripts": {
|
|
57
57
|
"build": "tsup src/index.ts src/contact-widget.tsx --format esm,cjs --dts --clean --external react --external react-native --external react-native-webview",
|
|
58
58
|
"typecheck": "tsc --noEmit",
|
|
59
|
-
"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"
|
|
59
|
+
"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"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@types/react": "^19.2.16",
|