hooks.ws.js

The WebSocket hooks file at src/hooks.ws.js (or .ts) controls the WebSocket lifecycle. It’s required for svelte-realtime to work.

Minimal setup

// src/hooks.ws.js
import { createMessage } from 'svelte-realtime/server';

export function upgrade() {
  return { id: crypto.randomUUID() };
}

export const message = createMessage();

Available hooks

upgrade({ cookies })

Runs on every new WebSocket connection. Return user data to attach, or false to reject.

export function upgrade({ cookies }) {
  const session = validateSession(cookies.session_id);
  if (!session) return false;
  return { id: session.userId, name: session.name };
}

authenticate({ cookies, ... })

Optional. Runs as a normal HTTP POST /__ws/auth before every WebSocket upgrade (including reconnects). cookies.set() becomes Set-Cookie on a standard 204 response, which proxies route correctly. Use this when you need to refresh a session cookie on connect behind Cloudflare Tunnel or any edge proxy that strips Set-Cookie from 101 Switching Protocols responses.

export function authenticate({ cookies }) {
  const session = validateSession(cookies.get('session_id'));
  if (!session) return false; // -> 401, client does not open the WebSocket

  if (shouldRotate(session)) {
    cookies.set('session_id', rotate(session), {
      httpOnly: true,
      secure: true,
      sameSite: 'lax',
      path: '/'
    });
  }
  return { id: session.userId, name: session.name };
}

The hook receives the SvelteKit event shape ({ request, headers, cookies, url, remoteAddress, getClientAddress, platform }). Return undefined for success, false for 401, or a full Response for full control. Requires svelte-adapter-uws >= 0.4.12 and configure({ auth: true }) on the client.

See Cloudflare-Tunnel cookie fix for the full diagnosis and end-to-end setup.

message

Routes incoming WebSocket messages to your src/live/ functions. Create it with createMessage():

import { createMessage } from 'svelte-realtime/server';

export const message = createMessage({
  async beforeExecute(ws, rpcPath) {
    // Rate limiting, logging, etc.
  }
});

open(ws, { platform })

Fires when a WebSocket connection is fully established.

subscribe(ws, topic, ctx)

Fires when a client subscribes to a stream topic.

unsubscribe(ws, topic, ctx)

Fires when a client’s subscription count for a topic reaches zero.

close(ws, ctx)

Fires when the WebSocket closes (tab closed, network drop, etc).


Custom message handling

When you need to mix RPC with custom WebSocket messages, use onUnhandled or drop to handleRpc directly.

onUnhandled

Pass onUnhandled to createMessage to handle non-RPC messages (binary data, custom protocols, etc.):

export const message = createMessage({
  onUnhandled(ws, data, platform) {
    // handle non-RPC messages (binary data, custom protocols, etc.)
  }
});

handleRpc

For full control, use handleRpc directly and handle non-RPC messages yourself:

import { handleRpc } from 'svelte-realtime/server';

export function message(ws, { data, platform }) {
  if (handleRpc(ws, data, platform)) return;
  // your custom message handling
}

handleRpc returns true if the message was an RPC call, false otherwise. This lets you branch on the result and handle everything else however you want.

Progression

export { message }createMessage({...}) → manual handleRpc. Start simple, add options when needed, drop to full control only if you have to.

Was this page helpful?