Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/smogon/pokemon-showdown-client/llms.txt

Use this file to discover all available pages before exploring further.

PSConnection is the single object responsible for keeping the Pokémon Showdown client talking to the game server. It tries to open the socket inside a dedicated Web Worker (so that the main thread is never blocked by socket I/O), falls back to a direct SockJS connection if workers aren’t available, and transparently queues outbound messages while the socket is down. The class is instantiated exactly once during boot and stored at PS.connection. You will almost never construct it directly — instead you interact with it through PS.send() or by subscribing to PS for connection state changes.
PSConnection is created automatically by PSConnection.connect() at the bottom of client-connection.ts. That call happens once, immediately after the module loads. Do not call new PSConnection() yourself unless you are writing tests or a custom embedding.

PSConnection

class PSConnection

Constructor

new PSConnection()
The constructor immediately calls PSStorage.init(). If storage initialisation is asynchronous (cross-origin iframe handshake required), the actual socket connection is deferred until the returned Promise resolves. Otherwise, initConnection() is called synchronously.
(no parameters)
PSConnection takes no constructor arguments. Server configuration is read from PS.server, which is populated by PSStorage before initConnection() runs.

Properties

socket
WebSocket | null
The active SockJS / WebSocket instance when using a direct connection. null when a Web Worker is handling the socket, or when the client is disconnected.
connected
boolean
true when the underlying transport (worker or direct socket) has successfully completed its handshake with the server. Flips to false on disconnect; the client re-renders via PS.update() whenever this changes.
lastMessageTimeBeforeReconnect
number
The parsed integer value of PS.lastMessageTime captured at the moment of the most recent successful connection. Used by rooms to determine which messages are “new” after a reconnect.
queue
string[]
Messages that were passed to send() while connected was false. Flushed in order as soon as the connection succeeds. Messages are stored as raw protocol strings (roomid|message).
reconnectDelay
number
Current delay in milliseconds before the next reconnect attempt. Starts at 1000 (1 second), doubles after each failed attempt up to a cap of 15000 (15 seconds). Resets to 1000 on a successful direct connection.
reconnectTimer
ReturnType<typeof setTimeout> | null
Handle for the pending reconnect setTimeout. null when no reconnect is scheduled. Checked by the internal retryConnection() method to prevent duplicate timers from being queued simultaneously.

Methods

initConnection()
void
Called once during construction (after storage is ready). Tries tryConnectInWorker() first; if that returns false it falls back to directConnect(). This is the only entry point for the initial connection.
initConnection() {
  if (!this.tryConnectInWorker()) this.directConnect();
}
tryConnectInWorker()
boolean
Attempts to establish the server connection inside a dedicated Web Worker (/js/client-connection-worker.js). Returns true if a worker was successfully spawned (or one is already running), false if workers are unavailable or if a direct socket already exists.
// Returns false if this.socket already exists — only one transport at a time
if (!this.tryConnectInWorker()) this.directConnect();
directConnect()
void
Opens a SockJS (or plain WebSocket) connection on the main thread. Used as the fallback when workers are unavailable or encounter a fatal error. Attaches onopen, onmessage, onclose, and onerror handlers that mirror the worker message protocol.
directConnect() and tryConnectInWorker() are mutually exclusive. If this.worker is set, directConnect() returns immediately. If this.socket is set, tryConnectInWorker() returns false. Never set both.
canReconnect()
boolean
Returns false (and prompts the user to refresh) if the client has been running for more than 24 hours (Date.now() - PS.startTime > 86_400_000). Otherwise returns the value of the internal shouldReconnect flag, which is set to false by disconnect().
// Called internally before every retry attempt
if (!this.canReconnect()) return;
send(msg)
void
Sends a raw protocol string to the server.
  • If connected is false, the message is pushed to queue and sent once the connection succeeds.
  • If a worker is active, the message is forwarded via worker.postMessage({ type: 'send', data: msg }).
  • If a direct socket is active, the message is sent via socket.send(msg).
// You almost always use PS.send() rather than calling this directly
PS.connection.send('lobby|Hello!');
disconnect()
void
Permanently closes the connection and prevents automatic reconnection. Terminates the worker (if any), closes the socket (if any), and sets the internal shouldReconnect flag to false.
reconnect()
void
Manually attempts to re-open the connection. No-op if already connected. Prefers the worker path; falls back to directConnect() if no worker exists.

Static Methods

PSConnection.connect()
void
The module-level bootstrap function. Called automatically when client-connection.ts loads.
  • If PS.connection does not exist, creates a new PSConnection.
  • If PS.connection exists but is not connected, calls reconnect().
  • Also calls PS.prefs.doAutojoin() to rejoin rooms from preferences.

PSLoginServer

PSLoginServer is an anonymous singleton (accessed as PSLoginServer) that wraps the PS authentication HTTP endpoint (/~~<serverid>/action.php).
const PSLoginServer = new class { … }

Methods

rawQuery(act, data)
Promise<string | null>
Posts a form-encoded request to the login server. Returns the raw response text, or null on any error.
query(act, data?)
Promise<Record<string, any> | null>
Convenience wrapper around rawQuery that strips the leading ] from PS login-server responses and JSON-parses the result.
const result = await PSLoginServer.query('login', {
  name: 'ash',
  pass: 'password123',
  challstr: PS.user.challstr,
});
// result is a plain object, or null on failure

Net — HTTP Fetch Utility

Net is a thin XMLHttpRequest wrapper that provides a promise-based API for GET and POST requests throughout the client.
function Net(uri: string): NetRequest
Call Net(url) to get a NetRequest instance, then chain .get() or .post().
Net(uri).get(opts?)
Promise<string>
Makes a GET request (or any method specified in opts.method). Resolves with the response body text, rejects with an HttpError if the status is not 200.
Net(uri).post(opts?, body?)
Promise<string>
Convenience wrapper for POST requests. Merges body into the options and calls get() with method: 'POST'.
Net.encodeQuery(data)
string
URL-encodes a PostData object (or passes a string through unchanged). Converts true → 'on', false / null / undefined → ''.
Net.formData(form)
Record<string, string | boolean>
Reads all input[name], select[name], and textarea[name] elements inside a <form> and returns their values as a plain object. Checkbox values follow HTML conventions.

PSStorage — Cross-Origin Storage Abstraction

PSStorage ensures that localStorage data (preferences, teams, server config) can be shared between the client and embedded third-party deployments, even when they run on different origins. It uses a hidden <iframe> pointing at play.pokemonshowdown.com and window.postMessage to proxy storage reads and writes.
PSStorage.init()
void | Promise<void>
Called in the PSConnection constructor. Returns a Promise if cross-origin initialisation is required (iframe handshake), or void if the client is already on the correct origin (or in test mode).
PSStorage.request(type, uri, data)
Promise<string> | void
Sends a proxied HTTP request through the cross-origin iframe. Returns void when not in cross-origin mode (direct requests are used instead).
PSStorage.postCrossOriginMessage(data)
boolean
Posts a raw message string to the cross-origin iframe. Message format is a single-character type prefix followed by JSON-encoded content (e.g. 'P{"theme":"dark"}' for a prefs update).

Code Examples

// High-level API — always use this
PS.send('/join lobby');

// PS.send() formats the message and delegates to PSConnection:
send(msg: string, roomid?: RoomID) {
  if (!this.connection) {
    PS.alert(`You are not connected and cannot send ${msg}.`);
    return;
  }
  // Prepends the room prefix: "lobby|/join lobby"
  this.connection.send(`${roomid || ''}|${msg}`);
}

// PSConnection.send() queues if disconnected, or routes to worker/socket:
send(msg: string) {
  if (!this.connected) {
    this.queue.push(msg);   // sent automatically on reconnect
    return;
  }
  if (this.worker) {
    this.worker.postMessage({ type: 'send', data: msg });
  } else if (this.socket) {
    this.socket.send(msg);
  }
}

Reconnection Logic

After a disconnect, PSConnection schedules a reconnect via setTimeout using reconnectDelay. Each failed attempt doubles the delay, capped at 15 seconds:
AttemptDelay
1st1 000 ms
2nd2 000 ms
3rd4 000 ms
4th8 000 ms
5th+15 000 ms
The delay resets to 1 000 ms on any successful direct-socket connection (socket.onopen). Worker connections do not reset the delay (since the worker may have been running for a while already).
canReconnect() compares Date.now() against PS.startTime (set when the PS object is constructed). If the difference exceeds 24 hours, the method returns false and shows a modal asking the user to refresh. This prevents very long-lived tabs from accumulating WebSocket state indefinitely.
When a disconnect occurs, every room whose connected flag is true has it set to 'autoreconnect'. This sentinel value tells room panels to display a “Reconnecting…” banner rather than a hard error. Once the connection is restored, rooms re-send their /join commands automatically via PS.prefs.doAutojoin().

Build docs developers (and LLMs) love