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 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>,
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
101
src/hooks.ts
101
src/hooks.ts
@@ -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]
|
||||||
|
>;
|
||||||
|
}
|
||||||
|
|||||||
23
src/index.ts
23
src/index.ts
@@ -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
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { toBufferLike } from "./_utils.ts";
|
import { toBufferLike } from "./utils.ts";
|
||||||
|
|
||||||
export class Message {
|
export class Message {
|
||||||
constructor(
|
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;
|
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;
|
|
||||||
}
|
|
||||||
@@ -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("");
|
||||||
|
|||||||
Reference in New Issue
Block a user