Hooks

Using hooks, you can define a WebSocket server that works across runtimes with the same syntax.

Crossws provides a cross-platform API to define WebSocket servers. An implementation with these hooks works across runtimes without needing you to go into details of each of them. You only define the life-cycle hooks that you only need.

Using defineHooks() wrapper is optional and for type support and code auto completion.
import { defineHooks } from "crossws";

const hooks = defineHooks({
  upgrade(req) {
    console.log(`[ws] upgrading ${req.url}...`);
    return {
      // namespace: new URL(req.url).pathname
      headers: {},
    };
  },

  open(peer) {
    console.log(`[ws] open: ${peer}`);
  },

  message(peer, message) {
    console.log("[ws] message", peer, message);
    if (message.text().includes("ping")) {
      peer.send("pong");
    }
  },

  close(peer, details) {
    console.log("[ws] close", peer, details);
  },

  error(peer, error) {
    console.log("[ws] error", peer, error);
  },

  drain(peer) {
    // Send buffer drained after backpressure — safe to resume sending.
    // Pair with `peer.bufferedAmount`. Not all adapters emit this.
    console.log("[ws] drain", peer);
  },
});

Context

You can attach data to the connection by returning a context object from the upgrade hook. This data is then available as peer.context in all other hooks for the lifetime of the connection.

Context can be volatile in some environments like cloudflare-durable.
import { defineHooks } from "crossws";

const hooks = defineHooks({
  upgrade(req) {
    return {
      context: { data: "myData" },
    };
  },

  open(peer) {
    console.log(peer.context.data); // myData
  },

  message(peer, message) {
    console.log(peer.context.data); // myData
  },

  close(peer, details) {
    console.log(peer.context.data); // myData
  },
});

Authentication

During the upgrade hook it is possible to authenticate the user before upgrading the connection. If the user is not authenticated, you can return (or throw) a Response to prevent the connection from being upgraded.

import { defineHooks } from "crossws";

const hooks = defineHooks({
  upgrade(req) {
    const authHeader = req.headers.get("Authorization");

    if (!authHeader || !authHeader.startsWith("Basic ")) {
      return new Response("Unauthorized", {
        status: 401,
        headers: {
          "WWW-Authenticate":
            'Basic realm="Websocket Authentication", charset="UTF-8"',
        },
      });
    }

    const base64Credentials = authHeader.split(" ")[1];
    const [username, password] = atob(base64Credentials).split(":");

    if (username !== "myUsername" || password !== "myPassword") {
      return new Response("Unauthorized", {
        status: 401,
        headers: {
          "WWW-Authenticate":
            'Basic realm="Websocket Authentication", charset="UTF-8"',
        },
      });
    }

    return {
      headers: {}, // Optionally return custom headers
    };
  },
});