refactor: better internal organization

This commit is contained in:
Pooya Parsa
2024-08-06 20:49:15 +02:00
parent a96dca352b
commit 2744f21f7d
14 changed files with 333 additions and 305 deletions

40
src/adapter.ts Normal file
View 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;
}

View File

@@ -1,15 +1,13 @@
// https://bun.sh/docs/api/websockets
import type { WebSocketHandler, ServerWebSocket, Server } from "bun"; import type { WebSocketHandler, ServerWebSocket, Server } from "bun";
import { Message } from "../message";
import { Peer } from "../peer"; import type { AdapterOptions, AdapterInstance } from "../adapter.ts";
import { import { toBufferLike } from "../utils.ts";
AdapterOptions, import { defineWebSocketAdapter, adapterUtils } from "../adapter.ts";
AdapterInstance, import { AdapterHookable } from "../hooks.ts";
defineWebSocketAdapter, import { Message } from "../message.ts";
} from "../types"; import { Peer } from "../peer.ts";
import { AdapterHookable } from "../hooks";
import { adapterUtils, toBufferLike } from "../_utils"; // --- types ---
export interface BunAdapter extends AdapterInstance { export interface BunAdapter extends AdapterInstance {
websocket: WebSocketHandler<ContextData>; websocket: WebSocketHandler<ContextData>;
@@ -25,6 +23,9 @@ type ContextData = {
server?: Server; server?: Server;
}; };
// --- adapter ---
// https://bun.sh/docs/api/websockets
export default defineWebSocketAdapter<BunAdapter, BunOptions>( export default defineWebSocketAdapter<BunAdapter, BunOptions>(
(options = {}) => { (options = {}) => {
const hooks = new AdapterHookable(options); const hooks = new AdapterHookable(options);
@@ -82,6 +83,8 @@ export default defineWebSocketAdapter<BunAdapter, BunOptions>(
}, },
); );
// --- peer ---
function getPeer( function getPeer(
ws: ServerWebSocket<ContextData>, ws: ServerWebSocket<ContextData>,
peers: Set<BunPeer>, peers: Set<BunPeer>,

View File

@@ -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 * as CF from "@cloudflare/workers-types";
import type { DurableObject } from "cloudflare:workers"; import type { DurableObject } from "cloudflare:workers";
import {
AdapterOptions, // --- types
AdapterInstance,
defineWebSocketAdapter,
} from "../types";
import { Peer } from "../peer";
import { Message } from "../message";
import { AdapterHookable } from "../hooks";
import { adapterUtils, toBufferLike } from "../_utils";
declare class DurableObjectPub extends DurableObject { declare class DurableObjectPub extends DurableObject {
public ctx: DurableObject["ctx"]; public ctx: DurableObject["ctx"];
@@ -58,6 +56,9 @@ export interface CloudflareOptions extends AdapterOptions {
instanceName?: string; instanceName?: string;
} }
// --- adapter ---
// https://developers.cloudflare.com/durable-objects/examples/websocket-hibernation-server/
export default defineWebSocketAdapter< export default defineWebSocketAdapter<
CloudflareDurableAdapter, CloudflareDurableAdapter,
CloudflareOptions CloudflareOptions
@@ -133,6 +134,8 @@ function peerFromDurableEvent(
return peer; return peer;
} }
// --- peer ---
class CloudflareDurablePeer extends Peer<{ class CloudflareDurablePeer extends Peer<{
peers?: never; peers?: never;
cloudflare: { cloudflare: {

View File

@@ -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 type * as _cf from "@cloudflare/workers-types";
import { Peer } from "../peer"; // --- types ---
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";
declare const WebSocketPair: typeof _cf.WebSocketPair; declare const WebSocketPair: typeof _cf.WebSocketPair;
declare const Response: typeof _cf.Response; declare const Response: typeof _cf.Response;
@@ -26,6 +23,9 @@ export interface CloudflareAdapter extends AdapterInstance {
export interface CloudflareOptions extends AdapterOptions {} export interface CloudflareOptions extends AdapterOptions {}
// --- adapter ---
// https://developers.cloudflare.com/workers/examples/websockets/
export default defineWebSocketAdapter<CloudflareAdapter, CloudflareOptions>( export default defineWebSocketAdapter<CloudflareAdapter, CloudflareOptions>(
(options = {}) => { (options = {}) => {
const hooks = new AdapterHookable(options); const hooks = new AdapterHookable(options);
@@ -76,6 +76,8 @@ export default defineWebSocketAdapter<CloudflareAdapter, CloudflareOptions>(
}, },
); );
// --- peer ---
class CloudflarePeer extends Peer<{ class CloudflarePeer extends Peer<{
peers: Set<CloudflarePeer>; peers: Set<CloudflarePeer>;
cloudflare: { cloudflare: {

View File

@@ -1,17 +1,12 @@
// https://deno.land/api?s=WebSocket import type { AdapterOptions, AdapterInstance } from "../adapter.ts";
// https://deno.land/api?s=Deno.upgradeWebSocket import { toBufferLike } from "../utils.ts";
// https://examples.deno.land/http-server-websocket import { defineWebSocketAdapter, adapterUtils } from "../adapter.ts";
import { AdapterHookable } from "../hooks.ts";
import { Message } from "../message.ts"; import { Message } from "../message.ts";
import { WSError } from "../error.ts"; import { WSError } from "../error.ts";
import { Peer } from "../peer.ts"; import { Peer } from "../peer.ts";
import {
AdapterOptions, // --- types ---
AdapterInstance,
defineWebSocketAdapter,
} from "../types.ts";
import { AdapterHookable } from "../hooks.ts";
import { adapterUtils, toBufferLike } from "../_utils.ts";
export interface DenoAdapter extends AdapterInstance { export interface DenoAdapter extends AdapterInstance {
handleUpgrade(req: Request, info: ServeHandlerInfo): Promise<Response>; handleUpgrade(req: Request, info: ServeHandlerInfo): Promise<Response>;
@@ -26,6 +21,11 @@ declare global {
type WebSocketUpgrade = import("@deno/types").Deno.WebSocketUpgrade; type WebSocketUpgrade = import("@deno/types").Deno.WebSocketUpgrade;
type ServeHandlerInfo = unknown; // TODO 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>( export default defineWebSocketAdapter<DenoAdapter, DenoOptions>(
(options = {}) => { (options = {}) => {
const hooks = new AdapterHookable(options); const hooks = new AdapterHookable(options);
@@ -70,6 +70,8 @@ export default defineWebSocketAdapter<DenoAdapter, DenoOptions>(
}, },
); );
// --- peer ---
class DenoPeer extends Peer<{ class DenoPeer extends Peer<{
peers: Set<DenoPeer>; peers: Set<DenoPeer>;
deno: { deno: {

View File

@@ -1,5 +1,10 @@
// https://github.com/websockets/ws import type { AdapterOptions, AdapterInstance } from "../adapter.ts";
// https://github.com/websockets/ws/blob/master/doc/ws.md 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 { ClientRequest, IncomingMessage } from "node:http";
import type { Duplex } from "node:stream"; import type { Duplex } from "node:stream";
@@ -10,16 +15,8 @@ import type {
WebSocketServer, WebSocketServer,
WebSocket as WebSocketT, WebSocket as WebSocketT,
} from "../../types/ws"; } from "../../types/ws";
import { Peer } from "../peer";
import { Message } from "../message"; // --- types ---
import { WSError } from "../error";
import {
AdapterOptions,
AdapterInstance,
defineWebSocketAdapter,
} from "../types";
import { AdapterHookable } from "../hooks";
import { adapterUtils, toBufferLike } from "../_utils";
type AugmentedReq = IncomingMessage & { _upgradeHeaders?: HeadersInit }; type AugmentedReq = IncomingMessage & { _upgradeHeaders?: HeadersInit };
@@ -33,6 +30,10 @@ export interface NodeOptions extends AdapterOptions {
serverOptions?: ServerOptions; serverOptions?: ServerOptions;
} }
// --- adapter ---
// https://github.com/websockets/ws
// https://github.com/websockets/ws/blob/master/doc/ws.md
export default defineWebSocketAdapter<NodeAdapter, NodeOptions>( export default defineWebSocketAdapter<NodeAdapter, NodeOptions>(
(options = {}) => { (options = {}) => {
const hooks = new AdapterHookable(options); const hooks = new AdapterHookable(options);
@@ -123,53 +124,7 @@ export default defineWebSocketAdapter<NodeAdapter, NodeOptions>(
}, },
); );
class NodeReqProxy { // --- peer ---
_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);
});
}
class NodePeer extends Peer<{ class NodePeer extends Peer<{
peers: Set<NodePeer>; peers: Set<NodePeer>;
@@ -246,3 +201,53 @@ class NodePeer extends Peer<{
this._internal.node.ws.terminate(); 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);
});
}

View File

@@ -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"; // --- types ---
import { Peer } from "../peer";
import {
AdapterOptions,
AdapterInstance,
defineWebSocketAdapter,
} from "../types";
import { AdapterHookable } from "../hooks";
import { adapterUtils, toBufferLike } from "../_utils";
export interface SSEAdapter extends AdapterInstance { export interface SSEAdapter extends AdapterInstance {
fetch(req: Request): Promise<Response>; fetch(req: Request): Promise<Response>;
@@ -16,6 +12,9 @@ export interface SSEAdapter extends AdapterInstance {
export interface SSEOptions extends AdapterOptions {} export interface SSEOptions extends AdapterOptions {}
// --- adapter ---
// https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events
export default defineWebSocketAdapter<SSEAdapter, SSEOptions>( export default defineWebSocketAdapter<SSEAdapter, SSEOptions>(
(options = {}) => { (options = {}) => {
const hooks = new AdapterHookable(options); const hooks = new AdapterHookable(options);
@@ -49,6 +48,8 @@ export default defineWebSocketAdapter<SSEAdapter, SSEOptions>(
}, },
); );
// --- peer ---
class SSEPeer extends Peer<{ class SSEPeer extends Peer<{
peers: Set<SSEPeer>; peers: Set<SSEPeer>;
sse: { sse: {

View File

@@ -1,5 +1,9 @@
// https://github.com/websockets/ws import type { AdapterOptions, AdapterInstance } from "../adapter.ts";
// https://github.com/websockets/ws/blob/master/doc/ws.md 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 { import type {
WebSocketBehavior, WebSocketBehavior,
@@ -8,15 +12,8 @@ import type {
HttpResponse, HttpResponse,
RecognizedString, RecognizedString,
} from "uWebSockets.js"; } from "uWebSockets.js";
import { Peer } from "../peer";
import { Message } from "../message"; // --- types ---
import {
AdapterOptions,
AdapterInstance,
defineWebSocketAdapter,
} from "../types";
import { AdapterHookable } from "../hooks";
import { adapterUtils, toBufferLike } from "../_utils";
type UserData = { type UserData = {
_peer?: any; _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>( export default defineWebSocketAdapter<UWSAdapter, UWSOptions>(
(options = {}) => { (options = {}) => {
const hooks = new AdapterHookable(options); const hooks = new AdapterHookable(options);
@@ -149,40 +150,7 @@ export default defineWebSocketAdapter<UWSAdapter, UWSOptions>(
}, },
); );
class UWSReqProxy { // --- peer ---
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;
}
}
function getPeer(ws: WebSocket<UserData>, peers: Set<UWSPeer>): UWSPeer { function getPeer(ws: WebSocket<UserData>, peers: Set<UWSPeer>): UWSPeer {
const userData = ws.getUserData(); const userData = ws.getUserData();
@@ -253,3 +221,40 @@ class UWSPeer extends Peer<{
this._internal.uws.ws.close(); 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;
}
}

View File

@@ -1,9 +1,7 @@
import type { import type { AdapterOptions } from "./adapter.ts";
AdapterHooks, import type { WSError } from "./error.ts";
AdapterOptions, import type { Peer } from "./peer.ts";
Hooks, import type { Message } from "./message.ts";
MaybePromise,
} from "./types.ts";
export class AdapterHookable { export class AdapterHookable {
options: AdapterOptions; options: AdapterOptions;
@@ -49,3 +47,94 @@ export class AdapterHookable {
return this.options.adapterHooks?.[name]?.apply(undefined, args); 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]
>;
}

View File

@@ -1,5 +1,18 @@
export * from "./hooks.ts"; // Hooks
export * from "./error.ts"; export { defineHooks } from "./hooks.ts";
export * from "./message.ts"; export type { Hooks, AdapterHooks, ResolveHooks } from "./hooks.ts";
export * from "./peer.ts";
export * from "./types.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

View File

@@ -1,4 +1,4 @@
import { toBufferLike } from "./_utils.ts"; import { toBufferLike } from "./utils.ts";
export class Message { export class Message {
constructor( constructor(

View File

@@ -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]
>;
}

View File

@@ -1,6 +1,3 @@
import type { Peer } from "./peer.ts";
import { AdapterInstance } from "./types.ts";
type BufferLike = string | Buffer | Uint8Array | ArrayBuffer; type BufferLike = string | Buffer | Uint8Array | ArrayBuffer;
export function toBufferLike(val: any): BufferLike { export function toBufferLike(val: any): BufferLike {
@@ -60,16 +57,3 @@ export function isPlainObject(value: unknown): boolean {
return true; 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;
}

View File

@@ -1,5 +1,5 @@
import { test, expect } from "vitest"; import { test, expect } from "vitest";
import { toBufferLike } from "../src/_utils"; import { toBufferLike } from "../src/utils";
test("toBufferLike", () => { test("toBufferLike", () => {
expect(toBufferLike(undefined)).toBe(""); expect(toBufferLike(undefined)).toBe("");