@bounded-sh/core 0.0.55 → 0.0.57
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 +20 -0
- package/dist/client/live.d.ts +9 -0
- package/dist/{index-BXvTkFw2.js → index-B30-XpaX.js} +194 -38
- package/dist/index-B30-XpaX.js.map +1 -0
- package/dist/{index-qcTp-5Mt.mjs → index-DYbzK8YT.mjs} +194 -39
- package/dist/index-DYbzK8YT.mjs.map +1 -0
- package/dist/index.d.ts +2 -3
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{realtime-store-BzKEmyxq.mjs → realtime-store-BSuV-Vyr.mjs} +2 -2
- package/dist/{realtime-store-BzKEmyxq.mjs.map → realtime-store-BSuV-Vyr.mjs.map} +1 -1
- package/dist/{realtime-store-DmzboN-H.js → realtime-store-BpQsQYhl.js} +2 -2
- package/dist/{realtime-store-DmzboN-H.js.map → realtime-store-BpQsQYhl.js.map} +1 -1
- package/dist/{sol-utils-oBMCMbe_.js → sol-utils-Cwxyl2h6.js} +2 -13
- package/dist/sol-utils-Cwxyl2h6.js.map +1 -0
- package/dist/{sol-utils-BhjxvQUj.mjs → sol-utils-DVjPeoMy.mjs} +2 -13
- package/dist/sol-utils-DVjPeoMy.mjs.map +1 -0
- package/dist/{solana-message-XxVgbtp2.mjs → solana-message-C2_VvNX7.mjs} +2 -2
- package/dist/{solana-message-XxVgbtp2.mjs.map → solana-message-C2_VvNX7.mjs.map} +1 -1
- package/dist/{solana-message-I9w1TbAj.js → solana-message-DKOPPJwk.js} +2 -2
- package/dist/{solana-message-I9w1TbAj.js.map → solana-message-DKOPPJwk.js.map} +1 -1
- package/dist/{solana-operations-BUJkyVxN.js → solana-operations-BOKHbEuy.js} +18 -6
- package/dist/solana-operations-BOKHbEuy.js.map +1 -0
- package/dist/{solana-operations-ehEsfO5g.mjs → solana-operations-CuYFYlyN.mjs} +18 -6
- package/dist/solana-operations-CuYFYlyN.mjs.map +1 -0
- package/dist/utils/server-session-manager.d.ts +1 -0
- package/dist/utils/session-binding.d.ts +17 -0
- package/dist/utils/sol/{poof4b5pk1L9tmThvBmaABjcyjfhFGbMbQP5BXk2QZpDevnet-program.d.ts → openTv7fbpYSseNHYmCZFZ1CZgj4r8D9fKNgEz1qo6FDevnet-program.d.ts} +97 -113
- package/dist/utils/sol/{poof4b5pk1L9tmThvBmaABjcyjfhFGbMbQP5BXk2QZpMainnet-program.d.ts → openTv7fbpYSseNHYmCZFZ1CZgj4r8D9fKNgEz1qo6FMainnet-program.d.ts} +97 -113
- package/package.json +3 -2
- package/dist/index-BXvTkFw2.js.map +0 -1
- package/dist/index-qcTp-5Mt.mjs.map +0 -1
- package/dist/sol-utils-BhjxvQUj.mjs.map +0 -1
- package/dist/sol-utils-oBMCMbe_.js.map +0 -1
- package/dist/solana-operations-BUJkyVxN.js.map +0 -1
- package/dist/solana-operations-ehEsfO5g.mjs.map +0 -1
- package/dist/utils/sol/taro6CvKqwrYrDc16ufYgzQ2NZcyyVKStffbtudrhRuDevnet-program.d.ts +0 -1161
package/dist/client/config.d.ts
CHANGED
|
@@ -92,6 +92,18 @@ export interface ClientConfig {
|
|
|
92
92
|
network?: string | null;
|
|
93
93
|
rpcUrl?: string | null;
|
|
94
94
|
};
|
|
95
|
+
/** LOGIN MODE for the unified login widget (default 'bounded'). 'bounded' runs
|
|
96
|
+
* the existing Better Auth email/social/OIDC flows. 'turnkey' opts the email
|
|
97
|
+
* lane into Turnkey-native OTP INLINE in the widget (no second Bounded OTP);
|
|
98
|
+
* social + wallet lanes are unchanged. A per-call openBoundedWidget({ authMode })
|
|
99
|
+
* can override this. */
|
|
100
|
+
authMode?: 'bounded' | 'turnkey';
|
|
101
|
+
/** SITE POLICY: require an email on file for every user (default false). Native
|
|
102
|
+
* wallet login carries no email, so when true the unified widget suppresses the
|
|
103
|
+
* "Continue with wallet" lane (email/social remain). Overrides walletLogin and
|
|
104
|
+
* any per-call `wallet: true`. Set `walletLogin: false` to hide the wallet lane
|
|
105
|
+
* without requiring email. */
|
|
106
|
+
requireEmail?: boolean;
|
|
95
107
|
/** Solana WALLET LOGIN provider configuration (advanced). Passing this object
|
|
96
108
|
* ALSO opts the app into wallet login (equivalent to walletLogin). The slim,
|
|
97
109
|
* dependency-free "connect wallet" path rides the injected Solana provider a
|
|
@@ -141,6 +153,14 @@ export declare const BOUNDED_NETWORKS: Record<string, BoundedEndpoints>;
|
|
|
141
153
|
* fail-closed production default (audit SDK LOW-7).
|
|
142
154
|
*/
|
|
143
155
|
export declare function getWebhookKeysUrl(): string | undefined;
|
|
156
|
+
/**
|
|
157
|
+
* The app id configured via `init({ appId })`, or `undefined` when none has been set.
|
|
158
|
+
* Synchronous + non-blocking on purpose (mirrors {@link getWebhookKeysUrl}): a pure webhook
|
|
159
|
+
* receiver may never call `init()`, and `getConfig()` would block on initialization. Used by
|
|
160
|
+
* `verifyWebhook` to bind a webhook to THIS app by default (#072) rather than trusting any
|
|
161
|
+
* app's shared-key-signed delivery.
|
|
162
|
+
*/
|
|
163
|
+
export declare function getConfiguredAppId(): string | undefined;
|
|
144
164
|
/**
|
|
145
165
|
* True when init() configured a Bounded network (the Cloudflare-native stack).
|
|
146
166
|
* Synchronous + non-blocking. Used to route reads/writes that behave differently
|
package/dist/client/live.d.ts
CHANGED
|
@@ -21,6 +21,8 @@ export type LiveStatusOptions = {
|
|
|
21
21
|
timeoutMs?: number;
|
|
22
22
|
/** Extra headers (advanced/testing). */
|
|
23
23
|
headers?: Record<string, string>;
|
|
24
|
+
/** @internal Per-call auth override used by server WalletClient. */
|
|
25
|
+
_overrides?: RequestOverrides;
|
|
24
26
|
};
|
|
25
27
|
export type LiveStopReason = 'idle' | 'lifetime' | 'error' | 'manual' | 'evicted' | string;
|
|
26
28
|
export type LiveStatus = {
|
|
@@ -71,6 +73,13 @@ export declare function intent(roomPath: string, intent: unknown, opts?: LiveInt
|
|
|
71
73
|
* This is diagnostic/ops surface. It reports whether the room facet exists,
|
|
72
74
|
* whether it is currently ticking, why it last stopped, which module etag is
|
|
73
75
|
* loaded, and the current generation used after terminal restarts.
|
|
76
|
+
*
|
|
77
|
+
* Auth: status attaches the caller's session token WHEN ONE EXISTS (mirroring
|
|
78
|
+
* `intent`), but does NOT require it. The server gates the DETAILED body on the
|
|
79
|
+
* room's read rule — an authenticated caller who can read the room gets full
|
|
80
|
+
* telemetry; an anonymous / unauthorized caller gets only the slim liveness
|
|
81
|
+
* shape `{available, started, module}` (#102). The caller's own `opts.headers`
|
|
82
|
+
* may never smuggle in Authorization: only the derived session token sets it.
|
|
74
83
|
*/
|
|
75
84
|
export declare function status(roomPath: string, opts?: LiveStatusOptions): Promise<LiveStatus>;
|
|
76
85
|
export type SubscribeViewOptions = {
|
|
@@ -82,6 +82,16 @@ function getWebhookKeysUrl() {
|
|
|
82
82
|
return undefined;
|
|
83
83
|
return host.replace(/\/$/, '') + '/.well-known/bounded-webhook-keys.json';
|
|
84
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* The app id configured via `init({ appId })`, or `undefined` when none has been set.
|
|
87
|
+
* Synchronous + non-blocking on purpose (mirrors {@link getWebhookKeysUrl}): a pure webhook
|
|
88
|
+
* receiver may never call `init()`, and `getConfig()` would block on initialization. Used by
|
|
89
|
+
* `verifyWebhook` to bind a webhook to THIS app by default (#072) rather than trusting any
|
|
90
|
+
* app's shared-key-signed delivery.
|
|
91
|
+
*/
|
|
92
|
+
function getConfiguredAppId() {
|
|
93
|
+
return clientConfig.appId || undefined;
|
|
94
|
+
}
|
|
85
95
|
/**
|
|
86
96
|
* True when init() configured a Bounded network (the Cloudflare-native stack).
|
|
87
97
|
* Synchronous + non-blocking. Used to route reads/writes that behave differently
|
|
@@ -2795,19 +2805,28 @@ function resolveIssuerBase(issuer, cfg) {
|
|
|
2795
2805
|
return want;
|
|
2796
2806
|
return norm(cfg.authApiUrl);
|
|
2797
2807
|
}
|
|
2798
|
-
|
|
2808
|
+
// #062: key the auth client by its baseURL. The old single memoized client froze the FIRST
|
|
2809
|
+
// environment's authApiUrl, so after re-initializing the SDK to a different issuer (staging->
|
|
2810
|
+
// prod, or a custom issuer) the mint calls (/nonce, /auth/nonce, /session) kept hitting the
|
|
2811
|
+
// original address forever - a "works only after a full reload" login bug, and in a multi-tenant
|
|
2812
|
+
// process one config's address could leak into another's. refresh/revoke already resolve per
|
|
2813
|
+
// call via resolveIssuerBase; this makes minting do the same while still caching per URL.
|
|
2814
|
+
const axiosClientsByUrl = new Map();
|
|
2799
2815
|
async function getAxiosAuthClient() {
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2816
|
+
const config = await getConfig();
|
|
2817
|
+
const baseURL = config.authApiUrl || '';
|
|
2818
|
+
let client = axiosClientsByUrl.get(baseURL);
|
|
2819
|
+
if (!client) {
|
|
2820
|
+
client = axios.create({
|
|
2821
|
+
baseURL,
|
|
2804
2822
|
headers: {
|
|
2805
2823
|
'Content-Type': 'application/json',
|
|
2806
2824
|
},
|
|
2807
2825
|
timeout: 30000, // 30s timeout, matching makeApiRequest
|
|
2808
2826
|
});
|
|
2827
|
+
axiosClientsByUrl.set(baseURL, client);
|
|
2809
2828
|
}
|
|
2810
|
-
return
|
|
2829
|
+
return client;
|
|
2811
2830
|
}
|
|
2812
2831
|
async function genAuthNonce() {
|
|
2813
2832
|
const client = await getAxiosAuthClient();
|
|
@@ -2972,6 +2991,44 @@ async function revokeSession(refreshToken, issuer) {
|
|
|
2972
2991
|
async function signSessionCreateMessage(_signMessageFunction) {
|
|
2973
2992
|
}
|
|
2974
2993
|
|
|
2994
|
+
// Shared session-binding guard for the web + React Native session managers, so
|
|
2995
|
+
// the cross-principal refresh guard (#220 / #221) lives in ONE place and the two
|
|
2996
|
+
// managers can't drift apart. Base64url decoding differs by platform (browser
|
|
2997
|
+
// atob vs a configured decoder), so the caller passes its own decoder in.
|
|
2998
|
+
/**
|
|
2999
|
+
* Extract the `custom:walletAddress` claim from a JWT idToken. Returns undefined
|
|
3000
|
+
* when the token has no wallet claim or cannot be decoded.
|
|
3001
|
+
*/
|
|
3002
|
+
function idTokenWalletAddress(idToken, decodeBase64Url) {
|
|
3003
|
+
try {
|
|
3004
|
+
const payloadB64 = idToken.split(".")[1];
|
|
3005
|
+
if (!payloadB64)
|
|
3006
|
+
return undefined;
|
|
3007
|
+
const wallet = JSON.parse(decodeBase64Url(payloadB64))["custom:walletAddress"];
|
|
3008
|
+
return typeof wallet === "string" && wallet.length > 0 ? wallet : undefined;
|
|
3009
|
+
}
|
|
3010
|
+
catch (_a) {
|
|
3011
|
+
return undefined;
|
|
3012
|
+
}
|
|
3013
|
+
}
|
|
3014
|
+
/**
|
|
3015
|
+
* Whether a refreshed idToken may be grafted onto the CURRENTLY-stored session.
|
|
3016
|
+
*
|
|
3017
|
+
* A silent refresh started for user A can complete after storage was replaced
|
|
3018
|
+
* with user B (a login, guest→wallet upgrade, or a cross-tab login). Overwriting
|
|
3019
|
+
* B's tokens with A's — the app-id matches, so the old guard let it through —
|
|
3020
|
+
* installs a genuine-but-WRONG session: the UI shows B while the app authenticates
|
|
3021
|
+
* as A (#220 / #221). Refuse the write when the refreshed token binds to a
|
|
3022
|
+
* DIFFERENT wallet than the one currently stored. A tokenless/addressless session
|
|
3023
|
+
* (guest / opaque) preserves the prior behavior (returns true).
|
|
3024
|
+
*/
|
|
3025
|
+
function refreshedTokenMayReplaceStored(newIdToken, storedAddress, decodeBase64Url) {
|
|
3026
|
+
const tokenWallet = idTokenWalletAddress(newIdToken, decodeBase64Url);
|
|
3027
|
+
if (!tokenWallet || !storedAddress)
|
|
3028
|
+
return true;
|
|
3029
|
+
return tokenWallet === storedAddress;
|
|
3030
|
+
}
|
|
3031
|
+
|
|
2975
3032
|
class WebSessionManager {
|
|
2976
3033
|
/**
|
|
2977
3034
|
* Decode a base64url-encoded string (used by JWT payloads).
|
|
@@ -3128,16 +3185,27 @@ class WebSessionManager {
|
|
|
3128
3185
|
var _a;
|
|
3129
3186
|
if (typeof window === "undefined")
|
|
3130
3187
|
return;
|
|
3188
|
+
// Resolve config FIRST so the read → check → write below is one synchronous
|
|
3189
|
+
// turn (an atomic CAS): no `await` can let a concurrent login interleave a
|
|
3190
|
+
// different session between the read and the write (#221).
|
|
3191
|
+
const config = await getConfig();
|
|
3131
3192
|
const session = localStorage.getItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
3132
3193
|
if (!session)
|
|
3133
3194
|
return;
|
|
3134
3195
|
const sessionObj = JSON.parse(session);
|
|
3135
3196
|
/* ---------- app-id guard ---------- */
|
|
3136
|
-
const config = await getConfig();
|
|
3137
3197
|
if (sessionObj.appId && sessionObj.appId !== config.appId) {
|
|
3138
3198
|
this.clearSession();
|
|
3139
3199
|
return;
|
|
3140
3200
|
}
|
|
3201
|
+
/* ---------- cross-principal guard (#221) ---------- */
|
|
3202
|
+
// Refuse to graft a refreshed token onto a DIFFERENT user's stored session
|
|
3203
|
+
// (a stale refresh completing after a login/upgrade replaced storage, incl.
|
|
3204
|
+
// a cross-tab login). Also re-runs the wallet-binding check storeSession does.
|
|
3205
|
+
if (!refreshedTokenMayReplaceStored(idToken, sessionObj.address, (s) => this.decodeBase64Url(s))) {
|
|
3206
|
+
console.warn("[WebSessionManager] updateIdTokenAndAccessToken: refreshed token wallet does not match the stored session address — discarding stale refresh");
|
|
3207
|
+
return;
|
|
3208
|
+
}
|
|
3141
3209
|
sessionObj.idToken = idToken;
|
|
3142
3210
|
sessionObj.accessToken = accessToken;
|
|
3143
3211
|
// Persist the rotated refresh token when the issuer returns a new one.
|
|
@@ -3346,16 +3414,27 @@ class ReactNativeSessionManager {
|
|
|
3346
3414
|
/* ------------------------------------------------------------------ */
|
|
3347
3415
|
static async updateIdTokenAndAccessToken(idToken, accessToken, refreshToken) {
|
|
3348
3416
|
var _a;
|
|
3417
|
+
// Resolve config FIRST so the read → check → write below is one synchronous
|
|
3418
|
+
// turn (an atomic CAS): no `await` can let a concurrent login interleave a
|
|
3419
|
+
// different session between the read and the write (#220).
|
|
3420
|
+
const config = await getConfig();
|
|
3349
3421
|
const session = this.getStorage().getItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
3350
3422
|
if (!session)
|
|
3351
3423
|
return;
|
|
3352
3424
|
const sessionObj = JSON.parse(session);
|
|
3353
3425
|
/* ---------- app-id guard ---------- */
|
|
3354
|
-
const config = await getConfig();
|
|
3355
3426
|
if (sessionObj.appId && sessionObj.appId !== config.appId) {
|
|
3356
3427
|
this.clearSession();
|
|
3357
3428
|
return;
|
|
3358
3429
|
}
|
|
3430
|
+
/* ---------- cross-principal guard (#220) ---------- */
|
|
3431
|
+
// Refuse to graft a refreshed token onto a DIFFERENT user's stored session
|
|
3432
|
+
// (a stale refresh completing after a login/upgrade replaced storage). Also
|
|
3433
|
+
// re-runs the wallet-binding check storeSession does.
|
|
3434
|
+
if (!refreshedTokenMayReplaceStored(idToken, sessionObj.address, (s) => this.decodeBase64Url(s))) {
|
|
3435
|
+
console.warn("[ReactNativeSessionManager] updateIdTokenAndAccessToken: refreshed token wallet does not match the stored session address — discarding stale refresh");
|
|
3436
|
+
return;
|
|
3437
|
+
}
|
|
3359
3438
|
sessionObj.idToken = idToken;
|
|
3360
3439
|
sessionObj.accessToken = accessToken;
|
|
3361
3440
|
if (typeof refreshToken === "string" && refreshToken.length > 0) {
|
|
@@ -3410,6 +3489,12 @@ class ServerSessionManager {
|
|
|
3410
3489
|
this.session = null;
|
|
3411
3490
|
/* Coalesce concurrent getSession() calls into a single in-flight request */
|
|
3412
3491
|
this.pendingSession = null;
|
|
3492
|
+
/* Monotonic generation, bumped on clear/set. An in-flight createSession() only
|
|
3493
|
+
* commits its result to the cache if the generation still matches the value it
|
|
3494
|
+
* captured when it started — so a clearSession()/setSession() that happens WHILE
|
|
3495
|
+
* a create is in flight deterministically wins, and the late result can't
|
|
3496
|
+
* silently re-install a session the caller just cleared/replaced (#259). */
|
|
3497
|
+
this.generation = 0;
|
|
3413
3498
|
this.keypair = keypair !== null && keypair !== void 0 ? keypair : null;
|
|
3414
3499
|
this.sessionFactory = sessionFactory !== null && sessionFactory !== void 0 ? sessionFactory : null;
|
|
3415
3500
|
}
|
|
@@ -3451,7 +3536,7 @@ class ServerSessionManager {
|
|
|
3451
3536
|
const address = kp.publicKey.toBase58();
|
|
3452
3537
|
/* fetch nonce from auth API */
|
|
3453
3538
|
const nonce = await genAuthNonce();
|
|
3454
|
-
const { genSolanaMessage } = await Promise.resolve().then(function () { return require('./solana-message-
|
|
3539
|
+
const { genSolanaMessage } = await Promise.resolve().then(function () { return require('./solana-message-DKOPPJwk.js'); });
|
|
3455
3540
|
const message = await genSolanaMessage(address, nonce);
|
|
3456
3541
|
/* sign the message */
|
|
3457
3542
|
const sigBytes = nacl.sign.detached(new TextEncoder().encode(message), kp.secretKey);
|
|
@@ -3472,9 +3557,13 @@ class ServerSessionManager {
|
|
|
3472
3557
|
if (this.pendingSession !== null) {
|
|
3473
3558
|
return this.pendingSession;
|
|
3474
3559
|
}
|
|
3560
|
+
const startedGeneration = this.generation;
|
|
3475
3561
|
this.pendingSession = this.createSession()
|
|
3476
3562
|
.then((session) => {
|
|
3477
|
-
this
|
|
3563
|
+
// Only commit if no clear/set happened while this create was in flight.
|
|
3564
|
+
if (this.generation === startedGeneration) {
|
|
3565
|
+
this.session = session;
|
|
3566
|
+
}
|
|
3478
3567
|
return session;
|
|
3479
3568
|
})
|
|
3480
3569
|
.finally(() => {
|
|
@@ -3486,12 +3575,14 @@ class ServerSessionManager {
|
|
|
3486
3575
|
* STORE (overwrites the cached value)
|
|
3487
3576
|
* ---------------------------------------------- */
|
|
3488
3577
|
setSession(session) {
|
|
3578
|
+
this.generation++;
|
|
3489
3579
|
this.session = session;
|
|
3490
3580
|
}
|
|
3491
3581
|
/* ---------------------------------------------- *
|
|
3492
3582
|
* CLEAR (e.g. after explicit logout)
|
|
3493
3583
|
* ---------------------------------------------- */
|
|
3494
3584
|
clearSession() {
|
|
3585
|
+
this.generation++;
|
|
3495
3586
|
this.session = null;
|
|
3496
3587
|
this.pendingSession = null;
|
|
3497
3588
|
}
|
|
@@ -4025,8 +4116,27 @@ axiosRetry(apiClient, {
|
|
|
4025
4116
|
},
|
|
4026
4117
|
});
|
|
4027
4118
|
const refreshInFlight = new Map();
|
|
4119
|
+
// Short, bounded, non-secret fingerprint of a refresh token — used only to KEY the
|
|
4120
|
+
// in-flight de-dup map (never logged, never the raw secret).
|
|
4121
|
+
function refreshTokenFingerprint(token) {
|
|
4122
|
+
let h = 5381;
|
|
4123
|
+
for (let i = 0; i < token.length; i++)
|
|
4124
|
+
h = ((h << 5) + h + token.charCodeAt(i)) >>> 0;
|
|
4125
|
+
return h.toString(16);
|
|
4126
|
+
}
|
|
4028
4127
|
async function refreshAuthSessionOnce(appId, isServer) {
|
|
4029
|
-
|
|
4128
|
+
// Capture the CURRENT session's refresh token up front and key the in-flight
|
|
4129
|
+
// refresh by it, not just by the app. Keyed by app only, a refresh started for
|
|
4130
|
+
// user A would be reused for a request issued after switching to user B (guest→
|
|
4131
|
+
// wallet upgrade, logout/login), so B's 401 recovery would piggyback on A's
|
|
4132
|
+
// refresh (#112). Per-session keying keeps de-dup for the SAME session while
|
|
4133
|
+
// making a post-switch refresh a distinct entry. (The token WRITE is separately
|
|
4134
|
+
// guarded per-principal in the session managers, #220/#221.)
|
|
4135
|
+
const refreshToken = await getRefreshToken(isServer);
|
|
4136
|
+
if (!refreshToken) {
|
|
4137
|
+
return false;
|
|
4138
|
+
}
|
|
4139
|
+
const key = `${isServer ? "server" : "web"}:${appId}:${refreshTokenFingerprint(refreshToken)}`;
|
|
4030
4140
|
const existing = refreshInFlight.get(key);
|
|
4031
4141
|
if (existing) {
|
|
4032
4142
|
return existing;
|
|
@@ -4034,10 +4144,6 @@ async function refreshAuthSessionOnce(appId, isServer) {
|
|
|
4034
4144
|
const refreshPromise = (async () => {
|
|
4035
4145
|
var _a, _b;
|
|
4036
4146
|
try {
|
|
4037
|
-
const refreshToken = await getRefreshToken(isServer);
|
|
4038
|
-
if (!refreshToken) {
|
|
4039
|
-
return false;
|
|
4040
|
-
}
|
|
4041
4147
|
const refreshData = await refreshSession(refreshToken, getSessionIssuer(isServer));
|
|
4042
4148
|
if (!(refreshData === null || refreshData === void 0 ? void 0 : refreshData.idToken) || !(refreshData === null || refreshData === void 0 ? void 0 : refreshData.accessToken)) {
|
|
4043
4149
|
return false;
|
|
@@ -4078,9 +4184,21 @@ async function makeApiRequest(method, urlPath, data, _overrides) {
|
|
|
4078
4184
|
? {}
|
|
4079
4185
|
: await createAuthHeader(config.isServer);
|
|
4080
4186
|
const headers = Object.assign({ "Content-Type": "application/json", "X-App-Id": config.appId }, authHeader);
|
|
4081
|
-
// Apply custom headers from _overrides
|
|
4187
|
+
// Apply custom headers from _overrides. #022/#023: HTTP header names are
|
|
4188
|
+
// case-insensitive, so normalize any override `authorization` (in any casing) onto the
|
|
4189
|
+
// single canonical `Authorization` key - never send two differently-cased Authorization
|
|
4190
|
+
// headers, and keep the credential the request sends identical to the one the read
|
|
4191
|
+
// cache key is derived from.
|
|
4082
4192
|
if (_overrides === null || _overrides === void 0 ? void 0 : _overrides.headers) {
|
|
4083
|
-
Object.
|
|
4193
|
+
for (const [name, value] of Object.entries(_overrides.headers)) {
|
|
4194
|
+
if (name.toLowerCase() === "authorization") {
|
|
4195
|
+
delete headers["authorization"];
|
|
4196
|
+
headers["Authorization"] = value;
|
|
4197
|
+
}
|
|
4198
|
+
else {
|
|
4199
|
+
headers[name] = value;
|
|
4200
|
+
}
|
|
4201
|
+
}
|
|
4084
4202
|
}
|
|
4085
4203
|
// Writes (PUT/POST/DELETE) can take significantly longer than reads —
|
|
4086
4204
|
// server-side they may trigger ad-hoc LUT creation (create + extend +
|
|
@@ -5892,7 +6010,7 @@ async function doReconnectWithNewAuth() {
|
|
|
5892
6010
|
console.warn('[WS v2] Failed to clear HTTP read cache on auth change:', error);
|
|
5893
6011
|
}
|
|
5894
6012
|
try {
|
|
5895
|
-
const { reconnectRealtimeStoreWithNewAuth } = await Promise.resolve().then(function () { return require('./realtime-store-
|
|
6013
|
+
const { reconnectRealtimeStoreWithNewAuth } = await Promise.resolve().then(function () { return require('./realtime-store-BpQsQYhl.js'); });
|
|
5896
6014
|
await reconnectRealtimeStoreWithNewAuth();
|
|
5897
6015
|
}
|
|
5898
6016
|
catch (error) {
|
|
@@ -6717,14 +6835,25 @@ function hashForKeyCached(value) {
|
|
|
6717
6835
|
principalHashCache.set(value, h);
|
|
6718
6836
|
return h;
|
|
6719
6837
|
}
|
|
6720
|
-
|
|
6838
|
+
// #022: HTTP header names are case-insensitive on the wire, so recognize `authorization`
|
|
6839
|
+
// in ANY casing when deriving the per-user cache key. Otherwise a caller-supplied
|
|
6840
|
+
// `AUTHORIZATION: <token B>` rides into the request while the cache key is computed from
|
|
6841
|
+
// the ambient token A - filing B's data under A's identity. The cache key must always be
|
|
6842
|
+
// derived from the same credential the request authenticates with.
|
|
6843
|
+
function authHeaderEntry(headers) {
|
|
6721
6844
|
if (!headers)
|
|
6722
|
-
return
|
|
6723
|
-
|
|
6724
|
-
|
|
6845
|
+
return undefined;
|
|
6846
|
+
for (const name of Object.keys(headers)) {
|
|
6847
|
+
if (name.toLowerCase() === 'authorization')
|
|
6848
|
+
return headers[name];
|
|
6849
|
+
}
|
|
6850
|
+
return undefined;
|
|
6851
|
+
}
|
|
6852
|
+
function hasAuthHeader(headers) {
|
|
6853
|
+
return authHeaderEntry(headers) !== undefined;
|
|
6725
6854
|
}
|
|
6726
6855
|
function authValueFromHeaders(headers) {
|
|
6727
|
-
return (headers
|
|
6856
|
+
return authHeaderEntry(headers) || '';
|
|
6728
6857
|
}
|
|
6729
6858
|
function principalFromAuthValue(authValue) {
|
|
6730
6859
|
return authValue ? `h${hashForKeyCached(authValue)}` : null;
|
|
@@ -7437,8 +7566,16 @@ async function runExpressionMany(many) {
|
|
|
7437
7566
|
let queries = [];
|
|
7438
7567
|
let overrides = undefined;
|
|
7439
7568
|
for (const { expression, queryArgs, returnType, _overrides } of many) {
|
|
7440
|
-
//
|
|
7441
|
-
|
|
7569
|
+
// All batched queries ride ONE request, so they must run under ONE credential.
|
|
7570
|
+
// Reject mixed per-entry _overrides (fail closed) instead of silently running
|
|
7571
|
+
// every query under the FIRST credential — that would evaluate a query meant
|
|
7572
|
+
// for user B as whoever came first (e.g. an admin), a cross-principal
|
|
7573
|
+
// authorization confusion the caller can't detect (#103). Split the batch by
|
|
7574
|
+
// credential to run under different identities.
|
|
7575
|
+
if (_overrides) {
|
|
7576
|
+
if (overrides && overrides !== _overrides) {
|
|
7577
|
+
throw new Error("[bounded-sdk] runExpressionMany: batched queries carry different _overrides; all queries in one batch share a single request and credential. Split the batch so each request uses one credential.");
|
|
7578
|
+
}
|
|
7442
7579
|
overrides = _overrides;
|
|
7443
7580
|
}
|
|
7444
7581
|
queries.push({ queryArgs, expression, returnType });
|
|
@@ -7580,11 +7717,11 @@ async function setMany(many, options) {
|
|
|
7580
7717
|
transactionResult = await handleEvmTransaction(curTx.evmTransaction, authProvider, options);
|
|
7581
7718
|
}
|
|
7582
7719
|
else if (curTx.serializedTransaction) {
|
|
7583
|
-
const { handlePreBuiltTransaction } = await Promise.resolve().then(function () { return require('./solana-operations-
|
|
7720
|
+
const { handlePreBuiltTransaction } = await Promise.resolve().then(function () { return require('./solana-operations-BOKHbEuy.js'); });
|
|
7584
7721
|
transactionResult = await handlePreBuiltTransaction(curTx, authProvider, options, expectedWritePaths);
|
|
7585
7722
|
}
|
|
7586
7723
|
else {
|
|
7587
|
-
const { handleSolanaTransaction } = await Promise.resolve().then(function () { return require('./solana-operations-
|
|
7724
|
+
const { handleSolanaTransaction } = await Promise.resolve().then(function () { return require('./solana-operations-BOKHbEuy.js'); });
|
|
7588
7725
|
transactionResult = await handleSolanaTransaction(curTx, authProvider, options, expectedWritePaths);
|
|
7589
7726
|
}
|
|
7590
7727
|
// Sync items after all transactions are confirmed
|
|
@@ -8110,17 +8247,25 @@ function isQueryLevelError(error) {
|
|
|
8110
8247
|
return (error === null || error === void 0 ? void 0 : error.isQueryError) === true || (error === null || error === void 0 ? void 0 : error.isBoundedDecline) === true || (error === null || error === void 0 ? void 0 : error.decline) != null;
|
|
8111
8248
|
}
|
|
8112
8249
|
/**
|
|
8113
|
-
* A RECOGNIZED transient transport failure that a retry might clear (HTTP
|
|
8114
|
-
*
|
|
8115
|
-
*
|
|
8116
|
-
* a retry won't fix) is NOT transient, so it gets surfaced once to onError
|
|
8117
|
-
* silently-dead subscription.
|
|
8250
|
+
* A RECOGNIZED transient transport failure that a retry might clear (HTTP 408/429/5xx, a
|
|
8251
|
+
* network/abort error). These back off SILENTLY - surfacing them per-poll would rebuild the
|
|
8252
|
+
* 429 noise. Anything else (a bad path 404, a permission/config 401/403, a malformed request
|
|
8253
|
+
* 400 - things a retry won't fix) is NOT transient, so it gets surfaced once to onError
|
|
8254
|
+
* instead of a silently-dead subscription.
|
|
8118
8255
|
*/
|
|
8119
8256
|
function isTransientTransportError(error) {
|
|
8120
8257
|
if (error == null)
|
|
8121
8258
|
return true; // unknown - be conservative and keep retrying
|
|
8122
|
-
|
|
8123
|
-
|
|
8259
|
+
// #060: classify by the actual status, not merely by "has a status". Almost every HTTP
|
|
8260
|
+
// failure carries a status, including the TERMINAL ones (400/401/403/404); the old
|
|
8261
|
+
// `typeof status === 'number'` check lumped them all into "transient" and retried them
|
|
8262
|
+
// forever with no onError - the exact silently-dead subscription this is meant to avoid.
|
|
8263
|
+
const status = typeof error.status === 'number' ? error.status
|
|
8264
|
+
: typeof error.statusCode === 'number' ? error.statusCode
|
|
8265
|
+
: undefined;
|
|
8266
|
+
if (status !== undefined) {
|
|
8267
|
+
return status === 408 || status === 429 || status >= 500;
|
|
8268
|
+
}
|
|
8124
8269
|
const code = error.code;
|
|
8125
8270
|
if (code === 'ECONNABORTED' || code === 'ERR_NETWORK' || code === 'ETIMEDOUT' || code === 'ERR_CANCELED')
|
|
8126
8271
|
return true;
|
|
@@ -8670,12 +8815,12 @@ function toMillis(seconds) {
|
|
|
8670
8815
|
|
|
8671
8816
|
/** Load the Anchor/Web3 transaction builder only when an onchain write uses it. */
|
|
8672
8817
|
async function buildSetDocumentsTransaction(connection, idl, programProvider, payerPublicKey, args, remainingAccounts, lutKey, preInstructions, simulate, additionalLutAddresses) {
|
|
8673
|
-
const implementation = await Promise.resolve().then(function () { return require('./sol-utils-
|
|
8818
|
+
const implementation = await Promise.resolve().then(function () { return require('./sol-utils-Cwxyl2h6.js'); });
|
|
8674
8819
|
return implementation.buildSetDocumentsTransaction(connection, idl, programProvider, payerPublicKey, args, remainingAccounts, lutKey, preInstructions, simulate, additionalLutAddresses);
|
|
8675
8820
|
}
|
|
8676
8821
|
/** SIWS message creation is async already, so its onchain module can be lazy. */
|
|
8677
8822
|
async function genSolanaMessage(address, nonce) {
|
|
8678
|
-
const implementation = await Promise.resolve().then(function () { return require('./solana-message-
|
|
8823
|
+
const implementation = await Promise.resolve().then(function () { return require('./solana-message-DKOPPJwk.js'); });
|
|
8679
8824
|
return implementation.genSolanaMessage(address, nonce);
|
|
8680
8825
|
}
|
|
8681
8826
|
const BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
@@ -9066,6 +9211,13 @@ async function intent(roomPath, intent, opts = {}) {
|
|
|
9066
9211
|
* This is diagnostic/ops surface. It reports whether the room facet exists,
|
|
9067
9212
|
* whether it is currently ticking, why it last stopped, which module etag is
|
|
9068
9213
|
* loaded, and the current generation used after terminal restarts.
|
|
9214
|
+
*
|
|
9215
|
+
* Auth: status attaches the caller's session token WHEN ONE EXISTS (mirroring
|
|
9216
|
+
* `intent`), but does NOT require it. The server gates the DETAILED body on the
|
|
9217
|
+
* room's read rule — an authenticated caller who can read the room gets full
|
|
9218
|
+
* telemetry; an anonymous / unauthorized caller gets only the slim liveness
|
|
9219
|
+
* shape `{available, started, module}` (#102). The caller's own `opts.headers`
|
|
9220
|
+
* may never smuggle in Authorization: only the derived session token sets it.
|
|
9069
9221
|
*/
|
|
9070
9222
|
async function status(roomPath, opts = {}) {
|
|
9071
9223
|
var _a, _b, _c, _d, _e;
|
|
@@ -9075,7 +9227,10 @@ async function status(roomPath, opts = {}) {
|
|
|
9075
9227
|
const normalizedRoomPath = roomPath.replace(/\/$/, '');
|
|
9076
9228
|
const config = await getConfig();
|
|
9077
9229
|
const base = realtimeHttpBase(config.wsApiUrl);
|
|
9078
|
-
const
|
|
9230
|
+
const authHeader = await liveAuthHeader(config.isServer, opts._overrides);
|
|
9231
|
+
const extraHeaders = withoutAuthorization(opts.headers);
|
|
9232
|
+
const overrideHeaders = withoutAuthorization((_a = opts._overrides) === null || _a === void 0 ? void 0 : _a.headers);
|
|
9233
|
+
const headers = Object.assign(Object.assign(Object.assign({ 'X-App-Id': config.appId }, (overrideHeaders !== null && overrideHeaders !== void 0 ? overrideHeaders : {})), (extraHeaders !== null && extraHeaders !== void 0 ? extraHeaders : {})), (hasAuthorization(authHeader) ? authHeader : {}));
|
|
9079
9234
|
const controller = new AbortController();
|
|
9080
9235
|
const timeoutMs = (_b = opts.timeoutMs) !== null && _b !== void 0 ? _b : 15000;
|
|
9081
9236
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -9250,6 +9405,7 @@ exports.get = get;
|
|
|
9250
9405
|
exports.getActiveSessionManager = getActiveSessionManager;
|
|
9251
9406
|
exports.getCachedData = getCachedData;
|
|
9252
9407
|
exports.getConfig = getConfig;
|
|
9408
|
+
exports.getConfiguredAppId = getConfiguredAppId;
|
|
9253
9409
|
exports.getFiles = getFiles;
|
|
9254
9410
|
exports.getIdToken = getIdToken;
|
|
9255
9411
|
exports.getMany = getMany;
|
|
@@ -9288,4 +9444,4 @@ exports.toLowerEvmAddress = toLowerEvmAddress;
|
|
|
9288
9444
|
exports.toMillis = toMillis;
|
|
9289
9445
|
exports.toSeconds = toSeconds;
|
|
9290
9446
|
exports.withEffects = withEffects;
|
|
9291
|
-
//# sourceMappingURL=index-
|
|
9447
|
+
//# sourceMappingURL=index-B30-XpaX.js.map
|