Skip to content
Meeko
Type to search the guides and the API reference. Open it from any page with / or Ctrl K; move with the arrow keys and open a result with Enter.
Get started

Contents

On this page

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.

adapter Since 1.0.0 @meekojs/meeko/cloudflare Source code
ts
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#

ts
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#

ts
function createSocketRoomClass<Env = Record<string, unknown>>(
  app: CloudflareApp<Env>,
  base: DurableObjectBase<Env>,
  options?: SocketRoomOptions,
): MeekoSocketRoomClass<Env>

SocketRoomOptions

ts
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
}

MeekoSocketRoomClass

ts
type MeekoSocketRoomClass<Env> = new (
  ctx: DurableObjectStateLike,
  env: Env,
) => MeekoSocketRoom

Parameters#

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#

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#

ts
// worker.ts
export const MeekoSocketRoom = createSocketRoomClass(createCloudflareApp, DurableObject, { keepHeaders: ["accept-language"] });
ts
// 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#

ts
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#

Incorrectts
export const MeekoSocketRoom = createSocketRoomClass(createCloudflareApp, undefined as never);

TypeError: createSocketRoomClass requires DurableObject from cloudflare:workers. Pass it from the Worker.

Correctts
import { DurableObject } from "cloudflare:workers";
export const MeekoSocketRoom = createSocketRoomClass(createCloudflareApp, DurableObject);

Uppercase headers#

Incorrectts
createSocketRoomClass(createCloudflareApp, DurableObject, { keepHeaders: ["Accept-Language"] });

TypeError: keepHeaders must list lowercase header names: Accept-Language.

Correctts
createSocketRoomClass(createCloudflareApp, DurableObject, { keepHeaders: ["accept-language"] });

An invalid limit#

Incorrectts
createSocketRoomClass(createCloudflareApp, DurableObject, { maxPendingPublications: 0, rebuildTimeoutMs: -1 });

RangeError: Invalid maxPendingPublications; with a valid value, RangeError: rebuildTimeoutMs must be a non-negative number.

Correctts
createSocketRoomClass(createCloudflareApp, DurableObject, { maxPendingPublications: 512, rebuildTimeoutMs: 2_000 });
Incorrectts
createSocketRoomClass(createCloudflareApp, DurableObject, { keepHeaders: ["cookie"] }); // 20 KB analytics cookies

Cloudflare 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.

Correctts
createSocketRoomClass(createCloudflareApp, DurableObject); // only Meeko's session cookie

Notes#

  • Security. By default, only the cookie named by SessionManager is stored, not the rest of the Cookie header; on wake, the hub authorizes each connection again with it and closes with 4401 or 4403 if 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 rebuildTimeoutMs for reconstructed connections.
  • Buffer. Workers WebSockets do not report bufferedAmount: socket.maxBufferBytes does not apply in rooms; maxMessageBytes and maxPendingDeliveries do.
  • Configuration. The Worker generated by meeko build uses defaults; changing keepHeaders, maxPendingPublications or rebuildTimeoutMs requires 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.

Was this page helpful?

Add a comment

It is sent when you choose “Yes” or “No”. Only the page, your answer and the comment are stored: no cookies and no personal data.

0 of 2000 characters

Edit on GitHub: docs/en/reference/adapters/createsocketroomclass.md