PlayMesh/Docs
HomeGitHubnpm

Client API

Complete reference for @playmesh/client - connecting, emitting, and receiving events.

PlayMeshClient

Import from @playmesh/client.

Constructor

import { PlayMeshClient } from '@playmesh/client';
const client = new PlayMeshClient(options: PlayMeshClientOptions);
Method / PropertyDescriptionReturns
urlServer URL. e.g. "https://game.example.com"string
authAuth payload for the server's authenticate hook, or a function called on every connection attempt (useful for token refresh).object | () => object
socketOptions forwarded to the underlying Socket.IO client.Partial<SocketOptions>
signingSign all application events with ECDSA P-256. The server must enable signing too.boolean

Connection

Method / PropertyDescriptionReturns
connect()Connect and authenticate. Resolves with SessionInfo once the server establishes the session.Promise<SessionInfo>
disconnect()Disconnect from the server.void
connectedWhether the client is currently connected.boolean
const session = await client.connect();
// session: { id: string, userId: string, instances: string[] }

console.log('Connected as', session.userId);
console.log('Instances:', session.instances);

client.disconnect();

Session

Method / PropertyDescriptionReturns
sessionCurrent session info. Updated automatically as instances change.SessionInfo | undefined
instancesInstance paths ("domainId/instanceId") this session belongs to.string[]

Events

Method / PropertyDescriptionReturns
emit(event, payload?)Send an event to the server. Throws if not connected.void
on(event, handler)Listen for an event from the server.this
off(event, handler)Remove a listener.this
join(instancePath)Ask to join an instance. The server can veto; rejects with the denial message.Promise<void>
leave(instancePath)Ask to leave an instance. Always honored.Promise<void>
chat(text)Send a chat message to all instances you are in (server-moderated).void
client.on('chat', payload => {
  const { sender, message } = payload as { sender: string; message: string };
  appendMessage(`${sender}: ${message}`);
});

client.emit('move', { x: 100, y: 250 });

const handler = (payload: unknown) => { /* ... */ };
client.on('update', handler);
client.off('update', handler);

Connection lifecycle

Method / PropertyDescriptionReturns
onDisconnect(handler)Called when the connection drops. Receives the reason string.this
onReconnect(handler)Called when the connection is automatically re-established.this
onError(handler)Called when the server reports an error.this
onKick(handler)Called when the server kicks this client, just before the disconnect.this
client.onDisconnect(reason => {
  console.warn('Disconnected:', reason);
  showReconnectUI();
});

client.onReconnect(() => {
  hideReconnectUI();
});

client.onError(error => {
  // { scope: 'connection' | 'event' | 'join' | 'chat',
  //   event?, instance?, message }
  console.error('Server error:', error.message);
});

Authentication

// Static token
const client = new PlayMeshClient({
  url: 'https://game.example.com',
  auth: { token: localStorage.getItem('jwt') },
});

// Dynamic - refreshed on each reconnect
const client = new PlayMeshClient({
  url: 'https://game.example.com',
  auth: async () => ({ token: await authService.getToken() }),
});

Instance membership

The client automatically maintains client.instances as the server sends playmesh:joined and playmesh:left events. Clients can also request to join or leave instances:
await client.join('world/vip-lounge'); // rejects with the server's denial message
await client.leave('world/vip-lounge'); // always honored
The server stays authoritative: client joins are denied by default, unless the instance opts in with an onJoinRequest hook server-side.

Synced state

The client keeps live read replicas of the synced state of every instance it is in, seeded on join and updated in real time. Public state is shared by all members; your per-user private state is delivered only to you. All of it is server-written - clients read.
client.stateOf('world/city');      // public state replica
client.userStateOf('world/city');  // your private state (only you receive it)
client.onStateChange(change => {
  // change.scope === 'user' marks your private state
});

Presence

See who is in each of your instances and react to joins and leaves.
client.presenceOf('world/city');   // { count, users }
client.onPresence(event => {
  // { instance, type: 'join' | 'leave', userId, sessionId, count }
});

Chat

Built-in chat broadcasts to every instance you are a member of. The server moderates each message in real time and may rewrite it, block it, or kick you.
client.chat('hello everyone');
client.onChat(message => console.log(`${message.userId}: ${message.text}`));
client.onKick(reason => console.log('Kicked:', reason));

Typed events

Pass event maps to type emit/on end to end (compile-time only).
type ClientEvents = { 'player:move': { x: number; y: number } };
type ServerEvents = { 'player:update': { x: number; y: number; by: string } };

const client = new PlayMeshClient<ClientEvents, ServerEvents>({ url });
client.emit('player:move', { x: 1, y: 2 }); // payload type-checked

Types

interface SessionInfo {
  id: string;
  userId: string;
  instances: string[];  // "domainId/instanceId" paths
}

interface ServerError {
  scope: 'connection' | 'event' | 'join' | 'chat';
  event?: string;
  instance?: string;    // set when scope is 'join'
  message: string;
}

interface ChatMessage {
  instance: string;
  sessionId: string;
  userId: string;
  text: string;
  at: number;           // server timestamp (epoch ms)
}

interface PresenceEvent {
  instance: string;
  type: 'join' | 'leave';
  sessionId: string;
  userId: string;
  count: number;        // cluster-wide, after this change
}

interface PresenceInfo {
  count: number;
  users: string[];      // unique user ids currently in the instance
}

interface StateChange {
  instance: string;
  scope?: 'user';       // marks your private per-user state
  key?: string;         // absent for a clear
  value?: unknown;      // absent on delete
  cleared?: boolean;
}

Full example

game-client.ts
import { PlayMeshClient } from '@playmesh/client';

const client = new PlayMeshClient({
  url: import.meta.env.VITE_SERVER_URL,
  auth: async () => ({ token: await authService.getAccessToken() }),
});

client.on('world-state', payload => gameRenderer.updateWorld(payload));
client.on('player-joined', payload => gameRenderer.spawnPlayer(payload.userId));
client.on('player-left', payload => gameRenderer.removePlayer(payload.userId));
client.onDisconnect(() => gameRenderer.showDisconnectedOverlay());
client.onReconnect(() => gameRenderer.hideDisconnectedOverlay());

export async function startGame() {
  const session = await client.connect();
  gameRenderer.setLocalPlayerId(session.userId);
}

export const movePlayer = (x: number, y: number) => client.emit('move', { x, y });
export const sendChat = (message: string) => client.emit('chat', { message });