@bounded-sh/core 0.0.26 → 0.0.35

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
@@ -19,14 +19,10 @@ let clientConfig = {
19
19
  humanAuthApiUrl: 'https://auth.bounded.sh',
20
20
  functionsUrl: 'https://functions.bounded.sh',
21
21
  appId: '',
22
- // No hidden auth fallback: browser clients must pass authMethod explicitly
23
- // (for example 'email', 'guest', 'phantom', 'privy', or 'privy-expo').
24
- // Hosted OAuth/social uses loginWithRedirect/loginWithPopup with authMethod:'email'.
25
- // Text OTP is off by default and uses hosted/headless text helpers only when
26
- // Bounded explicitly enables it for the issuer. For
27
- // crypto/onchain wallet login use authMethod:'phantom' (Solana / Phantom), or
28
- // signInAnonymously() for zero-friction 'guest' accounts. ('wallet' is an
29
- // unimplemented stub; don't use.)
22
+ // No hidden modal fallback: browser clients start hosted Bounded Auth with
23
+ // loginWithRedirect/loginWithPopup. For crypto/onchain wallet login use
24
+ // authMethod:'phantom' (Solana / Phantom), and use signInAnonymously() for
25
+ // zero-friction guest accounts. ('wallet' is an unimplemented stub; don't use.)
30
26
  authMethod: 'none',
31
27
  chain: '',
32
28
  rpcUrl: '',
@@ -2806,6 +2802,7 @@ async function genAuthNonce() {
2806
2802
  // from 'phantom' and couldn't be gated or flagged.
2807
2803
  const SIGNATURE_AUTH_METHODS = new Set(['phantom', 'mobile-wallet-adapter', 'guest']);
2808
2804
  async function createSessionWithSignature(address, message, signature) {
2805
+ var _a, _b;
2809
2806
  const client = await getAxiosAuthClient();
2810
2807
  const config = await getConfig();
2811
2808
  const appId = config.appId;
@@ -2815,14 +2812,30 @@ async function createSessionWithSignature(address, message, signature) {
2815
2812
  // for a signature exchange. This is what makes the server SDK + guest device
2816
2813
  // keypair work under the email-default config.
2817
2814
  const authMethod = SIGNATURE_AUTH_METHODS.has(config.authMethod) ? config.authMethod : 'phantom';
2818
- const response = await client.post('/session', {
2819
- appId,
2820
- address,
2821
- message,
2822
- signature,
2823
- authMethod
2824
- });
2825
- return response.data;
2815
+ try {
2816
+ const response = await client.post('/session', {
2817
+ appId,
2818
+ address,
2819
+ message,
2820
+ signature,
2821
+ authMethod
2822
+ });
2823
+ return response.data;
2824
+ }
2825
+ catch (err) {
2826
+ // Surface the issuer's actionable message (e.g. "Anonymous auth is not enabled for
2827
+ // this app. Add \"auth\": { \"anonymous\": true } to policy.json and redeploy.")
2828
+ // instead of axios's generic "Request failed with status code 403".
2829
+ const d = (_a = err === null || err === void 0 ? void 0 : err.response) === null || _a === void 0 ? void 0 : _a.data;
2830
+ if (d && (d.message || d.error)) {
2831
+ const e = new Error(String(d.message || d.error));
2832
+ e.code = d.error;
2833
+ e.status = (_b = err === null || err === void 0 ? void 0 : err.response) === null || _b === void 0 ? void 0 : _b.status;
2834
+ e.hint = d.hint;
2835
+ throw e;
2836
+ }
2837
+ throw err;
2838
+ }
2826
2839
  }
2827
2840
  // Deduplicate concurrent refreshSession calls to prevent multiple code paths
2828
2841
  // (WebSocket reconnection, periodic refresh, API 401 retry) from all hitting
@@ -6542,7 +6555,30 @@ async function wsIntent(appId, roomRoutePath, intent, isServer) {
6542
6555
  if (!connection || !connection.ws || connection.ws.readyState !== WS_READY_STATE_OPEN || connection.isAuthenticating) {
6543
6556
  return false;
6544
6557
  }
6545
- if (!(await canSendAuthenticatedLiveIntent(connection, isServer))) {
6558
+ // Identity-aware send gate. The server attributes intents to the
6559
+ // CONNECTION's authenticated principal, so the only correct reasons to
6560
+ // refuse the socket are identity ones:
6561
+ // - the socket is anonymous while the user is now authenticated, or
6562
+ // - the current principal DIFFERS from the socket principal.
6563
+ // A mere token rotation for the SAME principal, or a transiently-null
6564
+ // token mid-refresh, must NOT reroute a 30Hz command stream to
6565
+ // per-command HTTP — that collapses delivery rate and unbounds the game's
6566
+ // ack lag (field-debugged: server 500+ commands behind).
6567
+ const currentToken = await getIdToken(isServer).catch(() => null);
6568
+ if (!currentToken) {
6569
+ // Mid-refresh: the socket principal still governs server-side.
6570
+ if (!connection.authTokenFingerprint && !connection.authPrincipal)
6571
+ return false;
6572
+ }
6573
+ else if (!connection.authTokenFingerprint && !connection.authPrincipal) {
6574
+ // Anonymous socket + authenticated user: do not attribute this user's
6575
+ // input to the anon principal. Reauth the socket; caller may fall back.
6576
+ void maybeReauthRoomConnection(connection);
6577
+ return false;
6578
+ }
6579
+ else if (!connectionMatchesAuthToken(connection, currentToken)) {
6580
+ // Different principal (identity switch): never send as the old user.
6581
+ void maybeReauthRoomConnection(connection);
6546
6582
  return false;
6547
6583
  }
6548
6584
  try {
@@ -6553,6 +6589,26 @@ async function wsIntent(appId, roomRoutePath, intent, isServer) {
6553
6589
  return false;
6554
6590
  }
6555
6591
  }
6592
+ // Debounced background reauth: when the client's current token no longer
6593
+ // matches the connection's auth fingerprint (token rotation), refresh the
6594
+ // socket auth ONCE rather than degrading every subsequent send.
6595
+ const reauthInFlight = new WeakSet();
6596
+ async function maybeReauthRoomConnection(connection, isServer) {
6597
+ try {
6598
+ if (reauthInFlight.has(connection))
6599
+ return;
6600
+ reauthInFlight.add(connection);
6601
+ try {
6602
+ await reconnectWithNewAuthV2();
6603
+ }
6604
+ finally {
6605
+ reauthInFlight.delete(connection);
6606
+ }
6607
+ }
6608
+ catch (_a) {
6609
+ // Best-effort: the next send retries; the server still enforces auth.
6610
+ }
6611
+ }
6556
6612
  /**
6557
6613
  * Send a live-room intent over the EXISTING per-room socket and AWAIT the server
6558
6614
  * ack — so a policy/auth denial (401/403/404/503) REJECTS instead of being
@@ -7120,8 +7176,8 @@ async function status(roomPath, opts = {}) {
7120
7176
  * claims); pass `opts.userId` to override. Returns the unsubscribe function
7121
7177
  * (a Promise<() => Promise<void>>, same as `subscribe`).
7122
7178
  *
7123
- * Note: this is a browser-first helper (the WS subscription manager is
7124
- * browser-oriented). Server consumers should use `live.intent`.
7179
+ * Server/agent consumers: use `WalletClient.live.subscribeView` (it supplies
7180
+ * the wallet session + view id); browser apps just call this directly.
7125
7181
  */
7126
7182
  async function subscribeView(roomPath, opts) {
7127
7183
  if (!roomPath || typeof roomPath !== 'string') {
@@ -7151,7 +7207,7 @@ async function subscribeView(roomPath, opts) {
7151
7207
  // derives the room from the `?path=` the connection carries). Without this the
7152
7208
  // subscription lands on the app-level project DO and never sees the room's
7153
7209
  // ephemeral view writes.
7154
- return subscribeV2(viewPath, { onData: opts.onData, onError: opts.onError }, normalizedRoomPath);
7210
+ return subscribeV2(viewPath, { onData: opts.onData, onError: opts.onError, _overrides: opts._overrides }, normalizedRoomPath);
7155
7211
  }
7156
7212
  /** The `bounded.live` namespace surface. */
7157
7213
  const live = { intent, status, subscribeView };