createSocketRoomClass
Creates the Durable Object class hosting a Socket room's WebSockets in Cloudflare Workers, with the hibernation API, reconstruction on wake and per-room limits.
import { createSocketRoomClass } from "@meekojs/meeko/cloudflare";createSocketRoomClass(app, DurableObject, options) returns the class exported from the Worker for Socket rooms: one Durable Object per channel/room pair. The room accepts WebSockets with the hibernation API (ctx.acceptWebSocket), so idle connections do not keep the object in memory; on wake, it reconstructs each connection from its attachment and the hub authorizes it again. It distributes publications to its peers in order.
Import DurableObject from cloudflare:workers and pass it as an argument so @meekojs/meeko/cloudflare can load outside Workers. app is the same application or factory passed to createWorker.
Usage#
import { DurableObject } from "cloudflare:workers";
import { createSocketRoomClass, createWorker } from "@meekojs/meeko/cloudflare";
export default createWorker(createCloudflareApp);
export const MeekoSocketRoom = createSocketRoomClass(createCloudflareApp, DurableObject, {
keepHeaders: ["accept-language"],
rebuildTimeoutMs: 2_000,
});The exported name must match wrangler.jsonc (server.socketClassName, MeekoSocketRoom by default).
Type#
function createSocketRoomClass<Env = Record<string, unknown>>(
app: CloudflareApp<Env>,
base: DurableObjectBase<Env>,
options?: SocketRoomOptions,
): MeekoSocketRoomClass<Env>interface SocketRoomOptions {
readonly maxMessageBytes?: number
readonly autoResponse?: { readonly request: string; readonly response: string } | false
readonly keepHeaders?: readonly string[]
readonly maxPendingPublications?: number
readonly rebuildTimeoutMs?: number
readonly onError?: (error: unknown, requestId: string) => void
}type MeekoSocketRoomClass<Env> = new (
ctx: DurableObjectStateLike,
env: Env,
) => MeekoSocketRoomParameters#
createSocketRoomClass(app, base, options?):
| Option | Type | Default | Description |
|---|---|---|---|
maxMessageBytes |
number |
the application's socket.maxMessageBytes, or 65536 |
Largest incoming message; larger ones close the peer with 1009. |
autoResponse |
{ request, response } | false |
{ request: "ping", response: "pong" } |
Text message the runtime answers without waking the object. Protocol pings are always answered. |
keepHeaders |
readonly string[] |
[] |
Additional headers (lowercase) the channel sees in context.request before and after hibernation. By default the session cookie, origin and authorization are retained; "cookie" retains the full header. |
maxPendingPublications |
number |
1024 |
Undelivered publications a room accepts; above this, publish rejects with SOCKET_BACKLOG_EXCEEDED (503). |
rebuildTimeoutMs |
number |
5000 |
Milliseconds publications wait for connections being reconstructed after hibernation. |
onError |
(error, requestId) => void |
the application's | Error observer. |
Return value#
A MeekoSocketRoomClass<Env> class (new (ctx, env) => MeekoSocketRoom) with fetch (the upgrade forwarded by createWorker), webSocketMessage, webSocketClose, webSocketError and meekoPublish (DurableObjectSocketPubSub's RPC).
Examples#
The declaration in wrangler.jsonc#
// .meeko/output/cloudflare/wrangler.jsonc (generado; no lo edite)
{
"main": "./worker.js",
"durable_objects": { "bindings": [{ "name": "MEEKO_SOCKET", "class_name": "MeekoSocketRoom" }] },
"migrations": [{ "tag": "meeko-socket-v1", "new_sqlite_classes": ["MeekoSocketRoom"] }]
}With your own migrations history in server.wrangler, append that entry; the build stops with server.wrangler.migrations does not create the Socket room class …; append { "tag": "meeko-socket-v1", "new_sqlite_classes": ["MeekoSocketRoom"] } at the end of server.wrangler.migrations if missing.
A per-connection language surviving hibernation#
// worker.ts
export const MeekoSocketRoom = createSocketRoomClass(createCloudflareApp, DurableObject, { keepHeaders: ["accept-language"] });// server/socket/support.ts
export default defineSocketChannel({
clientToServer: { ask },
serverToClient: { answer },
handlers: {
ask: async ({ payload, context, emit }) => {
const language = context.request.headers.get("accept-language") ?? "es";
await emit("answer", await answerIn(language, payload));
},
},
});Very active rooms#
export const MeekoSocketRoom = createSocketRoomClass(createCloudflareApp, DurableObject, {
maxPendingPublications: 4096,
maxMessageBytes: 8 * 1024,
autoResponse: false,
});A Durable Object runs its code on a single thread: distribute traffic across multiple rooms (per document, per group) before increasing the limits of one global room.
Common mistakes#
Omit the DurableObject class#
export const MeekoSocketRoom = createSocketRoomClass(createCloudflareApp, undefined as never);TypeError: createSocketRoomClass requires DurableObject from cloudflare:workers. Pass it from the Worker.
import { DurableObject } from "cloudflare:workers";
export const MeekoSocketRoom = createSocketRoomClass(createCloudflareApp, DurableObject);Uppercase headers#
createSocketRoomClass(createCloudflareApp, DurableObject, { keepHeaders: ["Accept-Language"] });TypeError: keepHeaders must list lowercase header names: Accept-Language.
createSocketRoomClass(createCloudflareApp, DurableObject, { keepHeaders: ["accept-language"] });An invalid limit#
createSocketRoomClass(createCloudflareApp, DurableObject, { maxPendingPublications: 0, rebuildTimeoutMs: -1 });RangeError: Invalid maxPendingPublications; with a valid value, RangeError: rebuildTimeoutMs must be a non-negative number.
createSocketRoomClass(createCloudflareApp, DurableObject, { maxPendingPublications: 512, rebuildTimeoutMs: 2_000 });A cookie too large for the attachment#
createSocketRoomClass(createCloudflareApp, DurableObject, { keepHeaders: ["cookie"] }); // 20 KB analytics cookiesCloudflare limits a WebSocket's attachment to 16,384 bytes. Meeko estimates size before accepting: the upgrade responds with 431 and onError receives the error, rather than opening a connection that would close with 1011.
createSocketRoomClass(createCloudflareApp, DurableObject); // only Meeko's session cookieNotes#
- Security. By default, only the cookie named by
SessionManageris stored, not the rest of theCookieheader; on wake, the hub authorizes each connection again with it and closes with4401or4403if no longer valid. - Delivery. Each publication reaches already subscribed peers; a connection still being authorized does not delay others. On wake, publications wait at most
rebuildTimeoutMsfor reconstructed connections. - Buffer. Workers WebSockets do not report
bufferedAmount:socket.maxBufferBytesdoes not apply in rooms;maxMessageBytesandmaxPendingDeliveriesdo. - Configuration. The Worker generated by
meeko builduses defaults; changingkeepHeaders,maxPendingPublicationsorrebuildTimeoutMsrequires a custom Worker. - Failures on wake. If the application cannot be created, the room reports the error and closes the WebSocket that woke it with
1011.