@bounded-sh/core 0.0.97 → 0.0.99
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 +1 -0
- package/dist/{index-D-dJjY-Q.js → index-B8PpRj6x.js} +159 -108
- package/dist/index-B8PpRj6x.js.map +1 -0
- package/dist/{index-BDW82Uxu.mjs → index-DW5yMQwV.mjs} +159 -108
- package/dist/index-DW5yMQwV.mjs.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{realtime-store-DTS5OLm2.mjs → realtime-store-CeDT7vJF.mjs} +2 -2
- package/dist/{realtime-store-DTS5OLm2.mjs.map → realtime-store-CeDT7vJF.mjs.map} +1 -1
- package/dist/{realtime-store-Dytq73zO.js → realtime-store-D7mJ7rTj.js} +2 -2
- package/dist/{realtime-store-Dytq73zO.js.map → realtime-store-D7mJ7rTj.js.map} +1 -1
- package/dist/{solana-message-j1s9osZD.mjs → solana-message-BGkYFU3C.mjs} +2 -2
- package/dist/{solana-message-j1s9osZD.mjs.map → solana-message-BGkYFU3C.mjs.map} +1 -1
- package/dist/{solana-message-Cww3s9HW.js → solana-message-w2FxOkFA.js} +2 -2
- package/dist/{solana-message-Cww3s9HW.js.map → solana-message-w2FxOkFA.js.map} +1 -1
- package/dist/{solana-operations-da26Ject.mjs → solana-operations-CHgW7Pue.mjs} +4 -4
- package/dist/{solana-operations-da26Ject.mjs.map → solana-operations-CHgW7Pue.mjs.map} +1 -1
- package/dist/{solana-operations-SR1En23U.js → solana-operations-DJQDYdrk.js} +3 -3
- package/dist/{solana-operations-SR1En23U.js.map → solana-operations-DJQDYdrk.js.map} +1 -1
- package/dist/{solana-v1-operations-DRm8TzzJ.js → solana-v1-operations-BF2rjG23.js} +3 -3
- package/dist/{solana-v1-operations-DRm8TzzJ.js.map → solana-v1-operations-BF2rjG23.js.map} +1 -1
- package/dist/{solana-v1-operations-BQQfm-ni.mjs → solana-v1-operations-BPn-poee.mjs} +3 -3
- package/dist/{solana-v1-operations-BQQfm-ni.mjs.map → solana-v1-operations-BPn-poee.mjs.map} +1 -1
- package/dist/utils/web-session-manager.d.ts +7 -2
- package/package.json +8 -2
- package/dist/index-BDW82Uxu.mjs.map +0 -1
- package/dist/index-D-dJjY-Q.js.map +0 -1
package/dist/client/config.d.ts
CHANGED
|
@@ -185,6 +185,7 @@ export declare function getWebhookKeysUrl(): string | undefined;
|
|
|
185
185
|
* `verifyWebhook` to bind a webhook to THIS app by default (#072) rather than trusting any
|
|
186
186
|
* app's shared-key-signed delivery.
|
|
187
187
|
*/
|
|
188
|
+
export declare function getConfiguredSessionNamespace(): string;
|
|
188
189
|
export declare function getConfiguredAppId(): string | undefined;
|
|
189
190
|
/**
|
|
190
191
|
* True when init() configured a Bounded network (the Cloudflare-native stack).
|
|
@@ -89,6 +89,9 @@ function getWebhookKeysUrl() {
|
|
|
89
89
|
* `verifyWebhook` to bind a webhook to THIS app by default (#072) rather than trusting any
|
|
90
90
|
* app's shared-key-signed delivery.
|
|
91
91
|
*/
|
|
92
|
+
function getConfiguredSessionNamespace() {
|
|
93
|
+
return JSON.stringify([(clientConfig.humanAuthApiUrl || "https://auth.bounded.sh").replace(/\/$/, ""), clientConfig.appId]);
|
|
94
|
+
}
|
|
92
95
|
function getConfiguredAppId() {
|
|
93
96
|
return clientConfig.appId || undefined;
|
|
94
97
|
}
|
|
@@ -927,6 +930,30 @@ function decideCommit(current, expected, next) {
|
|
|
927
930
|
}
|
|
928
931
|
|
|
929
932
|
class WebSessionManager {
|
|
933
|
+
static scopedStorageKey() {
|
|
934
|
+
return 'bounded_session_storage:v2:' + getConfiguredSessionNamespace();
|
|
935
|
+
}
|
|
936
|
+
static get BOUNDED_SESSION_STORAGE_KEY() {
|
|
937
|
+
const scoped = this.scopedStorageKey();
|
|
938
|
+
return localStorage.getItem(scoped + ':scope') === 'app' || localStorage.getItem(scoped) !== null
|
|
939
|
+
? scoped : 'bounded_session_storage';
|
|
940
|
+
}
|
|
941
|
+
static logoutKey() { return this.scopedStorageKey() + ':logout'; }
|
|
942
|
+
/** Capture before login's network work; checked under the session mutation lock. */
|
|
943
|
+
static captureSessionEstablishment() {
|
|
944
|
+
var _a, _b;
|
|
945
|
+
const namespace = getConfiguredSessionNamespace();
|
|
946
|
+
const epoch = this.establishmentEpoch;
|
|
947
|
+
const generation = (_b = (_a = this.readRaw()) === null || _a === void 0 ? void 0 : _a.sessionGeneration) !== null && _b !== void 0 ? _b : null;
|
|
948
|
+
const logout = localStorage.getItem(this.logoutKey());
|
|
949
|
+
return () => {
|
|
950
|
+
var _a, _b;
|
|
951
|
+
if (namespace !== getConfiguredSessionNamespace() || epoch !== this.establishmentEpoch
|
|
952
|
+
|| generation !== ((_b = (_a = this.readRaw()) === null || _a === void 0 ? void 0 : _a.sessionGeneration) !== null && _b !== void 0 ? _b : null)
|
|
953
|
+
|| logout !== localStorage.getItem(this.logoutKey()))
|
|
954
|
+
throw new Error('session_establishment_superseded');
|
|
955
|
+
};
|
|
956
|
+
}
|
|
930
957
|
/** Raw read of the stored blob. Callers inside the lock use this, never a cached copy. */
|
|
931
958
|
static readRaw() {
|
|
932
959
|
if (typeof window === "undefined")
|
|
@@ -986,10 +1013,11 @@ class WebSessionManager {
|
|
|
986
1013
|
/* ------------------------------------------------------------------ */
|
|
987
1014
|
/* STORE */
|
|
988
1015
|
/* ------------------------------------------------------------------ */
|
|
989
|
-
static async storeSession(address, accessToken, idToken, refreshToken, issuer) {
|
|
1016
|
+
static async storeSession(address, accessToken, idToken, refreshToken, issuer, beforeCommit) {
|
|
990
1017
|
var _a;
|
|
991
1018
|
if (typeof window === "undefined")
|
|
992
1019
|
return;
|
|
1020
|
+
let appScoped = false;
|
|
993
1021
|
// JWT-wallet binding: refuse to store a session whose idToken is bound
|
|
994
1022
|
// to a different wallet than `address`. Prevents races that would otherwise
|
|
995
1023
|
// leave localStorage with mismatched address/token state.
|
|
@@ -997,6 +1025,7 @@ class WebSessionManager {
|
|
|
997
1025
|
const payloadB64 = idToken.split(".")[1];
|
|
998
1026
|
if (payloadB64) {
|
|
999
1027
|
const payload = JSON.parse(this.decodeBase64Url(payloadB64));
|
|
1028
|
+
appScoped = payload["custom:walletScope"] === "app";
|
|
1000
1029
|
const tokenWallet = payload["custom:walletAddress"];
|
|
1001
1030
|
if (tokenWallet && tokenWallet !== address) {
|
|
1002
1031
|
throw new Error(`[WebSessionManager] Refusing to store session: address (${address}) does not match idToken custom:walletAddress (${tokenWallet})`);
|
|
@@ -1014,6 +1043,9 @@ class WebSessionManager {
|
|
|
1014
1043
|
}
|
|
1015
1044
|
const config = await getConfig();
|
|
1016
1045
|
const currentAppId = config.appId;
|
|
1046
|
+
const namespace = getConfiguredSessionNamespace();
|
|
1047
|
+
const storageKey = appScoped || localStorage.getItem(this.scopedStorageKey() + ":scope") === "app"
|
|
1048
|
+
? this.scopedStorageKey() : "bounded_session_storage";
|
|
1017
1049
|
// The auth issuer base that minted this session, so silent refresh POSTs to
|
|
1018
1050
|
// the RIGHT /session/refresh. Email overrides with humanAuthApiUrl; all
|
|
1019
1051
|
// wallet/guest/signature flows default to authApiUrl (the bounded-auth issuer).
|
|
@@ -1021,12 +1053,33 @@ class WebSessionManager {
|
|
|
1021
1053
|
// A login always starts a NEW generation. Refresh commits preserve it, so
|
|
1022
1054
|
// "same generation" means "same login", which is what lets a post-refresh
|
|
1023
1055
|
// retry be told apart from a re-login by the same wallet.
|
|
1056
|
+
const generation = mintSessionGeneration();
|
|
1024
1057
|
await withSessionLock(() => {
|
|
1025
|
-
|
|
1058
|
+
beforeCommit === null || beforeCommit === void 0 ? void 0 : beforeCommit();
|
|
1059
|
+
if (namespace !== getConfiguredSessionNamespace())
|
|
1060
|
+
throw new Error("session_establishment_superseded");
|
|
1061
|
+
++this.establishmentEpoch;
|
|
1062
|
+
if (appScoped)
|
|
1063
|
+
localStorage.setItem(this.scopedStorageKey() + ":scope", "app");
|
|
1064
|
+
localStorage.setItem(storageKey, JSON.stringify(Object.assign({ address,
|
|
1026
1065
|
accessToken,
|
|
1027
1066
|
idToken,
|
|
1028
|
-
refreshToken, appId: currentAppId, sessionGeneration:
|
|
1067
|
+
refreshToken, appId: currentAppId, sessionGeneration: generation }, (sessionIssuer ? { issuer: sessionIssuer } : {}))));
|
|
1029
1068
|
});
|
|
1069
|
+
// Adoption can fail after persistence (for example, logout while the
|
|
1070
|
+
// provider is loading). Remove only this incomplete login, even if the
|
|
1071
|
+
// caller has since switched app configuration or another tab signed in.
|
|
1072
|
+
return async () => {
|
|
1073
|
+
const removed = await withSessionLock(() => {
|
|
1074
|
+
const raw = localStorage.getItem(storageKey);
|
|
1075
|
+
if (!raw || JSON.parse(raw).sessionGeneration !== generation)
|
|
1076
|
+
return false;
|
|
1077
|
+
localStorage.removeItem(storageKey);
|
|
1078
|
+
return true;
|
|
1079
|
+
});
|
|
1080
|
+
if (removed)
|
|
1081
|
+
notifySessionCleared();
|
|
1082
|
+
};
|
|
1030
1083
|
}
|
|
1031
1084
|
/* ------------------------------------------------------------------ */
|
|
1032
1085
|
/* GET */
|
|
@@ -1141,10 +1194,14 @@ class WebSessionManager {
|
|
|
1141
1194
|
static async clearSession() {
|
|
1142
1195
|
if (typeof window === "undefined")
|
|
1143
1196
|
return;
|
|
1197
|
+
++this.establishmentEpoch;
|
|
1198
|
+
const key = this.BOUNDED_SESSION_STORAGE_KEY;
|
|
1199
|
+
const logoutKey = this.logoutKey();
|
|
1144
1200
|
const removed = await withSessionLock(() => {
|
|
1145
|
-
|
|
1201
|
+
localStorage.setItem(logoutKey, mintSessionGeneration());
|
|
1202
|
+
if (localStorage.getItem(key) === null)
|
|
1146
1203
|
return false;
|
|
1147
|
-
localStorage.removeItem(
|
|
1204
|
+
localStorage.removeItem(key);
|
|
1148
1205
|
return true;
|
|
1149
1206
|
});
|
|
1150
1207
|
// Only announce a clear that actually happened, so a no-op cannot sign out
|
|
@@ -1291,7 +1348,7 @@ class WebSessionManager {
|
|
|
1291
1348
|
return { outcome: result.outcome, session: result.session };
|
|
1292
1349
|
}
|
|
1293
1350
|
}
|
|
1294
|
-
WebSessionManager.
|
|
1351
|
+
WebSessionManager.establishmentEpoch = 0;
|
|
1295
1352
|
|
|
1296
1353
|
let _config = null;
|
|
1297
1354
|
class ReactNativeSessionManager {
|
|
@@ -4133,7 +4190,7 @@ class ServerSessionManager {
|
|
|
4133
4190
|
const sessionConfig = (_a = this.sessionConfig) !== null && _a !== void 0 ? _a : undefined;
|
|
4134
4191
|
/* fetch nonce from auth API (bound to the snapshotted app+issuer) */
|
|
4135
4192
|
const nonce = await genAuthNonce(sessionConfig);
|
|
4136
|
-
const { genSolanaMessage } = await Promise.resolve().then(function () { return require('./solana-message-
|
|
4193
|
+
const { genSolanaMessage } = await Promise.resolve().then(function () { return require('./solana-message-w2FxOkFA.js'); });
|
|
4137
4194
|
const message = await genSolanaMessage(address, nonce, sessionConfig === null || sessionConfig === void 0 ? void 0 : sessionConfig.appId);
|
|
4138
4195
|
/* sign the message */
|
|
4139
4196
|
const sigBytes = nacl.sign.detached(new TextEncoder().encode(message), kp.secretKey);
|
|
@@ -5241,7 +5298,6 @@ const WS_V2_PATH = '/ws/v2';
|
|
|
5241
5298
|
let browserReconnectHooksAttached = false;
|
|
5242
5299
|
let lastBrowserTriggeredReconnectAt = 0;
|
|
5243
5300
|
let reconnectInProgress = null;
|
|
5244
|
-
let ambientAuthFailure = null;
|
|
5245
5301
|
// ============ Helper Functions ============
|
|
5246
5302
|
function generateSubscriptionId() {
|
|
5247
5303
|
return `sub_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
@@ -5369,34 +5425,6 @@ function getTokenExpirationTime(token) {
|
|
|
5369
5425
|
return null;
|
|
5370
5426
|
}
|
|
5371
5427
|
}
|
|
5372
|
-
/**
|
|
5373
|
-
* Latch a failure so a dead session does not re-hammer /session/refresh every tick.
|
|
5374
|
-
*
|
|
5375
|
-
* NOT latched when the failure came with the session being cleared or replaced.
|
|
5376
|
-
* The clear notifies auth-state listeners, which kicks a reconnect that resets this
|
|
5377
|
-
* — and if we re-latched afterwards, `getConnectionAuthToken` would keep throwing
|
|
5378
|
-
* even though storage is now empty, so an ordinary expiry left WebSocket
|
|
5379
|
-
* acquisition permanently broken instead of falling back to an anonymous socket.
|
|
5380
|
-
*/
|
|
5381
|
-
function rememberAmbientAuthFailure(error) {
|
|
5382
|
-
if ((error === null || error === void 0 ? void 0 : error.code) === AUTH_CHANGED_CODE)
|
|
5383
|
-
return error;
|
|
5384
|
-
// The session is gone: the right next move is an anonymous connection, not a
|
|
5385
|
-
// remembered failure.
|
|
5386
|
-
if (!getIdTokenSync())
|
|
5387
|
-
return error;
|
|
5388
|
-
ambientAuthFailure = error;
|
|
5389
|
-
return error;
|
|
5390
|
-
}
|
|
5391
|
-
/** Best-effort synchronous peek, only for deciding whether a session still exists. */
|
|
5392
|
-
function getIdTokenSync() {
|
|
5393
|
-
try {
|
|
5394
|
-
return getActiveSessionManager().getIdToken();
|
|
5395
|
-
}
|
|
5396
|
-
catch (_a) {
|
|
5397
|
-
return null;
|
|
5398
|
-
}
|
|
5399
|
-
}
|
|
5400
5428
|
function failConnectionAuth(connection, error) {
|
|
5401
5429
|
connection.authFailure = error;
|
|
5402
5430
|
connection.pendingAuthToken = null;
|
|
@@ -5451,8 +5479,8 @@ function scheduleTokenRefresh(connection, isServer) {
|
|
|
5451
5479
|
console.info('[WS v2] Token refreshed successfully, stored for next connection');
|
|
5452
5480
|
}
|
|
5453
5481
|
catch (refreshError) {
|
|
5454
|
-
const definitive = (refreshError === null || refreshError === void 0 ? void 0 : refreshError.code) ===
|
|
5455
|
-
|| (
|
|
5482
|
+
const definitive = (refreshError === null || refreshError === void 0 ? void 0 : refreshError.code) === AUTH_CHANGED_CODE
|
|
5483
|
+
|| !await getIdToken(isServer);
|
|
5456
5484
|
if (definitive) {
|
|
5457
5485
|
// The session is dead or belongs to someone else now. The shared
|
|
5458
5486
|
// helper has already cleared storage and notified auth-state
|
|
@@ -5482,28 +5510,19 @@ function scheduleTokenRefresh(connection, isServer) {
|
|
|
5482
5510
|
* WS-local wrapper around the shared freshness helper.
|
|
5483
5511
|
*
|
|
5484
5512
|
* The shared helper owns expiry classification, the deduplicated
|
|
5485
|
-
* snapshot -> refresh -> commit transaction, and conditional clearing.
|
|
5486
|
-
*
|
|
5487
|
-
* refresh on every reconnect attempt and every 5-minute tick.
|
|
5513
|
+
* snapshot -> refresh -> commit transaction, and conditional clearing. A transient
|
|
5514
|
+
* failure must remain retryable; reconnect backoff bounds automatic attempts.
|
|
5488
5515
|
*/
|
|
5489
5516
|
async function getWsAuthToken(isServer) {
|
|
5490
5517
|
var _a, _b;
|
|
5491
|
-
// A remembered failure short-circuits, but only while a session still exists.
|
|
5492
|
-
// See rememberAmbientAuthFailure: once storage is cleared the correct next move
|
|
5493
|
-
// is an anonymous connection, not a replayed error.
|
|
5494
|
-
if (ambientAuthFailure && getIdTokenSync()) {
|
|
5495
|
-
throw ambientAuthFailure;
|
|
5496
|
-
}
|
|
5497
5518
|
try {
|
|
5498
|
-
|
|
5499
|
-
ambientAuthFailure = null;
|
|
5500
|
-
return token;
|
|
5519
|
+
return await getFreshAuthToken(isServer);
|
|
5501
5520
|
}
|
|
5502
5521
|
catch (error) {
|
|
5503
5522
|
// Status only — the raw axios error carries config.data (the refresh token)
|
|
5504
5523
|
// and must never reach a console or log sink.
|
|
5505
5524
|
console.error('[WS v2] Error acquiring auth token. status:', (_b = (_a = error === null || error === void 0 ? void 0 : error.status) !== null && _a !== void 0 ? _a : error === null || error === void 0 ? void 0 : error.code) !== null && _b !== void 0 ? _b : 'unknown');
|
|
5506
|
-
throw
|
|
5525
|
+
throw normalizeAuthExpiredError(error, 'Authentication expired and refresh failed');
|
|
5507
5526
|
}
|
|
5508
5527
|
}
|
|
5509
5528
|
/**
|
|
@@ -5521,18 +5540,21 @@ async function recoverFromServerAuthRejection(connection) {
|
|
|
5521
5540
|
const rejected = connection.pendingAuthToken;
|
|
5522
5541
|
if (!rejected)
|
|
5523
5542
|
return;
|
|
5524
|
-
//
|
|
5525
|
-
//
|
|
5543
|
+
// Share an in-flight attempt and stop retrying a definitively refused token.
|
|
5544
|
+
// Transient failures may retry on the next socket reconnect, with its backoff.
|
|
5526
5545
|
if (connection.serverRejectedAuthToken === rejected)
|
|
5527
5546
|
return;
|
|
5528
5547
|
connection.serverRejectedAuthToken = rejected;
|
|
5529
5548
|
try {
|
|
5530
5549
|
await getFreshAuthToken(connection.isServer, { force: true });
|
|
5531
5550
|
}
|
|
5532
|
-
catch (
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5551
|
+
catch (error) {
|
|
5552
|
+
if ((error === null || error === void 0 ? void 0 : error.code) !== AUTH_CHANGED_CODE && (error === null || error === void 0 ? void 0 : error.status) !== 401 && (error === null || error === void 0 ? void 0 : error.status) !== 403
|
|
5553
|
+
&& connection.serverRejectedAuthToken === rejected) {
|
|
5554
|
+
connection.serverRejectedAuthToken = null;
|
|
5555
|
+
}
|
|
5556
|
+
// The shared helper clears definitively rejected sessions and preserves
|
|
5557
|
+
// transient failures. Never reuse the failed result as a credential.
|
|
5536
5558
|
}
|
|
5537
5559
|
}
|
|
5538
5560
|
async function getConnectionAuthToken(isServer, authTokenProvider, anonymous) {
|
|
@@ -5543,9 +5565,6 @@ async function getConnectionAuthToken(isServer, authTokenProvider, anonymous) {
|
|
|
5543
5565
|
return null;
|
|
5544
5566
|
}
|
|
5545
5567
|
if (!authTokenProvider) {
|
|
5546
|
-
// getWsAuthToken, not the shared helper directly: the wrapper carries the
|
|
5547
|
-
// reconnect-storm guard that keeps a dead session from re-refreshing on
|
|
5548
|
-
// every reconnect attempt.
|
|
5549
5568
|
return getWsAuthToken(isServer);
|
|
5550
5569
|
}
|
|
5551
5570
|
try {
|
|
@@ -5650,11 +5669,13 @@ function cancelIdleConnectionClose(connection) {
|
|
|
5650
5669
|
connection.idleCloseTimer = null;
|
|
5651
5670
|
}
|
|
5652
5671
|
function closeConnectionNow(connection) {
|
|
5672
|
+
var _a;
|
|
5653
5673
|
if (connections.get(connection.key) !== connection)
|
|
5654
5674
|
return;
|
|
5655
5675
|
if (!connection.ws)
|
|
5656
5676
|
return;
|
|
5657
5677
|
cancelIdleConnectionClose(connection);
|
|
5678
|
+
(_a = connection.authRetryWake) === null || _a === void 0 ? void 0 : _a.call(connection);
|
|
5658
5679
|
if (connection.tokenRefreshTimer) {
|
|
5659
5680
|
clearInterval(connection.tokenRefreshTimer);
|
|
5660
5681
|
connection.tokenRefreshTimer = null;
|
|
@@ -5675,7 +5696,7 @@ function closeConnectionNow(connection) {
|
|
|
5675
5696
|
try {
|
|
5676
5697
|
ws.close();
|
|
5677
5698
|
}
|
|
5678
|
-
catch ( /* already closing */
|
|
5699
|
+
catch ( /* already closing */_b) { /* already closing */ }
|
|
5679
5700
|
}
|
|
5680
5701
|
function scheduleConnectionCloseIfIdle(connection) {
|
|
5681
5702
|
var _a;
|
|
@@ -5761,6 +5782,7 @@ async function getOrCreateConnection(appId, isServer, routePath, authTokenProvid
|
|
|
5761
5782
|
}
|
|
5762
5783
|
}
|
|
5763
5784
|
async function getOrCreateConnectionOnce(appId, isServer, routePath, authTokenProvider, principalKey, principalToken, leaseKey, source) {
|
|
5785
|
+
var _a, _b;
|
|
5764
5786
|
attachBrowserReconnectHooksOnce();
|
|
5765
5787
|
const anonymous = (source === null || source === void 0 ? void 0 : source.anonymous) === true;
|
|
5766
5788
|
// A per-room subscription gets its OWN connection routed to the room DO; all
|
|
@@ -5807,9 +5829,6 @@ async function getOrCreateConnectionOnce(appId, isServer, routePath, authTokenPr
|
|
|
5807
5829
|
// lease also guards this window, but cancelling here avoids a needless
|
|
5808
5830
|
// grace-timer round-trip on the common reuse path.
|
|
5809
5831
|
cancelIdleConnectionClose(connection);
|
|
5810
|
-
if (connection.authFailure) {
|
|
5811
|
-
throw connection.authFailure;
|
|
5812
|
-
}
|
|
5813
5832
|
let currentAuthToken;
|
|
5814
5833
|
try {
|
|
5815
5834
|
currentAuthToken = await getConnectionAuthToken(isServer, authTokenProvider, anonymous);
|
|
@@ -5819,6 +5838,14 @@ async function getOrCreateConnectionOnce(appId, isServer, routePath, authTokenPr
|
|
|
5819
5838
|
failConnectionAuth(connection, authError);
|
|
5820
5839
|
throw authError;
|
|
5821
5840
|
}
|
|
5841
|
+
if (connection.authFailure) {
|
|
5842
|
+
// The preflight recovered, but failConnectionAuth invalidated this
|
|
5843
|
+
// socket's auth state. Authenticate again before replaying any reads.
|
|
5844
|
+
connection.authFailure = null;
|
|
5845
|
+
(_a = connection.authRetryWake) === null || _a === void 0 ? void 0 : _a.call(connection);
|
|
5846
|
+
connection.ws.reconnect();
|
|
5847
|
+
return connection;
|
|
5848
|
+
}
|
|
5822
5849
|
if (connection.isConnected &&
|
|
5823
5850
|
!connection.isAuthenticating &&
|
|
5824
5851
|
!connectionMatchesAuthToken(connection, currentAuthToken)) {
|
|
@@ -5861,50 +5888,59 @@ async function getOrCreateConnectionOnce(appId, isServer, routePath, authTokenPr
|
|
|
5861
5888
|
isServer,
|
|
5862
5889
|
serverRejectedAuthToken: null,
|
|
5863
5890
|
};
|
|
5891
|
+
// Resolve configuration before handing control to reconnecting-websocket.
|
|
5892
|
+
// Its 4.4.0 URL-provider promise has no rejection handler and a rejected
|
|
5893
|
+
// provider permanently holds _connectLock. Credential failures therefore
|
|
5894
|
+
// stay inside this bounded, cancellable acquisition loop.
|
|
5895
|
+
const wsApiUrl = (_b = source === null || source === void 0 ? void 0 : source.wsApiUrl) !== null && _b !== void 0 ? _b : (await getConfig()).wsApiUrl;
|
|
5896
|
+
const wsUrl = new URL(wsApiUrl);
|
|
5897
|
+
wsUrl.pathname = WS_V2_PATH;
|
|
5898
|
+
wsUrl.searchParams.append('appId', appId || (await getConfig()).appId);
|
|
5899
|
+
if (connection.routePath)
|
|
5900
|
+
wsUrl.searchParams.append('routePath', connection.routePath);
|
|
5864
5901
|
connections.set(connKey, connection);
|
|
5865
|
-
// URL provider for reconnection with fresh tokens
|
|
5866
5902
|
const urlProvider = async () => {
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
connection.pendingAuthSnapshot = authSnapshotFromToken(authToken);
|
|
5903
|
-
if (authToken) {
|
|
5904
|
-
// Successful token acquisition — reset failure counter
|
|
5903
|
+
let retryDelay = WS_CONFIG.minReconnectionDelay;
|
|
5904
|
+
while (connections.get(connKey) === connection) {
|
|
5905
|
+
let authToken;
|
|
5906
|
+
try {
|
|
5907
|
+
authToken = initialAuthToken !== undefined
|
|
5908
|
+
? initialAuthToken
|
|
5909
|
+
: await getConnectionAuthToken(isServer, connection.authTokenProvider, connection.anonymous);
|
|
5910
|
+
initialAuthToken = undefined;
|
|
5911
|
+
}
|
|
5912
|
+
catch (error) {
|
|
5913
|
+
if (connections.get(connKey) !== connection)
|
|
5914
|
+
break;
|
|
5915
|
+
const authError = normalizeAuthExpiredError(error, 'Authentication expired and refresh failed');
|
|
5916
|
+
failConnectionAuth(connection, authError);
|
|
5917
|
+
// An error callback may synchronously tear down or replace this
|
|
5918
|
+
// connection. Do not install a retry after its cleanup completed.
|
|
5919
|
+
if (connections.get(connKey) !== connection)
|
|
5920
|
+
break;
|
|
5921
|
+
await new Promise((resolve) => {
|
|
5922
|
+
const wake = () => {
|
|
5923
|
+
clearTimeout(timer);
|
|
5924
|
+
if (connection.authRetryWake === wake)
|
|
5925
|
+
connection.authRetryWake = undefined;
|
|
5926
|
+
resolve();
|
|
5927
|
+
};
|
|
5928
|
+
const timer = setTimeout(wake, retryDelay);
|
|
5929
|
+
connection.authRetryWake = wake;
|
|
5930
|
+
});
|
|
5931
|
+
retryDelay = Math.min(MAX_RECONNECT_DELAY_MS, retryDelay * RECONNECT_DELAY_GROW_FACTOR);
|
|
5932
|
+
continue;
|
|
5933
|
+
}
|
|
5934
|
+
if (connections.get(connKey) !== connection)
|
|
5935
|
+
break;
|
|
5936
|
+
connection.pendingAuthToken = authToken || null;
|
|
5937
|
+
connection.pendingAuthSnapshot = authSnapshotFromToken(authToken);
|
|
5905
5938
|
connection.consecutiveAuthFailures = 0;
|
|
5906
5939
|
connection.authFailure = null;
|
|
5940
|
+
return wsUrl.toString();
|
|
5907
5941
|
}
|
|
5942
|
+
// Teardown has called close() on the wrapper, which prevents creation
|
|
5943
|
+
// of a native socket when this abandoned provider resolves.
|
|
5908
5944
|
return wsUrl.toString();
|
|
5909
5945
|
};
|
|
5910
5946
|
// Create WebSocket connection
|
|
@@ -6713,6 +6749,13 @@ async function subscribeV2(path, subscriptionOptions, roomRoutePath) {
|
|
|
6713
6749
|
authPrincipal: connection.authPrincipal,
|
|
6714
6750
|
};
|
|
6715
6751
|
connection.pendingSubscriptions.set(subscriptionId, pending);
|
|
6752
|
+
// A fast auth refusal can open and close the socket before the first
|
|
6753
|
+
// caller finishes acquiring it. Keep this subscription for recovery,
|
|
6754
|
+
// but report the already observed disconnect without a timeout delay.
|
|
6755
|
+
if (pending.socketGeneration > 0 && socket.readyState === WS_READY_STATE_CLOSED) {
|
|
6756
|
+
rejectPendingSubscriptionConfirmation(connection, subscriptionId, new Error('WebSocket disconnected'));
|
|
6757
|
+
return;
|
|
6758
|
+
}
|
|
6716
6759
|
// Bound a cold connection/auth wait as well. Once the socket becomes
|
|
6717
6760
|
// ready, sendSubscribeWithConfirmation() replaces this timer with a
|
|
6718
6761
|
// fresh per-socket confirmation window.
|
|
@@ -6790,6 +6833,7 @@ async function removeCallbackFromSubscription(connection, subscriptionId, callba
|
|
|
6790
6833
|
* Close all v2 subscriptions.
|
|
6791
6834
|
*/
|
|
6792
6835
|
async function closeAllSubscriptionsV2() {
|
|
6836
|
+
var _a;
|
|
6793
6837
|
const closePromises = [];
|
|
6794
6838
|
for (const [appId, connection] of connections) {
|
|
6795
6839
|
cancelIdleConnectionClose(connection);
|
|
@@ -6816,8 +6860,14 @@ async function closeAllSubscriptionsV2() {
|
|
|
6816
6860
|
ws.close();
|
|
6817
6861
|
}));
|
|
6818
6862
|
}
|
|
6863
|
+
else {
|
|
6864
|
+
// CLOSED may mean a URL/auth acquisition is still pending.
|
|
6865
|
+
// close() also disables the wrapper's future native socket.
|
|
6866
|
+
ws.close();
|
|
6867
|
+
}
|
|
6819
6868
|
}
|
|
6820
6869
|
connections.delete(appId);
|
|
6870
|
+
(_a = connection.authRetryWake) === null || _a === void 0 ? void 0 : _a.call(connection);
|
|
6821
6871
|
}
|
|
6822
6872
|
connectionAcquisitions.clear();
|
|
6823
6873
|
await Promise.all(closePromises);
|
|
@@ -6896,7 +6946,7 @@ async function reconnectWithNewAuthV2() {
|
|
|
6896
6946
|
}
|
|
6897
6947
|
}
|
|
6898
6948
|
async function doReconnectWithNewAuth() {
|
|
6899
|
-
|
|
6949
|
+
var _a;
|
|
6900
6950
|
// SECURITY (H1): the logged-in identity is changing (login / logout / switch).
|
|
6901
6951
|
// Wipe ALL principal-scoped read caches so the new identity can never observe
|
|
6902
6952
|
// data cached for the previous one. Clear both the WS response cache and the
|
|
@@ -6918,7 +6968,7 @@ async function doReconnectWithNewAuth() {
|
|
|
6918
6968
|
console.warn('[WS v2] Failed to clear HTTP read cache on auth change:', error);
|
|
6919
6969
|
}
|
|
6920
6970
|
try {
|
|
6921
|
-
const { reconnectRealtimeStoreWithNewAuth } = await Promise.resolve().then(function () { return require('./realtime-store-
|
|
6971
|
+
const { reconnectRealtimeStoreWithNewAuth } = await Promise.resolve().then(function () { return require('./realtime-store-D7mJ7rTj.js'); });
|
|
6922
6972
|
await reconnectRealtimeStoreWithNewAuth();
|
|
6923
6973
|
}
|
|
6924
6974
|
catch (error) {
|
|
@@ -6943,6 +6993,7 @@ async function doReconnectWithNewAuth() {
|
|
|
6943
6993
|
// Reset auth failure counter — this is a proactive reconnect (login, token refresh)
|
|
6944
6994
|
connection.consecutiveAuthFailures = 0;
|
|
6945
6995
|
connection.authFailure = null;
|
|
6996
|
+
(_a = connection.authRetryWake) === null || _a === void 0 ? void 0 : _a.call(connection);
|
|
6946
6997
|
// Close the WebSocket (this triggers reconnection in ReconnectingWebSocket)
|
|
6947
6998
|
// We use reconnect() which will close and re-open with fresh URL (including new token)
|
|
6948
6999
|
try {
|
|
@@ -8368,7 +8419,7 @@ async function setMany(many, options) {
|
|
|
8368
8419
|
'Nothing was signed or submitted.');
|
|
8369
8420
|
}
|
|
8370
8421
|
const solanaChain = workerChain;
|
|
8371
|
-
const { handlePreBuiltTransaction, buildExpectedWriteIntent } = await Promise.resolve().then(function () { return require('./solana-operations-
|
|
8422
|
+
const { handlePreBuiltTransaction, buildExpectedWriteIntent } = await Promise.resolve().then(function () { return require('./solana-operations-DJQDYdrk.js'); });
|
|
8372
8423
|
// OA-0001: derive the expected intent from OUR OWN request (the one
|
|
8373
8424
|
// ground truth a tampered builder response cannot change) so the
|
|
8374
8425
|
// validator can reject any transaction whose operation semantics
|
|
@@ -9853,7 +9904,7 @@ function toMillis(seconds) {
|
|
|
9853
9904
|
|
|
9854
9905
|
/** SIWS message creation is async already, so its onchain module can be lazy. */
|
|
9855
9906
|
async function genSolanaMessage(address, nonce) {
|
|
9856
|
-
const implementation = await Promise.resolve().then(function () { return require('./solana-message-
|
|
9907
|
+
const implementation = await Promise.resolve().then(function () { return require('./solana-message-w2FxOkFA.js'); });
|
|
9857
9908
|
return implementation.genSolanaMessage(address, nonce);
|
|
9858
9909
|
}
|
|
9859
9910
|
|
|
@@ -10496,4 +10547,4 @@ exports.toMillis = toMillis;
|
|
|
10496
10547
|
exports.toSeconds = toSeconds;
|
|
10497
10548
|
exports.waitForSetResult = waitForSetResult;
|
|
10498
10549
|
exports.withEffects = withEffects;
|
|
10499
|
-
//# sourceMappingURL=index-
|
|
10550
|
+
//# sourceMappingURL=index-B8PpRj6x.js.map
|