@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/README.md CHANGED
@@ -1,16 +1,16 @@
1
- # @tarobase/core
1
+ # @bounded-sh/core
2
2
 
3
3
  Core functionality for Tarobase SDKs. This package provides the shared functionality used by both the web and server SDKs.
4
4
 
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- npm install @tarobase/core
8
+ npm install @bounded-sh/core
9
9
  ```
10
10
 
11
11
  ## Usage
12
12
 
13
- This package is typically not used directly, but is instead used as a dependency by `@tarobase/web` and `@tarobase/server`. If you're building a browser application, use `@tarobase/web`. If you're building a server application, use `@tarobase/server`.
13
+ This package is typically not used directly, but is instead used as a dependency by `@bounded-sh/client` and `@bounded-sh/server`. If you're building a browser application, use `@bounded-sh/client`. If you're building a server application, use `@bounded-sh/server`.
14
14
 
15
15
  ## API Reference
16
16
 
package/dist/index.js CHANGED
@@ -2943,6 +2943,7 @@ class WebSessionManager {
2943
2943
  /* STORE */
2944
2944
  /* ------------------------------------------------------------------ */
2945
2945
  static async storeSession(address, accessToken, idToken, refreshToken, issuer) {
2946
+ var _a;
2946
2947
  if (typeof window === "undefined")
2947
2948
  return;
2948
2949
  // JWT-wallet binding: refuse to store a session whose idToken is bound
@@ -2965,7 +2966,7 @@ class WebSessionManager {
2965
2966
  if (typeof (err === null || err === void 0 ? void 0 : err.message) === "string" && err.message.includes("Refusing to store session")) {
2966
2967
  throw err;
2967
2968
  }
2968
- console.warn("[WebSessionManager] storeSession: failed to decode idToken for validation:", err);
2969
+ 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");
2969
2970
  }
2970
2971
  const config = await getConfig();
2971
2972
  const currentAppId = config.appId;
@@ -2996,8 +2997,12 @@ class WebSessionManager {
2996
2997
  }
2997
2998
  /* ---------- check JWT expiration ---------- */
2998
2999
  try {
2999
- const { accessToken } = sessionObj;
3000
- const { exp } = JSON.parse(this.decodeBase64Url(accessToken.split(".")[1]));
3000
+ // Check expiry on the ID TOKEN — it is ALWAYS a JWT. The OIDC access_token for
3001
+ // email/social (hosted) logins is an OPAQUE string (not a JWT), so decoding IT
3002
+ // here threw and the catch below wiped a perfectly valid session — breaking
3003
+ // hosted login. The idToken carries the canonical identity (@user.id) + exp.
3004
+ const { idToken } = sessionObj;
3005
+ const { exp } = JSON.parse(this.decodeBase64Url(idToken.split(".")[1]));
3001
3006
  if (Date.now() > exp * 1000) {
3002
3007
  const { refreshToken } = sessionObj;
3003
3008
  if (!refreshToken)
@@ -3157,6 +3162,7 @@ class ReactNativeSessionManager {
3157
3162
  /* STORE */
3158
3163
  /* ------------------------------------------------------------------ */
3159
3164
  static async storeSession(address, accessToken, idToken, refreshToken, issuer) {
3165
+ var _a;
3160
3166
  // JWT-wallet binding: refuse to store a session whose idToken is bound
3161
3167
  // to a different wallet than `address`. Prevents races that would otherwise
3162
3168
  // leave storage with mismatched address/token state.
@@ -3177,7 +3183,7 @@ class ReactNativeSessionManager {
3177
3183
  if (typeof (err === null || err === void 0 ? void 0 : err.message) === "string" && err.message.includes("Refusing to store session")) {
3178
3184
  throw err;
3179
3185
  }
3180
- console.warn("[ReactNativeSessionManager] storeSession: failed to decode idToken for validation:", err);
3186
+ 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");
3181
3187
  }
3182
3188
  const config = await getConfig();
3183
3189
  const currentAppId = config.appId;
@@ -3205,8 +3211,10 @@ class ReactNativeSessionManager {
3205
3211
  }
3206
3212
  /* ---------- check JWT expiration ---------- */
3207
3213
  try {
3208
- const { accessToken } = sessionObj;
3209
- const { exp } = JSON.parse(this.decodeBase64Url(accessToken.split(".")[1]));
3214
+ // Expiry on the ID TOKEN (always a JWT). The OIDC access_token for email/social
3215
+ // logins is opaque, so decoding it threw → the catch wiped a valid session.
3216
+ const { idToken } = sessionObj;
3217
+ const { exp } = JSON.parse(this.decodeBase64Url(idToken.split(".")[1]));
3210
3218
  if (Date.now() > exp * 1000) {
3211
3219
  const { refreshToken } = sessionObj;
3212
3220
  if (!refreshToken)
@@ -5508,6 +5516,7 @@ function bearerFromAuthHeaders(headers) {
5508
5516
  // Monotonic counter for unique-ish fallback identity keys (no Date.now in some envs).
5509
5517
  let connectionEpoch = 0;
5510
5518
  function isTokenExpired(token) {
5519
+ var _a;
5511
5520
  try {
5512
5521
  const payload = JSON.parse(decodeBase64Url(token.split('.')[1]));
5513
5522
  const expirationTime = payload.exp * 1000;
@@ -5515,7 +5524,7 @@ function isTokenExpired(token) {
5515
5524
  return currentTime > (expirationTime - 60000); // 60 second buffer
5516
5525
  }
5517
5526
  catch (error) {
5518
- console.error('[WS v2] Error checking token expiration:', error);
5527
+ 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');
5519
5528
  return true;
5520
5529
  }
5521
5530
  }
@@ -6949,6 +6958,13 @@ function withoutAuthorization(headers) {
6949
6958
  }
6950
6959
  return Object.keys(clean).length > 0 ? clean : undefined;
6951
6960
  }
6961
+ function hasAuthorization(headers) {
6962
+ if (!headers)
6963
+ return false;
6964
+ return Object.entries(headers).some(([key, value]) => key.toLowerCase() === 'authorization' &&
6965
+ typeof value === 'string' &&
6966
+ /^Bearer\s+\S+$/i.test(value.trim()));
6967
+ }
6952
6968
  async function liveAuthHeader(configIsServer, overrides) {
6953
6969
  if (overrides === null || overrides === void 0 ? void 0 : overrides._getAuthHeaders) {
6954
6970
  return overrides._getAuthHeaders();
@@ -6974,6 +6990,12 @@ async function intent(roomPath, intent, opts = {}) {
6974
6990
  throw new LiveIntentError('A room path is required');
6975
6991
  }
6976
6992
  const config = await getConfig();
6993
+ const initialAuthHeader = await liveAuthHeader(config.isServer, opts._overrides);
6994
+ if (!hasAuthorization(initialAuthHeader)) {
6995
+ if (opts.fireAndForget)
6996
+ return { ok: true };
6997
+ throw new LiveIntentError('Authentication required to send a live intent. Call init() and sign in before sending live room intents.', 401);
6998
+ }
6977
6999
  // Ride the already-open per-room socket instead of a fresh HTTP POST per
6978
7000
  // intent. That is what makes high-frequency input (~20-30Hz) cheap — one
6979
7001
  // persistent connection, no per-intent TCP/TLS or connection-pool contention
@@ -7007,6 +7029,9 @@ async function intent(roomPath, intent, opts = {}) {
7007
7029
  const overrideHeaders = withoutAuthorization((_b = opts._overrides) === null || _b === void 0 ? void 0 : _b.headers);
7008
7030
  const buildHeaders = async () => {
7009
7031
  const authHeader = await liveAuthHeader(config.isServer, opts._overrides);
7032
+ if (!hasAuthorization(authHeader)) {
7033
+ throw new LiveIntentError('Authentication required to send a live intent. Call init() and sign in before sending live room intents.', 401);
7034
+ }
7010
7035
  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 : {}));
7011
7036
  };
7012
7037
  const controller = new AbortController();
@@ -7028,6 +7053,8 @@ async function intent(roomPath, intent, opts = {}) {
7028
7053
  }
7029
7054
  catch (err) {
7030
7055
  clearTimeout(timer);
7056
+ if (err instanceof LiveIntentError)
7057
+ throw err;
7031
7058
  if ((err === null || err === void 0 ? void 0 : err.name) === 'AbortError') {
7032
7059
  throw new LiveIntentError(`Live intent to "${roomPath}" timed out after ${timeoutMs}ms`);
7033
7060
  }