@bounded-sh/core 0.0.56 → 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.
Files changed (29) hide show
  1. package/dist/client/config.d.ts +20 -0
  2. package/dist/client/live.d.ts +9 -0
  3. package/dist/{index-2e3ksoSR.js → index-B30-XpaX.js} +193 -37
  4. package/dist/index-B30-XpaX.js.map +1 -0
  5. package/dist/{index-BZhK47ZR.mjs → index-DYbzK8YT.mjs} +193 -38
  6. package/dist/index-DYbzK8YT.mjs.map +1 -0
  7. package/dist/index.d.ts +1 -1
  8. package/dist/index.js +2 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.mjs +1 -1
  11. package/dist/{realtime-store-CpdBV8Ma.mjs → realtime-store-BSuV-Vyr.mjs} +2 -2
  12. package/dist/{realtime-store-CpdBV8Ma.mjs.map → realtime-store-BSuV-Vyr.mjs.map} +1 -1
  13. package/dist/{realtime-store-Cp7xYNS3.js → realtime-store-BpQsQYhl.js} +2 -2
  14. package/dist/{realtime-store-Cp7xYNS3.js.map → realtime-store-BpQsQYhl.js.map} +1 -1
  15. package/dist/{solana-message-Cfg_S81A.mjs → solana-message-C2_VvNX7.mjs} +2 -2
  16. package/dist/{solana-message-Cfg_S81A.mjs.map → solana-message-C2_VvNX7.mjs.map} +1 -1
  17. package/dist/{solana-message-DHJ8xSuS.js → solana-message-DKOPPJwk.js} +2 -2
  18. package/dist/{solana-message-DHJ8xSuS.js.map → solana-message-DKOPPJwk.js.map} +1 -1
  19. package/dist/{solana-operations-DcjCYmuL.js → solana-operations-BOKHbEuy.js} +16 -4
  20. package/dist/solana-operations-BOKHbEuy.js.map +1 -0
  21. package/dist/{solana-operations-BsnDXoeq.mjs → solana-operations-CuYFYlyN.mjs} +16 -4
  22. package/dist/solana-operations-CuYFYlyN.mjs.map +1 -0
  23. package/dist/utils/server-session-manager.d.ts +1 -0
  24. package/dist/utils/session-binding.d.ts +17 -0
  25. package/package.json +3 -2
  26. package/dist/index-2e3ksoSR.js.map +0 -1
  27. package/dist/index-BZhK47ZR.mjs.map +0 -1
  28. package/dist/solana-operations-BsnDXoeq.mjs.map +0 -1
  29. package/dist/solana-operations-DcjCYmuL.js.map +0 -1
@@ -80,6 +80,16 @@ function getWebhookKeysUrl() {
80
80
  return undefined;
81
81
  return host.replace(/\/$/, '') + '/.well-known/bounded-webhook-keys.json';
82
82
  }
83
+ /**
84
+ * The app id configured via `init({ appId })`, or `undefined` when none has been set.
85
+ * Synchronous + non-blocking on purpose (mirrors {@link getWebhookKeysUrl}): a pure webhook
86
+ * receiver may never call `init()`, and `getConfig()` would block on initialization. Used by
87
+ * `verifyWebhook` to bind a webhook to THIS app by default (#072) rather than trusting any
88
+ * app's shared-key-signed delivery.
89
+ */
90
+ function getConfiguredAppId() {
91
+ return clientConfig.appId || undefined;
92
+ }
83
93
  /**
84
94
  * True when init() configured a Bounded network (the Cloudflare-native stack).
85
95
  * Synchronous + non-blocking. Used to route reads/writes that behave differently
@@ -2793,19 +2803,28 @@ function resolveIssuerBase(issuer, cfg) {
2793
2803
  return want;
2794
2804
  return norm(cfg.authApiUrl);
2795
2805
  }
2796
- let axiosClient;
2806
+ // #062: key the auth client by its baseURL. The old single memoized client froze the FIRST
2807
+ // environment's authApiUrl, so after re-initializing the SDK to a different issuer (staging->
2808
+ // prod, or a custom issuer) the mint calls (/nonce, /auth/nonce, /session) kept hitting the
2809
+ // original address forever - a "works only after a full reload" login bug, and in a multi-tenant
2810
+ // process one config's address could leak into another's. refresh/revoke already resolve per
2811
+ // call via resolveIssuerBase; this makes minting do the same while still caching per URL.
2812
+ const axiosClientsByUrl = new Map();
2797
2813
  async function getAxiosAuthClient() {
2798
- if (!axiosClient) {
2799
- const config = await getConfig();
2800
- axiosClient = axios.create({
2801
- baseURL: config.authApiUrl,
2814
+ const config = await getConfig();
2815
+ const baseURL = config.authApiUrl || '';
2816
+ let client = axiosClientsByUrl.get(baseURL);
2817
+ if (!client) {
2818
+ client = axios.create({
2819
+ baseURL,
2802
2820
  headers: {
2803
2821
  'Content-Type': 'application/json',
2804
2822
  },
2805
2823
  timeout: 30000, // 30s timeout, matching makeApiRequest
2806
2824
  });
2825
+ axiosClientsByUrl.set(baseURL, client);
2807
2826
  }
2808
- return axiosClient;
2827
+ return client;
2809
2828
  }
2810
2829
  async function genAuthNonce() {
2811
2830
  const client = await getAxiosAuthClient();
@@ -2970,6 +2989,44 @@ async function revokeSession(refreshToken, issuer) {
2970
2989
  async function signSessionCreateMessage(_signMessageFunction) {
2971
2990
  }
2972
2991
 
2992
+ // Shared session-binding guard for the web + React Native session managers, so
2993
+ // the cross-principal refresh guard (#220 / #221) lives in ONE place and the two
2994
+ // managers can't drift apart. Base64url decoding differs by platform (browser
2995
+ // atob vs a configured decoder), so the caller passes its own decoder in.
2996
+ /**
2997
+ * Extract the `custom:walletAddress` claim from a JWT idToken. Returns undefined
2998
+ * when the token has no wallet claim or cannot be decoded.
2999
+ */
3000
+ function idTokenWalletAddress(idToken, decodeBase64Url) {
3001
+ try {
3002
+ const payloadB64 = idToken.split(".")[1];
3003
+ if (!payloadB64)
3004
+ return undefined;
3005
+ const wallet = JSON.parse(decodeBase64Url(payloadB64))["custom:walletAddress"];
3006
+ return typeof wallet === "string" && wallet.length > 0 ? wallet : undefined;
3007
+ }
3008
+ catch (_a) {
3009
+ return undefined;
3010
+ }
3011
+ }
3012
+ /**
3013
+ * Whether a refreshed idToken may be grafted onto the CURRENTLY-stored session.
3014
+ *
3015
+ * A silent refresh started for user A can complete after storage was replaced
3016
+ * with user B (a login, guest→wallet upgrade, or a cross-tab login). Overwriting
3017
+ * B's tokens with A's — the app-id matches, so the old guard let it through —
3018
+ * installs a genuine-but-WRONG session: the UI shows B while the app authenticates
3019
+ * as A (#220 / #221). Refuse the write when the refreshed token binds to a
3020
+ * DIFFERENT wallet than the one currently stored. A tokenless/addressless session
3021
+ * (guest / opaque) preserves the prior behavior (returns true).
3022
+ */
3023
+ function refreshedTokenMayReplaceStored(newIdToken, storedAddress, decodeBase64Url) {
3024
+ const tokenWallet = idTokenWalletAddress(newIdToken, decodeBase64Url);
3025
+ if (!tokenWallet || !storedAddress)
3026
+ return true;
3027
+ return tokenWallet === storedAddress;
3028
+ }
3029
+
2973
3030
  class WebSessionManager {
2974
3031
  /**
2975
3032
  * Decode a base64url-encoded string (used by JWT payloads).
@@ -3126,16 +3183,27 @@ class WebSessionManager {
3126
3183
  var _a;
3127
3184
  if (typeof window === "undefined")
3128
3185
  return;
3186
+ // Resolve config FIRST so the read → check → write below is one synchronous
3187
+ // turn (an atomic CAS): no `await` can let a concurrent login interleave a
3188
+ // different session between the read and the write (#221).
3189
+ const config = await getConfig();
3129
3190
  const session = localStorage.getItem(this.BOUNDED_SESSION_STORAGE_KEY);
3130
3191
  if (!session)
3131
3192
  return;
3132
3193
  const sessionObj = JSON.parse(session);
3133
3194
  /* ---------- app-id guard ---------- */
3134
- const config = await getConfig();
3135
3195
  if (sessionObj.appId && sessionObj.appId !== config.appId) {
3136
3196
  this.clearSession();
3137
3197
  return;
3138
3198
  }
3199
+ /* ---------- cross-principal guard (#221) ---------- */
3200
+ // Refuse to graft a refreshed token onto a DIFFERENT user's stored session
3201
+ // (a stale refresh completing after a login/upgrade replaced storage, incl.
3202
+ // a cross-tab login). Also re-runs the wallet-binding check storeSession does.
3203
+ if (!refreshedTokenMayReplaceStored(idToken, sessionObj.address, (s) => this.decodeBase64Url(s))) {
3204
+ console.warn("[WebSessionManager] updateIdTokenAndAccessToken: refreshed token wallet does not match the stored session address — discarding stale refresh");
3205
+ return;
3206
+ }
3139
3207
  sessionObj.idToken = idToken;
3140
3208
  sessionObj.accessToken = accessToken;
3141
3209
  // Persist the rotated refresh token when the issuer returns a new one.
@@ -3344,16 +3412,27 @@ class ReactNativeSessionManager {
3344
3412
  /* ------------------------------------------------------------------ */
3345
3413
  static async updateIdTokenAndAccessToken(idToken, accessToken, refreshToken) {
3346
3414
  var _a;
3415
+ // Resolve config FIRST so the read → check → write below is one synchronous
3416
+ // turn (an atomic CAS): no `await` can let a concurrent login interleave a
3417
+ // different session between the read and the write (#220).
3418
+ const config = await getConfig();
3347
3419
  const session = this.getStorage().getItem(this.BOUNDED_SESSION_STORAGE_KEY);
3348
3420
  if (!session)
3349
3421
  return;
3350
3422
  const sessionObj = JSON.parse(session);
3351
3423
  /* ---------- app-id guard ---------- */
3352
- const config = await getConfig();
3353
3424
  if (sessionObj.appId && sessionObj.appId !== config.appId) {
3354
3425
  this.clearSession();
3355
3426
  return;
3356
3427
  }
3428
+ /* ---------- cross-principal guard (#220) ---------- */
3429
+ // Refuse to graft a refreshed token onto a DIFFERENT user's stored session
3430
+ // (a stale refresh completing after a login/upgrade replaced storage). Also
3431
+ // re-runs the wallet-binding check storeSession does.
3432
+ if (!refreshedTokenMayReplaceStored(idToken, sessionObj.address, (s) => this.decodeBase64Url(s))) {
3433
+ console.warn("[ReactNativeSessionManager] updateIdTokenAndAccessToken: refreshed token wallet does not match the stored session address — discarding stale refresh");
3434
+ return;
3435
+ }
3357
3436
  sessionObj.idToken = idToken;
3358
3437
  sessionObj.accessToken = accessToken;
3359
3438
  if (typeof refreshToken === "string" && refreshToken.length > 0) {
@@ -3408,6 +3487,12 @@ class ServerSessionManager {
3408
3487
  this.session = null;
3409
3488
  /* Coalesce concurrent getSession() calls into a single in-flight request */
3410
3489
  this.pendingSession = null;
3490
+ /* Monotonic generation, bumped on clear/set. An in-flight createSession() only
3491
+ * commits its result to the cache if the generation still matches the value it
3492
+ * captured when it started — so a clearSession()/setSession() that happens WHILE
3493
+ * a create is in flight deterministically wins, and the late result can't
3494
+ * silently re-install a session the caller just cleared/replaced (#259). */
3495
+ this.generation = 0;
3411
3496
  this.keypair = keypair !== null && keypair !== void 0 ? keypair : null;
3412
3497
  this.sessionFactory = sessionFactory !== null && sessionFactory !== void 0 ? sessionFactory : null;
3413
3498
  }
@@ -3449,7 +3534,7 @@ class ServerSessionManager {
3449
3534
  const address = kp.publicKey.toBase58();
3450
3535
  /* fetch nonce from auth API */
3451
3536
  const nonce = await genAuthNonce();
3452
- const { genSolanaMessage } = await import('./solana-message-Cfg_S81A.mjs');
3537
+ const { genSolanaMessage } = await import('./solana-message-C2_VvNX7.mjs');
3453
3538
  const message = await genSolanaMessage(address, nonce);
3454
3539
  /* sign the message */
3455
3540
  const sigBytes = nacl.sign.detached(new TextEncoder().encode(message), kp.secretKey);
@@ -3470,9 +3555,13 @@ class ServerSessionManager {
3470
3555
  if (this.pendingSession !== null) {
3471
3556
  return this.pendingSession;
3472
3557
  }
3558
+ const startedGeneration = this.generation;
3473
3559
  this.pendingSession = this.createSession()
3474
3560
  .then((session) => {
3475
- this.session = session;
3561
+ // Only commit if no clear/set happened while this create was in flight.
3562
+ if (this.generation === startedGeneration) {
3563
+ this.session = session;
3564
+ }
3476
3565
  return session;
3477
3566
  })
3478
3567
  .finally(() => {
@@ -3484,12 +3573,14 @@ class ServerSessionManager {
3484
3573
  * STORE (overwrites the cached value)
3485
3574
  * ---------------------------------------------- */
3486
3575
  setSession(session) {
3576
+ this.generation++;
3487
3577
  this.session = session;
3488
3578
  }
3489
3579
  /* ---------------------------------------------- *
3490
3580
  * CLEAR (e.g. after explicit logout)
3491
3581
  * ---------------------------------------------- */
3492
3582
  clearSession() {
3583
+ this.generation++;
3493
3584
  this.session = null;
3494
3585
  this.pendingSession = null;
3495
3586
  }
@@ -4023,8 +4114,27 @@ axiosRetry(apiClient, {
4023
4114
  },
4024
4115
  });
4025
4116
  const refreshInFlight = new Map();
4117
+ // Short, bounded, non-secret fingerprint of a refresh token — used only to KEY the
4118
+ // in-flight de-dup map (never logged, never the raw secret).
4119
+ function refreshTokenFingerprint(token) {
4120
+ let h = 5381;
4121
+ for (let i = 0; i < token.length; i++)
4122
+ h = ((h << 5) + h + token.charCodeAt(i)) >>> 0;
4123
+ return h.toString(16);
4124
+ }
4026
4125
  async function refreshAuthSessionOnce(appId, isServer) {
4027
- const key = `${isServer ? "server" : "web"}:${appId}`;
4126
+ // Capture the CURRENT session's refresh token up front and key the in-flight
4127
+ // refresh by it, not just by the app. Keyed by app only, a refresh started for
4128
+ // user A would be reused for a request issued after switching to user B (guest→
4129
+ // wallet upgrade, logout/login), so B's 401 recovery would piggyback on A's
4130
+ // refresh (#112). Per-session keying keeps de-dup for the SAME session while
4131
+ // making a post-switch refresh a distinct entry. (The token WRITE is separately
4132
+ // guarded per-principal in the session managers, #220/#221.)
4133
+ const refreshToken = await getRefreshToken(isServer);
4134
+ if (!refreshToken) {
4135
+ return false;
4136
+ }
4137
+ const key = `${isServer ? "server" : "web"}:${appId}:${refreshTokenFingerprint(refreshToken)}`;
4028
4138
  const existing = refreshInFlight.get(key);
4029
4139
  if (existing) {
4030
4140
  return existing;
@@ -4032,10 +4142,6 @@ async function refreshAuthSessionOnce(appId, isServer) {
4032
4142
  const refreshPromise = (async () => {
4033
4143
  var _a, _b;
4034
4144
  try {
4035
- const refreshToken = await getRefreshToken(isServer);
4036
- if (!refreshToken) {
4037
- return false;
4038
- }
4039
4145
  const refreshData = await refreshSession(refreshToken, getSessionIssuer(isServer));
4040
4146
  if (!(refreshData === null || refreshData === void 0 ? void 0 : refreshData.idToken) || !(refreshData === null || refreshData === void 0 ? void 0 : refreshData.accessToken)) {
4041
4147
  return false;
@@ -4076,9 +4182,21 @@ async function makeApiRequest(method, urlPath, data, _overrides) {
4076
4182
  ? {}
4077
4183
  : await createAuthHeader(config.isServer);
4078
4184
  const headers = Object.assign({ "Content-Type": "application/json", "X-App-Id": config.appId }, authHeader);
4079
- // Apply custom headers from _overrides
4185
+ // Apply custom headers from _overrides. #022/#023: HTTP header names are
4186
+ // case-insensitive, so normalize any override `authorization` (in any casing) onto the
4187
+ // single canonical `Authorization` key - never send two differently-cased Authorization
4188
+ // headers, and keep the credential the request sends identical to the one the read
4189
+ // cache key is derived from.
4080
4190
  if (_overrides === null || _overrides === void 0 ? void 0 : _overrides.headers) {
4081
- Object.assign(headers, _overrides.headers);
4191
+ for (const [name, value] of Object.entries(_overrides.headers)) {
4192
+ if (name.toLowerCase() === "authorization") {
4193
+ delete headers["authorization"];
4194
+ headers["Authorization"] = value;
4195
+ }
4196
+ else {
4197
+ headers[name] = value;
4198
+ }
4199
+ }
4082
4200
  }
4083
4201
  // Writes (PUT/POST/DELETE) can take significantly longer than reads —
4084
4202
  // server-side they may trigger ad-hoc LUT creation (create + extend +
@@ -5890,7 +6008,7 @@ async function doReconnectWithNewAuth() {
5890
6008
  console.warn('[WS v2] Failed to clear HTTP read cache on auth change:', error);
5891
6009
  }
5892
6010
  try {
5893
- const { reconnectRealtimeStoreWithNewAuth } = await import('./realtime-store-CpdBV8Ma.mjs');
6011
+ const { reconnectRealtimeStoreWithNewAuth } = await import('./realtime-store-BSuV-Vyr.mjs');
5894
6012
  await reconnectRealtimeStoreWithNewAuth();
5895
6013
  }
5896
6014
  catch (error) {
@@ -6715,14 +6833,25 @@ function hashForKeyCached(value) {
6715
6833
  principalHashCache.set(value, h);
6716
6834
  return h;
6717
6835
  }
6718
- function hasAuthHeader(headers) {
6836
+ // #022: HTTP header names are case-insensitive on the wire, so recognize `authorization`
6837
+ // in ANY casing when deriving the per-user cache key. Otherwise a caller-supplied
6838
+ // `AUTHORIZATION: <token B>` rides into the request while the cache key is computed from
6839
+ // the ambient token A - filing B's data under A's identity. The cache key must always be
6840
+ // derived from the same credential the request authenticates with.
6841
+ function authHeaderEntry(headers) {
6719
6842
  if (!headers)
6720
- return false;
6721
- return (Object.prototype.hasOwnProperty.call(headers, 'Authorization') ||
6722
- Object.prototype.hasOwnProperty.call(headers, 'authorization'));
6843
+ return undefined;
6844
+ for (const name of Object.keys(headers)) {
6845
+ if (name.toLowerCase() === 'authorization')
6846
+ return headers[name];
6847
+ }
6848
+ return undefined;
6849
+ }
6850
+ function hasAuthHeader(headers) {
6851
+ return authHeaderEntry(headers) !== undefined;
6723
6852
  }
6724
6853
  function authValueFromHeaders(headers) {
6725
- return (headers === null || headers === void 0 ? void 0 : headers.Authorization) || (headers === null || headers === void 0 ? void 0 : headers.authorization) || '';
6854
+ return authHeaderEntry(headers) || '';
6726
6855
  }
6727
6856
  function principalFromAuthValue(authValue) {
6728
6857
  return authValue ? `h${hashForKeyCached(authValue)}` : null;
@@ -7435,8 +7564,16 @@ async function runExpressionMany(many) {
7435
7564
  let queries = [];
7436
7565
  let overrides = undefined;
7437
7566
  for (const { expression, queryArgs, returnType, _overrides } of many) {
7438
- // Collect overrides from first query that has them (all queries share same request)
7439
- if (_overrides && !overrides) {
7567
+ // All batched queries ride ONE request, so they must run under ONE credential.
7568
+ // Reject mixed per-entry _overrides (fail closed) instead of silently running
7569
+ // every query under the FIRST credential — that would evaluate a query meant
7570
+ // for user B as whoever came first (e.g. an admin), a cross-principal
7571
+ // authorization confusion the caller can't detect (#103). Split the batch by
7572
+ // credential to run under different identities.
7573
+ if (_overrides) {
7574
+ if (overrides && overrides !== _overrides) {
7575
+ 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.");
7576
+ }
7440
7577
  overrides = _overrides;
7441
7578
  }
7442
7579
  queries.push({ queryArgs, expression, returnType });
@@ -7578,11 +7715,11 @@ async function setMany(many, options) {
7578
7715
  transactionResult = await handleEvmTransaction(curTx.evmTransaction, authProvider, options);
7579
7716
  }
7580
7717
  else if (curTx.serializedTransaction) {
7581
- const { handlePreBuiltTransaction } = await import('./solana-operations-BsnDXoeq.mjs');
7718
+ const { handlePreBuiltTransaction } = await import('./solana-operations-CuYFYlyN.mjs');
7582
7719
  transactionResult = await handlePreBuiltTransaction(curTx, authProvider, options, expectedWritePaths);
7583
7720
  }
7584
7721
  else {
7585
- const { handleSolanaTransaction } = await import('./solana-operations-BsnDXoeq.mjs');
7722
+ const { handleSolanaTransaction } = await import('./solana-operations-CuYFYlyN.mjs');
7586
7723
  transactionResult = await handleSolanaTransaction(curTx, authProvider, options, expectedWritePaths);
7587
7724
  }
7588
7725
  // Sync items after all transactions are confirmed
@@ -8108,17 +8245,25 @@ function isQueryLevelError(error) {
8108
8245
  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;
8109
8246
  }
8110
8247
  /**
8111
- * A RECOGNIZED transient transport failure that a retry might clear (HTTP status like
8112
- * 429/5xx, a network/abort error). These back off SILENTLY - surfacing them per-poll
8113
- * would rebuild the 429 noise. Anything else (a bad path, a malformed response - things
8114
- * a retry won't fix) is NOT transient, so it gets surfaced once to onError instead of a
8115
- * silently-dead subscription.
8248
+ * A RECOGNIZED transient transport failure that a retry might clear (HTTP 408/429/5xx, a
8249
+ * network/abort error). These back off SILENTLY - surfacing them per-poll would rebuild the
8250
+ * 429 noise. Anything else (a bad path 404, a permission/config 401/403, a malformed request
8251
+ * 400 - things a retry won't fix) is NOT transient, so it gets surfaced once to onError
8252
+ * instead of a silently-dead subscription.
8116
8253
  */
8117
8254
  function isTransientTransportError(error) {
8118
8255
  if (error == null)
8119
8256
  return true; // unknown - be conservative and keep retrying
8120
- if (typeof error.status === 'number' || typeof error.statusCode === 'number')
8121
- return true;
8257
+ // #060: classify by the actual status, not merely by "has a status". Almost every HTTP
8258
+ // failure carries a status, including the TERMINAL ones (400/401/403/404); the old
8259
+ // `typeof status === 'number'` check lumped them all into "transient" and retried them
8260
+ // forever with no onError - the exact silently-dead subscription this is meant to avoid.
8261
+ const status = typeof error.status === 'number' ? error.status
8262
+ : typeof error.statusCode === 'number' ? error.statusCode
8263
+ : undefined;
8264
+ if (status !== undefined) {
8265
+ return status === 408 || status === 429 || status >= 500;
8266
+ }
8122
8267
  const code = error.code;
8123
8268
  if (code === 'ECONNABORTED' || code === 'ERR_NETWORK' || code === 'ETIMEDOUT' || code === 'ERR_CANCELED')
8124
8269
  return true;
@@ -8673,7 +8818,7 @@ async function buildSetDocumentsTransaction(connection, idl, programProvider, pa
8673
8818
  }
8674
8819
  /** SIWS message creation is async already, so its onchain module can be lazy. */
8675
8820
  async function genSolanaMessage(address, nonce) {
8676
- const implementation = await import('./solana-message-Cfg_S81A.mjs');
8821
+ const implementation = await import('./solana-message-C2_VvNX7.mjs');
8677
8822
  return implementation.genSolanaMessage(address, nonce);
8678
8823
  }
8679
8824
  const BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
@@ -9064,6 +9209,13 @@ async function intent(roomPath, intent, opts = {}) {
9064
9209
  * This is diagnostic/ops surface. It reports whether the room facet exists,
9065
9210
  * whether it is currently ticking, why it last stopped, which module etag is
9066
9211
  * loaded, and the current generation used after terminal restarts.
9212
+ *
9213
+ * Auth: status attaches the caller's session token WHEN ONE EXISTS (mirroring
9214
+ * `intent`), but does NOT require it. The server gates the DETAILED body on the
9215
+ * room's read rule — an authenticated caller who can read the room gets full
9216
+ * telemetry; an anonymous / unauthorized caller gets only the slim liveness
9217
+ * shape `{available, started, module}` (#102). The caller's own `opts.headers`
9218
+ * may never smuggle in Authorization: only the derived session token sets it.
9067
9219
  */
9068
9220
  async function status(roomPath, opts = {}) {
9069
9221
  var _a, _b, _c, _d, _e;
@@ -9073,7 +9225,10 @@ async function status(roomPath, opts = {}) {
9073
9225
  const normalizedRoomPath = roomPath.replace(/\/$/, '');
9074
9226
  const config = await getConfig();
9075
9227
  const base = realtimeHttpBase(config.wsApiUrl);
9076
- const headers = Object.assign({ 'X-App-Id': config.appId }, ((_a = opts.headers) !== null && _a !== void 0 ? _a : {}));
9228
+ const authHeader = await liveAuthHeader(config.isServer, opts._overrides);
9229
+ const extraHeaders = withoutAuthorization(opts.headers);
9230
+ const overrideHeaders = withoutAuthorization((_a = opts._overrides) === null || _a === void 0 ? void 0 : _a.headers);
9231
+ 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 : {}));
9077
9232
  const controller = new AbortController();
9078
9233
  const timeoutMs = (_b = opts.timeoutMs) !== null && _b !== void 0 ? _b : 15000;
9079
9234
  const timer = setTimeout(() => controller.abort(), timeoutMs);
@@ -9205,5 +9360,5 @@ function defineLiveModule(mod) {
9205
9360
  return mod;
9206
9361
  }
9207
9362
 
9208
- export { ServerSessionManager as $, reconnectWithNewAuth as A, createClient as B, subscribeQuery as C, closeAllQuerySubscriptions as D, canonicalizeArgs as E, canonicalizeResult as F, DEFAULT_QUERY_POLL_INTERVAL_MS as G, BoundedDeclineError as H, InsufficientBalanceError as I, deriveDecline as J, dispatchDeclineEvent as K, DECLINE_EVENT_NAME as L, MIN_QUERY_POLL_INTERVAL_MS as M, dispatchRateLimitEvent as N, hasActiveConnection as O, increment as P, serverTimestamp as Q, RATE_LIMIT_EVENT_NAME as R, ScopedClientReadOnlyError as S, now as T, toSeconds as U, toMillis as V, getIdToken as W, deriveUserIdentityFromIdToken as X, WebSessionManager as Y, ReactNativeSessionManager as Z, getActiveSessionManager as _, getWebhookKeysUrl as a, createSessionWithSignature as a0, refreshSession as a1, revokeSession as a2, signSessionCreateMessage as a3, genAuthNonce as a4, buildSetDocumentsTransaction as a5, convertRemainingAccounts as a6, genSolanaMessage as a7, genSiweMessage as a8, normalizeEvmAddress as a9, toLowerEvmAddress as aa, assertCanonicalBoundedRuntimeWriteCalldata as ab, assertEvmTransactionMatchesWrite as ac, BOUNDED_RUNTIME_SET_DOCUMENTS_SELECTOR as ad, BOUNDED_RUNTIME_SET_DOCUMENTS_ATTESTED_SELECTOR as ae, functions as af, invoke as ag, FunctionInvokeError as ah, live as ai, intent as aj, status as ak, subscribeView as al, LiveIntentError as am, withEffects as an, isEffectResult as ao, defineLiveModule as ap, EFFECT_INTENT_ADDRESS as aq, bufferExports as b, get as c, getMany as d, docId as e, setMany as f, getConfig as g, setFile as h, init as i, getFiles as j, search as k, runQueryMany as l, runExpression as m, runExpressionMany as n, signMessage as o, signTransaction as p, queryAggregate as q, runQuery as r, set as s, signAndSubmitTransaction as t, count as u, aggregate as v, subscribe as w, closeAllSubscriptions as x, clearCache as y, getCachedData as z };
9209
- //# sourceMappingURL=index-BZhK47ZR.mjs.map
9363
+ export { getActiveSessionManager as $, getCachedData as A, reconnectWithNewAuth as B, createClient as C, subscribeQuery as D, closeAllQuerySubscriptions as E, canonicalizeArgs as F, canonicalizeResult as G, DEFAULT_QUERY_POLL_INTERVAL_MS as H, InsufficientBalanceError as I, BoundedDeclineError as J, deriveDecline as K, dispatchDeclineEvent as L, MIN_QUERY_POLL_INTERVAL_MS as M, DECLINE_EVENT_NAME as N, dispatchRateLimitEvent as O, hasActiveConnection as P, increment as Q, RATE_LIMIT_EVENT_NAME as R, ScopedClientReadOnlyError as S, serverTimestamp as T, now as U, toSeconds as V, toMillis as W, getIdToken as X, deriveUserIdentityFromIdToken as Y, WebSessionManager as Z, ReactNativeSessionManager as _, getWebhookKeysUrl as a, ServerSessionManager as a0, createSessionWithSignature as a1, refreshSession as a2, revokeSession as a3, signSessionCreateMessage as a4, genAuthNonce as a5, buildSetDocumentsTransaction as a6, convertRemainingAccounts as a7, genSolanaMessage as a8, genSiweMessage as a9, normalizeEvmAddress as aa, toLowerEvmAddress as ab, assertCanonicalBoundedRuntimeWriteCalldata as ac, assertEvmTransactionMatchesWrite as ad, BOUNDED_RUNTIME_SET_DOCUMENTS_SELECTOR as ae, BOUNDED_RUNTIME_SET_DOCUMENTS_ATTESTED_SELECTOR as af, functions as ag, invoke as ah, FunctionInvokeError as ai, live as aj, intent as ak, status as al, subscribeView as am, LiveIntentError as an, withEffects as ao, isEffectResult as ap, defineLiveModule as aq, EFFECT_INTENT_ADDRESS as ar, bufferExports as b, getConfiguredAppId as c, get as d, getMany as e, docId as f, getConfig as g, setMany as h, init as i, setFile as j, getFiles as k, search as l, runQueryMany as m, runExpression as n, runExpressionMany as o, signMessage as p, queryAggregate as q, runQuery as r, set as s, signTransaction as t, signAndSubmitTransaction as u, count as v, aggregate as w, subscribe as x, closeAllSubscriptions as y, clearCache as z };
9364
+ //# sourceMappingURL=index-DYbzK8YT.mjs.map