@bounded-sh/core 0.0.22 → 0.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -2923,6 +2923,7 @@ class WebSessionManager {
2923
2923
  /* STORE */
2924
2924
  /* ------------------------------------------------------------------ */
2925
2925
  static async storeSession(address, accessToken, idToken, refreshToken, issuer) {
2926
+ var _a;
2926
2927
  if (typeof window === "undefined")
2927
2928
  return;
2928
2929
  // JWT-wallet binding: refuse to store a session whose idToken is bound
@@ -2945,7 +2946,7 @@ class WebSessionManager {
2945
2946
  if (typeof (err === null || err === void 0 ? void 0 : err.message) === "string" && err.message.includes("Refusing to store session")) {
2946
2947
  throw err;
2947
2948
  }
2948
- console.warn("[WebSessionManager] storeSession: failed to decode idToken for validation:", err);
2949
+ console.warn("[WebSessionManager] storeSession: failed to decode idToken for validation. reason:", (_a = err === null || err === void 0 ? void 0 : err.name) !== null && _a !== void 0 ? _a : "decode_failed");
2949
2950
  }
2950
2951
  const config = await getConfig();
2951
2952
  const currentAppId = config.appId;
@@ -2976,8 +2977,12 @@ class WebSessionManager {
2976
2977
  }
2977
2978
  /* ---------- check JWT expiration ---------- */
2978
2979
  try {
2979
- const { accessToken } = sessionObj;
2980
- const { exp } = JSON.parse(this.decodeBase64Url(accessToken.split(".")[1]));
2980
+ // Check expiry on the ID TOKEN — it is ALWAYS a JWT. The OIDC access_token for
2981
+ // email/social (hosted) logins is an OPAQUE string (not a JWT), so decoding IT
2982
+ // here threw and the catch below wiped a perfectly valid session — breaking
2983
+ // hosted login. The idToken carries the canonical identity (@user.id) + exp.
2984
+ const { idToken } = sessionObj;
2985
+ const { exp } = JSON.parse(this.decodeBase64Url(idToken.split(".")[1]));
2981
2986
  if (Date.now() > exp * 1000) {
2982
2987
  const { refreshToken } = sessionObj;
2983
2988
  if (!refreshToken)
@@ -3137,6 +3142,7 @@ class ReactNativeSessionManager {
3137
3142
  /* STORE */
3138
3143
  /* ------------------------------------------------------------------ */
3139
3144
  static async storeSession(address, accessToken, idToken, refreshToken, issuer) {
3145
+ var _a;
3140
3146
  // JWT-wallet binding: refuse to store a session whose idToken is bound
3141
3147
  // to a different wallet than `address`. Prevents races that would otherwise
3142
3148
  // leave storage with mismatched address/token state.
@@ -3157,7 +3163,7 @@ class ReactNativeSessionManager {
3157
3163
  if (typeof (err === null || err === void 0 ? void 0 : err.message) === "string" && err.message.includes("Refusing to store session")) {
3158
3164
  throw err;
3159
3165
  }
3160
- console.warn("[ReactNativeSessionManager] storeSession: failed to decode idToken for validation:", err);
3166
+ console.warn("[ReactNativeSessionManager] storeSession: failed to decode idToken for validation. reason:", (_a = err === null || err === void 0 ? void 0 : err.name) !== null && _a !== void 0 ? _a : "decode_failed");
3161
3167
  }
3162
3168
  const config = await getConfig();
3163
3169
  const currentAppId = config.appId;
@@ -3185,8 +3191,10 @@ class ReactNativeSessionManager {
3185
3191
  }
3186
3192
  /* ---------- check JWT expiration ---------- */
3187
3193
  try {
3188
- const { accessToken } = sessionObj;
3189
- const { exp } = JSON.parse(this.decodeBase64Url(accessToken.split(".")[1]));
3194
+ // Expiry on the ID TOKEN (always a JWT). The OIDC access_token for email/social
3195
+ // logins is opaque, so decoding it threw → the catch wiped a valid session.
3196
+ const { idToken } = sessionObj;
3197
+ const { exp } = JSON.parse(this.decodeBase64Url(idToken.split(".")[1]));
3190
3198
  if (Date.now() > exp * 1000) {
3191
3199
  const { refreshToken } = sessionObj;
3192
3200
  if (!refreshToken)
@@ -5488,6 +5496,7 @@ function bearerFromAuthHeaders(headers) {
5488
5496
  // Monotonic counter for unique-ish fallback identity keys (no Date.now in some envs).
5489
5497
  let connectionEpoch = 0;
5490
5498
  function isTokenExpired(token) {
5499
+ var _a;
5491
5500
  try {
5492
5501
  const payload = JSON.parse(decodeBase64Url(token.split('.')[1]));
5493
5502
  const expirationTime = payload.exp * 1000;
@@ -5495,7 +5504,7 @@ function isTokenExpired(token) {
5495
5504
  return currentTime > (expirationTime - 60000); // 60 second buffer
5496
5505
  }
5497
5506
  catch (error) {
5498
- console.error('[WS v2] Error checking token expiration:', error);
5507
+ console.error('[WS v2] Error checking token expiration. reason:', (_a = error === null || error === void 0 ? void 0 : error.name) !== null && _a !== void 0 ? _a : 'decode_failed');
5499
5508
  return true;
5500
5509
  }
5501
5510
  }
@@ -6929,6 +6938,13 @@ function withoutAuthorization(headers) {
6929
6938
  }
6930
6939
  return Object.keys(clean).length > 0 ? clean : undefined;
6931
6940
  }
6941
+ function hasAuthorization(headers) {
6942
+ if (!headers)
6943
+ return false;
6944
+ return Object.entries(headers).some(([key, value]) => key.toLowerCase() === 'authorization' &&
6945
+ typeof value === 'string' &&
6946
+ /^Bearer\s+\S+$/i.test(value.trim()));
6947
+ }
6932
6948
  async function liveAuthHeader(configIsServer, overrides) {
6933
6949
  if (overrides === null || overrides === void 0 ? void 0 : overrides._getAuthHeaders) {
6934
6950
  return overrides._getAuthHeaders();
@@ -6954,6 +6970,12 @@ async function intent(roomPath, intent, opts = {}) {
6954
6970
  throw new LiveIntentError('A room path is required');
6955
6971
  }
6956
6972
  const config = await getConfig();
6973
+ const initialAuthHeader = await liveAuthHeader(config.isServer, opts._overrides);
6974
+ if (!hasAuthorization(initialAuthHeader)) {
6975
+ if (opts.fireAndForget)
6976
+ return { ok: true };
6977
+ throw new LiveIntentError('Authentication required to send a live intent. Call init() and sign in before sending live room intents.', 401);
6978
+ }
6957
6979
  // Ride the already-open per-room socket instead of a fresh HTTP POST per
6958
6980
  // intent. That is what makes high-frequency input (~20-30Hz) cheap — one
6959
6981
  // persistent connection, no per-intent TCP/TLS or connection-pool contention
@@ -6987,6 +7009,9 @@ async function intent(roomPath, intent, opts = {}) {
6987
7009
  const overrideHeaders = withoutAuthorization((_b = opts._overrides) === null || _b === void 0 ? void 0 : _b.headers);
6988
7010
  const buildHeaders = async () => {
6989
7011
  const authHeader = await liveAuthHeader(config.isServer, opts._overrides);
7012
+ if (!hasAuthorization(authHeader)) {
7013
+ throw new LiveIntentError('Authentication required to send a live intent. Call init() and sign in before sending live room intents.', 401);
7014
+ }
6990
7015
  return Object.assign(Object.assign(Object.assign({ 'Content-Type': 'application/json', 'X-App-Id': config.appId }, (overrideHeaders !== null && overrideHeaders !== void 0 ? overrideHeaders : {})), (extraHeaders !== null && extraHeaders !== void 0 ? extraHeaders : {})), (authHeader !== null && authHeader !== void 0 ? authHeader : {}));
6991
7016
  };
6992
7017
  const controller = new AbortController();
@@ -7008,6 +7033,8 @@ async function intent(roomPath, intent, opts = {}) {
7008
7033
  }
7009
7034
  catch (err) {
7010
7035
  clearTimeout(timer);
7036
+ if (err instanceof LiveIntentError)
7037
+ throw err;
7011
7038
  if ((err === null || err === void 0 ? void 0 : err.name) === 'AbortError') {
7012
7039
  throw new LiveIntentError(`Live intent to "${roomPath}" timed out after ${timeoutMs}ms`);
7013
7040
  }