@bounded-sh/core 0.0.70 → 0.0.72
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/client/config.d.ts +22 -1
- package/dist/{index-Csf_2uZF.mjs → index-P4eUuhOB.mjs} +100 -17
- package/dist/index-P4eUuhOB.mjs.map +1 -0
- package/dist/{index-CqTl6vHg.js → index-yT8ZfzrL.js} +100 -17
- package/dist/index-yT8ZfzrL.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{realtime-store-DfeKV55-.js → realtime-store-BDpIOFMH.js} +2 -2
- package/dist/{realtime-store-DfeKV55-.js.map → realtime-store-BDpIOFMH.js.map} +1 -1
- package/dist/{realtime-store-ByP5wYsa.mjs → realtime-store-Bcnv3f0g.mjs} +2 -2
- package/dist/{realtime-store-ByP5wYsa.mjs.map → realtime-store-Bcnv3f0g.mjs.map} +1 -1
- package/dist/{solana-message-BecAT47y.js → solana-message-Dh9ryO-Y.js} +2 -2
- package/dist/{solana-message-BecAT47y.js.map → solana-message-Dh9ryO-Y.js.map} +1 -1
- package/dist/{solana-message-BFqre_Oi.mjs → solana-message-x8ElgC9P.mjs} +2 -2
- package/dist/{solana-message-BFqre_Oi.mjs.map → solana-message-x8ElgC9P.mjs.map} +1 -1
- package/dist/{solana-operations-ByMDnjog.mjs → solana-operations-DEWIxJS8.mjs} +2 -2
- package/dist/{solana-operations-ByMDnjog.mjs.map → solana-operations-DEWIxJS8.mjs.map} +1 -1
- package/dist/{solana-operations-uN89eqO2.js → solana-operations-DGEGqFd9.js} +2 -2
- package/dist/{solana-operations-uN89eqO2.js.map → solana-operations-DGEGqFd9.js.map} +1 -1
- package/dist/utils/refresh-error.d.ts +9 -0
- package/package.json +2 -2
- package/dist/index-CqTl6vHg.js.map +0 -1
- package/dist/index-Csf_2uZF.mjs.map +0 -1
|
@@ -25,7 +25,8 @@ const DEFAULT_CLIENT_CONFIG = {
|
|
|
25
25
|
// No hidden modal fallback: browser clients start hosted Bounded Auth with
|
|
26
26
|
// loginWithRedirect/loginWithPopup. For crypto/onchain wallet login use
|
|
27
27
|
// authMethod:'phantom' (Solana / Phantom), and use signInAnonymously() for
|
|
28
|
-
// zero-friction guest accounts. ('wallet'
|
|
28
|
+
// zero-friction guest accounts. ('wallet' and 'mobile-wallet-adapter' are
|
|
29
|
+
// accepted spellings of the same lane and canonicalize to 'phantom'.)
|
|
29
30
|
authMethod: 'none',
|
|
30
31
|
chain: '',
|
|
31
32
|
rpcUrl: '',
|
|
@@ -642,6 +643,53 @@ async function revokeSession(refreshToken, issuer) {
|
|
|
642
643
|
async function signSessionCreateMessage(_signMessageFunction) {
|
|
643
644
|
}
|
|
644
645
|
|
|
646
|
+
// Classify a session-refresh failure as a DEFINITIVE rejection of the credential
|
|
647
|
+
// vs a TRANSPORT failure (DS4-0054).
|
|
648
|
+
//
|
|
649
|
+
// The restore path refreshes an expired token at boot. The old code destroyed the
|
|
650
|
+
// local session on ANY refresh failure, so a network blip - common on mobile -
|
|
651
|
+
// signed the user out. Only a real HTTP rejection of the refresh token proves the
|
|
652
|
+
// session is invalid; anything else is unknown, and the safe move is to RETAIN local
|
|
653
|
+
// state and retry. Keeping a genuinely dead session locally is harmless: every
|
|
654
|
+
// request still fails closed server-side, and the next refresh that reaches the
|
|
655
|
+
// server clears it. Destroying a live session on a blip is user-visible damage.
|
|
656
|
+
//
|
|
657
|
+
// `/session/refresh` answers 401 (session_revoked / invalid_grant) when the token is
|
|
658
|
+
// dead. 400 (bad request), 408/429 (transient), and 5xx are NOT proof the session is
|
|
659
|
+
// invalid, and a network/DNS/timeout error carries no HTTP response at all.
|
|
660
|
+
//
|
|
661
|
+
// This is the single classifier for every refresh path: the per-request freshness
|
|
662
|
+
// check (auth-token-freshness) and the boot-time restore paths (web + RN session
|
|
663
|
+
// managers) all import it, so "definitive" means the same thing everywhere. It lives
|
|
664
|
+
// in this leaf module to avoid an import cycle between those consumers.
|
|
665
|
+
function isDefinitiveRejection(err) {
|
|
666
|
+
var _a;
|
|
667
|
+
const status = (_a = err === null || err === void 0 ? void 0 : err.response) === null || _a === void 0 ? void 0 : _a.status;
|
|
668
|
+
return status === 401 || status === 403;
|
|
669
|
+
}
|
|
670
|
+
// How long past the id token's own `exp` a session may survive purely on
|
|
671
|
+
// transport failures.
|
|
672
|
+
//
|
|
673
|
+
// "Retain on a blip" must not become "retain forever". A statusless failure is
|
|
674
|
+
// unknown, so the first ones are absorbed - but a revoked session, or a gateway
|
|
675
|
+
// that rewrites the issuer's 401 into a 502, would otherwise keep
|
|
676
|
+
// isAuthenticated() reporting a signed-in user for as long as the device never
|
|
677
|
+
// reaches the issuer again. One day covers an offline flight or a bad-network day
|
|
678
|
+
// while making the retention finite: past it, an unrefreshable session is cleared
|
|
679
|
+
// and the user is asked to sign in again.
|
|
680
|
+
const TRANSPORT_RETENTION_GRACE_MS = 24 * 60 * 60 * 1000;
|
|
681
|
+
/**
|
|
682
|
+
* May a session whose refresh failed WITHOUT a definitive rejection be kept?
|
|
683
|
+
*
|
|
684
|
+
* `expSeconds` is the id token's `exp` claim. Fails closed: a missing or
|
|
685
|
+
* non-numeric expiry is not a usable bound, so such a session is never retained.
|
|
686
|
+
*/
|
|
687
|
+
function mayRetainAfterTransportFailure(expSeconds, now = Date.now()) {
|
|
688
|
+
if (typeof expSeconds !== 'number' || !Number.isFinite(expSeconds))
|
|
689
|
+
return false;
|
|
690
|
+
return now <= expSeconds * 1000 + TRANSPORT_RETENTION_GRACE_MS;
|
|
691
|
+
}
|
|
692
|
+
|
|
645
693
|
// Shared session-binding guard for the web + React Native session managers, so
|
|
646
694
|
// the cross-principal refresh guard (#220 / #221) lives in ONE place and the two
|
|
647
695
|
// managers can't drift apart. Base64url decoding differs by platform (browser
|
|
@@ -1023,7 +1071,26 @@ class WebSessionManager {
|
|
|
1023
1071
|
await this.clearSessionIfGeneration(generation);
|
|
1024
1072
|
return null;
|
|
1025
1073
|
}
|
|
1026
|
-
|
|
1074
|
+
let refreshed;
|
|
1075
|
+
try {
|
|
1076
|
+
refreshed = await refreshSession(refreshToken, sessionObj.issuer);
|
|
1077
|
+
}
|
|
1078
|
+
catch (refreshErr) {
|
|
1079
|
+
// DS4-0054: only a DEFINITIVE server rejection (401/403) proves the
|
|
1080
|
+
// refresh token is dead. A transport failure - offline, DNS, timeout,
|
|
1081
|
+
// 5xx - is unknown, not proof the session is invalid. Destroying local
|
|
1082
|
+
// state here signs the user out on a network blip (common on mobile),
|
|
1083
|
+
// so RETAIN it and return the existing session; the next request will
|
|
1084
|
+
// retry the refresh. The retention is BOUNDED by the token's own
|
|
1085
|
+
// expiry plus a grace window, so a revoked session - or an issuer 401
|
|
1086
|
+
// a gateway rewrote into a 5xx - cannot keep isAuthenticated() true
|
|
1087
|
+
// forever on a device that never reaches the issuer again.
|
|
1088
|
+
if (isDefinitiveRejection(refreshErr) || !mayRetainAfterTransportFailure(exp)) {
|
|
1089
|
+
await this.clearSessionIfGeneration(generation);
|
|
1090
|
+
return null;
|
|
1091
|
+
}
|
|
1092
|
+
return { address: sessionObj.address, session: sessionObj };
|
|
1093
|
+
}
|
|
1027
1094
|
if ((refreshed === null || refreshed === void 0 ? void 0 : refreshed.idToken) && (refreshed === null || refreshed === void 0 ? void 0 : refreshed.accessToken)) {
|
|
1028
1095
|
const committed = await this.commitRefreshedTokens({ generation, idToken: sessionObj.idToken }, refreshed.idToken, refreshed.accessToken,
|
|
1029
1096
|
// Persist the ROTATED refresh token — the old one is now
|
|
@@ -1044,8 +1111,9 @@ class WebSessionManager {
|
|
|
1044
1111
|
return null;
|
|
1045
1112
|
return { address: newObj.address, session: newObj };
|
|
1046
1113
|
}
|
|
1047
|
-
//
|
|
1048
|
-
//
|
|
1114
|
+
// The server answered but issued no usable tokens - a definitive refusal,
|
|
1115
|
+
// not a transport failure - so clear the stale session to stop retry loops,
|
|
1116
|
+
// but only if it is still the session we set out to refresh.
|
|
1049
1117
|
await this.clearSessionIfGeneration(generation);
|
|
1050
1118
|
return null;
|
|
1051
1119
|
}
|
|
@@ -1431,7 +1499,26 @@ class ReactNativeSessionManager {
|
|
|
1431
1499
|
await this.clearSessionIfGeneration(generation);
|
|
1432
1500
|
return null;
|
|
1433
1501
|
}
|
|
1434
|
-
|
|
1502
|
+
let refreshed;
|
|
1503
|
+
try {
|
|
1504
|
+
refreshed = await refreshSession(refreshToken, sessionObj.issuer);
|
|
1505
|
+
}
|
|
1506
|
+
catch (refreshErr) {
|
|
1507
|
+
// DS4-0054: only a DEFINITIVE server rejection (401/403) proves the
|
|
1508
|
+
// refresh token is dead. A transport failure - offline, DNS, timeout,
|
|
1509
|
+
// 5xx - is unknown, not proof the session is invalid. On mobile these
|
|
1510
|
+
// blips are common, so destroying local state here signs the user out
|
|
1511
|
+
// on a bad network. RETAIN it and return the existing session; the next
|
|
1512
|
+
// request retries the refresh. The retention is BOUNDED by the token's
|
|
1513
|
+
// own expiry plus a grace window, so a revoked session - or an issuer
|
|
1514
|
+
// 401 a gateway rewrote into a 5xx - cannot keep isAuthenticated() true
|
|
1515
|
+
// forever on a device that never reaches the issuer again.
|
|
1516
|
+
if (isDefinitiveRejection(refreshErr) || !mayRetainAfterTransportFailure(exp)) {
|
|
1517
|
+
await this.clearSessionIfGeneration(generation);
|
|
1518
|
+
return null;
|
|
1519
|
+
}
|
|
1520
|
+
return { address: sessionObj.address, session: sessionObj };
|
|
1521
|
+
}
|
|
1435
1522
|
if ((refreshed === null || refreshed === void 0 ? void 0 : refreshed.idToken) && (refreshed === null || refreshed === void 0 ? void 0 : refreshed.accessToken)) {
|
|
1436
1523
|
const committed = await this.commitRefreshedTokens({ generation, idToken: sessionObj.idToken }, refreshed.idToken, refreshed.accessToken, refreshed.refreshToken);
|
|
1437
1524
|
// Superseded means a login or logout landed mid-refresh. Returning
|
|
@@ -1448,8 +1535,9 @@ class ReactNativeSessionManager {
|
|
|
1448
1535
|
return null;
|
|
1449
1536
|
return { address: newObj.address, session: newObj };
|
|
1450
1537
|
}
|
|
1451
|
-
//
|
|
1452
|
-
//
|
|
1538
|
+
// The server answered but issued no usable tokens - a definitive refusal,
|
|
1539
|
+
// not a transport failure - so clear the stale session to stop retry loops,
|
|
1540
|
+
// but only if it is still the session we set out to refresh.
|
|
1453
1541
|
await this.clearSessionIfGeneration(generation);
|
|
1454
1542
|
return null;
|
|
1455
1543
|
}
|
|
@@ -4045,7 +4133,7 @@ class ServerSessionManager {
|
|
|
4045
4133
|
const sessionConfig = (_a = this.sessionConfig) !== null && _a !== void 0 ? _a : undefined;
|
|
4046
4134
|
/* fetch nonce from auth API (bound to the snapshotted app+issuer) */
|
|
4047
4135
|
const nonce = await genAuthNonce(sessionConfig);
|
|
4048
|
-
const { genSolanaMessage } = await Promise.resolve().then(function () { return require('./solana-message-
|
|
4136
|
+
const { genSolanaMessage } = await Promise.resolve().then(function () { return require('./solana-message-Dh9ryO-Y.js'); });
|
|
4049
4137
|
const message = await genSolanaMessage(address, nonce, sessionConfig === null || sessionConfig === void 0 ? void 0 : sessionConfig.appId);
|
|
4050
4138
|
/* sign the message */
|
|
4051
4139
|
const sigBytes = nacl.sign.detached(new TextEncoder().encode(message), kp.secretKey);
|
|
@@ -4417,11 +4505,6 @@ const refreshTransactions = new Map();
|
|
|
4417
4505
|
function transactionKey(scope, appId, generation, refreshToken) {
|
|
4418
4506
|
return `${scope}:${appId}:${generation !== null && generation !== void 0 ? generation : 'none'}:${tokenFingerprint(refreshToken)}`;
|
|
4419
4507
|
}
|
|
4420
|
-
function isDefinitiveRejection(error) {
|
|
4421
|
-
var _a;
|
|
4422
|
-
const status = (_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.status;
|
|
4423
|
-
return status === 401 || status === 403;
|
|
4424
|
-
}
|
|
4425
4508
|
async function readSessionParts(isServer) {
|
|
4426
4509
|
var _a, _b, _c, _d, _e, _f;
|
|
4427
4510
|
if (isServer) {
|
|
@@ -6818,7 +6901,7 @@ async function doReconnectWithNewAuth() {
|
|
|
6818
6901
|
console.warn('[WS v2] Failed to clear HTTP read cache on auth change:', error);
|
|
6819
6902
|
}
|
|
6820
6903
|
try {
|
|
6821
|
-
const { reconnectRealtimeStoreWithNewAuth } = await Promise.resolve().then(function () { return require('./realtime-store-
|
|
6904
|
+
const { reconnectRealtimeStoreWithNewAuth } = await Promise.resolve().then(function () { return require('./realtime-store-BDpIOFMH.js'); });
|
|
6822
6905
|
await reconnectRealtimeStoreWithNewAuth();
|
|
6823
6906
|
}
|
|
6824
6907
|
catch (error) {
|
|
@@ -8261,7 +8344,7 @@ async function setMany(many, options) {
|
|
|
8261
8344
|
'Nothing was signed or submitted.');
|
|
8262
8345
|
}
|
|
8263
8346
|
const solanaChain = workerChain;
|
|
8264
|
-
const { handlePreBuiltTransaction } = await Promise.resolve().then(function () { return require('./solana-operations-
|
|
8347
|
+
const { handlePreBuiltTransaction } = await Promise.resolve().then(function () { return require('./solana-operations-DGEGqFd9.js'); });
|
|
8265
8348
|
const solanaOutcome = await handlePreBuiltTransaction(curTx, authProvider, options, expectedWritePaths);
|
|
8266
8349
|
// `shouldSubmitTx: false` signed WITHOUT sending — no signature exists,
|
|
8267
8350
|
// and there is nothing to mirror, so no synchronization is attempted.
|
|
@@ -9702,7 +9785,7 @@ function toMillis(seconds) {
|
|
|
9702
9785
|
|
|
9703
9786
|
/** SIWS message creation is async already, so its onchain module can be lazy. */
|
|
9704
9787
|
async function genSolanaMessage(address, nonce) {
|
|
9705
|
-
const implementation = await Promise.resolve().then(function () { return require('./solana-message-
|
|
9788
|
+
const implementation = await Promise.resolve().then(function () { return require('./solana-message-Dh9ryO-Y.js'); });
|
|
9706
9789
|
return implementation.genSolanaMessage(address, nonce);
|
|
9707
9790
|
}
|
|
9708
9791
|
|
|
@@ -10263,4 +10346,4 @@ exports.toMillis = toMillis;
|
|
|
10263
10346
|
exports.toSeconds = toSeconds;
|
|
10264
10347
|
exports.waitForSetResult = waitForSetResult;
|
|
10265
10348
|
exports.withEffects = withEffects;
|
|
10266
|
-
//# sourceMappingURL=index-
|
|
10349
|
+
//# sourceMappingURL=index-yT8ZfzrL.js.map
|