| export * from "../dist/adapters/bunny.mjs"; | ||
| export { default } from "../dist/adapters/bunny.mjs"; |
| import { n as AdapterInstance, r as AdapterOptions, t as Adapter } from "./adapter.mjs"; | ||
| interface BunnyAdapter extends AdapterInstance { | ||
| handleUpgrade(req: Request): Promise<Response>; | ||
| } | ||
| interface BunnyOptions extends AdapterOptions { | ||
| /** | ||
| * The WebSocket subprotocol to use for the connection. | ||
| */ | ||
| protocol?: string; | ||
| /** | ||
| * The number of seconds to wait for a pong response before closing the connection. | ||
| * If the client does not respond within this timeout, the connection is deemed | ||
| * unhealthy and closed, emitting the close and error events. | ||
| * If no data is transmitted from the client for 2 minutes, the connection | ||
| * will be closed regardless of this configuration. | ||
| * | ||
| * @default 30 | ||
| */ | ||
| idleTimeout?: number; | ||
| } | ||
| declare const bunnyAdapter: Adapter<BunnyAdapter, BunnyOptions>; | ||
| export { BunnyOptions as n, bunnyAdapter as r, BunnyAdapter as t }; |
| import { i as AdapterHookable, r as getPeers, t as adapterUtils } from "./adapter.mjs"; | ||
| import { n as import_websocket_server } from "./libs/ws.mjs"; | ||
| import { n as Message, r as toBufferLike, t as Peer } from "./peer.mjs"; | ||
| import { t as WSError } from "./error.mjs"; | ||
| import { t as StubRequest } from "./_request.mjs"; | ||
| function fromNodeUpgradeHandler(handler) { | ||
| return { async upgrade(request) { | ||
| const node = request.runtime?.node; | ||
| if (!node?.upgrade) throw new Error("[crossws] `fromNodeUpgradeHandler` must be mounted via `crossws/server/node`."); | ||
| await handler(node.req, node.upgrade.socket, node.upgrade.head); | ||
| return { handled: true }; | ||
| } }; | ||
| } | ||
| const nodeAdapter = (options = {}) => { | ||
| if ("Deno" in globalThis || "Bun" in globalThis) throw new Error("[crossws] Using Node.js adapter in an incompatible environment."); | ||
| const hooks = new AdapterHookable(options); | ||
| const globalPeers = /* @__PURE__ */ new Map(); | ||
| const wss = options.wss || new import_websocket_server.default({ | ||
| noServer: true, | ||
| handleProtocols: () => false, | ||
| ...options.serverOptions | ||
| }); | ||
| wss.on("connection", (ws, nodeReq) => { | ||
| const request = new NodeReqProxy(nodeReq); | ||
| const peers = getPeers(globalPeers, nodeReq._namespace); | ||
| const peer = new NodePeer({ | ||
| ws, | ||
| request, | ||
| peers, | ||
| nodeReq, | ||
| namespace: nodeReq._namespace | ||
| }); | ||
| peers.add(peer); | ||
| hooks.callHook("open", peer); | ||
| ws.on("message", (data, isBinary) => { | ||
| if (Array.isArray(data)) data = Buffer.concat(data); | ||
| if (!isBinary && Buffer.isBuffer(data)) data = data.toString("utf8"); | ||
| hooks.callHook("message", peer, new Message(data, peer)); | ||
| }); | ||
| ws.on("error", (error) => { | ||
| peers.delete(peer); | ||
| hooks.callHook("error", peer, new WSError(error)); | ||
| }); | ||
| ws.on("close", (code, reason) => { | ||
| peers.delete(peer); | ||
| hooks.callHook("close", peer, { | ||
| code, | ||
| reason: reason?.toString() | ||
| }); | ||
| }); | ||
| }); | ||
| wss.on("headers", (outgoingHeaders, req) => { | ||
| const upgradeHeaders = req._upgradeHeaders; | ||
| if (upgradeHeaders) for (const [key, value] of new Headers(upgradeHeaders)) outgoingHeaders.push(`${key}: ${value}`); | ||
| }); | ||
| return { | ||
| ...adapterUtils(globalPeers), | ||
| handleUpgrade: async (nodeReq, socket, head, webRequest) => { | ||
| const request = webRequest || new NodeReqProxy(nodeReq); | ||
| const { upgradeHeaders, endResponse, handled, context, namespace } = await hooks.upgrade(request); | ||
| if (endResponse) return sendResponse(socket, endResponse); | ||
| if (handled) return; | ||
| nodeReq._request = request; | ||
| nodeReq._upgradeHeaders = upgradeHeaders; | ||
| nodeReq._context = context; | ||
| nodeReq._namespace = namespace; | ||
| wss.handleUpgrade(nodeReq, socket, head, (ws) => { | ||
| wss.emit("connection", ws, nodeReq); | ||
| }); | ||
| }, | ||
| closeAll: (code, data, force) => { | ||
| for (const client of wss.clients) if (force) client.terminate(); | ||
| else client.close(code, data); | ||
| } | ||
| }; | ||
| }; | ||
| var NodePeer = class extends Peer { | ||
| get remoteAddress() { | ||
| return this._internal.nodeReq.socket?.remoteAddress; | ||
| } | ||
| get context() { | ||
| return this._internal.nodeReq._context; | ||
| } | ||
| send(data, options) { | ||
| const dataBuff = toBufferLike(data); | ||
| const isBinary = typeof dataBuff !== "string"; | ||
| this._internal.ws.send(dataBuff, { | ||
| compress: options?.compress, | ||
| binary: isBinary, | ||
| ...options | ||
| }); | ||
| return 0; | ||
| } | ||
| publish(topic, data, options) { | ||
| const dataBuff = toBufferLike(data); | ||
| const isBinary = typeof data !== "string"; | ||
| const sendOptions = { | ||
| compress: options?.compress, | ||
| binary: isBinary, | ||
| ...options | ||
| }; | ||
| for (const peer of this._internal.peers) if (peer !== this && peer._topics.has(topic)) peer._internal.ws.send(dataBuff, sendOptions); | ||
| } | ||
| close(code, data) { | ||
| this._internal.ws.close(code, data); | ||
| } | ||
| terminate() { | ||
| this._internal.ws.terminate(); | ||
| } | ||
| }; | ||
| var NodeReqProxy = class extends StubRequest { | ||
| constructor(req) { | ||
| const host = req.headers["host"] || "localhost"; | ||
| const url = `${req.socket?.encrypted ?? req.headers["x-forwarded-proto"] === "https" ? "https" : "http"}://${host}${req.url}`; | ||
| super(url, { headers: req.headers }); | ||
| } | ||
| }; | ||
| async function sendResponse(socket, res) { | ||
| const head = [`HTTP/1.1 ${res.status || 200} ${res.statusText || ""}`, ...[...res.headers.entries()].map(([key, value]) => `${key}: ${value}`)]; | ||
| socket.write(head.join("\r\n") + "\r\n\r\n"); | ||
| if (res.body) for await (const chunk of res.body) socket.write(chunk); | ||
| return new Promise((resolve) => { | ||
| socket.end(() => { | ||
| socket.destroy(); | ||
| resolve(); | ||
| }); | ||
| }); | ||
| } | ||
| export { fromNodeUpgradeHandler as n, nodeAdapter as t }; |
| import { n as BunnyOptions, r as bunnyAdapter, t as BunnyAdapter } from "../_chunks/bunny.mjs"; | ||
| export { BunnyAdapter, BunnyOptions, bunnyAdapter as default }; |
| import { i as AdapterHookable, r as getPeers, t as adapterUtils } from "../_chunks/adapter.mjs"; | ||
| import { n as Message, r as toBufferLike, t as Peer } from "../_chunks/peer.mjs"; | ||
| import { t as WSError } from "../_chunks/error.mjs"; | ||
| const bunnyAdapter = (options = {}) => { | ||
| const hooks = new AdapterHookable(options); | ||
| const globalPeers = /* @__PURE__ */ new Map(); | ||
| return { | ||
| ...adapterUtils(globalPeers), | ||
| handleUpgrade: async (request) => { | ||
| if (!request.upgradeWebSocket || typeof request.upgradeWebSocket !== "function") throw new Error("[crossws] Bunny adapter requires the request to have an upgradeWebSocket method."); | ||
| const { endResponse, context, namespace, upgradeHeaders } = await hooks.upgrade(request); | ||
| if (endResponse) return endResponse; | ||
| const negotiatedProtocol = (upgradeHeaders instanceof Headers ? upgradeHeaders : new Headers(upgradeHeaders)).get("sec-websocket-protocol") ?? options.protocol; | ||
| const upgradeOptions = {}; | ||
| if (negotiatedProtocol) upgradeOptions.protocol = negotiatedProtocol; | ||
| if (options.idleTimeout !== void 0) upgradeOptions.idleTimeout = options.idleTimeout; | ||
| const { response, socket } = request.upgradeWebSocket(Object.keys(upgradeOptions).length > 0 ? upgradeOptions : void 0); | ||
| const remoteAddress = request.headers.get("x-real-ip") || void 0; | ||
| const peers = getPeers(globalPeers, namespace); | ||
| const peer = new BunnyPeer({ | ||
| ws: socket, | ||
| request, | ||
| namespace, | ||
| remoteAddress, | ||
| peers, | ||
| context | ||
| }); | ||
| peers.add(peer); | ||
| socket.addEventListener("open", () => { | ||
| hooks.callHook("open", peer); | ||
| }); | ||
| socket.addEventListener("message", (event) => { | ||
| hooks.callHook("message", peer, new Message(event.data, peer, event)); | ||
| }); | ||
| socket.addEventListener("close", (event) => { | ||
| peers.delete(peer); | ||
| hooks.callHook("close", peer, { | ||
| code: event.code, | ||
| reason: event.reason | ||
| }); | ||
| }); | ||
| socket.addEventListener("error", (error) => { | ||
| peers.delete(peer); | ||
| hooks.callHook("error", peer, new WSError(error)); | ||
| }); | ||
| return response; | ||
| } | ||
| }; | ||
| }; | ||
| var BunnyPeer = class extends Peer { | ||
| get remoteAddress() { | ||
| return this._internal.remoteAddress; | ||
| } | ||
| send(data) { | ||
| return this._internal.ws.send(toBufferLike(data)); | ||
| } | ||
| publish(topic, data) { | ||
| const dataBuff = toBufferLike(data); | ||
| for (const peer of this._internal.peers) if (peer !== this && peer._topics.has(topic)) peer._internal.ws.send(dataBuff); | ||
| } | ||
| close(code, reason) { | ||
| this._internal.ws.close(code, reason); | ||
| } | ||
| terminate() { | ||
| this._internal.ws.close(); | ||
| } | ||
| }; | ||
| export { bunnyAdapter as default }; |
| import { n as WSOptions, t as ServerWithWSOptions } from "../_chunks/_types.mjs"; | ||
| import { Server, ServerPlugin } from "srvx"; | ||
| declare function plugin(wsOpts: WSOptions): ServerPlugin; | ||
| declare function serve(options: ServerWithWSOptions): Server; | ||
| export { plugin, serve }; |
| import bunnyAdapter from "../adapters/bunny.mjs"; | ||
| import { serve as serve$1 } from "srvx/bunny"; | ||
| function plugin(wsOpts) { | ||
| return (server) => { | ||
| const ws = bunnyAdapter({ | ||
| hooks: wsOpts, | ||
| resolve: wsOpts.resolve, | ||
| ...wsOpts.options?.bunny | ||
| }); | ||
| server.options.middleware.unshift((req, next) => { | ||
| if (req.headers.get("upgrade")?.toLowerCase() === "websocket") return ws.handleUpgrade(req); | ||
| return next(); | ||
| }); | ||
| }; | ||
| } | ||
| function serve(options) { | ||
| if (options.websocket) { | ||
| options.plugins ||= []; | ||
| options.plugins.push(plugin(options.websocket)); | ||
| } | ||
| return serve$1(options); | ||
| } | ||
| export { plugin, serve }; |
| # Licenses of Bundled Dependencies | ||
| The published artifact additionally contains code with the following licenses: | ||
| MIT | ||
| # Bundled Dependencies | ||
| ## ws | ||
| License: MIT | ||
| By: Einar Otto Stangvik | ||
| Repository: https://github.com/websockets/ws | ||
| > Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com> | ||
| > Copyright (c) 2013 Arnout Kazemier and contributors | ||
| > Copyright (c) 2016 Luigi Pinca and contributors | ||
| > | ||
| > Permission is hereby granted, free of charge, to any person obtaining a copy of | ||
| > this software and associated documentation files (the "Software"), to deal in | ||
| > the Software without restriction, including without limitation the rights to | ||
| > use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of | ||
| > the Software, and to permit persons to whom the Software is furnished to do so, | ||
| > subject to the following conditions: | ||
| > | ||
| > The above copyright notice and this permission notice shall be included in all | ||
| > copies or substantial portions of the Software. | ||
| > | ||
| > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS | ||
| > FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR | ||
| > COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | ||
| > IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | ||
| > CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| export * from "../dist/server/bunny.mjs"; | ||
| export { default } from "../dist/server/bunny.mjs"; |
@@ -1,2 +0,1 @@ | ||
| //#region src/_request.ts | ||
| const StubRequest = /* @__PURE__ */ (() => { | ||
@@ -81,4 +80,2 @@ class StubRequest { | ||
| })(); | ||
| //#endregion | ||
| export { StubRequest as t }; | ||
| export { StubRequest as t }; |
| import { a as Hooks } from "./adapter.mjs"; | ||
| import { n as BunOptions } from "./bun.mjs"; | ||
| import { n as BunnyOptions } from "./bunny.mjs"; | ||
| import { n as CloudflareOptions } from "./cloudflare.mjs"; | ||
@@ -8,4 +9,2 @@ import { n as DenoOptions } from "./deno.mjs"; | ||
| import { Server, ServerOptions, ServerPlugin, ServerRequest } from "srvx"; | ||
| //#region src/server/_types.d.ts | ||
| type WSOptions = Partial<Hooks> & { | ||
@@ -15,2 +14,3 @@ resolve?: (req: ServerRequest) => Partial<Hooks> | Promise<Partial<Hooks>>; | ||
| bun?: BunOptions; | ||
| bunny?: BunnyOptions; | ||
| deno?: DenoOptions; | ||
@@ -25,3 +25,2 @@ node?: NodeOptions; | ||
| }; | ||
| //#endregion | ||
| export { WSOptions as n, ServerWithWSOptions as t }; |
| import { a as WebSocket } from "./web.mjs"; | ||
| //#region src/error.d.ts | ||
| declare class WSError extends Error { | ||
| constructor(...args: any[]); | ||
| } | ||
| //#endregion | ||
| //#region src/utils.d.ts | ||
| declare const kNodeInspect: unique symbol; | ||
| //#endregion | ||
| //#region src/peer.d.ts | ||
| interface PeerContext extends Record<string, unknown> {} | ||
@@ -69,4 +63,2 @@ interface AdapterInternal { | ||
| } | ||
| //#endregion | ||
| //#region src/message.d.ts | ||
| declare class Message implements Partial<MessageEvent> { | ||
@@ -120,4 +112,2 @@ #private; | ||
| } | ||
| //#endregion | ||
| //#region src/hooks.d.ts | ||
| declare function defineHooks<T extends Partial<Hooks> = Partial<Hooks>>(hooks: T): T; | ||
@@ -136,2 +126,6 @@ type ResolveHooks = (request: Request & { | ||
| * - You can return { context } to provide a custom peer context. | ||
| * - You can return { handled: true } to signal that the upgrade has | ||
| * already been performed by the hook (e.g. delegated to an external | ||
| * node-style `(req, socket, head)` handler). The adapter will then | ||
| * leave the socket alone and skip its own upgrade. | ||
| * | ||
@@ -147,2 +141,3 @@ * @param request | ||
| context?: PeerContext; | ||
| handled?: boolean; | ||
| } | Response | void>; | ||
@@ -161,4 +156,2 @@ /** A message is received */ | ||
| } | ||
| //#endregion | ||
| //#region src/adapter.d.ts | ||
| interface AdapterInstance { | ||
@@ -178,3 +171,2 @@ readonly peers: Map<string, Set<Peer>>; | ||
| declare function defineWebSocketAdapter<AdapterT extends AdapterInstance = AdapterInstance, Options extends AdapterOptions = AdapterOptions>(factory: Adapter<AdapterT, Options>): Adapter<AdapterT, Options>; | ||
| //#endregion | ||
| export { Hooks as a, Message as c, PeerContext as d, WSError as f, defineWebSocketAdapter as i, AdapterInternal as l, AdapterInstance as n, ResolveHooks as o, AdapterOptions as r, defineHooks as s, Adapter as t, Peer as u }; |
@@ -1,2 +0,1 @@ | ||
| //#region src/hooks.ts | ||
| var AdapterHookable = class { | ||
@@ -35,2 +34,7 @@ options; | ||
| }; | ||
| if (res.handled) return { | ||
| context, | ||
| namespace, | ||
| handled: true | ||
| }; | ||
| if (res.headers) return { | ||
@@ -59,5 +63,2 @@ context, | ||
| } | ||
| //#endregion | ||
| //#region src/adapter.ts | ||
| function adapterUtils(globalPeers) { | ||
@@ -93,4 +94,2 @@ return { | ||
| } | ||
| //#endregion | ||
| export { defineHooks as a, AdapterHookable as i, defineWebSocketAdapter as n, getPeers as r, adapterUtils as t }; | ||
| export { defineHooks as a, AdapterHookable as i, defineWebSocketAdapter as n, getPeers as r, adapterUtils as t }; |
| import { d as PeerContext, n as AdapterInstance, r as AdapterOptions, t as Adapter, u as Peer } from "./adapter.mjs"; | ||
| import { Server, ServerWebSocket, WebSocketHandler } from "bun"; | ||
| //#region src/adapters/bun.d.ts | ||
| interface BunAdapter extends AdapterInstance { | ||
@@ -37,3 +35,2 @@ websocket: WebSocketHandler<ContextData>; | ||
| } | ||
| //#endregion | ||
| export { BunOptions as n, bunAdapter as r, BunAdapter as t }; |
@@ -5,4 +5,2 @@ import { n as AdapterInstance, r as AdapterOptions, t as Adapter } from "./adapter.mjs"; | ||
| import * as CF from "@cloudflare/workers-types"; | ||
| //#region src/adapters/cloudflare.d.ts | ||
| type WSDurableObjectStub = CF.DurableObjectStub & { | ||
@@ -45,3 +43,2 @@ webSocketPublish?: (topic: string, data: unknown, opts: any) => Promise<void>; | ||
| } | ||
| //#endregion | ||
| export { CloudflareOptions as n, cloudflareAdapter as r, CloudflareDurableAdapter as t }; |
| import { n as AdapterInstance, r as AdapterOptions, t as Adapter } from "./adapter.mjs"; | ||
| //#region src/adapters/deno.d.ts | ||
| interface DenoAdapter extends AdapterInstance { | ||
@@ -16,3 +14,2 @@ handleUpgrade(req: Request, info: ServeHandlerInfo): Promise<Response>; | ||
| declare const denoAdapter: Adapter<DenoAdapter, DenoOptions>; | ||
| //#endregion | ||
| export { DenoOptions as n, denoAdapter as r, DenoAdapter as t }; |
@@ -1,2 +0,1 @@ | ||
| //#region src/error.ts | ||
| var WSError = class extends Error { | ||
@@ -8,4 +7,2 @@ constructor(...args) { | ||
| }; | ||
| //#endregion | ||
| export { WSError as t }; | ||
| export { WSError as t }; |
@@ -1,2 +0,2 @@ | ||
| import { n as AdapterInstance, r as AdapterOptions, t as Adapter } from "./adapter.mjs"; | ||
| import { a as Hooks, n as AdapterInstance, r as AdapterOptions, t as Adapter } from "./adapter.mjs"; | ||
| import { EventEmitter } from "events"; | ||
@@ -9,4 +9,2 @@ import { Agent, ClientRequest, ClientRequestArgs, IncomingMessage, OutgoingHttpHeaders, Server } from "node:http"; | ||
| import { ZlibOptions } from "node:zlib"; | ||
| //#region types/ws.d.ts | ||
| type BufferLike = string | Buffer | DataView | number | ArrayBufferView | Uint8Array | ArrayBuffer | SharedArrayBuffer | readonly any[] | readonly number[] | { | ||
@@ -288,4 +286,36 @@ valueOf(): ArrayBuffer; | ||
| declare function createWebSocketStream(websocket: WebSocket, options?: DuplexOptions): Duplex; | ||
| //#endregion | ||
| //#region src/adapters/node.d.ts | ||
| /** | ||
| * A Node.js `(req, socket, head)` upgrade handler. | ||
| */ | ||
| type NodeUpgradeHandler = (req: IncomingMessage, socket: Duplex, head: Buffer) => void | Promise<void>; | ||
| /** | ||
| * Wrap a Node.js `(req, socket, head)` upgrade handler as a {@link Hooks} | ||
| * object that can be mounted via `crossws/server/node`. | ||
| * | ||
| * The wrapped handler takes ownership of the socket; crossws's other | ||
| * lifecycle hooks (`open`/`message`/`close`/`error`) are **not** invoked | ||
| * for connections routed through it. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * import { WebSocketServer } from "ws"; | ||
| * import { fromNodeUpgradeHandler } from "crossws/adapters/node"; | ||
| * import { serve } from "crossws/server/node"; | ||
| * | ||
| * const wss = new WebSocketServer({ noServer: true }); | ||
| * wss.on("connection", (ws) => { | ||
| * ws.on("message", (data) => ws.send(data)); | ||
| * }); | ||
| * | ||
| * serve({ | ||
| * websocket: fromNodeUpgradeHandler((req, socket, head) => { | ||
| * wss.handleUpgrade(req, socket, head, (ws) => { | ||
| * wss.emit("connection", ws, req); | ||
| * }); | ||
| * }), | ||
| * fetch: () => new Response("ok"), | ||
| * }); | ||
| * ``` | ||
| */ | ||
| declare function fromNodeUpgradeHandler(handler: NodeUpgradeHandler): Partial<Hooks>; | ||
| interface NodeAdapter extends AdapterInstance { | ||
@@ -300,3 +330,2 @@ handleUpgrade(req: IncomingMessage, socket: Duplex, head: Buffer, webRequest?: Request): Promise<void>; | ||
| declare const nodeAdapter: Adapter<NodeAdapter, NodeOptions>; | ||
| //#endregion | ||
| export { NodeOptions as n, nodeAdapter as r, NodeAdapter as t }; | ||
| export { fromNodeUpgradeHandler as a, NodeUpgradeHandler as i, NodeOptions as n, nodeAdapter as r, NodeAdapter as t }; |
@@ -1,2 +0,1 @@ | ||
| //#region src/utils.ts | ||
| const kNodeInspect = /* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom"); | ||
@@ -27,11 +26,5 @@ function toBufferLike(val) { | ||
| } | ||
| //#endregion | ||
| //#region src/message.ts | ||
| var Message = class { | ||
| /** Access to the original [message event](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/message_event) if available. */ | ||
| event; | ||
| /** Access to the Peer that emitted the message. */ | ||
| peer; | ||
| /** Raw message data (can be of any type). */ | ||
| rawData; | ||
@@ -49,5 +42,2 @@ #id; | ||
| } | ||
| /** | ||
| * Unique random [uuid v4](https://developer.mozilla.org/en-US/docs/Glossary/UUID) identifier for the message. | ||
| */ | ||
| get id() { | ||
@@ -57,7 +47,2 @@ if (!this.#id) this.#id = crypto.randomUUID(); | ||
| } | ||
| /** | ||
| * Get data as [Uint8Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) value. | ||
| * | ||
| * If raw data is in any other format or string, it will be automatically converted and encoded. | ||
| */ | ||
| uint8Array() { | ||
@@ -81,7 +66,2 @@ const _uint8Array = this.#uint8Array; | ||
| } | ||
| /** | ||
| * Get data as [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) or [SharedArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer) value. | ||
| * | ||
| * If raw data is in any other format or string, it will be automatically converted and encoded. | ||
| */ | ||
| arrayBuffer() { | ||
@@ -94,6 +74,2 @@ const _arrayBuffer = this.#arrayBuffer; | ||
| } | ||
| /** | ||
| * Get data as [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob) value. | ||
| * | ||
| * If raw data is in any other format or string, it will be automatically converted and encoded. */ | ||
| blob() { | ||
@@ -106,7 +82,2 @@ const _blob = this.#blob; | ||
| } | ||
| /** | ||
| * Get stringified text version of the message. | ||
| * | ||
| * If raw data is in any other format, it will be automatically converted and decoded. | ||
| */ | ||
| text() { | ||
@@ -119,5 +90,2 @@ const _text = this.#text; | ||
| } | ||
| /** | ||
| * Get parsed version of the message text with [`JSON.parse()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse). | ||
| */ | ||
| json() { | ||
@@ -128,5 +96,2 @@ const _json = this.#json; | ||
| } | ||
| /** | ||
| * Message data (value varies based on `peer.websocket.binaryType`). | ||
| */ | ||
| get data() { | ||
@@ -156,5 +121,2 @@ switch (this.peer?.websocket?.binaryType) { | ||
| }; | ||
| //#endregion | ||
| //#region src/peer.ts | ||
| var Peer = class { | ||
@@ -175,5 +137,2 @@ _internal; | ||
| } | ||
| /** | ||
| * Unique random [uuid v4](https://developer.mozilla.org/en-US/docs/Glossary/UUID) identifier for the peer. | ||
| */ | ||
| get id() { | ||
@@ -183,16 +142,6 @@ if (!this._id) this._id = crypto.randomUUID(); | ||
| } | ||
| /** IP address of the peer */ | ||
| get remoteAddress() {} | ||
| /** upgrade request */ | ||
| get request() { | ||
| return this._internal.request; | ||
| } | ||
| /** | ||
| * Get the [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) instance. | ||
| * | ||
| * **Note:** crossws adds polyfill for the following properties if native values are not available: | ||
| * - `protocol`: Extracted from the `sec-websocket-protocol` header. | ||
| * - `extensions`: Extracted from the `sec-websocket-extensions` header. | ||
| * - `url`: Extracted from the request URL (http -> ws). | ||
| * */ | ||
| get websocket() { | ||
@@ -206,19 +155,14 @@ if (!this.#ws) { | ||
| } | ||
| /** All connected peers to the server */ | ||
| get peers() { | ||
| return this._internal.peers || /* @__PURE__ */ new Set(); | ||
| } | ||
| /** All topics, this peer has been subscribed to. */ | ||
| get topics() { | ||
| return this._topics; | ||
| } | ||
| /** Abruptly close the connection */ | ||
| terminate() { | ||
| this.close(); | ||
| } | ||
| /** Subscribe to a topic */ | ||
| subscribe(topic) { | ||
| this._topics.add(topic); | ||
| } | ||
| /** Unsubscribe from a topic */ | ||
| unsubscribe(topic) { | ||
@@ -254,4 +198,2 @@ this._topics.delete(topic); | ||
| } | ||
| //#endregion | ||
| export { toString as i, Message as n, toBufferLike as r, Peer as t }; | ||
| export { toString as i, Message as n, toBufferLike as r, Peer as t }; |
| import { createRequire } from "node:module"; | ||
| //#region rolldown:runtime | ||
| var __create = Object.create; | ||
@@ -12,12 +10,8 @@ var __defProp = Object.defineProperty; | ||
| var __copyProps = (to, from, except, desc) => { | ||
| if (from && typeof from === "object" || typeof from === "function") { | ||
| for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) { | ||
| key = keys[i]; | ||
| if (!__hasOwnProp.call(to, key) && key !== except) { | ||
| __defProp(to, key, { | ||
| get: ((k) => from[k]).bind(null, key), | ||
| enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable | ||
| }); | ||
| } | ||
| } | ||
| if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) { | ||
| key = keys[i]; | ||
| if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { | ||
| get: ((k) => from[k]).bind(null, key), | ||
| enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable | ||
| }); | ||
| } | ||
@@ -31,4 +25,2 @@ return to; | ||
| var __require = /* @__PURE__ */ createRequire(import.meta.url); | ||
| //#endregion | ||
| export { __require as n, __toESM as r, __commonJSMin as t }; | ||
| export { __require as n, __toESM as r, __commonJSMin as t }; |
| import { n as AdapterInstance, r as AdapterOptions, t as Adapter } from "./adapter.mjs"; | ||
| //#region src/adapters/sse.d.ts | ||
| interface SSEAdapter extends AdapterInstance { | ||
@@ -11,3 +9,2 @@ fetch(req: Request): Promise<Response>; | ||
| declare const sseAdapter: Adapter<SSEAdapter, SSEOptions>; | ||
| //#endregion | ||
| export { SSEOptions as n, sseAdapter as r, SSEAdapter as t }; |
@@ -1,2 +0,1 @@ | ||
| //#region types/web.d.ts | ||
| /** | ||
@@ -297,3 +296,2 @@ * A CloseEvent is sent to clients using WebSockets when the connection is closed. This is delivered to the listener indicated by the WebSocket object's onclose attribute. | ||
| } | ||
| //#endregion | ||
| export { WebSocket as a, MessageEvent as i, Event as n, EventTarget as r, CloseEvent as t }; |
| import { i as AdapterHookable, r as getPeers, t as adapterUtils } from "../_chunks/adapter.mjs"; | ||
| import { n as Message, r as toBufferLike, t as Peer } from "../_chunks/peer.mjs"; | ||
| //#region src/adapters/bun.ts | ||
| const bunAdapter = (options = {}) => { | ||
@@ -47,3 +45,2 @@ if (typeof Bun === "undefined") throw new Error("[crossws] Using Bun adapter in an incompatible environment."); | ||
| }; | ||
| var bun_default = bunAdapter; | ||
| function getPeer(ws, peers) { | ||
@@ -88,4 +85,2 @@ if (ws.data.peer) return ws.data.peer; | ||
| }; | ||
| //#endregion | ||
| export { bun_default as default }; | ||
| export { bunAdapter as default }; |
| import { i as AdapterHookable, r as getPeers, t as adapterUtils } from "../_chunks/adapter.mjs"; | ||
| import { n as Message, r as toBufferLike, t as Peer } from "../_chunks/peer.mjs"; | ||
| import { t as WSError } from "../_chunks/error.mjs"; | ||
| import { t as StubRequest } from "../_chunks/_request.mjs"; | ||
| import { t as WSError } from "../_chunks/error.mjs"; | ||
| import { env as env$1 } from "cloudflare:workers"; | ||
| //#region src/adapters/cloudflare.ts | ||
| import { env } from "cloudflare:workers"; | ||
| const cloudflareAdapter = (opts = {}) => { | ||
| const hooks = new AdapterHookable(opts); | ||
| const globalPeers = /* @__PURE__ */ new Map(); | ||
| const resolveDurableStub = opts.resolveDurableStub || ((_req, env, _context) => { | ||
| const resolveDurableStub = opts.resolveDurableStub || ((_req, env$1, _context) => { | ||
| const bindingName = opts.bindingName || "$DurableObject"; | ||
| const binding = (env || env$1)[bindingName]; | ||
| const binding = (env$1 || env)[bindingName]; | ||
| if (binding) { | ||
@@ -62,3 +60,3 @@ const instanceId = binding.idFromName(opts.instanceName || "crossws"); | ||
| }, | ||
| handleDurableInit: async (obj, state, env) => {}, | ||
| handleDurableInit: async (_obj, _state, _env) => {}, | ||
| handleDurableUpgrade: async (obj, request) => { | ||
@@ -99,3 +97,3 @@ const { upgradeHeaders, endResponse, namespace } = await hooks.upgrade(request); | ||
| publish: async (topic, data, opts) => { | ||
| const stub = await resolveDurableStub(void 0, env$1, void 0); | ||
| const stub = await resolveDurableStub(void 0, env, void 0); | ||
| if (!stub) throw new Error("[crossws] Durable Object binding cannot be resolved."); | ||
@@ -111,3 +109,2 @@ try { | ||
| }; | ||
| var cloudflare_default = cloudflareAdapter; | ||
| var CloudflareDurablePeer = class CloudflareDurablePeer extends Peer { | ||
@@ -182,4 +179,2 @@ get peers() { | ||
| } | ||
| //#endregion | ||
| export { cloudflare_default as default }; | ||
| export { cloudflareAdapter as default }; |
| import { i as AdapterHookable, r as getPeers, t as adapterUtils } from "../_chunks/adapter.mjs"; | ||
| import { n as Message, r as toBufferLike, t as Peer } from "../_chunks/peer.mjs"; | ||
| import { t as WSError } from "../_chunks/error.mjs"; | ||
| //#region src/adapters/deno.ts | ||
| const denoAdapter = (options = {}) => { | ||
@@ -48,3 +46,2 @@ if (typeof Deno === "undefined") throw new Error("[crossws] Using Deno adapter in an incompatible environment."); | ||
| }; | ||
| var deno_default = denoAdapter; | ||
| var DenoPeer = class extends Peer { | ||
@@ -68,4 +65,2 @@ get remoteAddress() { | ||
| }; | ||
| //#endregion | ||
| export { deno_default as default }; | ||
| export { denoAdapter as default }; |
@@ -1,2 +0,2 @@ | ||
| import { n as NodeOptions, r as nodeAdapter, t as NodeAdapter } from "../_chunks/node.mjs"; | ||
| export { NodeAdapter, NodeOptions, nodeAdapter as default }; | ||
| import { a as fromNodeUpgradeHandler, i as NodeUpgradeHandler, n as NodeOptions, r as nodeAdapter, t as NodeAdapter } from "../_chunks/node.mjs"; | ||
| export { NodeAdapter, NodeOptions, NodeUpgradeHandler, nodeAdapter as default, fromNodeUpgradeHandler }; |
+2
-125
@@ -1,125 +0,2 @@ | ||
| import "../_chunks/rolldown-runtime.mjs"; | ||
| import { i as AdapterHookable, r as getPeers, t as adapterUtils } from "../_chunks/adapter.mjs"; | ||
| import { n as import_websocket_server } from "../_chunks/libs/ws.mjs"; | ||
| import { n as Message, r as toBufferLike, t as Peer } from "../_chunks/peer.mjs"; | ||
| import { t as StubRequest } from "../_chunks/_request.mjs"; | ||
| import { t as WSError } from "../_chunks/error.mjs"; | ||
| //#region src/adapters/node.ts | ||
| const nodeAdapter = (options = {}) => { | ||
| if ("Deno" in globalThis || "Bun" in globalThis) throw new Error("[crossws] Using Node.js adapter in an incompatible environment."); | ||
| const hooks = new AdapterHookable(options); | ||
| const globalPeers = /* @__PURE__ */ new Map(); | ||
| const wss = options.wss || new import_websocket_server.default({ | ||
| noServer: true, | ||
| handleProtocols: () => false, | ||
| ...options.serverOptions | ||
| }); | ||
| wss.on("connection", (ws, nodeReq) => { | ||
| const request = new NodeReqProxy(nodeReq); | ||
| const peers = getPeers(globalPeers, nodeReq._namespace); | ||
| const peer = new NodePeer({ | ||
| ws, | ||
| request, | ||
| peers, | ||
| nodeReq, | ||
| namespace: nodeReq._namespace | ||
| }); | ||
| peers.add(peer); | ||
| hooks.callHook("open", peer); | ||
| ws.on("message", (data) => { | ||
| if (Array.isArray(data)) data = Buffer.concat(data); | ||
| hooks.callHook("message", peer, new Message(data, peer)); | ||
| }); | ||
| ws.on("error", (error) => { | ||
| peers.delete(peer); | ||
| hooks.callHook("error", peer, new WSError(error)); | ||
| }); | ||
| ws.on("close", (code, reason) => { | ||
| peers.delete(peer); | ||
| hooks.callHook("close", peer, { | ||
| code, | ||
| reason: reason?.toString() | ||
| }); | ||
| }); | ||
| }); | ||
| wss.on("headers", (outgoingHeaders, req) => { | ||
| const upgradeHeaders = req._upgradeHeaders; | ||
| if (upgradeHeaders) for (const [key, value] of new Headers(upgradeHeaders)) outgoingHeaders.push(`${key}: ${value}`); | ||
| }); | ||
| return { | ||
| ...adapterUtils(globalPeers), | ||
| handleUpgrade: async (nodeReq, socket, head, webRequest) => { | ||
| const request = webRequest || new NodeReqProxy(nodeReq); | ||
| const { upgradeHeaders, endResponse, context, namespace } = await hooks.upgrade(request); | ||
| if (endResponse) return sendResponse(socket, endResponse); | ||
| nodeReq._request = request; | ||
| nodeReq._upgradeHeaders = upgradeHeaders; | ||
| nodeReq._context = context; | ||
| nodeReq._namespace = namespace; | ||
| wss.handleUpgrade(nodeReq, socket, head, (ws) => { | ||
| wss.emit("connection", ws, nodeReq); | ||
| }); | ||
| }, | ||
| closeAll: (code, data, force) => { | ||
| for (const client of wss.clients) if (force) client.terminate(); | ||
| else client.close(code, data); | ||
| } | ||
| }; | ||
| }; | ||
| var node_default = nodeAdapter; | ||
| var NodePeer = class extends Peer { | ||
| get remoteAddress() { | ||
| return this._internal.nodeReq.socket?.remoteAddress; | ||
| } | ||
| get context() { | ||
| return this._internal.nodeReq._context; | ||
| } | ||
| send(data, options) { | ||
| const dataBuff = toBufferLike(data); | ||
| const isBinary = typeof dataBuff !== "string"; | ||
| this._internal.ws.send(dataBuff, { | ||
| compress: options?.compress, | ||
| binary: isBinary, | ||
| ...options | ||
| }); | ||
| return 0; | ||
| } | ||
| publish(topic, data, options) { | ||
| const dataBuff = toBufferLike(data); | ||
| const isBinary = typeof data !== "string"; | ||
| const sendOptions = { | ||
| compress: options?.compress, | ||
| binary: isBinary, | ||
| ...options | ||
| }; | ||
| for (const peer of this._internal.peers) if (peer !== this && peer._topics.has(topic)) peer._internal.ws.send(dataBuff, sendOptions); | ||
| } | ||
| close(code, data) { | ||
| this._internal.ws.close(code, data); | ||
| } | ||
| terminate() { | ||
| this._internal.ws.terminate(); | ||
| } | ||
| }; | ||
| var NodeReqProxy = class extends StubRequest { | ||
| constructor(req) { | ||
| const host = req.headers["host"] || "localhost"; | ||
| const url = `${req.socket?.encrypted ?? req.headers["x-forwarded-proto"] === "https" ? "https" : "http"}://${host}${req.url}`; | ||
| super(url, { headers: req.headers }); | ||
| } | ||
| }; | ||
| async function sendResponse(socket, res) { | ||
| const head = [`HTTP/1.1 ${res.status || 200} ${res.statusText || ""}`, ...[...res.headers.entries()].map(([key, value]) => `${encodeURIComponent(key)}: ${encodeURIComponent(value)}`)]; | ||
| socket.write(head.join("\r\n") + "\r\n\r\n"); | ||
| if (res.body) for await (const chunk of res.body) socket.write(chunk); | ||
| return new Promise((resolve) => { | ||
| socket.end(() => { | ||
| socket.destroy(); | ||
| resolve(); | ||
| }); | ||
| }); | ||
| } | ||
| //#endregion | ||
| export { node_default as default }; | ||
| import { n as fromNodeUpgradeHandler, t as nodeAdapter } from "../_chunks/node.mjs"; | ||
| export { nodeAdapter as default, fromNodeUpgradeHandler }; |
| import { i as AdapterHookable, r as getPeers, t as adapterUtils } from "../_chunks/adapter.mjs"; | ||
| import { i as toString, n as Message, t as Peer } from "../_chunks/peer.mjs"; | ||
| //#region src/adapters/sse.ts | ||
| const sseAdapter = (opts = {}) => { | ||
@@ -58,3 +56,2 @@ const hooks = new AdapterHookable(opts); | ||
| }; | ||
| var sse_default = sseAdapter; | ||
| var SSEPeer = class extends Peer { | ||
@@ -101,4 +98,2 @@ _sseStream; | ||
| }; | ||
| //#endregion | ||
| export { sse_default as default }; | ||
| export { sseAdapter as default }; |
| import { d as PeerContext, n as AdapterInstance, r as AdapterOptions, t as Adapter, u as Peer } from "../_chunks/adapter.mjs"; | ||
| import { a as WebSocket } from "../_chunks/web.mjs"; | ||
| import uws from "uWebSockets.js"; | ||
| //#region src/_request.d.ts | ||
| declare const StubRequest: { | ||
| new (url: string, init?: RequestInit): Request; | ||
| }; | ||
| //#endregion | ||
| //#region src/adapters/uws.d.ts | ||
| type UserData = { | ||
@@ -61,3 +57,2 @@ peer?: UWSPeer; | ||
| } | ||
| //#endregion | ||
| export { UWSAdapter, UWSOptions, uwsAdapter as default }; |
| import { i as AdapterHookable, r as getPeers, t as adapterUtils } from "../_chunks/adapter.mjs"; | ||
| import { n as Message, r as toBufferLike, t as Peer } from "../_chunks/peer.mjs"; | ||
| import { t as StubRequest } from "../_chunks/_request.mjs"; | ||
| //#region src/adapters/uws.ts | ||
| const uwsAdapter = (options = {}) => { | ||
@@ -24,3 +22,3 @@ const hooks = new AdapterHookable(options); | ||
| }, | ||
| message(ws, message, isBinary) { | ||
| message(ws, message, _isBinary) { | ||
| const peer = getPeer(ws, getPeers(globalPeers, ws.getUserData().namespace)); | ||
@@ -76,3 +74,2 @@ hooks.callHook("message", peer, new Message(message, peer)); | ||
| }; | ||
| var uws_default = uwsAdapter; | ||
| function getPeer(uws, peers) { | ||
@@ -158,4 +155,2 @@ const uwsData = uws.getUserData(); | ||
| }; | ||
| //#endregion | ||
| export { uws_default as default }; | ||
| export { uwsAdapter as default }; |
+81
-1
| import { a as Hooks, c as Message, d as PeerContext, f as WSError, i as defineWebSocketAdapter, l as AdapterInternal, n as AdapterInstance, o as ResolveHooks, r as AdapterOptions, s as defineHooks, t as Adapter, u as Peer } from "./_chunks/adapter.mjs"; | ||
| export { type Adapter, type AdapterInstance, type AdapterInternal, type AdapterOptions, type Hooks, type Message, type Peer, type PeerContext, type ResolveHooks, type WSError, defineHooks, defineWebSocketAdapter }; | ||
| import { n as WSOptions, t as ServerWithWSOptions } from "./_chunks/_types.mjs"; | ||
| interface WebSocketProxyOptions { | ||
| /** | ||
| * Target WebSocket URL to proxy to (`ws://` or `wss://`). | ||
| * | ||
| * Can be a static string/URL or a function that resolves the target dynamically | ||
| * based on the incoming {@link Peer}. | ||
| */ | ||
| target: string | URL | ((peer: Peer) => string | URL); | ||
| /** | ||
| * Forward the client's `sec-websocket-protocol` header to the upstream. | ||
| * | ||
| * @default true | ||
| */ | ||
| forwardProtocol?: boolean; | ||
| /** | ||
| * Maximum number of bytes buffered per peer while the upstream connection | ||
| * is still opening. If exceeded, the peer is closed with code `1009` | ||
| * (Message Too Big). Set to `0` to disable the limit. | ||
| * | ||
| * @default 1048576 (1 MiB) | ||
| */ | ||
| maxBufferSize?: number; | ||
| /** | ||
| * Milliseconds to wait for the upstream WebSocket handshake to complete. | ||
| * If the upstream does not open within the timeout, the peer is closed | ||
| * with code `1011`. Set to `0` to disable the timeout. | ||
| * | ||
| * @default 10000 | ||
| */ | ||
| connectTimeout?: number; | ||
| /** | ||
| * Custom `WebSocket` constructor used to dial the upstream. Useful when | ||
| * the runtime does not expose a global `WebSocket` (Node.js < 22) or | ||
| * when you want to use a different client implementation (e.g. `ws`, | ||
| * `undici`, a mock for tests). | ||
| * | ||
| * @default globalThis.WebSocket | ||
| */ | ||
| WebSocket?: typeof WebSocket; | ||
| /** | ||
| * Extra headers to send on the upstream handshake. Can be a static | ||
| * object or a resolver called per peer. | ||
| * | ||
| * Useful to forward identity from the incoming request (`cookie`, | ||
| * `authorization`, `origin`), or to inject a shared secret the | ||
| * upstream expects. | ||
| * | ||
| * > [!NOTE] | ||
| * > The WHATWG global `WebSocket` constructor does not accept custom | ||
| * > headers — this option is only honored by `WebSocket` constructors | ||
| * > that take a third options argument (e.g. `ws`, `undici`). Pass | ||
| * > one via the {@link WebSocket} option to use it. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * createWebSocketProxy({ | ||
| * target: "wss://backend.example.com", | ||
| * WebSocket: WsFromNodeWs, | ||
| * headers: (peer) => ({ | ||
| * cookie: peer.request.headers.get("cookie") ?? "", | ||
| * "x-forwarded-for": peer.remoteAddress ?? "", | ||
| * }), | ||
| * }); | ||
| * ``` | ||
| */ | ||
| headers?: HeadersInit | ((peer: Peer) => HeadersInit | undefined | void); | ||
| } | ||
| /** | ||
| * Create a set of crossws hooks that proxy incoming WebSocket connections | ||
| * to an upstream `ws://` or `wss://` target. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * import { createWebSocketProxy } from "crossws"; | ||
| * | ||
| * const hooks = createWebSocketProxy("wss://echo.websocket.org"); | ||
| * ``` | ||
| */ | ||
| declare function createWebSocketProxy(target: WebSocketProxyOptions["target"] | WebSocketProxyOptions): Partial<Hooks>; | ||
| export { type Adapter, type AdapterInstance, type AdapterInternal, type AdapterOptions, type Hooks, type Message, type Peer, type PeerContext, type ResolveHooks, type ServerWithWSOptions, type WSError, type WSOptions, type WebSocketProxyOptions, createWebSocketProxy, defineHooks, defineWebSocketAdapter }; |
+161
-2
| import { a as defineHooks, n as defineWebSocketAdapter } from "./_chunks/adapter.mjs"; | ||
| export { defineHooks, defineWebSocketAdapter }; | ||
| const DEFAULT_MAX_BUFFER_SIZE = 1024 * 1024; | ||
| const DEFAULT_CONNECT_TIMEOUT = 1e4; | ||
| const TOKEN_RE = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/; | ||
| function createWebSocketProxy(target) { | ||
| const options = typeof target === "string" || target instanceof URL || typeof target === "function" ? { target } : target; | ||
| const WebSocketCtor = options.WebSocket ?? globalThis.WebSocket; | ||
| if (typeof WebSocketCtor !== "function") throw new TypeError("createWebSocketProxy requires a `WebSocket` constructor. Pass one via the `WebSocket` option, or use a runtime that provides a global `WebSocket` (Node.js >= 22, Bun, Deno, Cloudflare Workers, browsers)."); | ||
| const upstreams = /* @__PURE__ */ new Map(); | ||
| return { | ||
| upgrade(request) { | ||
| const reqProtocol = request.headers.get("sec-websocket-protocol"); | ||
| if (options.forwardProtocol === false || !reqProtocol) return; | ||
| const accepted = reqProtocol.split(",")[0].trim(); | ||
| if (!TOKEN_RE.test(accepted)) return; | ||
| return { headers: { "sec-websocket-protocol": accepted } }; | ||
| }, | ||
| open(peer) { | ||
| let ws; | ||
| try { | ||
| const url = _resolveTarget(options.target, peer); | ||
| const protocols = _resolveProtocols(peer, options.forwardProtocol); | ||
| const wsOptions = _resolveWsOptions(options.headers, peer); | ||
| ws = wsOptions ? new WebSocketCtor(url, protocols, wsOptions) : new WebSocketCtor(url, protocols); | ||
| ws.binaryType = "arraybuffer"; | ||
| } catch { | ||
| _safeClose(peer, 1011, "Upstream setup failed"); | ||
| return; | ||
| } | ||
| const state = { | ||
| ws, | ||
| buffer: [], | ||
| bufferSize: 0, | ||
| open: false, | ||
| timeout: void 0 | ||
| }; | ||
| upstreams.set(peer.id, state); | ||
| const timeoutMs = options.connectTimeout ?? DEFAULT_CONNECT_TIMEOUT; | ||
| if (timeoutMs > 0) state.timeout = setTimeout(() => { | ||
| if (upstreams.get(peer.id) !== state || state.open) return; | ||
| _cleanupState(upstreams, peer.id, state); | ||
| _safeClose(peer, 1011, "Upstream connect timeout"); | ||
| }, timeoutMs); | ||
| ws.addEventListener("open", () => { | ||
| _clearTimeout(state); | ||
| state.open = true; | ||
| for (const data of state.buffer) ws.send(data); | ||
| state.buffer.length = 0; | ||
| state.bufferSize = 0; | ||
| }); | ||
| ws.addEventListener("message", (event) => { | ||
| _safeSend(peer, event.data); | ||
| }); | ||
| ws.addEventListener("close", (event) => { | ||
| if (upstreams.get(peer.id) !== state) return; | ||
| _cleanupState(upstreams, peer.id, state); | ||
| _safeClose(peer, _remapIncomingCode(event.code), event.reason); | ||
| }); | ||
| ws.addEventListener("error", () => { | ||
| if (upstreams.get(peer.id) !== state) return; | ||
| _cleanupState(upstreams, peer.id, state); | ||
| _safeClose(peer, 1011, "Upstream error"); | ||
| }); | ||
| }, | ||
| message(peer, message) { | ||
| const state = upstreams.get(peer.id); | ||
| if (!state) return; | ||
| const raw = typeof message.rawData === "string" ? message.rawData : message.uint8Array(); | ||
| if (state.open) { | ||
| try { | ||
| state.ws.send(raw); | ||
| } catch {} | ||
| return; | ||
| } | ||
| const size = typeof raw === "string" ? raw.length * 3 : raw.byteLength; | ||
| const limit = options.maxBufferSize ?? DEFAULT_MAX_BUFFER_SIZE; | ||
| if (limit > 0 && state.bufferSize + size > limit) { | ||
| _cleanupState(upstreams, peer.id, state); | ||
| _safeClose(peer, 1009, "Proxy buffer limit exceeded"); | ||
| return; | ||
| } | ||
| state.buffer.push(typeof raw === "string" ? raw : Uint8Array.from(raw)); | ||
| state.bufferSize += size; | ||
| }, | ||
| close(peer, details) { | ||
| const state = upstreams.get(peer.id); | ||
| if (!state) return; | ||
| _clearTimeout(state); | ||
| upstreams.delete(peer.id); | ||
| try { | ||
| state.ws.close(_normalizeOutgoingCode(details.code), _truncateReason(details.reason)); | ||
| } catch {} | ||
| }, | ||
| error(peer) { | ||
| const state = upstreams.get(peer.id); | ||
| if (!state) return; | ||
| _clearTimeout(state); | ||
| upstreams.delete(peer.id); | ||
| try { | ||
| state.ws.close(1011, "Peer error"); | ||
| } catch {} | ||
| } | ||
| }; | ||
| } | ||
| function _cleanupState(upstreams, id, state) { | ||
| _clearTimeout(state); | ||
| upstreams.delete(id); | ||
| try { | ||
| state.ws.close(); | ||
| } catch {} | ||
| } | ||
| function _clearTimeout(state) { | ||
| if (state.timeout !== void 0) { | ||
| clearTimeout(state.timeout); | ||
| state.timeout = void 0; | ||
| } | ||
| } | ||
| function _resolveTarget(target, peer) { | ||
| const raw = typeof target === "function" ? target(peer) : target; | ||
| return raw instanceof URL ? raw : new URL(raw); | ||
| } | ||
| function _resolveWsOptions(headers, peer) { | ||
| if (!headers) return; | ||
| const resolved = typeof headers === "function" ? headers(peer) : headers; | ||
| if (!resolved) return; | ||
| return { headers: resolved }; | ||
| } | ||
| function _resolveProtocols(peer, forwardProtocol) { | ||
| if (forwardProtocol === false) return; | ||
| const header = peer.request?.headers.get("sec-websocket-protocol"); | ||
| if (!header) return; | ||
| return header.split(",").map((p) => p.trim()).filter(Boolean); | ||
| } | ||
| function _safeClose(peer, code, reason) { | ||
| try { | ||
| peer.close(code, _truncateReason(reason)); | ||
| } catch {} | ||
| } | ||
| function _safeSend(peer, data) { | ||
| try { | ||
| peer.send(data); | ||
| } catch {} | ||
| } | ||
| function _truncateReason(reason) { | ||
| if (!reason) return reason; | ||
| const bytes = new TextEncoder().encode(reason); | ||
| if (bytes.length <= 123) return reason; | ||
| return new TextDecoder("utf-8", { fatal: false }).decode(bytes.subarray(0, 123)); | ||
| } | ||
| function _remapIncomingCode(code) { | ||
| if (code === void 0) return void 0; | ||
| if (code === 1005) return 1e3; | ||
| if (code === 1006 || code === 1015) return 1011; | ||
| return code; | ||
| } | ||
| function _normalizeOutgoingCode(code) { | ||
| if (code === void 0) return void 0; | ||
| if (code === 1e3) return 1e3; | ||
| if (code >= 3e3 && code <= 4999) return code; | ||
| return 1e3; | ||
| } | ||
| export { createWebSocketProxy, defineHooks, defineWebSocketAdapter }; |
@@ -1,11 +0,5 @@ | ||
| import "../_chunks/bun.mjs"; | ||
| import "../_chunks/cloudflare.mjs"; | ||
| import "../_chunks/node.mjs"; | ||
| import { n as WSOptions, t as ServerWithWSOptions } from "../_chunks/_types.mjs"; | ||
| import { Server, ServerPlugin } from "srvx"; | ||
| //#region src/server/bun.d.ts | ||
| declare function plugin(wsOpts: WSOptions): ServerPlugin; | ||
| declare function serve(options: ServerWithWSOptions): Server; | ||
| //#endregion | ||
| export { plugin, serve }; |
@@ -1,8 +0,6 @@ | ||
| import bun_default from "../adapters/bun.mjs"; | ||
| import bunAdapter from "../adapters/bun.mjs"; | ||
| import { serve as serve$1 } from "srvx/bun"; | ||
| //#region src/server/bun.ts | ||
| function plugin(wsOpts) { | ||
| return (server) => { | ||
| const ws = bun_default({ | ||
| const ws = bunAdapter({ | ||
| hooks: wsOpts, | ||
@@ -28,4 +26,2 @@ resolve: wsOpts.resolve, | ||
| } | ||
| //#endregion | ||
| export { plugin, serve }; | ||
| export { plugin, serve }; |
@@ -1,11 +0,5 @@ | ||
| import "../_chunks/bun.mjs"; | ||
| import "../_chunks/cloudflare.mjs"; | ||
| import "../_chunks/node.mjs"; | ||
| import { n as WSOptions, t as ServerWithWSOptions } from "../_chunks/_types.mjs"; | ||
| import { Server, ServerPlugin } from "srvx"; | ||
| //#region src/server/cloudflare.d.ts | ||
| declare function plugin(wsOpts: WSOptions): ServerPlugin; | ||
| declare function serve(options: ServerWithWSOptions): Server; | ||
| //#endregion | ||
| export { plugin, serve }; |
@@ -1,8 +0,6 @@ | ||
| import cloudflare_default from "../adapters/cloudflare.mjs"; | ||
| import cloudflareAdapter from "../adapters/cloudflare.mjs"; | ||
| import { serve as serve$1 } from "srvx/cloudflare"; | ||
| //#region src/server/cloudflare.ts | ||
| function plugin(wsOpts) { | ||
| return (server) => { | ||
| const ws = cloudflare_default({ | ||
| const ws = cloudflareAdapter({ | ||
| hooks: wsOpts, | ||
@@ -25,4 +23,2 @@ resolve: wsOpts.resolve, | ||
| } | ||
| //#endregion | ||
| export { plugin, serve }; | ||
| export { plugin, serve }; |
@@ -1,11 +0,5 @@ | ||
| import "../_chunks/bun.mjs"; | ||
| import "../_chunks/cloudflare.mjs"; | ||
| import "../_chunks/node.mjs"; | ||
| import { n as WSOptions, t as ServerWithWSOptions } from "../_chunks/_types.mjs"; | ||
| import { Server, ServerPlugin } from "srvx"; | ||
| //#region src/server/default.d.ts | ||
| declare function plugin(wsOpts: WSOptions): ServerPlugin; | ||
| declare function serve(options: ServerWithWSOptions): Server; | ||
| //#endregion | ||
| export { plugin, serve }; |
@@ -1,7 +0,5 @@ | ||
| import sse_default from "../adapters/sse.mjs"; | ||
| import sseAdapter from "../adapters/sse.mjs"; | ||
| import { serve as serve$1 } from "srvx"; | ||
| //#region src/server/default.ts | ||
| function plugin(wsOpts) { | ||
| const ws = sse_default({ | ||
| const ws = sseAdapter({ | ||
| hooks: wsOpts, | ||
@@ -26,4 +24,2 @@ resolve: wsOpts.resolve, | ||
| } | ||
| //#endregion | ||
| export { plugin, serve }; | ||
| export { plugin, serve }; |
@@ -1,11 +0,5 @@ | ||
| import "../_chunks/bun.mjs"; | ||
| import "../_chunks/cloudflare.mjs"; | ||
| import "../_chunks/node.mjs"; | ||
| import { n as WSOptions, t as ServerWithWSOptions } from "../_chunks/_types.mjs"; | ||
| import { Server, ServerPlugin } from "srvx"; | ||
| //#region src/server/deno.d.ts | ||
| declare function plugin(wsOpts: WSOptions): ServerPlugin; | ||
| declare function serve(options: ServerWithWSOptions): Server; | ||
| //#endregion | ||
| export { plugin, serve }; |
@@ -1,8 +0,6 @@ | ||
| import deno_default from "../adapters/deno.mjs"; | ||
| import denoAdapter from "../adapters/deno.mjs"; | ||
| import { serve as serve$1 } from "srvx/deno"; | ||
| //#region src/server/deno.ts | ||
| function plugin(wsOpts) { | ||
| return (server) => { | ||
| const ws = deno_default({ | ||
| const ws = denoAdapter({ | ||
| hooks: wsOpts, | ||
@@ -25,4 +23,2 @@ resolve: wsOpts.resolve, | ||
| } | ||
| //#endregion | ||
| export { plugin, serve }; | ||
| export { plugin, serve }; |
@@ -1,11 +0,5 @@ | ||
| import "../_chunks/bun.mjs"; | ||
| import "../_chunks/cloudflare.mjs"; | ||
| import "../_chunks/node.mjs"; | ||
| import { n as WSOptions, t as ServerWithWSOptions } from "../_chunks/_types.mjs"; | ||
| import { Server, ServerPlugin } from "srvx"; | ||
| //#region src/server/node.d.ts | ||
| declare function plugin(wsOpts: WSOptions): ServerPlugin; | ||
| declare function serve(options: ServerWithWSOptions): Server; | ||
| //#endregion | ||
| export { plugin, serve }; |
@@ -1,10 +0,6 @@ | ||
| import "../_chunks/rolldown-runtime.mjs"; | ||
| import "../_chunks/libs/ws.mjs"; | ||
| import node_default from "../adapters/node.mjs"; | ||
| import { t as nodeAdapter } from "../_chunks/node.mjs"; | ||
| import { NodeRequest, serve as serve$1 } from "srvx/node"; | ||
| //#region src/server/node.ts | ||
| function plugin(wsOpts) { | ||
| return (server) => { | ||
| const ws = node_default({ | ||
| const ws = nodeAdapter({ | ||
| hooks: wsOpts, | ||
@@ -36,4 +32,2 @@ resolve: wsOpts.resolve, | ||
| } | ||
| //#endregion | ||
| export { plugin, serve }; | ||
| export { plugin, serve }; |
@@ -1,4 +0,2 @@ | ||
| //#region src/websocket/native.d.ts | ||
| declare const WebSocket: typeof globalThis.WebSocket; | ||
| //#endregion | ||
| export { WebSocket as default }; |
@@ -1,6 +0,2 @@ | ||
| //#region src/websocket/native.ts | ||
| const WebSocket = globalThis.WebSocket; | ||
| var native_default = WebSocket; | ||
| //#endregion | ||
| export { native_default as default }; | ||
| export { WebSocket as default }; |
@@ -1,4 +0,2 @@ | ||
| //#region src/websocket/node.d.ts | ||
| declare const Websocket: typeof globalThis.WebSocket; | ||
| //#endregion | ||
| export { Websocket as default }; |
@@ -1,9 +0,3 @@ | ||
| import "../_chunks/rolldown-runtime.mjs"; | ||
| import { t as import_websocket } from "../_chunks/libs/ws.mjs"; | ||
| //#region src/websocket/node.ts | ||
| const Websocket = globalThis.WebSocket || import_websocket.default; | ||
| var node_default = Websocket; | ||
| //#endregion | ||
| export { node_default as default }; | ||
| export { Websocket as default }; |
| import { a as WebSocket, i as MessageEvent, n as Event, r as EventTarget, t as CloseEvent } from "../_chunks/web.mjs"; | ||
| //#region src/websocket/sse.d.ts | ||
| type Ctor<T> = { | ||
@@ -41,3 +39,2 @@ prototype: T; | ||
| } | ||
| //#endregion | ||
| export { WebSocketSSE, WebSocketSSEOptions }; |
@@ -1,2 +0,1 @@ | ||
| //#region src/websocket/sse.ts | ||
| const _EventTarget = EventTarget; | ||
@@ -114,4 +113,2 @@ const defaultOptions = Object.freeze({ | ||
| }; | ||
| //#endregion | ||
| export { WebSocketSSE }; | ||
| export { WebSocketSSE }; |
+42
-40
| { | ||
| "name": "crossws", | ||
| "version": "0.4.4", | ||
| "version": "0.4.5", | ||
| "description": "Cross-platform WebSocket Servers for Node.js, Deno, Bun and Cloudflare Workers", | ||
| "homepage": "https://crossws.h3.dev", | ||
| "license": "MIT", | ||
| "repository": "h3js/crossws", | ||
| "license": "MIT", | ||
| "files": [ | ||
| "dist", | ||
| "adapters", | ||
| "websocket", | ||
| "server", | ||
| "*.d.ts" | ||
| ], | ||
| "type": "module", | ||
| "sideEffects": false, | ||
| "type": "module", | ||
| "main": "./dist/index.mjs", | ||
| "module": "./dist/index.mjs", | ||
| "types": "./dist/index.d.mts", | ||
| "exports": { | ||
| ".": "./dist/index.mjs", | ||
| "./adapters/bun": "./dist/adapters/bun.mjs", | ||
| "./adapters/bunny": "./dist/adapters/bunny.mjs", | ||
| "./adapters/deno": "./dist/adapters/deno.mjs", | ||
@@ -19,2 +30,3 @@ "./adapters/cloudflare": "./dist/adapters/cloudflare.mjs", | ||
| "./server/bun": "./dist/server/bun.mjs", | ||
| "./server/bunny": "./dist/server/bunny.mjs", | ||
| "./server/deno": "./dist/server/deno.mjs", | ||
@@ -41,17 +53,7 @@ "./server/node": "./dist/server/node.mjs", | ||
| }, | ||
| "main": "./dist/index.mjs", | ||
| "module": "./dist/index.mjs", | ||
| "types": "./dist/index.d.mts", | ||
| "files": [ | ||
| "dist", | ||
| "adapters", | ||
| "websocket", | ||
| "server", | ||
| "*.d.ts" | ||
| ], | ||
| "scripts": { | ||
| "build": "obuild", | ||
| "dev": "vitest", | ||
| "lint": "eslint --cache . && prettier -c src test", | ||
| "lint:fix": "eslint --cache . --fix && prettier -w src test", | ||
| "lint": "oxlint . && oxfmt --check src test", | ||
| "fmt": "oxlint . --fix && oxfmt src test", | ||
| "prepack": "pnpm run build", | ||
@@ -66,21 +68,17 @@ "play:bun": "bun --watch test/fixture/bun.ts", | ||
| "release": "pnpm test && pnpm build && changelogen --release && npm publish && git push --follow-tags", | ||
| "test": "pnpm lint && pnpm test:types && vitest run --coverage", | ||
| "test:types": "tsgo --noEmit --skipLibCheck" | ||
| "test": "pnpm lint && pnpm typecheck && vitest run --coverage", | ||
| "typecheck": "tsgo --noEmit --skipLibCheck" | ||
| }, | ||
| "resolutions": { | ||
| "crossws": "workspace:*" | ||
| }, | ||
| "devDependencies": { | ||
| "@cloudflare/workers-types": "^4.20260127.0", | ||
| "@types/bun": "^1.3.6", | ||
| "@cloudflare/workers-types": "^4.20260410.1", | ||
| "@types/bun": "^1.3.12", | ||
| "@types/deno": "^2.5.0", | ||
| "@types/node": "^25.0.10", | ||
| "@types/web": "^0.0.323", | ||
| "@types/node": "^25.6.0", | ||
| "@types/web": "^0.0.345", | ||
| "@types/ws": "^8.18.1", | ||
| "@typescript/native-preview": "^7.0.0-dev.20260127.1", | ||
| "@vitest/coverage-v8": "^4.0.18", | ||
| "automd": "^0.4.2", | ||
| "@typescript/native-preview": "^7.0.0-dev.20260410.1", | ||
| "@vitest/coverage-v8": "^4.1.4", | ||
| "automd": "^0.4.3", | ||
| "changelogen": "^0.6.2", | ||
| "consola": "^3.4.2", | ||
| "eslint": "^9.39.2", | ||
| "eslint-config-unjs": "^0.6.2", | ||
@@ -90,18 +88,19 @@ "eventsource": "^4.1.0", | ||
| "get-port-please": "^3.2.0", | ||
| "h3": "^2.0.1-rc.11", | ||
| "h3": "^2.0.1-rc.20", | ||
| "jiti": "^2.6.1", | ||
| "listhen": "^1.9.0", | ||
| "obuild": "^0.4.20", | ||
| "prettier": "^3.8.1", | ||
| "srvx": "^0.10.1", | ||
| "typescript": "^5.9.3", | ||
| "listhen": "^1.9.1", | ||
| "obuild": "^0.4.33", | ||
| "oxfmt": "^0.44.0", | ||
| "oxlint": "^1.59.0", | ||
| "srvx": "^0.11.15", | ||
| "typescript": "^6.0.2", | ||
| "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.57.0", | ||
| "unbuild": "^3.6.1", | ||
| "undici": "^7.19.1", | ||
| "vitest": "^4.0.18", | ||
| "wrangler": "^4.60.0", | ||
| "ws": "^8.19.0" | ||
| "undici": "^8.0.2", | ||
| "vitest": "^4.1.4", | ||
| "wrangler": "^4.81.1", | ||
| "ws": "^8.20.0" | ||
| }, | ||
| "peerDependencies": { | ||
| "srvx": ">=0.7.1" | ||
| "srvx": ">=0.11.5" | ||
| }, | ||
@@ -113,3 +112,6 @@ "peerDependenciesMeta": { | ||
| }, | ||
| "packageManager": "pnpm@10.28.2", | ||
| "resolutions": { | ||
| "crossws": "workspace:*" | ||
| }, | ||
| "packageManager": "pnpm@10.33.0", | ||
| "pnpm": { | ||
@@ -116,0 +118,0 @@ "ignoredBuiltDependencies": [ |
Sorry, the diff of this file is too big to display
Unidentified License
LicenseSomething that seems like a license was found, but its contents could not be matched with a known license.
68
15.25%192834
-9.49%1
Infinity%80
-20%4051
-18.23%