mirror of
https://github.com/LukeHagar/crossws.git
synced 2025-12-06 12:27:46 +00:00
refactor: better internal organization
This commit is contained in:
40
src/adapter.ts
Normal file
40
src/adapter.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import type { AdapterHooks, Hooks, ResolveHooks } from "./hooks.ts";
|
||||
import type { Peer } from "./peer.ts";
|
||||
|
||||
export function adapterUtils(peers: Set<Peer>) {
|
||||
return {
|
||||
peers,
|
||||
publish(topic: string, message: any, options) {
|
||||
const firstPeer = peers.values().next().value as Peer;
|
||||
if (firstPeer) {
|
||||
firstPeer.send(message, options);
|
||||
firstPeer.publish(topic, message, options);
|
||||
}
|
||||
},
|
||||
} satisfies AdapterInstance;
|
||||
}
|
||||
|
||||
// --- types ---
|
||||
|
||||
export interface AdapterInstance {
|
||||
readonly peers: Set<Peer>;
|
||||
readonly publish: Peer["publish"];
|
||||
}
|
||||
|
||||
export interface AdapterOptions {
|
||||
resolve?: ResolveHooks;
|
||||
hooks?: Hooks;
|
||||
adapterHooks?: AdapterHooks;
|
||||
}
|
||||
|
||||
export type Adapter<
|
||||
AdapterT extends AdapterInstance = AdapterInstance,
|
||||
Options extends AdapterOptions = AdapterOptions,
|
||||
> = (options?: Options) => AdapterT;
|
||||
|
||||
export function defineWebSocketAdapter<
|
||||
AdapterT extends AdapterInstance = AdapterInstance,
|
||||
Options extends AdapterOptions = AdapterOptions,
|
||||
>(factory: Adapter<AdapterT, Options>) {
|
||||
return factory;
|
||||
}
|
||||
@@ -1,15 +1,13 @@
|
||||
// https://bun.sh/docs/api/websockets
|
||||
|
||||
import type { WebSocketHandler, ServerWebSocket, Server } from "bun";
|
||||
import { Message } from "../message";
|
||||
import { Peer } from "../peer";
|
||||
import {
|
||||
AdapterOptions,
|
||||
AdapterInstance,
|
||||
defineWebSocketAdapter,
|
||||
} from "../types";
|
||||
import { AdapterHookable } from "../hooks";
|
||||
import { adapterUtils, toBufferLike } from "../_utils";
|
||||
|
||||
import type { AdapterOptions, AdapterInstance } from "../adapter.ts";
|
||||
import { toBufferLike } from "../utils.ts";
|
||||
import { defineWebSocketAdapter, adapterUtils } from "../adapter.ts";
|
||||
import { AdapterHookable } from "../hooks.ts";
|
||||
import { Message } from "../message.ts";
|
||||
import { Peer } from "../peer.ts";
|
||||
|
||||
// --- types ---
|
||||
|
||||
export interface BunAdapter extends AdapterInstance {
|
||||
websocket: WebSocketHandler<ContextData>;
|
||||
@@ -25,6 +23,9 @@ type ContextData = {
|
||||
server?: Server;
|
||||
};
|
||||
|
||||
// --- adapter ---
|
||||
|
||||
// https://bun.sh/docs/api/websockets
|
||||
export default defineWebSocketAdapter<BunAdapter, BunOptions>(
|
||||
(options = {}) => {
|
||||
const hooks = new AdapterHookable(options);
|
||||
@@ -82,6 +83,8 @@ export default defineWebSocketAdapter<BunAdapter, BunOptions>(
|
||||
},
|
||||
);
|
||||
|
||||
// --- peer ---
|
||||
|
||||
function getPeer(
|
||||
ws: ServerWebSocket<ContextData>,
|
||||
peers: Set<BunPeer>,
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
// https://developers.cloudflare.com/durable-objects/examples/websocket-hibernation-server/
|
||||
import type { AdapterOptions, AdapterInstance } from "../adapter.ts";
|
||||
import { toBufferLike } from "../utils.ts";
|
||||
import { defineWebSocketAdapter, adapterUtils } from "../adapter.ts";
|
||||
import { AdapterHookable } from "../hooks.ts";
|
||||
import { Message } from "../message.ts";
|
||||
import { Peer } from "../peer.ts";
|
||||
|
||||
import type * as CF from "@cloudflare/workers-types";
|
||||
import type { DurableObject } from "cloudflare:workers";
|
||||
import {
|
||||
AdapterOptions,
|
||||
AdapterInstance,
|
||||
defineWebSocketAdapter,
|
||||
} from "../types";
|
||||
import { Peer } from "../peer";
|
||||
import { Message } from "../message";
|
||||
import { AdapterHookable } from "../hooks";
|
||||
import { adapterUtils, toBufferLike } from "../_utils";
|
||||
|
||||
// --- types
|
||||
|
||||
declare class DurableObjectPub extends DurableObject {
|
||||
public ctx: DurableObject["ctx"];
|
||||
@@ -58,6 +56,9 @@ export interface CloudflareOptions extends AdapterOptions {
|
||||
instanceName?: string;
|
||||
}
|
||||
|
||||
// --- adapter ---
|
||||
|
||||
// https://developers.cloudflare.com/durable-objects/examples/websocket-hibernation-server/
|
||||
export default defineWebSocketAdapter<
|
||||
CloudflareDurableAdapter,
|
||||
CloudflareOptions
|
||||
@@ -133,6 +134,8 @@ function peerFromDurableEvent(
|
||||
return peer;
|
||||
}
|
||||
|
||||
// --- peer ---
|
||||
|
||||
class CloudflareDurablePeer extends Peer<{
|
||||
peers?: never;
|
||||
cloudflare: {
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
// https://developers.cloudflare.com/workers/examples/websockets/
|
||||
import type { AdapterOptions, AdapterInstance } from "../adapter.ts";
|
||||
import { toBufferLike } from "../utils.ts";
|
||||
import { defineWebSocketAdapter, adapterUtils } from "../adapter.ts";
|
||||
import { AdapterHookable } from "../hooks.ts";
|
||||
import { Message } from "../message.ts";
|
||||
import { WSError } from "../error.ts";
|
||||
import { Peer } from "../peer.ts";
|
||||
|
||||
import type * as _cf from "@cloudflare/workers-types";
|
||||
|
||||
import { Peer } from "../peer";
|
||||
import {
|
||||
AdapterOptions,
|
||||
AdapterInstance,
|
||||
defineWebSocketAdapter,
|
||||
} from "../types.js";
|
||||
import { Message } from "../message";
|
||||
import { WSError } from "../error";
|
||||
import { AdapterHookable } from "../hooks.js";
|
||||
import { adapterUtils, toBufferLike } from "../_utils";
|
||||
// --- types ---
|
||||
|
||||
declare const WebSocketPair: typeof _cf.WebSocketPair;
|
||||
declare const Response: typeof _cf.Response;
|
||||
@@ -26,6 +23,9 @@ export interface CloudflareAdapter extends AdapterInstance {
|
||||
|
||||
export interface CloudflareOptions extends AdapterOptions {}
|
||||
|
||||
// --- adapter ---
|
||||
|
||||
// https://developers.cloudflare.com/workers/examples/websockets/
|
||||
export default defineWebSocketAdapter<CloudflareAdapter, CloudflareOptions>(
|
||||
(options = {}) => {
|
||||
const hooks = new AdapterHookable(options);
|
||||
@@ -76,6 +76,8 @@ export default defineWebSocketAdapter<CloudflareAdapter, CloudflareOptions>(
|
||||
},
|
||||
);
|
||||
|
||||
// --- peer ---
|
||||
|
||||
class CloudflarePeer extends Peer<{
|
||||
peers: Set<CloudflarePeer>;
|
||||
cloudflare: {
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
// https://deno.land/api?s=WebSocket
|
||||
// https://deno.land/api?s=Deno.upgradeWebSocket
|
||||
// https://examples.deno.land/http-server-websocket
|
||||
|
||||
import type { AdapterOptions, AdapterInstance } from "../adapter.ts";
|
||||
import { toBufferLike } from "../utils.ts";
|
||||
import { defineWebSocketAdapter, adapterUtils } from "../adapter.ts";
|
||||
import { AdapterHookable } from "../hooks.ts";
|
||||
import { Message } from "../message.ts";
|
||||
import { WSError } from "../error.ts";
|
||||
import { Peer } from "../peer.ts";
|
||||
import {
|
||||
AdapterOptions,
|
||||
AdapterInstance,
|
||||
defineWebSocketAdapter,
|
||||
} from "../types.ts";
|
||||
import { AdapterHookable } from "../hooks.ts";
|
||||
import { adapterUtils, toBufferLike } from "../_utils.ts";
|
||||
|
||||
// --- types ---
|
||||
|
||||
export interface DenoAdapter extends AdapterInstance {
|
||||
handleUpgrade(req: Request, info: ServeHandlerInfo): Promise<Response>;
|
||||
@@ -26,6 +21,11 @@ declare global {
|
||||
type WebSocketUpgrade = import("@deno/types").Deno.WebSocketUpgrade;
|
||||
type ServeHandlerInfo = unknown; // TODO
|
||||
|
||||
// --- adapter ---
|
||||
|
||||
// https://deno.land/api?s=WebSocket
|
||||
// https://deno.land/api?s=Deno.upgradeWebSocket
|
||||
// https://examples.deno.land/http-server-websocket
|
||||
export default defineWebSocketAdapter<DenoAdapter, DenoOptions>(
|
||||
(options = {}) => {
|
||||
const hooks = new AdapterHookable(options);
|
||||
@@ -70,6 +70,8 @@ export default defineWebSocketAdapter<DenoAdapter, DenoOptions>(
|
||||
},
|
||||
);
|
||||
|
||||
// --- peer ---
|
||||
|
||||
class DenoPeer extends Peer<{
|
||||
peers: Set<DenoPeer>;
|
||||
deno: {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
// https://github.com/websockets/ws
|
||||
// https://github.com/websockets/ws/blob/master/doc/ws.md
|
||||
import type { AdapterOptions, AdapterInstance } from "../adapter.ts";
|
||||
import { toBufferLike } from "../utils.ts";
|
||||
import { defineWebSocketAdapter, adapterUtils } from "../adapter.ts";
|
||||
import { AdapterHookable } from "../hooks.ts";
|
||||
import { Message } from "../message.ts";
|
||||
import { WSError } from "../error.ts";
|
||||
import { Peer } from "../peer.ts";
|
||||
|
||||
import type { ClientRequest, IncomingMessage } from "node:http";
|
||||
import type { Duplex } from "node:stream";
|
||||
@@ -10,16 +15,8 @@ import type {
|
||||
WebSocketServer,
|
||||
WebSocket as WebSocketT,
|
||||
} from "../../types/ws";
|
||||
import { Peer } from "../peer";
|
||||
import { Message } from "../message";
|
||||
import { WSError } from "../error";
|
||||
import {
|
||||
AdapterOptions,
|
||||
AdapterInstance,
|
||||
defineWebSocketAdapter,
|
||||
} from "../types";
|
||||
import { AdapterHookable } from "../hooks";
|
||||
import { adapterUtils, toBufferLike } from "../_utils";
|
||||
|
||||
// --- types ---
|
||||
|
||||
type AugmentedReq = IncomingMessage & { _upgradeHeaders?: HeadersInit };
|
||||
|
||||
@@ -33,6 +30,10 @@ export interface NodeOptions extends AdapterOptions {
|
||||
serverOptions?: ServerOptions;
|
||||
}
|
||||
|
||||
// --- adapter ---
|
||||
|
||||
// https://github.com/websockets/ws
|
||||
// https://github.com/websockets/ws/blob/master/doc/ws.md
|
||||
export default defineWebSocketAdapter<NodeAdapter, NodeOptions>(
|
||||
(options = {}) => {
|
||||
const hooks = new AdapterHookable(options);
|
||||
@@ -123,53 +124,7 @@ export default defineWebSocketAdapter<NodeAdapter, NodeOptions>(
|
||||
},
|
||||
);
|
||||
|
||||
class NodeReqProxy {
|
||||
_req: IncomingMessage;
|
||||
_headers?: Headers;
|
||||
_url?: string;
|
||||
|
||||
constructor(req: IncomingMessage) {
|
||||
this._req = req;
|
||||
}
|
||||
|
||||
get url(): string {
|
||||
if (!this._url) {
|
||||
const req = this._req;
|
||||
const host = req.headers["host"] || "localhost";
|
||||
const isSecure =
|
||||
(req.socket as any)?.encrypted ??
|
||||
req.headers["x-forwarded-proto"] === "https";
|
||||
this._url = `${isSecure ? "https" : "http"}://${host}${req.url}`;
|
||||
}
|
||||
return this._url;
|
||||
}
|
||||
|
||||
get headers(): Headers {
|
||||
if (!this._headers) {
|
||||
this._headers = new Headers(this._req.headers as HeadersInit);
|
||||
}
|
||||
return this._headers;
|
||||
}
|
||||
}
|
||||
|
||||
async function sendResponse(socket: Duplex, res: Response) {
|
||||
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<void>((resolve) => {
|
||||
socket.end(resolve);
|
||||
});
|
||||
}
|
||||
// --- peer ---
|
||||
|
||||
class NodePeer extends Peer<{
|
||||
peers: Set<NodePeer>;
|
||||
@@ -246,3 +201,53 @@ class NodePeer extends Peer<{
|
||||
this._internal.node.ws.terminate();
|
||||
}
|
||||
}
|
||||
|
||||
// --- web compat ---
|
||||
|
||||
class NodeReqProxy {
|
||||
_req: IncomingMessage;
|
||||
_headers?: Headers;
|
||||
_url?: string;
|
||||
|
||||
constructor(req: IncomingMessage) {
|
||||
this._req = req;
|
||||
}
|
||||
|
||||
get url(): string {
|
||||
if (!this._url) {
|
||||
const req = this._req;
|
||||
const host = req.headers["host"] || "localhost";
|
||||
const isSecure =
|
||||
(req.socket as any)?.encrypted ??
|
||||
req.headers["x-forwarded-proto"] === "https";
|
||||
this._url = `${isSecure ? "https" : "http"}://${host}${req.url}`;
|
||||
}
|
||||
return this._url;
|
||||
}
|
||||
|
||||
get headers(): Headers {
|
||||
if (!this._headers) {
|
||||
this._headers = new Headers(this._req.headers as HeadersInit);
|
||||
}
|
||||
return this._headers;
|
||||
}
|
||||
}
|
||||
|
||||
async function sendResponse(socket: Duplex, res: Response) {
|
||||
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<void>((resolve) => {
|
||||
socket.end(resolve);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events
|
||||
import type { AdapterOptions, AdapterInstance } from "../adapter.ts";
|
||||
import { toBufferLike } from "../utils.ts";
|
||||
import { defineWebSocketAdapter, adapterUtils } from "../adapter.ts";
|
||||
import { AdapterHookable } from "../hooks.ts";
|
||||
import { Peer } from "../peer.ts";
|
||||
|
||||
import { WebSocketServer as _WebSocketServer } from "ws";
|
||||
import { Peer } from "../peer";
|
||||
import {
|
||||
AdapterOptions,
|
||||
AdapterInstance,
|
||||
defineWebSocketAdapter,
|
||||
} from "../types";
|
||||
import { AdapterHookable } from "../hooks";
|
||||
import { adapterUtils, toBufferLike } from "../_utils";
|
||||
// --- types ---
|
||||
|
||||
export interface SSEAdapter extends AdapterInstance {
|
||||
fetch(req: Request): Promise<Response>;
|
||||
@@ -16,6 +12,9 @@ export interface SSEAdapter extends AdapterInstance {
|
||||
|
||||
export interface SSEOptions extends AdapterOptions {}
|
||||
|
||||
// --- adapter ---
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events
|
||||
export default defineWebSocketAdapter<SSEAdapter, SSEOptions>(
|
||||
(options = {}) => {
|
||||
const hooks = new AdapterHookable(options);
|
||||
@@ -49,6 +48,8 @@ export default defineWebSocketAdapter<SSEAdapter, SSEOptions>(
|
||||
},
|
||||
);
|
||||
|
||||
// --- peer ---
|
||||
|
||||
class SSEPeer extends Peer<{
|
||||
peers: Set<SSEPeer>;
|
||||
sse: {
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
// https://github.com/websockets/ws
|
||||
// https://github.com/websockets/ws/blob/master/doc/ws.md
|
||||
import type { AdapterOptions, AdapterInstance } from "../adapter.ts";
|
||||
import { toBufferLike } from "../utils.ts";
|
||||
import { defineWebSocketAdapter, adapterUtils } from "../adapter.ts";
|
||||
import { AdapterHookable } from "../hooks.ts";
|
||||
import { Message } from "../message.ts";
|
||||
import { Peer } from "../peer.ts";
|
||||
|
||||
import type {
|
||||
WebSocketBehavior,
|
||||
@@ -8,15 +12,8 @@ import type {
|
||||
HttpResponse,
|
||||
RecognizedString,
|
||||
} from "uWebSockets.js";
|
||||
import { Peer } from "../peer";
|
||||
import { Message } from "../message";
|
||||
import {
|
||||
AdapterOptions,
|
||||
AdapterInstance,
|
||||
defineWebSocketAdapter,
|
||||
} from "../types";
|
||||
import { AdapterHookable } from "../hooks";
|
||||
import { adapterUtils, toBufferLike } from "../_utils";
|
||||
|
||||
// --- types ---
|
||||
|
||||
type UserData = {
|
||||
_peer?: any;
|
||||
@@ -45,6 +42,10 @@ export interface UWSOptions extends AdapterOptions {
|
||||
>;
|
||||
}
|
||||
|
||||
// --- adapter ---
|
||||
|
||||
// https://github.com/websockets/ws
|
||||
// https://github.com/websockets/ws/blob/master/doc/ws.md
|
||||
export default defineWebSocketAdapter<UWSAdapter, UWSOptions>(
|
||||
(options = {}) => {
|
||||
const hooks = new AdapterHookable(options);
|
||||
@@ -149,40 +150,7 @@ export default defineWebSocketAdapter<UWSAdapter, UWSOptions>(
|
||||
},
|
||||
);
|
||||
|
||||
class UWSReqProxy {
|
||||
private _headers?: Headers;
|
||||
private _rawHeaders: [string, string][] = [];
|
||||
url: string;
|
||||
|
||||
constructor(_req: HttpRequest) {
|
||||
// We need to precompute values since uws doesn't provide them after handler.
|
||||
|
||||
// Headers
|
||||
let host = "localhost";
|
||||
let proto = "http";
|
||||
// eslint-disable-next-line unicorn/no-array-for-each
|
||||
_req.forEach((key, value) => {
|
||||
if (key === "host") {
|
||||
host = value;
|
||||
} else if (key === "x-forwarded-proto" && value === "https") {
|
||||
proto = "https";
|
||||
}
|
||||
this._rawHeaders.push([key, value]);
|
||||
});
|
||||
|
||||
// URL
|
||||
const query = _req.getQuery();
|
||||
const pathname = _req.getUrl();
|
||||
this.url = `${proto}://${host}${pathname}${query ? `?${query}` : ""}`;
|
||||
}
|
||||
|
||||
get headers(): Headers {
|
||||
if (!this._headers) {
|
||||
this._headers = new Headers(this._rawHeaders);
|
||||
}
|
||||
return this._headers;
|
||||
}
|
||||
}
|
||||
// --- peer ---
|
||||
|
||||
function getPeer(ws: WebSocket<UserData>, peers: Set<UWSPeer>): UWSPeer {
|
||||
const userData = ws.getUserData();
|
||||
@@ -253,3 +221,40 @@ class UWSPeer extends Peer<{
|
||||
this._internal.uws.ws.close();
|
||||
}
|
||||
}
|
||||
|
||||
// --- web compat ---
|
||||
|
||||
class UWSReqProxy {
|
||||
private _headers?: Headers;
|
||||
private _rawHeaders: [string, string][] = [];
|
||||
url: string;
|
||||
|
||||
constructor(_req: HttpRequest) {
|
||||
// We need to precompute values since uws doesn't provide them after handler.
|
||||
|
||||
// Headers
|
||||
let host = "localhost";
|
||||
let proto = "http";
|
||||
// eslint-disable-next-line unicorn/no-array-for-each
|
||||
_req.forEach((key, value) => {
|
||||
if (key === "host") {
|
||||
host = value;
|
||||
} else if (key === "x-forwarded-proto" && value === "https") {
|
||||
proto = "https";
|
||||
}
|
||||
this._rawHeaders.push([key, value]);
|
||||
});
|
||||
|
||||
// URL
|
||||
const query = _req.getQuery();
|
||||
const pathname = _req.getUrl();
|
||||
this.url = `${proto}://${host}${pathname}${query ? `?${query}` : ""}`;
|
||||
}
|
||||
|
||||
get headers(): Headers {
|
||||
if (!this._headers) {
|
||||
this._headers = new Headers(this._rawHeaders);
|
||||
}
|
||||
return this._headers;
|
||||
}
|
||||
}
|
||||
|
||||
101
src/hooks.ts
101
src/hooks.ts
@@ -1,9 +1,7 @@
|
||||
import type {
|
||||
AdapterHooks,
|
||||
AdapterOptions,
|
||||
Hooks,
|
||||
MaybePromise,
|
||||
} from "./types.ts";
|
||||
import type { AdapterOptions } from "./adapter.ts";
|
||||
import type { WSError } from "./error.ts";
|
||||
import type { Peer } from "./peer.ts";
|
||||
import type { Message } from "./message.ts";
|
||||
|
||||
export class AdapterHookable {
|
||||
options: AdapterOptions;
|
||||
@@ -49,3 +47,94 @@ export class AdapterHookable {
|
||||
return this.options.adapterHooks?.[name]?.apply(undefined, args);
|
||||
}
|
||||
}
|
||||
|
||||
// --- types ---
|
||||
|
||||
export function defineHooks<T extends Partial<Hooks> = Partial<Hooks>>(
|
||||
hooks: T,
|
||||
): T {
|
||||
return hooks;
|
||||
}
|
||||
|
||||
export type ResolveHooks = (
|
||||
info: RequestInit | Peer,
|
||||
) => Partial<Hooks> | Promise<Partial<Hooks>>;
|
||||
|
||||
export type MaybePromise<T> = T | Promise<T>;
|
||||
|
||||
type HookFn<ArgsT extends any[] = any, RT = void> = (
|
||||
info: Peer,
|
||||
...args: ArgsT
|
||||
) => MaybePromise<RT>;
|
||||
|
||||
export interface Hooks {
|
||||
/** Upgrading */
|
||||
upgrade: (
|
||||
request:
|
||||
| Request
|
||||
| {
|
||||
url: string;
|
||||
headers: Headers;
|
||||
},
|
||||
) => MaybePromise<Response | ResponseInit | void>;
|
||||
|
||||
/** A message is received */
|
||||
message: (peer: Peer, message: Message) => MaybePromise<void>;
|
||||
|
||||
/** A socket is opened */
|
||||
open: (peer: Peer) => MaybePromise<void>;
|
||||
|
||||
/** A socket is closed */
|
||||
close: (
|
||||
peer: Peer,
|
||||
details: { code?: number; reason?: string },
|
||||
) => MaybePromise<void>;
|
||||
|
||||
/** An error occurs */
|
||||
error: (peer: Peer, error: WSError) => MaybePromise<void>;
|
||||
}
|
||||
|
||||
export interface AdapterHooks extends Record<string, HookFn<any[], any>> {
|
||||
// Bun
|
||||
"bun:message": HookFn<[ws: any, message: any]>;
|
||||
"bun:open": HookFn<[ws: any]>;
|
||||
"bun:close": HookFn<[ws: any]>;
|
||||
"bun:drain": HookFn<[]>;
|
||||
"bun:error": HookFn<[ws: any, error: any]>;
|
||||
"bun:ping": HookFn<[ws: any, data: any]>;
|
||||
"bun:pong": HookFn<[ws: any, data: any]>;
|
||||
|
||||
// Cloudflare
|
||||
"cloudflare:accept": HookFn<[]>;
|
||||
"cloudflare:message": HookFn<[event: any]>;
|
||||
"cloudflare:error": HookFn<[event: any]>;
|
||||
"cloudflare:close": HookFn<[event: any]>;
|
||||
|
||||
// Deno
|
||||
"deno:open": HookFn<[]>;
|
||||
"deno:message": HookFn<[event: any]>;
|
||||
"deno:close": HookFn<[]>;
|
||||
"deno:error": HookFn<[error: any]>;
|
||||
|
||||
// ws (Node)
|
||||
"node:open": HookFn<[]>;
|
||||
"node:message": HookFn<[data: any, isBinary: boolean]>;
|
||||
"node:close": HookFn<[code: number, reason: Buffer]>;
|
||||
"node:error": HookFn<[error: any]>;
|
||||
"node:ping": HookFn<[data: Buffer]>;
|
||||
"node:pong": HookFn<[data: Buffer]>;
|
||||
"node:unexpected-response": HookFn<[req: any, res: any]>;
|
||||
"node:upgrade": HookFn<[req: any]>;
|
||||
|
||||
// uws (Node)
|
||||
"uws:open": HookFn<[ws: any]>;
|
||||
"uws:message": HookFn<[ws: any, message: any, isBinary: boolean]>;
|
||||
"uws:close": HookFn<[ws: any, code: number, message: any]>;
|
||||
"uws:ping": HookFn<[ws: any, message: any]>;
|
||||
"uws:pong": HookFn<[ws: any, message: any]>;
|
||||
"uws:drain": HookFn<[ws: any]>;
|
||||
"uws:upgrade": HookFn<[res: any, req: any, context: any]>;
|
||||
"uws:subscription": HookFn<
|
||||
[ws: any, topic: any, newCount: number, oldCount: number]
|
||||
>;
|
||||
}
|
||||
|
||||
23
src/index.ts
23
src/index.ts
@@ -1,5 +1,18 @@
|
||||
export * from "./hooks.ts";
|
||||
export * from "./error.ts";
|
||||
export * from "./message.ts";
|
||||
export * from "./peer.ts";
|
||||
export * from "./types.ts";
|
||||
// Hooks
|
||||
export { defineHooks } from "./hooks.ts";
|
||||
export type { Hooks, AdapterHooks, ResolveHooks } from "./hooks.ts";
|
||||
|
||||
// Adapter
|
||||
export { defineWebSocketAdapter } from "./adapter.ts";
|
||||
export type { Adapter, AdapterInstance, AdapterOptions } from "./adapter.ts";
|
||||
|
||||
// Message
|
||||
export type { Message } from "./message.ts";
|
||||
|
||||
// Peer
|
||||
export type { Peer } from "./peer.ts";
|
||||
|
||||
// Error
|
||||
export type { WSError } from "./error.ts";
|
||||
|
||||
// Removed from 0.2.x: createCrossWS, Caller, WSRequest, CrossWS
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { toBufferLike } from "./_utils.ts";
|
||||
import { toBufferLike } from "./utils.ts";
|
||||
|
||||
export class Message {
|
||||
constructor(
|
||||
|
||||
119
src/types.ts
119
src/types.ts
@@ -1,119 +0,0 @@
|
||||
import { WSError } from "./error.ts";
|
||||
import type { Message } from "./message.ts";
|
||||
import type { Peer } from "./peer.ts";
|
||||
|
||||
// --- Adapter ---
|
||||
|
||||
export interface AdapterInstance {
|
||||
readonly peers: Set<Peer>;
|
||||
readonly publish: Peer["publish"];
|
||||
}
|
||||
|
||||
export interface AdapterOptions {
|
||||
resolve?: ResolveHooks;
|
||||
hooks?: Hooks;
|
||||
adapterHooks?: AdapterHooks;
|
||||
}
|
||||
|
||||
export type Adapter<
|
||||
AdapterT extends AdapterInstance = AdapterInstance,
|
||||
Options extends AdapterOptions = AdapterOptions,
|
||||
> = (options?: Options) => AdapterT;
|
||||
|
||||
export function defineWebSocketAdapter<
|
||||
AdapterT extends AdapterInstance = AdapterInstance,
|
||||
Options extends AdapterOptions = AdapterOptions,
|
||||
>(factory: Adapter<AdapterT, Options>) {
|
||||
return factory;
|
||||
}
|
||||
|
||||
// --- Hooks ---
|
||||
|
||||
export function defineHooks<T extends Partial<Hooks> = Partial<Hooks>>(
|
||||
hooks: T,
|
||||
): T {
|
||||
return hooks;
|
||||
}
|
||||
|
||||
export type ResolveHooks = (
|
||||
info: RequestInit | Peer,
|
||||
) => Partial<Hooks> | Promise<Partial<Hooks>>;
|
||||
|
||||
export type MaybePromise<T> = T | Promise<T>;
|
||||
|
||||
type HookFn<ArgsT extends any[] = any, RT = void> = (
|
||||
info: Peer,
|
||||
...args: ArgsT
|
||||
) => MaybePromise<RT>;
|
||||
|
||||
export interface Hooks {
|
||||
/** Upgrading */
|
||||
upgrade: (
|
||||
request:
|
||||
| Request
|
||||
| {
|
||||
url: string;
|
||||
headers: Headers;
|
||||
},
|
||||
) => MaybePromise<Response | ResponseInit | void>;
|
||||
|
||||
/** A message is received */
|
||||
message: (peer: Peer, message: Message) => MaybePromise<void>;
|
||||
|
||||
/** A socket is opened */
|
||||
open: (peer: Peer) => MaybePromise<void>;
|
||||
|
||||
/** A socket is closed */
|
||||
close: (
|
||||
peer: Peer,
|
||||
details: { code?: number; reason?: string },
|
||||
) => MaybePromise<void>;
|
||||
|
||||
/** An error occurs */
|
||||
error: (peer: Peer, error: WSError) => MaybePromise<void>;
|
||||
}
|
||||
|
||||
export interface AdapterHooks extends Record<string, HookFn<any[], any>> {
|
||||
// Bun
|
||||
"bun:message": HookFn<[ws: any, message: any]>;
|
||||
"bun:open": HookFn<[ws: any]>;
|
||||
"bun:close": HookFn<[ws: any]>;
|
||||
"bun:drain": HookFn<[]>;
|
||||
"bun:error": HookFn<[ws: any, error: any]>;
|
||||
"bun:ping": HookFn<[ws: any, data: any]>;
|
||||
"bun:pong": HookFn<[ws: any, data: any]>;
|
||||
|
||||
// Cloudflare
|
||||
"cloudflare:accept": HookFn<[]>;
|
||||
"cloudflare:message": HookFn<[event: any]>;
|
||||
"cloudflare:error": HookFn<[event: any]>;
|
||||
"cloudflare:close": HookFn<[event: any]>;
|
||||
|
||||
// Deno
|
||||
"deno:open": HookFn<[]>;
|
||||
"deno:message": HookFn<[event: any]>;
|
||||
"deno:close": HookFn<[]>;
|
||||
"deno:error": HookFn<[error: any]>;
|
||||
|
||||
// ws (Node)
|
||||
"node:open": HookFn<[]>;
|
||||
"node:message": HookFn<[data: any, isBinary: boolean]>;
|
||||
"node:close": HookFn<[code: number, reason: Buffer]>;
|
||||
"node:error": HookFn<[error: any]>;
|
||||
"node:ping": HookFn<[data: Buffer]>;
|
||||
"node:pong": HookFn<[data: Buffer]>;
|
||||
"node:unexpected-response": HookFn<[req: any, res: any]>;
|
||||
"node:upgrade": HookFn<[req: any]>;
|
||||
|
||||
// uws (Node)
|
||||
"uws:open": HookFn<[ws: any]>;
|
||||
"uws:message": HookFn<[ws: any, message: any, isBinary: boolean]>;
|
||||
"uws:close": HookFn<[ws: any, code: number, message: any]>;
|
||||
"uws:ping": HookFn<[ws: any, message: any]>;
|
||||
"uws:pong": HookFn<[ws: any, message: any]>;
|
||||
"uws:drain": HookFn<[ws: any]>;
|
||||
"uws:upgrade": HookFn<[res: any, req: any, context: any]>;
|
||||
"uws:subscription": HookFn<
|
||||
[ws: any, topic: any, newCount: number, oldCount: number]
|
||||
>;
|
||||
}
|
||||
@@ -1,6 +1,3 @@
|
||||
import type { Peer } from "./peer.ts";
|
||||
import { AdapterInstance } from "./types.ts";
|
||||
|
||||
type BufferLike = string | Buffer | Uint8Array | ArrayBuffer;
|
||||
|
||||
export function toBufferLike(val: any): BufferLike {
|
||||
@@ -60,16 +57,3 @@ export function isPlainObject(value: unknown): boolean {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function adapterUtils(peers: Set<Peer>) {
|
||||
return {
|
||||
peers,
|
||||
publish(topic: string, message: any, options) {
|
||||
const firstPeer = peers.values().next().value as Peer;
|
||||
if (firstPeer) {
|
||||
firstPeer.send(message, options);
|
||||
firstPeer.publish(topic, message, options);
|
||||
}
|
||||
},
|
||||
} satisfies AdapterInstance;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { test, expect } from "vitest";
|
||||
import { toBufferLike } from "../src/_utils";
|
||||
import { toBufferLike } from "../src/utils";
|
||||
|
||||
test("toBufferLike", () => {
|
||||
expect(toBufferLike(undefined)).toBe("");
|
||||
|
||||
Reference in New Issue
Block a user