@fuman/node
Advanced tools
| import { IListener, IWebSocketServerConnection, IWebSocketServerConnectionFramed, TcpEndpoint } from '@fuman/net'; | ||
| import { IncomingMessage } from 'node:http'; | ||
| import { Duplex } from 'node:stream'; | ||
| import { ServerOptions, WebSocket, WebSocketServer } from 'ws'; | ||
| import { ConditionVariable } from '@fuman/utils'; | ||
| declare abstract class NodeWebSocketConnectionBase { | ||
| #private; | ||
| readonly socket: WebSocket; | ||
| readonly request: IncomingMessage; | ||
| protected _error: Error | null; | ||
| protected _cv: ConditionVariable; | ||
| abstract onMessage(data: Buffer, isBinary: boolean): void; | ||
| constructor(socket: WebSocket, request: IncomingMessage); | ||
| get headers(): Headers; | ||
| get url(): string; | ||
| get localAddress(): null; | ||
| get remoteAddress(): TcpEndpoint | null; | ||
| close(): void; | ||
| } | ||
| declare class NodeWebSocketConnection extends NodeWebSocketConnectionBase implements IWebSocketServerConnection { | ||
| #private; | ||
| onMessage(data: Buffer): void; | ||
| read(into: Uint8Array): Promise<number>; | ||
| write(bytes: Uint8Array): Promise<void>; | ||
| } | ||
| declare class NodeWebSocketConnectionFramed extends NodeWebSocketConnectionBase implements IWebSocketServerConnectionFramed { | ||
| #private; | ||
| onMessage(data: Buffer, isBinary: boolean): void; | ||
| readFrame(): Promise<Uint8Array | string>; | ||
| writeFrame(data: Uint8Array | string): Promise<void>; | ||
| } | ||
| declare abstract class NodeWebSocketServerBase<Connection> { | ||
| #private; | ||
| /** Underlying server */ | ||
| readonly server: WebSocketServer; | ||
| abstract makeConnection(socket: WebSocket, request: IncomingMessage): Connection; | ||
| constructor( | ||
| /** Underlying server */ | ||
| server: WebSocketServer); | ||
| get address(): TcpEndpoint | null; | ||
| close(): void; | ||
| accept(): Promise<Connection>; | ||
| handleUpgrade(req: IncomingMessage, socket: Duplex, head: Buffer): void; | ||
| } | ||
| export declare class NodeWebSocketServer extends NodeWebSocketServerBase<NodeWebSocketConnection> implements IListener<TcpEndpoint, IWebSocketServerConnection> { | ||
| makeConnection(socket: WebSocket, request: IncomingMessage): NodeWebSocketConnection; | ||
| } | ||
| export declare class NodeWebSocketServerFramed extends NodeWebSocketServerBase<NodeWebSocketConnectionFramed> { | ||
| makeConnection(socket: WebSocket, request: IncomingMessage): NodeWebSocketConnectionFramed; | ||
| } | ||
| export declare function listenWs(options: ServerOptions): NodeWebSocketServer; | ||
| export declare function listenWsFramed(options: ServerOptions): NodeWebSocketServerFramed; | ||
| export {}; |
| import { IListener, IWebSocketServerConnection, IWebSocketServerConnectionFramed, TcpEndpoint } from '@fuman/net'; | ||
| import { IncomingMessage } from 'node:http'; | ||
| import { Duplex } from 'node:stream'; | ||
| import { ServerOptions, WebSocket, WebSocketServer } from 'ws'; | ||
| import { ConditionVariable } from '@fuman/utils'; | ||
| declare abstract class NodeWebSocketConnectionBase { | ||
| #private; | ||
| readonly socket: WebSocket; | ||
| readonly request: IncomingMessage; | ||
| protected _error: Error | null; | ||
| protected _cv: ConditionVariable; | ||
| abstract onMessage(data: Buffer, isBinary: boolean): void; | ||
| constructor(socket: WebSocket, request: IncomingMessage); | ||
| get headers(): Headers; | ||
| get url(): string; | ||
| get localAddress(): null; | ||
| get remoteAddress(): TcpEndpoint | null; | ||
| close(): void; | ||
| } | ||
| declare class NodeWebSocketConnection extends NodeWebSocketConnectionBase implements IWebSocketServerConnection { | ||
| #private; | ||
| onMessage(data: Buffer): void; | ||
| read(into: Uint8Array): Promise<number>; | ||
| write(bytes: Uint8Array): Promise<void>; | ||
| } | ||
| declare class NodeWebSocketConnectionFramed extends NodeWebSocketConnectionBase implements IWebSocketServerConnectionFramed { | ||
| #private; | ||
| onMessage(data: Buffer, isBinary: boolean): void; | ||
| readFrame(): Promise<Uint8Array | string>; | ||
| writeFrame(data: Uint8Array | string): Promise<void>; | ||
| } | ||
| declare abstract class NodeWebSocketServerBase<Connection> { | ||
| #private; | ||
| /** Underlying server */ | ||
| readonly server: WebSocketServer; | ||
| abstract makeConnection(socket: WebSocket, request: IncomingMessage): Connection; | ||
| constructor( | ||
| /** Underlying server */ | ||
| server: WebSocketServer); | ||
| get address(): TcpEndpoint | null; | ||
| close(): void; | ||
| accept(): Promise<Connection>; | ||
| handleUpgrade(req: IncomingMessage, socket: Duplex, head: Buffer): void; | ||
| } | ||
| export declare class NodeWebSocketServer extends NodeWebSocketServerBase<NodeWebSocketConnection> implements IListener<TcpEndpoint, IWebSocketServerConnection> { | ||
| makeConnection(socket: WebSocket, request: IncomingMessage): NodeWebSocketConnection; | ||
| } | ||
| export declare class NodeWebSocketServerFramed extends NodeWebSocketServerBase<NodeWebSocketConnectionFramed> { | ||
| makeConnection(socket: WebSocket, request: IncomingMessage): NodeWebSocketConnectionFramed; | ||
| } | ||
| export declare function listenWs(options: ServerOptions): NodeWebSocketServer; | ||
| export declare function listenWsFramed(options: ServerOptions): NodeWebSocketServerFramed; | ||
| export {}; |
+195
| "use strict"; | ||
| Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); | ||
| const io = require("@fuman/io"); | ||
| const net = require("@fuman/net"); | ||
| const utils = require("@fuman/utils"); | ||
| const ws = require("ws"); | ||
| class NodeWebSocketConnectionBase { | ||
| constructor(socket, request) { | ||
| this.socket = socket; | ||
| this.request = request; | ||
| socket.binaryType = "nodebuffer"; | ||
| socket.on("message", (data, isBinary) => { | ||
| const data_ = data; | ||
| this.onMessage(data_, isBinary); | ||
| this._cv.notify(); | ||
| }); | ||
| socket.on("close", (code, reason) => { | ||
| if (this._error) return; | ||
| this._error = new net.WebSocketConnectionClosedError(code, reason.toString("utf-8")); | ||
| this._cv.notify(); | ||
| }); | ||
| socket.on("error", (error) => { | ||
| if (this._error) return; | ||
| this._error = error; | ||
| this._cv.notify(); | ||
| }); | ||
| } | ||
| _error = null; | ||
| _cv = new utils.ConditionVariable(); | ||
| #headers; | ||
| get headers() { | ||
| if (!this.#headers) { | ||
| const headers = new Headers(); | ||
| for (const [key, value] of Object.entries(this.request.headers)) { | ||
| if (value == null) continue; | ||
| if (Array.isArray(value)) { | ||
| for (const v of value) { | ||
| headers.append(key, v); | ||
| } | ||
| } else { | ||
| headers.set(key, value); | ||
| } | ||
| } | ||
| this.#headers = headers; | ||
| } | ||
| return this.#headers; | ||
| } | ||
| get url() { | ||
| return this.request.url ?? this.socket.url; | ||
| } | ||
| get localAddress() { | ||
| return null; | ||
| } | ||
| get remoteAddress() { | ||
| if (this.request.socket.remoteAddress == null) return null; | ||
| return { | ||
| address: this.request.socket.remoteAddress, | ||
| port: this.request.socket.remotePort ?? 0 | ||
| }; | ||
| } | ||
| close() { | ||
| this.socket.close(); | ||
| this._error = new net.ConnectionClosedError(); | ||
| this._cv.notify(); | ||
| } | ||
| } | ||
| class NodeWebSocketConnection extends NodeWebSocketConnectionBase { | ||
| #buffer = io.Bytes.alloc(0); | ||
| onMessage(data) { | ||
| this.#buffer.writeSync(data.length).set(data); | ||
| this.#buffer.disposeWriteSync(); | ||
| } | ||
| async read(into) { | ||
| if (this.#buffer.available > 0) { | ||
| const size2 = Math.min(this.#buffer.available, into.length); | ||
| into.set(this.#buffer.readSync(size2)); | ||
| this.#buffer.reclaim(); | ||
| return size2; | ||
| } | ||
| if (this._error !== null) throw this._error; | ||
| await this._cv.wait(); | ||
| if (this._error !== null) throw this._error; | ||
| const size = Math.min(this.#buffer.available, into.length); | ||
| into.set(this.#buffer.readSync(size)); | ||
| this.#buffer.reclaim(); | ||
| return size; | ||
| } | ||
| async write(bytes) { | ||
| if (this._error) throw this._error; | ||
| if (!bytes.length) return; | ||
| this.socket.send(bytes); | ||
| } | ||
| } | ||
| class NodeWebSocketConnectionFramed extends NodeWebSocketConnectionBase { | ||
| #buffer = new utils.Deque(); | ||
| onMessage(data, isBinary) { | ||
| if (isBinary) { | ||
| this.#buffer.pushBack(new Uint8Array(data.buffer, data.byteOffset, data.byteLength)); | ||
| } else { | ||
| this.#buffer.pushBack(data.toString("utf-8")); | ||
| } | ||
| } | ||
| async readFrame() { | ||
| if (!this.#buffer.isEmpty()) { | ||
| return this.#buffer.popFront(); | ||
| } | ||
| if (this._error !== null) throw this._error; | ||
| await this._cv.wait(); | ||
| if (this._error !== null) throw this._error; | ||
| return this.#buffer.popFront(); | ||
| } | ||
| async writeFrame(data) { | ||
| if (this._error) throw this._error; | ||
| this.socket.send(data); | ||
| } | ||
| } | ||
| class NodeWebSocketServerBase { | ||
| constructor(server) { | ||
| this.server = server; | ||
| server.on("connection", (socket, request) => { | ||
| if (!this.#waiter) { | ||
| socket.close(); | ||
| return; | ||
| } | ||
| this.#waiter.resolve(this.makeConnection(socket, request)); | ||
| this.#waiter = void 0; | ||
| }); | ||
| server.on("error", (error) => { | ||
| this.#waiter?.reject(error); | ||
| }); | ||
| server.on("close", () => { | ||
| this.#waiter?.reject(new net.ListenerClosedError()); | ||
| }); | ||
| } | ||
| #closed = false; | ||
| #waiter; | ||
| get address() { | ||
| const addr = this.server.address(); | ||
| if (addr == null) return null; | ||
| if (typeof addr === "string") { | ||
| const [host, port] = addr.split(":"); | ||
| return { | ||
| address: host, | ||
| port: Number.parseInt(port) | ||
| }; | ||
| } | ||
| return { | ||
| address: addr.address, | ||
| port: addr.port | ||
| }; | ||
| } | ||
| close() { | ||
| this.server.close(); | ||
| } | ||
| async accept() { | ||
| if (this.#closed) { | ||
| throw new net.ListenerClosedError(); | ||
| } | ||
| this.#waiter = new utils.Deferred(); | ||
| const connection = await this.#waiter.promise; | ||
| this.#waiter = void 0; | ||
| return connection; | ||
| } | ||
| handleUpgrade(req, socket, head) { | ||
| if (!this.#waiter) { | ||
| socket.destroy(); | ||
| return; | ||
| } | ||
| const waiter = this.#waiter; | ||
| this.#waiter = void 0; | ||
| this.server.handleUpgrade(req, socket, head, (socket2, req2) => { | ||
| waiter.resolve(this.makeConnection(socket2, req2)); | ||
| }); | ||
| } | ||
| } | ||
| class NodeWebSocketServer extends NodeWebSocketServerBase { | ||
| makeConnection(socket, request) { | ||
| return new NodeWebSocketConnection(socket, request); | ||
| } | ||
| } | ||
| class NodeWebSocketServerFramed extends NodeWebSocketServerBase { | ||
| makeConnection(socket, request) { | ||
| return new NodeWebSocketConnectionFramed(socket, request); | ||
| } | ||
| } | ||
| function listenWs(options) { | ||
| return new NodeWebSocketServer(new ws.WebSocketServer(options)); | ||
| } | ||
| function listenWsFramed(options) { | ||
| return new NodeWebSocketServerFramed(new ws.WebSocketServer(options)); | ||
| } | ||
| exports.NodeWebSocketServer = NodeWebSocketServer; | ||
| exports.NodeWebSocketServerFramed = NodeWebSocketServerFramed; | ||
| exports.listenWs = listenWs; | ||
| exports.listenWsFramed = listenWsFramed; |
| export * from "./net/websocket.js" |
| export * from "./net/websocket.js" |
+195
| import { Bytes } from "@fuman/io"; | ||
| import { ListenerClosedError, WebSocketConnectionClosedError, ConnectionClosedError } from "@fuman/net"; | ||
| import { Deque, Deferred, ConditionVariable } from "@fuman/utils"; | ||
| import { WebSocketServer } from "ws"; | ||
| class NodeWebSocketConnectionBase { | ||
| constructor(socket, request) { | ||
| this.socket = socket; | ||
| this.request = request; | ||
| socket.binaryType = "nodebuffer"; | ||
| socket.on("message", (data, isBinary) => { | ||
| const data_ = data; | ||
| this.onMessage(data_, isBinary); | ||
| this._cv.notify(); | ||
| }); | ||
| socket.on("close", (code, reason) => { | ||
| if (this._error) return; | ||
| this._error = new WebSocketConnectionClosedError(code, reason.toString("utf-8")); | ||
| this._cv.notify(); | ||
| }); | ||
| socket.on("error", (error) => { | ||
| if (this._error) return; | ||
| this._error = error; | ||
| this._cv.notify(); | ||
| }); | ||
| } | ||
| _error = null; | ||
| _cv = new ConditionVariable(); | ||
| #headers; | ||
| get headers() { | ||
| if (!this.#headers) { | ||
| const headers = new Headers(); | ||
| for (const [key, value] of Object.entries(this.request.headers)) { | ||
| if (value == null) continue; | ||
| if (Array.isArray(value)) { | ||
| for (const v of value) { | ||
| headers.append(key, v); | ||
| } | ||
| } else { | ||
| headers.set(key, value); | ||
| } | ||
| } | ||
| this.#headers = headers; | ||
| } | ||
| return this.#headers; | ||
| } | ||
| get url() { | ||
| return this.request.url ?? this.socket.url; | ||
| } | ||
| get localAddress() { | ||
| return null; | ||
| } | ||
| get remoteAddress() { | ||
| if (this.request.socket.remoteAddress == null) return null; | ||
| return { | ||
| address: this.request.socket.remoteAddress, | ||
| port: this.request.socket.remotePort ?? 0 | ||
| }; | ||
| } | ||
| close() { | ||
| this.socket.close(); | ||
| this._error = new ConnectionClosedError(); | ||
| this._cv.notify(); | ||
| } | ||
| } | ||
| class NodeWebSocketConnection extends NodeWebSocketConnectionBase { | ||
| #buffer = Bytes.alloc(0); | ||
| onMessage(data) { | ||
| this.#buffer.writeSync(data.length).set(data); | ||
| this.#buffer.disposeWriteSync(); | ||
| } | ||
| async read(into) { | ||
| if (this.#buffer.available > 0) { | ||
| const size2 = Math.min(this.#buffer.available, into.length); | ||
| into.set(this.#buffer.readSync(size2)); | ||
| this.#buffer.reclaim(); | ||
| return size2; | ||
| } | ||
| if (this._error !== null) throw this._error; | ||
| await this._cv.wait(); | ||
| if (this._error !== null) throw this._error; | ||
| const size = Math.min(this.#buffer.available, into.length); | ||
| into.set(this.#buffer.readSync(size)); | ||
| this.#buffer.reclaim(); | ||
| return size; | ||
| } | ||
| async write(bytes) { | ||
| if (this._error) throw this._error; | ||
| if (!bytes.length) return; | ||
| this.socket.send(bytes); | ||
| } | ||
| } | ||
| class NodeWebSocketConnectionFramed extends NodeWebSocketConnectionBase { | ||
| #buffer = new Deque(); | ||
| onMessage(data, isBinary) { | ||
| if (isBinary) { | ||
| this.#buffer.pushBack(new Uint8Array(data.buffer, data.byteOffset, data.byteLength)); | ||
| } else { | ||
| this.#buffer.pushBack(data.toString("utf-8")); | ||
| } | ||
| } | ||
| async readFrame() { | ||
| if (!this.#buffer.isEmpty()) { | ||
| return this.#buffer.popFront(); | ||
| } | ||
| if (this._error !== null) throw this._error; | ||
| await this._cv.wait(); | ||
| if (this._error !== null) throw this._error; | ||
| return this.#buffer.popFront(); | ||
| } | ||
| async writeFrame(data) { | ||
| if (this._error) throw this._error; | ||
| this.socket.send(data); | ||
| } | ||
| } | ||
| class NodeWebSocketServerBase { | ||
| constructor(server) { | ||
| this.server = server; | ||
| server.on("connection", (socket, request) => { | ||
| if (!this.#waiter) { | ||
| socket.close(); | ||
| return; | ||
| } | ||
| this.#waiter.resolve(this.makeConnection(socket, request)); | ||
| this.#waiter = void 0; | ||
| }); | ||
| server.on("error", (error) => { | ||
| this.#waiter?.reject(error); | ||
| }); | ||
| server.on("close", () => { | ||
| this.#waiter?.reject(new ListenerClosedError()); | ||
| }); | ||
| } | ||
| #closed = false; | ||
| #waiter; | ||
| get address() { | ||
| const addr = this.server.address(); | ||
| if (addr == null) return null; | ||
| if (typeof addr === "string") { | ||
| const [host, port] = addr.split(":"); | ||
| return { | ||
| address: host, | ||
| port: Number.parseInt(port) | ||
| }; | ||
| } | ||
| return { | ||
| address: addr.address, | ||
| port: addr.port | ||
| }; | ||
| } | ||
| close() { | ||
| this.server.close(); | ||
| } | ||
| async accept() { | ||
| if (this.#closed) { | ||
| throw new ListenerClosedError(); | ||
| } | ||
| this.#waiter = new Deferred(); | ||
| const connection = await this.#waiter.promise; | ||
| this.#waiter = void 0; | ||
| return connection; | ||
| } | ||
| handleUpgrade(req, socket, head) { | ||
| if (!this.#waiter) { | ||
| socket.destroy(); | ||
| return; | ||
| } | ||
| const waiter = this.#waiter; | ||
| this.#waiter = void 0; | ||
| this.server.handleUpgrade(req, socket, head, (socket2, req2) => { | ||
| waiter.resolve(this.makeConnection(socket2, req2)); | ||
| }); | ||
| } | ||
| } | ||
| class NodeWebSocketServer extends NodeWebSocketServerBase { | ||
| makeConnection(socket, request) { | ||
| return new NodeWebSocketConnection(socket, request); | ||
| } | ||
| } | ||
| class NodeWebSocketServerFramed extends NodeWebSocketServerBase { | ||
| makeConnection(socket, request) { | ||
| return new NodeWebSocketConnectionFramed(socket, request); | ||
| } | ||
| } | ||
| function listenWs(options) { | ||
| return new NodeWebSocketServer(new WebSocketServer(options)); | ||
| } | ||
| function listenWsFramed(options) { | ||
| return new NodeWebSocketServerFramed(new WebSocketServer(options)); | ||
| } | ||
| export { | ||
| NodeWebSocketServer, | ||
| NodeWebSocketServerFramed, | ||
| listenWs, | ||
| listenWsFramed | ||
| }; |
+22
-4
| { | ||
| "name": "@fuman/node", | ||
| "type": "module", | ||
| "version": "0.0.10", | ||
| "version": "0.0.11", | ||
| "description": "node-specific utilities", | ||
@@ -9,6 +9,9 @@ "license": "MIT", | ||
| "dependencies": { | ||
| "@fuman/io": "^0.0.10", | ||
| "@fuman/net": "^0.0.10", | ||
| "@fuman/utils": "^0.0.10" | ||
| "@fuman/io": "^0.0.11", | ||
| "@fuman/net": "^0.0.11", | ||
| "@fuman/utils": "^0.0.11" | ||
| }, | ||
| "peerDependencies": { | ||
| "ws": "^8.18.1" | ||
| }, | ||
| "exports": { | ||
@@ -24,5 +27,20 @@ ".": { | ||
| } | ||
| }, | ||
| "./websocket": { | ||
| "import": { | ||
| "types": "./websocket.d.ts", | ||
| "default": "./websocket.js" | ||
| }, | ||
| "require": { | ||
| "types": "./websocket.d.cts", | ||
| "default": "./websocket.cjs" | ||
| } | ||
| } | ||
| }, | ||
| "sideEffects": false, | ||
| "peerDependenciesMeta": { | ||
| "ws": { | ||
| "optional": true | ||
| } | ||
| }, | ||
| "author": "", | ||
@@ -29,0 +47,0 @@ "repository": { |
60998
36.71%40
17.65%1734
34.31%4
33.33%+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
Updated
Updated
Updated