🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

crossws

Package Overview
Dependencies
Maintainers
1
Versions
28
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

crossws - npm Package Compare versions

Comparing version
0.4.8
to
0.4.9
+50
dist/_chunks/_resolve.mjs
const HOOK_NAMES = [
"upgrade",
"message",
"open",
"close",
"drain",
"error",
"ping",
"pong"
];
function defaultResolve(server, wsOpts) {
if (wsOpts.resolve) return wsOpts.resolve;
if (HOOK_NAMES.some((name) => typeof wsOpts[name] === "function")) return;
const fetch = server.options.fetch;
if (typeof fetch !== "function") throw new Error("[crossws] server has no fetch handler to resolve WebSocket hooks from");
return (req) => Promise.resolve(fetch(req)).then((res) => hooksFromFetchResult(res));
}
function hooksFromFetchResult(res) {
const crossws = res?.crossws;
if (res instanceof Response) {
if (crossws) {
res.body?.cancel().catch(() => {});
return crossws;
}
if (!res.ok && res.status !== 101) return { upgrade: () => res };
res.body?.cancel().catch(() => {});
return;
}
const headers = res?.headers;
if (!headers) return crossws;
const userUpgrade = crossws?.upgrade;
return {
...crossws,
async upgrade(request) {
const result = await userUpgrade?.(request);
if (result instanceof Response) return result;
return {
...result,
headers: mergeHeaders(headers, result?.headers)
};
}
};
}
function mergeHeaders(base, extra) {
if (!extra) return base;
const merged = new Headers(base);
for (const [key, value] of new Headers(extra)) merged.set(key, value);
return merged;
}
export { defaultResolve };
+29
-2

@@ -1,2 +0,2 @@

import { Hooks } from "./adapter.mjs";
import { Hooks, MaybePromise } from "./adapter.mjs";
import { BunOptions } from "./bun.mjs";

@@ -10,2 +10,11 @@ import { BunnyOptions } from "./bunny.mjs";

type WSOptions = Partial<Hooks> & {
/**
* Resolve the WebSocket hooks for an incoming request.
*
* When omitted, hooks are resolved by calling the server's `fetch` handler
* and reading the `crossws` property off the returned `Response`. Provide
* `resolve` only to customize routing (e.g. resolve hooks without invoking
* the app). The default is skipped when inline hooks are passed directly
* (e.g. `ws({ message })`), which run with zero per-event overhead instead.
*/
resolve?: (req: ServerRequest) => Partial<Hooks> | Promise<Partial<Hooks>>;

@@ -21,5 +30,23 @@ options?: {

};
type ServerWithWSOptions = ServerOptions & {
/**
* Value the app `fetch` handler may return for a WebSocket upgrade request when
* the default resolver is used. Either:
* - a `Response` carrying hooks on its `crossws` property (the srvx convention),
* or
* - a plain `{ crossws, headers }` object with the hooks and optional headers to
* send on the WebSocket handshake response.
*
* Returning a normal `Response` (no `crossws`) is always valid — the connection
* simply upgrades without hooks.
*/
type WSUpgradeResult = (Response & {
crossws?: Partial<Hooks>;
}) | {
crossws?: Partial<Hooks>;
headers?: HeadersInit;
};
type ServerWithWSOptions = Omit<ServerOptions, "fetch"> & {
fetch: (request: ServerRequest) => MaybePromise<WSUpgradeResult>;
websocket?: WSOptions;
};
export { ServerWithWSOptions, WSOptions };

@@ -438,2 +438,18 @@ import { WebSocket } from "./web.mjs";

terminate(): void;
/**
* Send an application-level WebSocket ping control frame to the client.
*
* Pair with the {@link Hooks.pong} hook (e.g. embedding a timestamp in
* `data`) to measure round-trip latency, or rely on the {@link Hooks.ping}
* hook to observe pings the client sends unprompted.
*
* `data` is optional and, per RFC 6455, must not exceed 125 bytes (a ping is
* a control frame); an over-long or otherwise invalid payload is reported via
* the {@link Hooks.error} hook instead of being sent.
*
* **Note:** Not all adapters can send a ping frame; unsupported adapters
* warn once and no-op. Refer to the
* [compatibility table](https://crossws.h3.dev/guide/peer#compatibility).
*/
ping(_data?: unknown): number | void | undefined;
/** Subscribe to a topic */

@@ -519,2 +535,18 @@ subscribe(topic: string): void;

}
declare class AdapterHookable {
#private;
options: AdapterOptions;
constructor(options?: AdapterOptions);
callHook<N extends keyof Hooks>(name: N, arg1: Parameters<Hooks[N]>[0], arg2?: Parameters<Hooks[N]>[1], connection?: object): MaybePromise<ReturnType<Hooks[N]>>;
upgrade(request: Request & {
readonly context?: Record<string, unknown>;
}): Promise<{
context: PeerContext;
namespace: string;
upgradeHeaders?: HeadersInit;
endResponse?: Response;
handled?: boolean;
}>;
_resolveProtocol(request: Request, upgradeHeaders: HeadersInit | undefined, protocolFromHook: string | undefined): Promise<string | undefined>;
}
declare function defineHooks<T extends Partial<Hooks> = Partial<Hooks>>(hooks: T): T;

@@ -531,2 +563,8 @@ type ResolveHooks = (request: Request & {

* - You can return { headers } to modify the response.
* - You can return { protocol } to accept a WebSocket subprotocol for this
* connection (echoed back as `Sec-WebSocket-Protocol`). This is the
* per-connection counterpart to the global
* {@link AdapterOptions.handleProtocols} option and takes precedence over
* it. It should be one of the subprotocols the client offered (the values
* in the request's `Sec-WebSocket-Protocol` header).
* - You can return { namespace } to change the pub/sub namespace.

@@ -546,2 +584,3 @@ * - You can return { context } to provide a custom peer context.

headers?: HeadersInit;
protocol?: string;
namespace?: string;

@@ -570,2 +609,20 @@ context?: PeerContext;

error: (peer: Peer, error: WSError) => MaybePromise<void>;
/**
* An application-level WebSocket ping control frame was received from the
* peer (e.g. sent by the client, or by another server via
* {@link Peer.ping}).
*
* **Note:** Only emitted by adapters that surface inbound ping frames.
* Refer to the [compatibility table](https://crossws.h3.dev/guide/peer#compatibility).
*/
ping: (peer: Peer, data: Uint8Array) => MaybePromise<void>;
/**
* An application-level WebSocket pong control frame was received from the
* peer, typically in reply to {@link Peer.ping}. Use together with a
* timestamp embedded in the ping payload to measure round-trip latency.
*
* **Note:** Only emitted by adapters that surface inbound pong frames.
* Refer to the [compatibility table](https://crossws.h3.dev/guide/peer#compatibility).
*/
pong: (peer: Peer, data: Uint8Array) => MaybePromise<void>;
}

@@ -608,2 +665,24 @@ interface AdapterInstance {

/**
* Select the WebSocket subprotocol to accept during the handshake.
*
* Browsers that open `new WebSocket(url, protocols)` send their offer in the
* `Sec-WebSocket-Protocol` request header and **reject the connection** if
* the server's `101` response doesn't echo one of the offered values back.
* By default crossws negotiates nothing (a server never claims to speak a
* protocol the app didn't opt into), so supply this to accept one.
*
* Called with the set of subprotocols the client offered and the upgrade
* request. Return the single subprotocol to accept (must be one of the
* offered values), or `false`/`undefined` to accept none. Only invoked when
* the client actually offered at least one subprotocol.
*
* This is the global default; the {@link Hooks.upgrade} hook may return
* `{ protocol }` to override it per connection.
*
* @example
* handleProtocols: (protocols) =>
* protocols.has("graphql-transport-ws") ? "graphql-transport-ws" : false
*/
handleProtocols?: (protocols: Set<string>, request: Request) => MaybePromise<string | false | null | undefined>;
/**
* Optional sync backplane to relay pub/sub between multiple crossws

@@ -623,5 +702,34 @@ * instances (e.g. across regions/processes). Opt-in: when absent, pub/sub

onError?: (error: unknown, context: SyncErrorContext) => void;
/**
* Close a connection that has stayed idle — no incoming messages and no
* pong replies — for roughly this many **seconds**. This reclaims peers
* whose transport died silently ("half-open" sockets: laptop sleep,
* NAT/mobile idle timeout, power loss, a cut cable) without the TCP stack
* ever delivering a `FIN`/`RST`, which would otherwise leak forever.
*
* Implemented per runtime, but with a single consistent knob:
* - **Node** — the `ws` library has no built-in liveness, so crossws pings
* each peer on this interval and terminates any that miss the pong. Honors
* sub-second (fractional) values.
* - **Bun / Deno / uWebSockets / Bunny** — mapped to the runtime's native
* WebSocket idle timeout, which also auto-sends keepalive pings. These
* runtimes take **whole seconds**, so a fractional value is rounded down —
* a value below `1` may become `0` and disable liveness there; use `>= 1`.
*
* Terminated peers surface through the normal `close` hook (Node reports
* code `1006`), so any `close`/`error` teardown — including
* `createWebSocketProxy` closing its upstream — runs unchanged.
*
* Set to `0` to disable. Defaults to {@link DEFAULT_IDLE_TIMEOUT} (30s) on
* every runtime — low enough to keep idle connections alive through the
* typical ~60s reverse-proxy / load-balancer idle timeout, while reclaiming
* dead sockets promptly. Pings are a few bytes and standards clients auto-pong,
* so a live connection is never disconnected.
*
* @default 30 (seconds)
*/
idleTimeout?: number;
}
type Adapter<AdapterT extends AdapterInstance = AdapterInstance, Options extends AdapterOptions = AdapterOptions> = (options?: Options) => AdapterT;
declare function defineWebSocketAdapter<AdapterT extends AdapterInstance = AdapterInstance, Options extends AdapterOptions = AdapterOptions>(factory: Adapter<AdapterT, Options>): Adapter<AdapterT, Options>;
export { Adapter, AdapterInstance, AdapterInternal, AdapterOptions, Hooks, Message, Peer, PeerContext, PostgresClientLike, RedisClientLike, ResolveHooks, SyncAdapter, SyncDriver, SyncDriverName, SyncDriverOptions, SyncErrorContext, SyncMessage, WSError, WaitForDrainOptions, broadcastChannel, cluster, decodeEnvelope, defineHooks, defineWebSocketAdapter, encodeEnvelope, pgsql, redis, setupPrimaryCluster, syncDrivers };
export { Adapter, AdapterHookable, AdapterInstance, AdapterInternal, AdapterOptions, Hooks, MaybePromise, Message, Peer, PeerContext, PostgresClientLike, RedisClientLike, ResolveHooks, SyncAdapter, SyncDriver, SyncDriverName, SyncDriverOptions, SyncErrorContext, SyncMessage, WSError, WaitForDrainOptions, broadcastChannel, cluster, decodeEnvelope, defineHooks, defineWebSocketAdapter, encodeEnvelope, pgsql, redis, setupPrimaryCluster, syncDrivers };
+69
-25
var AdapterHookable = class {
options;
#resolveCache = /* @__PURE__ */ new WeakMap();
constructor(options) {
this.options = options || {};
}
callHook(name, arg1, arg2) {
callHook(name, arg1, arg2, connection) {
const globalHook = this.options.hooks?.[name];
const globalPromise = globalHook?.(arg1, arg2);
const resolve = this.options.resolve;
if (!resolve) return globalPromise;
const request = arg1.request || arg1;
const resolveHooksPromise = this.options.resolve?.(request);
const cacheKey = connection || arg1.context || request;
let resolveHooksPromise;
if (this.#resolveCache.has(cacheKey)) resolveHooksPromise = this.#resolveCache.get(cacheKey);
else {
try {
resolveHooksPromise = resolve(request);
} catch (error) {
resolveHooksPromise = Promise.reject(error);
}
this.#resolveCache.set(cacheKey, resolveHooksPromise);
if (resolveHooksPromise instanceof Promise) resolveHooksPromise.catch(() => {
if (this.#resolveCache.get(cacheKey) === resolveHooksPromise) this.#resolveCache.delete(cacheKey);
});
}
if (!resolveHooksPromise) return globalPromise;

@@ -21,25 +37,22 @@ const resolvePromise = resolveHooksPromise instanceof Promise ? resolveHooksPromise.then((hooks) => hooks?.[name]) : resolveHooksPromise?.[name];

const context = request.context || {};
let upgradeHeaders;
let protocolFromHook;
try {
const res = await this.callHook("upgrade", request);
if (!res) return {
context,
namespace
};
if (res.namespace) namespace = res.namespace;
if (res.context) Object.assign(context, res.context);
if (res instanceof Response) return {
context,
namespace,
endResponse: res
};
if (res.handled) return {
context,
namespace,
handled: true
};
if (res.headers) return {
context,
namespace,
upgradeHeaders: res.headers
};
const res = await this.callHook("upgrade", request, void 0, context);
if (res) {
if (res.namespace) namespace = res.namespace;
if (res.context) Object.assign(context, res.context);
if (res instanceof Response) return {
context,
namespace,
endResponse: res
};
if (res.handled) return {
context,
namespace,
handled: true
};
upgradeHeaders = res.headers;
protocolFromHook = res.protocol;
}
} catch (error) {

@@ -54,8 +67,39 @@ const errResponse = error.response || error;

}
const protocol = await this._resolveProtocol(request, upgradeHeaders, protocolFromHook);
if (protocol) {
const merged = new Headers(upgradeHeaders);
merged.set("sec-websocket-protocol", protocol);
upgradeHeaders = merged;
}
return {
context,
namespace
namespace,
upgradeHeaders
};
}
async _resolveProtocol(request, upgradeHeaders, protocolFromHook) {
if (protocolFromHook) return protocolFromHook;
if (upgradeHeaders) {
const fromHeader = (upgradeHeaders instanceof Headers ? upgradeHeaders : new Headers(upgradeHeaders)).get("sec-websocket-protocol");
if (fromHeader) return fromHeader;
}
const handleProtocols = this.options.handleProtocols;
if (handleProtocols) {
const offered = _parseProtocols(request.headers.get("sec-websocket-protocol"));
if (offered.size > 0) {
const chosen = await handleProtocols(offered, request);
if (chosen) return chosen;
}
}
}
};
function _parseProtocols(header) {
const protocols = /* @__PURE__ */ new Set();
if (!header) return protocols;
for (const part of header.split(",")) {
const token = part.trim();
if (token) protocols.add(token);
}
return protocols;
}
function defineHooks(hooks) {

@@ -62,0 +106,0 @@ return hooks;

@@ -1,2 +0,2 @@

import { Adapter, AdapterInstance, AdapterOptions, Peer, PeerContext, SyncDriver } from "./adapter.mjs";
import { Adapter, AdapterHookable, AdapterInstance, AdapterOptions, Peer, PeerContext, SyncDriver } from "./adapter.mjs";
import { Server, ServerWebSocket, WebSocketHandler } from "bun";

@@ -22,2 +22,3 @@ interface BunAdapter extends AdapterInstance {

sync?: SyncDriver;
hooks: AdapterHookable;
}> {

@@ -37,3 +38,4 @@ get remoteAddress(): string;

terminate(): void;
ping(data?: unknown): number;
}
export { BunAdapter, BunOptions, bunAdapter };

@@ -14,2 +14,3 @@ import { import_websocket_server } from "./libs/ws.mjs";

}
const HEARTBEAT_PING = Buffer.from("crossws-ping");
const nodeAdapter = (options = {}) => {

@@ -25,2 +26,5 @@ if ("Deno" in globalThis || "Bun" in globalThis) throw new Error("[crossws] Using Node.js adapter in an incompatible environment.");

});
const liveSockets = /* @__PURE__ */ new Set();
const idleTimeoutMs = (options.idleTimeout ?? 30) * 1e3;
const sweepMs = Math.max(1, Math.floor(idleTimeoutMs));
wss.on("connection", (ws, nodeReq) => {

@@ -35,5 +39,16 @@ const request = new NodeReqProxy(nodeReq);

namespace: nodeReq._namespace,
sync: baseUtils.sync
sync: baseUtils.sync,
hooks
});
peers.add(peer);
liveSockets.add(ws);
if (idleTimeoutMs > 0) {
ws._isAlive = true;
const markAlive = () => {
ws._isAlive = true;
};
ws.on("pong", markAlive);
ws.on("ping", markAlive);
ws.on("message", markAlive);
}
hooks.callHook("open", peer);

@@ -45,2 +60,9 @@ ws.on("message", (data, isBinary) => {

});
ws.on("ping", (data) => {
hooks.callHook("ping", peer, data);
});
ws.on("pong", (data) => {
if (data.equals(HEARTBEAT_PING)) return;
hooks.callHook("pong", peer, data);
});
ws.on("error", (error) => {

@@ -55,2 +77,3 @@ peers.delete(peer);

peers.delete(peer);
liveSockets.delete(ws);
socket?.off("drain", onDrain);

@@ -63,2 +86,25 @@ hooks.callHook("close", peer, {

});
let idleTimer;
const stopSweep = () => {
if (idleTimer) {
clearInterval(idleTimer);
idleTimer = void 0;
}
};
if (idleTimeoutMs > 0) {
idleTimer = setInterval(() => {
for (const ws of liveSockets) {
if (ws._isAlive === false) {
ws.terminate();
continue;
}
ws._isAlive = false;
try {
ws.ping(HEARTBEAT_PING);
} catch {}
}
}, sweepMs);
idleTimer.unref?.();
wss.on("close", stopSweep);
}
wss.on("headers", (outgoingHeaders, req) => {

@@ -70,5 +116,15 @@ const upgradeHeaders = req._upgradeHeaders;

...baseUtils,
close: async (code, reason) => {
stopSweep();
await baseUtils.close(code, reason);
},
handleUpgrade: async (nodeReq, socket, head, webRequest) => {
const request = webRequest || new NodeReqProxy(nodeReq);
const { upgradeHeaders, endResponse, handled, context, namespace } = await hooks.upgrade(request);
let upgraded;
try {
upgraded = await hooks.upgrade(request);
} catch {
return sendResponse(socket, new Response("Internal Server Error", { status: 500 }));
}
const { upgradeHeaders, endResponse, handled, context, namespace } = upgraded;
if (endResponse) return sendResponse(socket, endResponse);

@@ -85,4 +141,4 @@ if (handled) return;

closeAll: (code, data, force) => {
for (const client of wss.clients) if (force) client.terminate();
else client.close(code, data);
for (const ws of liveSockets) if (force) ws.terminate();
else ws.close(code, data);
}

@@ -124,2 +180,9 @@ };

}
ping(data) {
try {
this._internal.ws.ping(data);
} catch (error) {
this._internal.hooks.callHook("error", this, new WSError(error));
}
}
};

@@ -126,0 +189,0 @@ var NodeReqProxy = class extends StubRequest {

@@ -94,2 +94,3 @@ import { kNodeInspect, serializeMessage } from "./adapter.mjs";

#ws;
#pingUnsupportedWarned = false;
constructor(internal) {

@@ -158,2 +159,8 @@ this._topics = /* @__PURE__ */ new Set();

}
ping(_data) {
if (!this.#pingUnsupportedWarned) {
this.#pingUnsupportedWarned = true;
console.warn("[crossws] `peer.ping()` is not supported by this adapter.");
}
}
subscribe(topic) {

@@ -160,0 +167,0 @@ this._topics.add(topic);

import { AdapterHookable, adapterUtils, getPeers, toBufferLike } from "../_chunks/adapter.mjs";
import { Message, Peer } from "../_chunks/peer.mjs";
import { WSError } from "../_chunks/error.mjs";
const bunAdapter = (options = {}) => {

@@ -24,4 +25,5 @@ if (typeof Bun === "undefined") throw new Error("[crossws] Using Bun adapter in an incompatible environment.");

websocket: {
idleTimeout: options.idleTimeout ?? 30,
message: (ws, message) => {
const peer = getPeer(ws, getPeers(globalPeers, ws.data.namespace), baseUtils.sync);
const peer = getPeer(ws, getPeers(globalPeers, ws.data.namespace), baseUtils.sync, hooks);
hooks.callHook("message", peer, new Message(message, peer));

@@ -31,3 +33,3 @@ },

const peers = getPeers(globalPeers, ws.data.namespace);
const peer = getPeer(ws, peers, baseUtils.sync);
const peer = getPeer(ws, peers, baseUtils.sync, hooks);
peers.add(peer);

@@ -38,3 +40,3 @@ hooks.callHook("open", peer);

const peers = getPeers(globalPeers, ws.data.namespace);
const peer = getPeer(ws, peers, baseUtils.sync);
const peer = getPeer(ws, peers, baseUtils.sync, hooks);
peers.delete(peer);

@@ -47,4 +49,12 @@ hooks.callHook("close", peer, {

drain: (ws) => {
const peer = getPeer(ws, getPeers(globalPeers, ws.data.namespace));
const peer = getPeer(ws, getPeers(globalPeers, ws.data.namespace), baseUtils.sync, hooks);
hooks.callHook("drain", peer);
},
ping: (ws, data) => {
const peer = getPeer(ws, getPeers(globalPeers, ws.data.namespace), baseUtils.sync, hooks);
hooks.callHook("ping", peer, data);
},
pong: (ws, data) => {
const peer = getPeer(ws, getPeers(globalPeers, ws.data.namespace), baseUtils.sync, hooks);
hooks.callHook("pong", peer, data);
}

@@ -54,3 +64,3 @@ }

};
function getPeer(ws, peers, sync) {
function getPeer(ws, peers, sync, hooks) {
if (ws.data.peer) return ws.data.peer;

@@ -62,3 +72,4 @@ const peer = new BunPeer({

namespace: ws.data.namespace,
sync
sync,
hooks
});

@@ -98,3 +109,11 @@ ws.data.peer = peer;

}
ping(data) {
try {
return this._internal.ws.ping(data);
} catch (error) {
this._internal.hooks.callHook("error", this, new WSError(error));
return 0;
}
}
};
export { bunAdapter as default };

@@ -17,3 +17,3 @@ import { AdapterHookable, adapterUtils, getPeers, toBufferLike } from "../_chunks/adapter.mjs";

if (negotiatedProtocol) upgradeOptions.protocol = negotiatedProtocol;
if (options.idleTimeout !== void 0) upgradeOptions.idleTimeout = options.idleTimeout;
upgradeOptions.idleTimeout = options.idleTimeout ?? 30;
const { response, socket } = request.upgradeWebSocket(Object.keys(upgradeOptions).length > 0 ? upgradeOptions : void 0);

@@ -20,0 +20,0 @@ const remoteAddress = request.headers.get("x-real-ip") || void 0;

@@ -19,3 +19,4 @@ import { AdapterHookable, adapterUtils, getPeers, toBufferLike } from "../_chunks/adapter.mjs";

headers,
protocol: headers.get("sec-websocket-protocol") ?? ""
protocol: headers.get("sec-websocket-protocol") ?? "",
idleTimeout: options.idleTimeout ?? 30
});

@@ -22,0 +23,0 @@ const peers = getPeers(globalPeers, namespace);

@@ -1,2 +0,2 @@

import { Adapter, AdapterInstance, AdapterOptions, Peer, PeerContext, SyncDriver } from "../_chunks/adapter.mjs";
import { Adapter, AdapterHookable, AdapterInstance, AdapterOptions, Peer, PeerContext, SyncDriver } from "../_chunks/adapter.mjs";
import { WebSocket } from "../_chunks/web.mjs";

@@ -33,2 +33,3 @@ import uws from "uWebSockets.js";

sync?: SyncDriver;
hooks: AdapterHookable;
}> {

@@ -47,2 +48,3 @@ get remoteAddress(): string | undefined;

terminate(): void;
ping(data?: uws.RecognizedString): number;
}

@@ -49,0 +51,0 @@ declare class UWSReqProxy extends StubRequest {

import { AdapterHookable, adapterUtils, getPeers, toBufferLike } from "../_chunks/adapter.mjs";
import { Message, Peer } from "../_chunks/peer.mjs";
import { WSError } from "../_chunks/error.mjs";
import { StubRequest } from "../_chunks/_request.mjs";

@@ -11,6 +12,7 @@ const uwsAdapter = (options = {}) => {

websocket: {
idleTimeout: options.idleTimeout ?? 30,
...options.uws,
close(ws, code, message) {
const peers = getPeers(globalPeers, ws.getUserData().namespace);
const peer = getPeer(ws, peers, baseUtils.sync);
const peer = getPeer(ws, peers, baseUtils.sync, hooks);
peer._internal.ws.readyState = 2;

@@ -25,12 +27,22 @@ peers.delete(peer);

message(ws, message, _isBinary) {
const peer = getPeer(ws, getPeers(globalPeers, ws.getUserData().namespace), baseUtils.sync);
const peer = getPeer(ws, getPeers(globalPeers, ws.getUserData().namespace), baseUtils.sync, hooks);
hooks.callHook("message", peer, new Message(message, peer));
},
drain(ws) {
const peer = getPeer(ws, getPeers(globalPeers, ws.getUserData().namespace));
const peer = getPeer(ws, getPeers(globalPeers, ws.getUserData().namespace), baseUtils.sync, hooks);
hooks.callHook("drain", peer);
},
ping(ws, message) {
if (!hooks.options.hooks?.ping && !hooks.options.resolve) return;
const peer = getPeer(ws, getPeers(globalPeers, ws.getUserData().namespace), baseUtils.sync, hooks);
hooks.callHook("ping", peer, new Uint8Array(message));
},
pong(ws, message) {
if (!hooks.options.hooks?.pong && !hooks.options.resolve) return;
const peer = getPeer(ws, getPeers(globalPeers, ws.getUserData().namespace), baseUtils.sync, hooks);
hooks.callHook("pong", peer, new Uint8Array(message));
},
open(ws) {
const peers = getPeers(globalPeers, ws.getUserData().namespace);
const peer = getPeer(ws, peers, baseUtils.sync);
const peer = getPeer(ws, peers, baseUtils.sync, hooks);
peers.add(peer);

@@ -80,3 +92,3 @@ hooks.callHook("open", peer);

};
function getPeer(uws, peers, sync) {
function getPeer(uws, peers, sync, hooks) {
const uwsData = uws.getUserData();

@@ -91,3 +103,4 @@ if (uwsData.peer) return uwsData.peer;

uwsData,
sync
sync,
hooks
});

@@ -131,2 +144,10 @@ uwsData.peer = peer;

}
ping(data) {
try {
return this._internal.uws.ping(data);
} catch (error) {
this._internal.hooks.callHook("error", this, new WSError(error));
return 0;
}
}
};

@@ -133,0 +154,0 @@ var UWSReqProxy = class extends StubRequest {

import bunAdapter from "../adapters/bun.mjs";
import { defaultResolve } from "../_chunks/_resolve.mjs";
import { serve as serve$1 } from "srvx/bun";

@@ -7,3 +8,3 @@ function plugin(wsOpts) {

hooks: wsOpts,
resolve: wsOpts.resolve,
resolve: defaultResolve(server, wsOpts),
...wsOpts.options?.bun

@@ -10,0 +11,0 @@ });

import bunnyAdapter from "../adapters/bunny.mjs";
import { defaultResolve } from "../_chunks/_resolve.mjs";
import { serve as serve$1 } from "srvx/bunny";

@@ -7,3 +8,3 @@ function plugin(wsOpts) {

hooks: wsOpts,
resolve: wsOpts.resolve,
resolve: defaultResolve(server, wsOpts),
...wsOpts.options?.bunny

@@ -10,0 +11,0 @@ });

import cloudflareAdapter from "../adapters/cloudflare.mjs";
import { defaultResolve } from "../_chunks/_resolve.mjs";
import { serve as serve$1 } from "srvx/cloudflare";

@@ -7,3 +8,3 @@ function plugin(wsOpts) {

hooks: wsOpts,
resolve: wsOpts.resolve,
resolve: defaultResolve(server, wsOpts),
...wsOpts.options?.cloudflare

@@ -10,0 +11,0 @@ });

import sseAdapter from "../adapters/sse.mjs";
import { defaultResolve } from "../_chunks/_resolve.mjs";
import { serve as serve$1 } from "srvx";
function plugin(wsOpts) {
const ws = sseAdapter({
hooks: wsOpts,
resolve: wsOpts.resolve,
...wsOpts.options?.sse
});
console.warn("[crossws] Using SSE adapter for WebSocket support. This requires a custom WebSocket client (https://crossws.h3.dev/adapters/sse).");
return (server) => {
const ws = sseAdapter({
hooks: wsOpts,
resolve: defaultResolve(server, wsOpts),
...wsOpts.options?.sse
});
server.options.middleware.unshift((req, next) => {

@@ -12,0 +13,0 @@ if (req.headers.get("upgrade")?.toLowerCase() === "websocket") return ws.fetch(req);

import denoAdapter from "../adapters/deno.mjs";
import { defaultResolve } from "../_chunks/_resolve.mjs";
import { serve as serve$1 } from "srvx/deno";

@@ -7,3 +8,3 @@ function plugin(wsOpts) {

hooks: wsOpts,
resolve: wsOpts.resolve,
resolve: defaultResolve(server, wsOpts),
...wsOpts.options?.deno

@@ -10,0 +11,0 @@ });

import { nodeAdapter } from "../_chunks/node.mjs";
import { defaultResolve } from "../_chunks/_resolve.mjs";
import { NodeRequest, serve as serve$1 } from "srvx/node";

@@ -7,4 +8,4 @@ function plugin(wsOpts) {

hooks: wsOpts,
resolve: wsOpts.resolve,
...wsOpts.options?.deno
resolve: defaultResolve(server, wsOpts),
...wsOpts.options?.node
});

@@ -11,0 +12,0 @@ const originalServe = server.serve;

{
"name": "crossws",
"version": "0.4.8",
"version": "0.4.9",
"description": "Cross-platform WebSocket Servers for Node.js, Deno, Bun and Cloudflare Workers",

@@ -5,0 +5,0 @@ "homepage": "https://crossws.h3.dev",