Skip to content

Troubleshooting

Structured Error Format

When a request fails, the server responds with a frame whose payload contains a structured error:

json
{
  "header": {
    "id": "msg_2",
    "resource": "session",
    "method": "join",
    "kind": "response",
    "replyTo": "msg_1"
  },
  "payload": {
    "status": "error",
    "error": {
      "code": "session.not_found",
      "message": "Session does not exist.",
      "resource": "session",
      "retry": false,
      "details": null
    }
  }
}

The SDK surfaces these as StarfishError instances with code, message, resource, retry, and details fields. The retry field indicates whether the client should retry the operation.

Error Codes

Connection Errors

CONNECTION_FAILED

The WebSocket connection could not be established.

Causes:

  • Server is not running
  • Wrong server URL
  • Firewall or proxy blocking WebSocket connections

Fix: Verify the server is running and the URL is correct. Check that your network allows WebSocket connections (some corporate proxies block them).

NO_WEBSOCKET

No WebSocket implementation is available.

Causes:

  • Running in Node.js without providing a WebSocket factory

Fix: Install ws and pass it via the webSocketFactory option:

ts
import WebSocket from "ws";

const client = new StarfishClient({
  server: "ws://localhost:4000",
  webSocketFactory: (url) => new WebSocket(url),
});

NOT_CONNECTED

Attempted to send a frame while not connected.

Causes:

  • Calling publish, send, subscribe, or other methods before connect() completes
  • Connection dropped and reconnection hasn't succeeded yet

Fix: Ensure await client.connect() has resolved before calling other methods. Monitor connectionState$ to know when you're connected.

CONNECTION_LOST

The WebSocket connection closed unexpectedly.

Causes:

  • Network interruption
  • Server shutdown or restart
  • Idle timeout

Fix: The client will automatically attempt to reconnect if reconnect.enabled is true (the default). Pending requests will be rejected with this error — retry them after reconnection.

DISCONNECTED

The client was intentionally disconnected.

Causes:

  • disconnect() was called while requests were pending

Fix: This is expected behavior. Complete or cancel pending operations before disconnecting.

Session Errors

NO_SESSION

Attempted an operation that requires a session without joining one first.

Causes:

  • Calling publish, subscribe, presence.set, save, or send before join()

Fix: Call await client.join("session-name") before using session features.

Project & Matchmaking Errors

These arise from the Project model: credential/Project binding, the lockdown flag, and declared matchmaking policies.

project.forbidden

The credential is valid but binds to a different Project than the one the client declared.

Causes:

  • Your credential maps to Project A (via its keys) but you declared project: "B"
  • You requested a Project the authenticator does not grant

Fix: Declare the Project your credential grants, or omit project and let the authenticator assign it. A credential keys binding is authoritative — see Projects and authentication.

project.not_found

A referenced Project was not found where a declared one is required.

Fix: Provision the Project through the control plane, or reference an existing one. Unprovisioned Projects are otherwise implicit and permissive.

project.lockdown

Ad-hoc creation (create: true on a session or pool) was attempted in a locked-down Project.

Causes:

  • The Project was provisioned with lockdown: true, which permits only declared policies

Fix: Enter a declared policy by name instead of creating a pool/session ad-hoc, or clear lockdown on the Project config.

pool.policy_not_found

The referenced matchmaking policy is not declared on the connection's Project.

Causes:

  • Typo in the policy name
  • The Project is implicit (has no declared policies)

Fix: Reference a policy that exists on the Project (see its config), or provision one.

pool.policy_required

No policy was named and the Project declares no default policy.

Fix: Name a policy explicitly — joinProject(project, { policy }) — or declare a default: true policy on the Project.

The runtime pool errors (pool.not_found, pool.mode_mismatch, and the other pool.* codes) are documented in the Pool Matchmaking API reference.

Data Errors

PAYLOAD_TOO_LARGE

A payload exceeds the size limit.

Causes:

  • Presence data exceeds 8 KB
  • Data value exceeds 256 KB
  • WebSocket message exceeds 64 KB

Fix: Reduce the payload size. For large data, store it externally and share a reference. See Best Practices for all limits.

TOPIC_NAME_TOO_LONG

A topic name exceeds 128 characters.

Fix: Use shorter topic names. Consider using a hierarchical naming scheme like "drawing/layer1" instead of encoding data in the topic name.

WebRTC Errors

RTC_NOT_ENABLED

Attempted an RTC operation without providing RTC options.

Fix: Pass rtc options when creating the client:

ts
const client = new StarfishClient({
  server: "ws://localhost:4000",
  rtc: {
    peerConnectionFactory: (config) => new RTCPeerConnection(config),
  },
});

TRANSPORT_UNAVAILABLE

RTC transport was requested with fallback: false but no RTC peers are connected.

Fix: Either set fallback: true (the default) to fall back to WebSocket, or ensure RTC peer connections are established before sending.

Common Issues

Messages Not Arriving

  1. Check subscription order: Set up your topic$ listener before calling subscribe() to avoid missing messages that arrive between the subscription and listener setup.

  2. Check session membership: Both sender and receiver must be in the same session.

  3. Check topic name: Topic names are case-sensitive. "Cursor" and "cursor" are different topics.

  4. Check includeSelf: By default, you don't receive your own published messages. Set delivery.includeSelf: true if you need them.

Connection Keeps Dropping

  1. Check heartbeat: If the server doesn't receive heartbeats within the expected interval, it may close the connection. Ensure no long-running synchronous code blocks the event loop.

  2. Check proxy/load balancer timeouts: Some proxies close idle WebSocket connections. The heartbeat should prevent this, but verify your proxy's timeout settings.

  3. Check reconnection settings: If maxRetries is too low, the client may give up reconnecting. The default is Infinity.

High Latency

  1. Use unreliable delivery for high-frequency data like cursor positions. Reliable delivery through the server adds round-trip latency.

  2. Enable WebRTC for peer-to-peer communication. This bypasses the server for data delivery.

  3. Sync clocks with client.clock.sync() before using time-sensitive features. Without sync, client.clock.runAt() may fire at the wrong time.

Node.js Specific

Process doesn't exit after disconnect:

The WebSocket connection may keep the Node.js event loop alive. Call disconnect() and ensure all listeners are cleaned up:

ts
await client.disconnect();
process.exit(0);

ws package version:

Use ws version 8.x or later. Older versions may not support all features used by the SDK.