@bounded-sh/core 0.0.34 → 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.
@@ -61,6 +61,8 @@ export declare function hasActiveConnection(): boolean;
61
61
  * client never names a destination.
62
62
  */
63
63
  export declare function wsIntent(appId: string, roomRoutePath: string, intent: unknown, isServer: boolean): Promise<boolean>;
64
+ /** True when an OPEN room socket exists for this room path. */
65
+ export declare function hasOpenRoomConnection(appId: string, roomRoutePath: string): boolean;
64
66
  /**
65
67
  * Send a live-room intent over the EXISTING per-room socket and AWAIT the server
66
68
  * ack — so a policy/auth denial (401/403/404/503) REJECTS instead of being
package/dist/index.js CHANGED
@@ -6575,7 +6575,30 @@ async function wsIntent(appId, roomRoutePath, intent, isServer) {
6575
6575
  if (!connection || !connection.ws || connection.ws.readyState !== WS_READY_STATE_OPEN || connection.isAuthenticating) {
6576
6576
  return false;
6577
6577
  }
6578
- if (!(await canSendAuthenticatedLiveIntent(connection, isServer))) {
6578
+ // Identity-aware send gate. The server attributes intents to the
6579
+ // CONNECTION's authenticated principal, so the only correct reasons to
6580
+ // refuse the socket are identity ones:
6581
+ // - the socket is anonymous while the user is now authenticated, or
6582
+ // - the current principal DIFFERS from the socket principal.
6583
+ // A mere token rotation for the SAME principal, or a transiently-null
6584
+ // token mid-refresh, must NOT reroute a 30Hz command stream to
6585
+ // per-command HTTP — that collapses delivery rate and unbounds the game's
6586
+ // ack lag (field-debugged: server 500+ commands behind).
6587
+ const currentToken = await getIdToken(isServer).catch(() => null);
6588
+ if (!currentToken) {
6589
+ // Mid-refresh: the socket principal still governs server-side.
6590
+ if (!connection.authTokenFingerprint && !connection.authPrincipal)
6591
+ return false;
6592
+ }
6593
+ else if (!connection.authTokenFingerprint && !connection.authPrincipal) {
6594
+ // Anonymous socket + authenticated user: do not attribute this user's
6595
+ // input to the anon principal. Reauth the socket; caller may fall back.
6596
+ void maybeReauthRoomConnection(connection);
6597
+ return false;
6598
+ }
6599
+ else if (!connectionMatchesAuthToken(connection, currentToken)) {
6600
+ // Different principal (identity switch): never send as the old user.
6601
+ void maybeReauthRoomConnection(connection);
6579
6602
  return false;
6580
6603
  }
6581
6604
  try {
@@ -6586,6 +6609,26 @@ async function wsIntent(appId, roomRoutePath, intent, isServer) {
6586
6609
  return false;
6587
6610
  }
6588
6611
  }
6612
+ // Debounced background reauth: when the client's current token no longer
6613
+ // matches the connection's auth fingerprint (token rotation), refresh the
6614
+ // socket auth ONCE rather than degrading every subsequent send.
6615
+ const reauthInFlight = new WeakSet();
6616
+ async function maybeReauthRoomConnection(connection, isServer) {
6617
+ try {
6618
+ if (reauthInFlight.has(connection))
6619
+ return;
6620
+ reauthInFlight.add(connection);
6621
+ try {
6622
+ await reconnectWithNewAuthV2();
6623
+ }
6624
+ finally {
6625
+ reauthInFlight.delete(connection);
6626
+ }
6627
+ }
6628
+ catch (_a) {
6629
+ // Best-effort: the next send retries; the server still enforces auth.
6630
+ }
6631
+ }
6589
6632
  /**
6590
6633
  * Send a live-room intent over the EXISTING per-room socket and AWAIT the server
6591
6634
  * ack — so a policy/auth denial (401/403/404/503) REJECTS instead of being