Home Getting Started Server Client Shared Types GitHub ↗
sockr-server@1.2.0

Server SDK

Plugin-based WebSocket server built on Socket.IO and Express. Full API reference.

Server SDK Reference

Complete reference for sockr-server. All classes, methods, configuration options, plugins, and adapters.

SocketServer

The central class that wires together the HTTP server, Socket.IO, plugins, and providers.

import { SocketServer } from 'sockr-server';

const server = new SocketServer(config?: ServerConfig);

ServerConfig

All fields are optional. Defaults are production-friendly out of the box.

PropertyTypeDefaultDescription
portnumber3000Listening port (standalone & attachToExpress modes).
cors.originstring | string[]'*'Allowed CORS origins.
cors.credentialsbooleantrueAllow credentials in CORS requests.
pingTimeoutnumber60000Socket.IO ping timeout in milliseconds.
pingIntervalnumber25000Socket.IO ping interval in milliseconds.
transports('websocket'|'polling')[]['websocket','polling']Allowed Socket.IO transports.
providers.messageStoreIMessageStoreInMemoryMessageStoreMessage persistence adapter.
providers.queueIQueueProviderInMemoryQueueProviderOffline message queue adapter.
providers.cacheICacheProviderInMemoryCacheProviderCache adapter.
voiceVoiceConfignoneVoice calling config: static ICE servers and/or Coturn TURN credentials. Required by .useVoice().

Initialization Methods

Call exactly one of these before registering plugins. All return this for chaining.

createStandalone(): this

Creates and owns an internal HTTP server. Use listen() to start it.

server.createStandalone().useAuth(handler).useMessaging();
await server.listen(3000);

attach(httpServer: HTTPServer | HTTPSServer): this

Attaches Socket.IO to an existing Node.js HTTP or HTTPS server. After registering plugins, call initialize() (not listen()), then start your own server.

import { createServer } from 'http';
const httpServer = createServer(app);
server.attach(httpServer).useAuth(handler).useMessaging();
await server.initialize();
httpServer.listen(3000);

attachToExpress(app: Express): this

Wraps an Express app in an HTTP server internally. Use listen() to start.

server.attachToExpress(expressApp).useAuth(handler).useMessaging();
await server.listen(3000);

Plugin Registration

All plugin methods return this for fluent chaining. Register plugins before calling listen() or initialize().

useAuth(handler: AuthHandler): this

Enables token-based authentication. AuthHandler is defined as:

type AuthHandler = (token: string) => Promise<{ userId: string } | null>;

Return { userId } to authenticate, or null to reject. Rejected connections receive an auth_error event.

usePresence(): this

Enables online/offline presence broadcasting. Broadcasts user_online when a user authenticates and user_offline when their last socket disconnects.

useMessaging(): this

Enables direct 1:1 messaging, read receipts, typing indicators, and message history retrieval. Requires useAuth().

useGroupMessaging(): this

Enables full group chat: create, join, leave, group messaging, group typing indicators, and group message history. Requires useAuth().

useVoice(config?: VoiceConfig): this

Enables WebRTC P2P voice (and video) calling via ICE/STUN/TURN signaling. Requires useAuth(). Pass an optional VoiceConfig to supply ICE servers and TURN credentials, or read them from the top-level voice field of ServerConfig.

server.useVoice({
  iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
  turn: {
    urls: 'turn:turn.example.com:3478',
    secret: process.env.TURN_SECRET!,
    ttl: 3600,
  },
});

useConference(sfuProvider: ISFUProvider, sfuUrl: string): this

Enables SFU-based group conference calling. The sfuProvider creates rooms and generates access tokens on the server; the sfuUrl is delivered to clients so they can connect directly to the SFU. Requires useAuth() and useGroupMessaging().

import { LiveKitSFUProvider } from 'sockr-server';

server.useConference(
  new LiveKitSFUProvider({
    apiKey: process.env.LIVEKIT_API_KEY!,
    apiSecret: process.env.LIVEKIT_API_SECRET!,
    host: 'https://your-livekit.example.com',
  }),
  'wss://your-livekit.example.com'
);

use(plugin: Plugin): this

Register a custom plugin. See Custom Plugins.

Lifecycle Methods

listen(port?: number): Promise<void>

Connects all providers, initializes plugins, then starts listening. Use with createStandalone() or attachToExpress(). The port argument overrides the config value.

Warning: Do not call listen() when using attach() with an external server. Use initialize() instead and call listen() on your own HTTP server.

initialize(): Promise<this>

Connects providers and initializes plugins without starting a listener. Use when attached to an external HTTP server.

close(): Promise<void>

Disconnects all providers and closes Socket.IO (and the HTTP server if owned by Sockr).

Connection lifecycle: When a user authenticates, Sockr automatically:
  • Broadcasts user_online (if PresencePlugin is active)
  • Flushes queued 1:1 messages to the newly connected socket (if MessagePlugin is active)
  • Restores Socket.IO group room memberships silently (if GroupPlugin is active)
On disconnect, user_offline is only broadcast when this was the user's last active socket — supporting multi-device connections transparently.

Accessors

MethodReturnsDescription
getIO()ServerRaw Socket.IO Server instance (from socket.io).
getConnectionManager()ConnectionManagerManages all active socket connections and userId↔socketId mappings.
getProviders()ResolvedProvidersResolved message store, queue, and cache provider instances.

AuthPlugin

Registered via .useAuth(handler). Handles the authentication handshake.

Flow

  1. Client connects and emits authenticate with { token: string }.
  2. Server calls your AuthHandler with the token.
  3. On success: server emits authenticated with { userId, socketId } to the client, then fires the internal authenticated event to trigger message flushing and group restore.
  4. On failure: server emits auth_error with { message: string }.
server.useAuth(async (token: string) => {
  // JWT verification example
  try {
    const payload = jwt.verify(token, process.env.JWT_SECRET!);
    return { userId: payload.sub as string };
  } catch {
    return null; // reject
  }
});

PresencePlugin

Registered via .usePresence(). Broadcasts online/offline events to all connected clients.

Events emitted to clients

EventPayloadWhen
user_online{ userId: string }A user successfully authenticates
user_offline{ userId: string }A user's last socket disconnects
online_status{ statuses: Record<string, boolean> }Response to get_online_status

Events received from clients

EventPayloadDescription
get_online_status{ userIds: string[] }Query the online status of one or more users

MessagePlugin (Direct Messaging)

Registered via .useMessaging(). Handles all 1:1 messaging, typing indicators, read receipts, and message history.

Events received from clients

EventPayloadDescription
send_message{ to, content, metadata? }Send a direct message to another user
message_read{ messageId, from? }Mark a message as read
typing_start{ to }Notify a user you're typing
typing_stop{ to }Notify a user you've stopped typing
get_message_history{ with, limit?, before? }Fetch paginated message history. before is a Unix timestamp for pagination.

Events emitted to clients

EventRecipientPayload
receive_messageTarget user{ from, content, timestamp, messageId, metadata? }
message_deliveredSender{ messageId }
message_readOriginal sender{ messageId, from? }
message_errorSender{ messageId, error }
typing_startTarget user{ from }
typing_stopTarget user{ from }
message_historyRequester{ conversationId, messages: PersistedMessage[] }
Offline queuing: If the recipient is offline and a queue provider is configured, the message is enqueued. When the user next authenticates, flushQueuedMessages() is called automatically to deliver all pending messages.

GroupPlugin

Registered via .useGroupMessaging(). Handles group creation, membership, messaging, typing, and history.

Group management events

Client sendsPayloadServer responds
group_create{ name, members?, metadata? }group_created with full Group object
group_join{ groupId }group_joined with { groupId, group, queuedMessages? }
group_leave{ groupId }group_left to leaver; group_member_left broadcast to remaining members
get_group_members{ groupId }group_members with GroupMember[]
get_user_groups{}user_groups with Group[]

Group messaging events

Client sendsPayloadServer responds
group_send_message{ groupId, content, metadata? }group_receive_message to all members; group_message_delivered to sender
group_message_read{ groupId, messageId }group_message_read broadcast to the group
group_typing_start{ groupId }group_typing_start with { groupId, from } to all group members
group_typing_stop{ groupId }group_typing_stop with { groupId, from } to all group members
get_group_message_history{ groupId, limit?, before? }group_message_history with { groupId, messages: PersistedMessage[] }
Queued messages on join: The group_joined payload includes a queuedMessages array — messages sent to the group while the user was offline that haven't been delivered yet.
Multi-device group restore: On authentication, restoreGroupMemberships(userId, socketId) is called automatically. The user is silently re-joined to all their Socket.IO group rooms without re-emitting group_joined events.

VoicePlugin (P2P Voice & Video)

Registered via .useVoice(). Acts as the signaling layer for WebRTC peer-to-peer calls. It relays SDP offers/answers and ICE candidates between peers — it does not process media itself.

VoiceConfig

PropertyTypeDescription
iceServersIceServer[]Static ICE servers (STUN). Always included as-is in responses.
turn.urlsstring | string[]TURN server URL(s), e.g. "turn:turn.example.com:3478".
turn.secretstringThe static-auth-secret from your Coturn config. Never sent to clients.
turn.ttlnumberCredential lifetime in seconds. Defaults to 3600.
TURN credential security: When turn config is provided, Sockr generates short-lived HMAC-SHA1 credentials per user per call. The TURN shared secret never reaches the client — only the derived time-limited credentials are sent.

Events received from clients

EventPayloadDescription
call_get_ice_servers{}Request ICE/TURN server list. Server responds with call_ice_servers.
call_initiate{ to: string; sdpOffer: string }Begin a call with another user. Server forwards to callee as call_incoming.
call_answer{ callId: string; sdpAnswer: string }Accept an incoming call with an SDP answer.
call_reject{ callId: string }Decline an incoming call.
call_hangup{ callId: string }End an active or ringing call.
call_ice_candidate{ callId: string; candidate: IceCandidateInit }Trickle an ICE candidate to the remote peer.

Events emitted to clients

EventRecipientPayload
call_ice_serversRequester{ iceServers: IceServer[] } — includes STUN and generated TURN credentials
call_incomingCallee{ callId, from, sdpOffer, iceServers: IceServer[] }
call_ringingCaller{ callId } — callee socket received the call
call_answeredCaller{ callId, sdpAnswer }
call_rejectedCaller{ callId }
call_endedBoth parties{ callId }
call_busyCaller{ callId } — callee is already in a call
call_ice_candidateRemote peer{ callId, candidate: IceCandidateInit }

Automatic cleanup

When a user disconnects while in an active or ringing call, cleanupCallsForUser() is called automatically. The remote peer receives a call_ended event.

Video support

The VoicePlugin is video-capable — it relays SDP without inspecting media types. On the client, pass { video: true } to useVoiceCall() to include a video track in the offer.

ConferencePlugin (SFU Group Calls)

Registered via .useConference(sfuProvider, sfuUrl). Enables multi-party group conference calling via a Selective Forwarding Unit (SFU). The server handles room creation and token generation; clients connect directly to the SFU using the delivered token.

ISFUProvider interface

Implement this interface to connect any SFU. Sockr ships LiveKitSFUProvider as the reference implementation.

interface ISFUProvider {
  connect?(): Promise<void>;
  disconnect?(): Promise<void>;
  generateToken(roomName: string, participantIdentity: string, opts?: TokenOptions): Promise<string>;
  createRoom?(roomName: string, opts?: RoomOptions): Promise<void>;
  deleteRoom?(roomName: string): Promise<void>;
}

LiveKitSFUProvider

The built-in LiveKit provider. Uses lazy require('livekit-server-sdk') — install it separately:

npm install livekit-server-sdk
import { LiveKitSFUProvider } from 'sockr-server';

const provider = new LiveKitSFUProvider({
  apiKey: process.env.LIVEKIT_API_KEY!,
  apiSecret: process.env.LIVEKIT_API_SECRET!,
  host: 'https://your-livekit.example.com',
});

Events received from clients

EventPayloadDescription
conference_join{ groupId: string }Join or create a conference for the group. User must be a member of the group.
conference_leave{ groupId: string }Leave the conference. If last participant, the SFU room is deleted.

Events emitted to clients

EventRecipientPayload
conference_tokenJoining user{ groupId, sfuUrl, token } — connect to the SFU using this token
conference_startedAll group members{ groupId, startedBy, participantCount: 1 } — first participant joined
conference_endedAll group members{ groupId } — last participant left
conference_participant_joinedAll group members{ groupId, userId, participantCount }
conference_participant_leftAll group members{ groupId, userId, participantCount }
conference_errorRequesting user{ groupId, error: string } — e.g. not a group member
Membership check: CONFERENCE_JOIN validates that the requesting user is a member of the group (via IMessageStore.getGroup()). Non-members receive a conference_error.

Automatic conference cleanup

When a user disconnects while in a conference, cleanupParticipant() is called automatically. If they were the last participant, the SFU room is deleted and all group members receive conference_ended. Multi-device connections are handled — only the last socket triggers cleanup.

Database Adapters

Pass any of these to providers.messageStore in ServerConfig. All implement the IMessageStore interface from sockr-shared.

AdapterImportConstructor arg
InMemoryMessageStoresockr-servernone
MongoMessageStoresockr-server/adapters/db/mongoMongoDB connection string
PostgresMessageStoresockr-server/adapters/db/postgresPostgres connection string
MySQLMessageStoresockr-server/adapters/db/mysqlMySQL connection string
import { MongoMessageStore } from 'sockr-server/adapters/db/mongo';

const server = new SocketServer({
  providers: {
    messageStore: new MongoMessageStore('mongodb://localhost:27017/myapp'),
  },
});

Queue Adapters

Pass to providers.queue. Used for offline message queuing. All implement IQueueProvider.

AdapterImportConstructor arg
InMemoryQueueProvidersockr-servernone
RedisQueueProvidersockr-server/adapters/queue/redisRedis URL string

Cache Adapters

Pass to providers.cache. All implement ICacheProvider.

AdapterImportConstructor arg
InMemoryCacheProvidersockr-servernone
RedisCacheProvidersockr-server/adapters/cache/redisRedis URL string

Custom Plugins

Extend the Plugin abstract class from sockr-server. Override initialize() and handleConnection().

import { Plugin } from 'sockr-server';
import { Socket, Server } from 'socket.io';
import { ConnectionManager } from 'sockr-server';

class AnalyticsPlugin extends Plugin {
  private eventCount = 0;

  // Called once after server.listen() / server.initialize()
  initialize(): void {
    console.log('[Analytics] Plugin initialized');
  }

  // Called for every new socket connection
  handleConnection(socket: Socket): void {
    this.eventCount++;
    console.log(`[Analytics] Connection #${this.eventCount}: ${socket.id}`);

    socket.on('my_custom_event', (data) => {
      // handle custom event
      socket.emit('my_custom_response', { received: true, data });
    });

    socket.on('disconnect', () => {
      console.log(`[Analytics] Disconnected: ${socket.id}`);
    });
  }
}

// Register with the server
// The Plugin constructor receives (io, connectionManager)
const io = server.getIO();
const cm = server.getConnectionManager();
server.use(new AnalyticsPlugin(io, cm));
Tip: Call server.use() before server.listen(). The initialize() method is called during listen() / initialize(). The handleConnection() method is called for each new socket connection automatically.
Client Reference → Shared Types →