@rvncom/socket-bun-engine 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +148 -0
- package/dist/cors.d.ts +11 -0
- package/dist/cors.js +83 -0
- package/dist/event-emitter.d.ts +105 -0
- package/dist/event-emitter.js +129 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/parser.d.ts +14 -0
- package/dist/parser.js +73 -0
- package/dist/server.d.ts +146 -0
- package/dist/server.js +348 -0
- package/dist/socket.d.ts +123 -0
- package/dist/socket.js +281 -0
- package/dist/transport.d.ts +64 -0
- package/dist/transport.js +59 -0
- package/dist/transports/polling.d.ts +32 -0
- package/dist/transports/polling.js +113 -0
- package/dist/transports/websocket.d.ts +16 -0
- package/dist/transports/websocket.js +38 -0
- package/dist/util.d.ts +1 -0
- package/dist/util.js +4 -0
- package/package.json +36 -0
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { EventEmitter } from "./event-emitter";
|
|
2
|
+
import { Socket } from "./socket";
|
|
3
|
+
import { type BunWebSocket, type WebSocketData } from "./transports/websocket";
|
|
4
|
+
import { type CorsOptions } from "./cors";
|
|
5
|
+
import type { RawData } from "./parser";
|
|
6
|
+
export interface ServerOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Name of the request path to handle
|
|
9
|
+
* @default "/engine.io/"
|
|
10
|
+
*/
|
|
11
|
+
path: string;
|
|
12
|
+
/**
|
|
13
|
+
* Duration in milliseconds without a pong packet to consider the connection closed
|
|
14
|
+
* @default 20000
|
|
15
|
+
*/
|
|
16
|
+
pingTimeout: number;
|
|
17
|
+
/**
|
|
18
|
+
* Duration in milliseconds before sending a new ping packet
|
|
19
|
+
* @default 25000
|
|
20
|
+
*/
|
|
21
|
+
pingInterval: number;
|
|
22
|
+
/**
|
|
23
|
+
* Duration in milliseconds before an uncompleted transport upgrade is cancelled
|
|
24
|
+
* @default 10000
|
|
25
|
+
*/
|
|
26
|
+
upgradeTimeout: number;
|
|
27
|
+
/**
|
|
28
|
+
* Maximum size in bytes or number of characters a message can be, before closing the session (to avoid DoS).
|
|
29
|
+
* @default 1e6 (1 MB)
|
|
30
|
+
*/
|
|
31
|
+
maxHttpBufferSize: number;
|
|
32
|
+
/**
|
|
33
|
+
* Maximum number of concurrent clients. Set to 0 for unlimited.
|
|
34
|
+
* @default 0
|
|
35
|
+
*/
|
|
36
|
+
maxClients: number;
|
|
37
|
+
/**
|
|
38
|
+
* A function that receives a given handshake or upgrade request as its first parameter,
|
|
39
|
+
* and can decide whether to continue or not.
|
|
40
|
+
*/
|
|
41
|
+
allowRequest?: (req: Request, server: Bun.Server<WebSocketData>) => Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* The options related to Cross-Origin Resource Sharing (CORS)
|
|
44
|
+
*/
|
|
45
|
+
cors?: CorsOptions;
|
|
46
|
+
/**
|
|
47
|
+
* A function that allows to edit the response headers of the handshake request
|
|
48
|
+
*/
|
|
49
|
+
editHandshakeHeaders?: (responseHeaders: Headers, req: Request, server: Bun.Server<WebSocketData>) => void | Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* A function that allows to edit the response headers of all requests
|
|
52
|
+
*/
|
|
53
|
+
editResponseHeaders?: (responseHeaders: Headers, req: Request, server: Bun.Server<WebSocketData>) => void | Promise<void>;
|
|
54
|
+
}
|
|
55
|
+
interface ConnectionError {
|
|
56
|
+
req: Request;
|
|
57
|
+
code: number;
|
|
58
|
+
message: string;
|
|
59
|
+
context: Record<string, unknown>;
|
|
60
|
+
}
|
|
61
|
+
interface ServerReservedEvents {
|
|
62
|
+
connection: (socket: Socket, request: Request, server: Bun.Server<WebSocketData>) => void;
|
|
63
|
+
connection_error: (err: ConnectionError) => void;
|
|
64
|
+
}
|
|
65
|
+
export declare class Server extends EventEmitter<Record<never, never>, Record<never, never>, ServerReservedEvents> {
|
|
66
|
+
readonly opts: ServerOptions;
|
|
67
|
+
private clients;
|
|
68
|
+
get clientsCount(): number;
|
|
69
|
+
constructor(opts?: Partial<ServerOptions>);
|
|
70
|
+
/**
|
|
71
|
+
* Handles an HTTP request.
|
|
72
|
+
*
|
|
73
|
+
* @param req
|
|
74
|
+
* @param server
|
|
75
|
+
*/
|
|
76
|
+
handleRequest(req: Request, server: Bun.Server<WebSocketData>): Promise<Response>;
|
|
77
|
+
onWebSocketOpen(ws: BunWebSocket): void;
|
|
78
|
+
onWebSocketMessage(ws: BunWebSocket, message: RawData): void;
|
|
79
|
+
onWebSocketClose(ws: BunWebSocket, code: number, message: string): void;
|
|
80
|
+
/**
|
|
81
|
+
* Verifies a request.
|
|
82
|
+
*
|
|
83
|
+
* @param req
|
|
84
|
+
* @param url
|
|
85
|
+
* @private
|
|
86
|
+
*/
|
|
87
|
+
private verify;
|
|
88
|
+
/**
|
|
89
|
+
* Handshakes a new client.
|
|
90
|
+
*
|
|
91
|
+
* @param req
|
|
92
|
+
* @param server
|
|
93
|
+
* @param url
|
|
94
|
+
* @param responseHeaders
|
|
95
|
+
* @private
|
|
96
|
+
*/
|
|
97
|
+
private handshake;
|
|
98
|
+
/**
|
|
99
|
+
* Closes all clients.
|
|
100
|
+
*/
|
|
101
|
+
close(): void;
|
|
102
|
+
/**
|
|
103
|
+
* Creates a request handler.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* Bun.serve({
|
|
107
|
+
* port: 3000,
|
|
108
|
+
* ...engine.handler()
|
|
109
|
+
* });
|
|
110
|
+
*
|
|
111
|
+
* // expanded
|
|
112
|
+
* Bun.serve({
|
|
113
|
+
* port: 3000,
|
|
114
|
+
*
|
|
115
|
+
* fetch(req, server) {
|
|
116
|
+
* return engine.handleRequest(req, server);
|
|
117
|
+
* },
|
|
118
|
+
*
|
|
119
|
+
* websocket: {
|
|
120
|
+
* open(ws: BunWebSocket) {
|
|
121
|
+
* engine.onWebSocketOpen(ws);
|
|
122
|
+
* },
|
|
123
|
+
*
|
|
124
|
+
* message(ws: BunWebSocket, message: RawData) {
|
|
125
|
+
* engine.onWebSocketMessage(ws, message);
|
|
126
|
+
* },
|
|
127
|
+
*
|
|
128
|
+
* close(ws: BunWebSocket, code: number, message: string) {
|
|
129
|
+
* engine.onWebSocketClose(ws, code, message);
|
|
130
|
+
* },
|
|
131
|
+
* },
|
|
132
|
+
* });
|
|
133
|
+
*/
|
|
134
|
+
handler(): {
|
|
135
|
+
fetch: (req: Request, server: Bun.Server<WebSocketData>) => Response | Promise<Response>;
|
|
136
|
+
websocket: {
|
|
137
|
+
open: (ws: BunWebSocket) => void;
|
|
138
|
+
message: (ws: BunWebSocket, message: RawData) => void;
|
|
139
|
+
close: (ws: BunWebSocket, code: number, message: string) => void;
|
|
140
|
+
maxPayloadLength: number;
|
|
141
|
+
};
|
|
142
|
+
idleTimeout: number;
|
|
143
|
+
maxRequestBodySize: number;
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
export {};
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import { EventEmitter } from "./event-emitter";
|
|
2
|
+
import { Socket } from "./socket";
|
|
3
|
+
import { Polling } from "./transports/polling";
|
|
4
|
+
import { WS, } from "./transports/websocket";
|
|
5
|
+
import { addCorsHeaders } from "./cors";
|
|
6
|
+
import { Transport } from "./transport";
|
|
7
|
+
import { generateId } from "./util";
|
|
8
|
+
import { debuglog } from "node:util";
|
|
9
|
+
const debug = debuglog("engine.io");
|
|
10
|
+
const TRANSPORTS = ["polling", "websocket"];
|
|
11
|
+
var ERROR_CODES;
|
|
12
|
+
(function (ERROR_CODES) {
|
|
13
|
+
ERROR_CODES[ERROR_CODES["UNKNOWN_TRANSPORT"] = 0] = "UNKNOWN_TRANSPORT";
|
|
14
|
+
ERROR_CODES[ERROR_CODES["UNKNOWN_SID"] = 1] = "UNKNOWN_SID";
|
|
15
|
+
ERROR_CODES[ERROR_CODES["BAD_HANDSHAKE_METHOD"] = 2] = "BAD_HANDSHAKE_METHOD";
|
|
16
|
+
ERROR_CODES[ERROR_CODES["BAD_REQUEST"] = 3] = "BAD_REQUEST";
|
|
17
|
+
ERROR_CODES[ERROR_CODES["FORBIDDEN"] = 4] = "FORBIDDEN";
|
|
18
|
+
ERROR_CODES[ERROR_CODES["UNSUPPORTED_PROTOCOL_VERSION"] = 5] = "UNSUPPORTED_PROTOCOL_VERSION";
|
|
19
|
+
})(ERROR_CODES || (ERROR_CODES = {}));
|
|
20
|
+
const ERROR_MESSAGES = new Map([
|
|
21
|
+
[ERROR_CODES.UNKNOWN_TRANSPORT, "Transport unknown"],
|
|
22
|
+
[ERROR_CODES.UNKNOWN_SID, "Session ID unknown"],
|
|
23
|
+
[ERROR_CODES.BAD_HANDSHAKE_METHOD, "Bad handshake method"],
|
|
24
|
+
[ERROR_CODES.BAD_REQUEST, "Bad request"],
|
|
25
|
+
[ERROR_CODES.FORBIDDEN, "Forbidden"],
|
|
26
|
+
[ERROR_CODES.UNSUPPORTED_PROTOCOL_VERSION, "Unsupported protocol version"],
|
|
27
|
+
]);
|
|
28
|
+
export class Server extends EventEmitter {
|
|
29
|
+
opts;
|
|
30
|
+
clients = new Map();
|
|
31
|
+
get clientsCount() {
|
|
32
|
+
return this.clients.size;
|
|
33
|
+
}
|
|
34
|
+
constructor(opts = {}) {
|
|
35
|
+
super();
|
|
36
|
+
this.opts = Object.assign({
|
|
37
|
+
path: "/engine.io/",
|
|
38
|
+
pingTimeout: 20000,
|
|
39
|
+
pingInterval: 25000,
|
|
40
|
+
upgradeTimeout: 10000,
|
|
41
|
+
maxHttpBufferSize: 1e6,
|
|
42
|
+
maxClients: 0,
|
|
43
|
+
}, opts);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Handles an HTTP request.
|
|
47
|
+
*
|
|
48
|
+
* @param req
|
|
49
|
+
* @param server
|
|
50
|
+
*/
|
|
51
|
+
async handleRequest(req, server) {
|
|
52
|
+
const url = new URL(req.url);
|
|
53
|
+
debug(`handling ${req.method} ${req.url}`);
|
|
54
|
+
const responseHeaders = new Headers();
|
|
55
|
+
if (this.opts.cors) {
|
|
56
|
+
addCorsHeaders(responseHeaders, this.opts.cors, req);
|
|
57
|
+
if (req.method === "OPTIONS") {
|
|
58
|
+
return new Response(null, { status: 204, headers: responseHeaders });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (this.opts.editResponseHeaders) {
|
|
62
|
+
await this.opts.editResponseHeaders(responseHeaders, req, server);
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
await this.verify(req, url);
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
const { code, context } = err;
|
|
69
|
+
const message = ERROR_MESSAGES.get(code);
|
|
70
|
+
this.emitReserved("connection_error", {
|
|
71
|
+
req,
|
|
72
|
+
code,
|
|
73
|
+
message,
|
|
74
|
+
context,
|
|
75
|
+
});
|
|
76
|
+
const body = JSON.stringify({
|
|
77
|
+
code,
|
|
78
|
+
message,
|
|
79
|
+
});
|
|
80
|
+
responseHeaders.set("Content-Type", "application/json");
|
|
81
|
+
return new Response(body, {
|
|
82
|
+
status: 400,
|
|
83
|
+
headers: responseHeaders,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
if (this.opts.allowRequest) {
|
|
87
|
+
try {
|
|
88
|
+
await this.opts.allowRequest(req, server);
|
|
89
|
+
}
|
|
90
|
+
catch (reason) {
|
|
91
|
+
this.emitReserved("connection_error", {
|
|
92
|
+
req,
|
|
93
|
+
code: ERROR_CODES.FORBIDDEN,
|
|
94
|
+
message: ERROR_MESSAGES.get(ERROR_CODES.FORBIDDEN),
|
|
95
|
+
context: {
|
|
96
|
+
message: reason,
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
const body = JSON.stringify({
|
|
100
|
+
code: ERROR_CODES.FORBIDDEN,
|
|
101
|
+
message: reason,
|
|
102
|
+
});
|
|
103
|
+
responseHeaders.set("Content-Type", "application/json");
|
|
104
|
+
return new Response(body, {
|
|
105
|
+
status: 403,
|
|
106
|
+
headers: responseHeaders,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const sid = url.searchParams.get("sid");
|
|
111
|
+
if (sid) {
|
|
112
|
+
// the client must exist since we have checked it in the verify method
|
|
113
|
+
const socket = this.clients.get(sid);
|
|
114
|
+
if (req.headers.has("upgrade")) {
|
|
115
|
+
const transport = new WS(this.opts);
|
|
116
|
+
const isSuccess = server.upgrade(req, {
|
|
117
|
+
headers: responseHeaders,
|
|
118
|
+
data: {
|
|
119
|
+
transport,
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
debug(`upgrade was successful: ${isSuccess}`);
|
|
123
|
+
if (!isSuccess) {
|
|
124
|
+
return new Response(null, { status: 500 });
|
|
125
|
+
}
|
|
126
|
+
socket._maybeUpgrade(transport);
|
|
127
|
+
return new Response(null);
|
|
128
|
+
}
|
|
129
|
+
debug("setting new request for existing socket");
|
|
130
|
+
return socket.transport.onRequest(req, responseHeaders);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
return this.handshake(req, server, url, responseHeaders);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
onWebSocketOpen(ws) {
|
|
137
|
+
debug("on ws open");
|
|
138
|
+
ws.data.transport.onOpen(ws);
|
|
139
|
+
}
|
|
140
|
+
onWebSocketMessage(ws, message) {
|
|
141
|
+
debug("on ws message");
|
|
142
|
+
ws.data.transport.onMessage(message);
|
|
143
|
+
}
|
|
144
|
+
onWebSocketClose(ws, code, message) {
|
|
145
|
+
debug("on ws close");
|
|
146
|
+
ws.data.transport.onCloseEvent(code, message);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Verifies a request.
|
|
150
|
+
*
|
|
151
|
+
* @param req
|
|
152
|
+
* @param url
|
|
153
|
+
* @private
|
|
154
|
+
*/
|
|
155
|
+
verify(req, url) {
|
|
156
|
+
const transport = url.searchParams.get("transport") || "";
|
|
157
|
+
if (!TRANSPORTS.includes(transport)) {
|
|
158
|
+
debug(`unknown transport "${transport}"`);
|
|
159
|
+
return Promise.reject({
|
|
160
|
+
code: ERROR_CODES.UNKNOWN_TRANSPORT,
|
|
161
|
+
context: {
|
|
162
|
+
transport,
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
const sid = url.searchParams.get("sid");
|
|
167
|
+
if (sid) {
|
|
168
|
+
const client = this.clients.get(sid);
|
|
169
|
+
if (!client) {
|
|
170
|
+
debug(`unknown client with sid ${sid}`);
|
|
171
|
+
return Promise.reject({
|
|
172
|
+
code: ERROR_CODES.UNKNOWN_SID,
|
|
173
|
+
context: {
|
|
174
|
+
sid,
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
const previousTransport = client.transport.name;
|
|
179
|
+
if (previousTransport === "websocket") {
|
|
180
|
+
debug("unexpected transport without upgrade");
|
|
181
|
+
return Promise.reject({
|
|
182
|
+
code: ERROR_CODES.BAD_REQUEST,
|
|
183
|
+
context: {
|
|
184
|
+
name: "TRANSPORT_MISMATCH",
|
|
185
|
+
transport,
|
|
186
|
+
previousTransport,
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
// handshake is GET only
|
|
193
|
+
if (req.method !== "GET") {
|
|
194
|
+
return Promise.reject({
|
|
195
|
+
code: ERROR_CODES.BAD_HANDSHAKE_METHOD,
|
|
196
|
+
context: {
|
|
197
|
+
method: req.method,
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
const protocol = url.searchParams.get("EIO") === "4" ? 4 : 3; // 3rd revision by default
|
|
202
|
+
if (protocol === 3) {
|
|
203
|
+
return Promise.reject({
|
|
204
|
+
code: ERROR_CODES.UNSUPPORTED_PROTOCOL_VERSION,
|
|
205
|
+
context: {
|
|
206
|
+
protocol,
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return Promise.resolve();
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Handshakes a new client.
|
|
215
|
+
*
|
|
216
|
+
* @param req
|
|
217
|
+
* @param server
|
|
218
|
+
* @param url
|
|
219
|
+
* @param responseHeaders
|
|
220
|
+
* @private
|
|
221
|
+
*/
|
|
222
|
+
async handshake(req, server, url, responseHeaders) {
|
|
223
|
+
if (this.opts.maxClients > 0 && this.clients.size >= this.opts.maxClients) {
|
|
224
|
+
this.emitReserved("connection_error", {
|
|
225
|
+
req,
|
|
226
|
+
code: ERROR_CODES.FORBIDDEN,
|
|
227
|
+
message: "Server capacity reached",
|
|
228
|
+
context: {
|
|
229
|
+
maxClients: this.opts.maxClients,
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
responseHeaders.set("Content-Type", "application/json");
|
|
233
|
+
return new Response(JSON.stringify({
|
|
234
|
+
code: ERROR_CODES.FORBIDDEN,
|
|
235
|
+
message: "Server capacity reached",
|
|
236
|
+
}), { status: 503, headers: responseHeaders });
|
|
237
|
+
}
|
|
238
|
+
const id = generateId();
|
|
239
|
+
if (this.opts.editHandshakeHeaders) {
|
|
240
|
+
await this.opts.editHandshakeHeaders(responseHeaders, req, server);
|
|
241
|
+
}
|
|
242
|
+
let isUpgrade = req.headers.has("upgrade");
|
|
243
|
+
let transport;
|
|
244
|
+
if (isUpgrade) {
|
|
245
|
+
transport = new WS(this.opts);
|
|
246
|
+
const isSuccess = server.upgrade(req, {
|
|
247
|
+
headers: responseHeaders,
|
|
248
|
+
data: {
|
|
249
|
+
transport: transport,
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
if (!isSuccess) {
|
|
253
|
+
return new Response(null, { status: 500 });
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
transport = new Polling(this.opts);
|
|
258
|
+
}
|
|
259
|
+
debug(`new socket ${id}`);
|
|
260
|
+
const socket = new Socket(id, this.opts, transport, {
|
|
261
|
+
url: req.url,
|
|
262
|
+
headers: Object.fromEntries(req.headers.entries()),
|
|
263
|
+
_query: Object.fromEntries(url.searchParams.entries()),
|
|
264
|
+
connection: {
|
|
265
|
+
encrypted: ["https", "wss"].includes(url.protocol),
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
this.clients.set(id, socket);
|
|
269
|
+
socket.once("close", (reason) => {
|
|
270
|
+
debug(`socket ${id} closed due to ${reason}`);
|
|
271
|
+
this.clients.delete(id);
|
|
272
|
+
});
|
|
273
|
+
if (isUpgrade) {
|
|
274
|
+
this.emitReserved("connection", socket, req, server);
|
|
275
|
+
return new Response(null);
|
|
276
|
+
}
|
|
277
|
+
const promise = transport.onRequest(req, responseHeaders);
|
|
278
|
+
this.emitReserved("connection", socket, req, server);
|
|
279
|
+
return promise;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Closes all clients.
|
|
283
|
+
*/
|
|
284
|
+
close() {
|
|
285
|
+
debug("closing all open clients");
|
|
286
|
+
this.clients.forEach((client) => client.close());
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Creates a request handler.
|
|
290
|
+
*
|
|
291
|
+
* @example
|
|
292
|
+
* Bun.serve({
|
|
293
|
+
* port: 3000,
|
|
294
|
+
* ...engine.handler()
|
|
295
|
+
* });
|
|
296
|
+
*
|
|
297
|
+
* // expanded
|
|
298
|
+
* Bun.serve({
|
|
299
|
+
* port: 3000,
|
|
300
|
+
*
|
|
301
|
+
* fetch(req, server) {
|
|
302
|
+
* return engine.handleRequest(req, server);
|
|
303
|
+
* },
|
|
304
|
+
*
|
|
305
|
+
* websocket: {
|
|
306
|
+
* open(ws: BunWebSocket) {
|
|
307
|
+
* engine.onWebSocketOpen(ws);
|
|
308
|
+
* },
|
|
309
|
+
*
|
|
310
|
+
* message(ws: BunWebSocket, message: RawData) {
|
|
311
|
+
* engine.onWebSocketMessage(ws, message);
|
|
312
|
+
* },
|
|
313
|
+
*
|
|
314
|
+
* close(ws: BunWebSocket, code: number, message: string) {
|
|
315
|
+
* engine.onWebSocketClose(ws, code, message);
|
|
316
|
+
* },
|
|
317
|
+
* },
|
|
318
|
+
* });
|
|
319
|
+
*/
|
|
320
|
+
handler() {
|
|
321
|
+
const idleTimeoutInSeconds = Math.ceil((2 * this.opts.pingInterval) / 1000);
|
|
322
|
+
return {
|
|
323
|
+
fetch: (req, server) => {
|
|
324
|
+
const url = new URL(req.url);
|
|
325
|
+
if (url.pathname === this.opts.path) {
|
|
326
|
+
return this.handleRequest(req, server);
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
return new Response(null, { status: 404 });
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
websocket: {
|
|
333
|
+
open: (ws) => {
|
|
334
|
+
this.onWebSocketOpen(ws);
|
|
335
|
+
},
|
|
336
|
+
message: (ws, message) => {
|
|
337
|
+
this.onWebSocketMessage(ws, message);
|
|
338
|
+
},
|
|
339
|
+
close: (ws, code, message) => {
|
|
340
|
+
this.onWebSocketClose(ws, code, message);
|
|
341
|
+
},
|
|
342
|
+
maxPayloadLength: this.opts.maxHttpBufferSize,
|
|
343
|
+
},
|
|
344
|
+
idleTimeout: idleTimeoutInSeconds,
|
|
345
|
+
maxRequestBodySize: this.opts.maxHttpBufferSize,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
}
|
package/dist/socket.d.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { EventEmitter } from "./event-emitter";
|
|
2
|
+
import { type Packet, type RawData } from "./parser";
|
|
3
|
+
import { Transport } from "./transport";
|
|
4
|
+
import { type ServerOptions } from "./server";
|
|
5
|
+
type ReadyState = "opening" | "open" | "closing" | "closed";
|
|
6
|
+
export interface HandshakeRequestReference {
|
|
7
|
+
url: string;
|
|
8
|
+
headers: Record<string, string>;
|
|
9
|
+
_query: Record<string, string>;
|
|
10
|
+
connection: {
|
|
11
|
+
encrypted: boolean;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export type CloseReason = "transport error" | "transport close" | "forced close" | "ping timeout" | "parse error";
|
|
15
|
+
interface SocketEvents {
|
|
16
|
+
open: () => void;
|
|
17
|
+
packet: (packet: Packet) => void;
|
|
18
|
+
packetCreate: (packet: Packet) => void;
|
|
19
|
+
data: (data: RawData) => void;
|
|
20
|
+
flush: (writeBuffer: Packet[]) => void;
|
|
21
|
+
drain: () => void;
|
|
22
|
+
heartbeat: () => void;
|
|
23
|
+
upgrading: (transport: Transport) => void;
|
|
24
|
+
upgrade: (transport: Transport) => void;
|
|
25
|
+
close: (reason: CloseReason) => void;
|
|
26
|
+
}
|
|
27
|
+
export declare class Socket extends EventEmitter<Record<never, never>, Record<never, never>, SocketEvents> {
|
|
28
|
+
readonly id: string;
|
|
29
|
+
readyState: ReadyState;
|
|
30
|
+
transport: Transport;
|
|
31
|
+
readonly request: HandshakeRequestReference;
|
|
32
|
+
private readonly opts;
|
|
33
|
+
private upgradeState;
|
|
34
|
+
private writeBuffer;
|
|
35
|
+
private pingIntervalTimer?;
|
|
36
|
+
private pingTimeoutTimer?;
|
|
37
|
+
constructor(id: string, opts: ServerOptions, transport: Transport, req: HandshakeRequestReference);
|
|
38
|
+
/**
|
|
39
|
+
* Called upon transport considered open.
|
|
40
|
+
*
|
|
41
|
+
* @private
|
|
42
|
+
*/
|
|
43
|
+
private onOpen;
|
|
44
|
+
/**
|
|
45
|
+
* Called upon transport packet.
|
|
46
|
+
*
|
|
47
|
+
* @param packet
|
|
48
|
+
* @private
|
|
49
|
+
*/
|
|
50
|
+
private onPacket;
|
|
51
|
+
/**
|
|
52
|
+
* Called upon transport error.
|
|
53
|
+
*
|
|
54
|
+
* @param err
|
|
55
|
+
* @private
|
|
56
|
+
*/
|
|
57
|
+
private onError;
|
|
58
|
+
/**
|
|
59
|
+
* Pings client every `pingInterval` and expects response
|
|
60
|
+
* within `pingTimeout` or closes connection.
|
|
61
|
+
*
|
|
62
|
+
* @private
|
|
63
|
+
*/
|
|
64
|
+
private schedulePing;
|
|
65
|
+
/**
|
|
66
|
+
* Resets ping timeout.
|
|
67
|
+
*
|
|
68
|
+
* @private
|
|
69
|
+
*/
|
|
70
|
+
private resetPingTimeout;
|
|
71
|
+
/**
|
|
72
|
+
* Attaches handlers for the given transport.
|
|
73
|
+
*
|
|
74
|
+
* @param transport
|
|
75
|
+
* @private
|
|
76
|
+
*/
|
|
77
|
+
private bindTransport;
|
|
78
|
+
/**
|
|
79
|
+
* Upgrades socket to the given transport
|
|
80
|
+
*
|
|
81
|
+
* @param transport
|
|
82
|
+
* @private
|
|
83
|
+
*/
|
|
84
|
+
_maybeUpgrade(transport: Transport): void;
|
|
85
|
+
/**
|
|
86
|
+
* Called upon transport considered closed.
|
|
87
|
+
*
|
|
88
|
+
* @param reason
|
|
89
|
+
* @private
|
|
90
|
+
*/
|
|
91
|
+
private onClose;
|
|
92
|
+
/**
|
|
93
|
+
* Sends a "message" packet.
|
|
94
|
+
*
|
|
95
|
+
* @param data
|
|
96
|
+
*/
|
|
97
|
+
write(data: RawData): Socket;
|
|
98
|
+
/**
|
|
99
|
+
* Sends a packet.
|
|
100
|
+
*
|
|
101
|
+
* @param type
|
|
102
|
+
* @param data
|
|
103
|
+
* @private
|
|
104
|
+
*/
|
|
105
|
+
private sendPacket;
|
|
106
|
+
/**
|
|
107
|
+
* Attempts to flush the packets buffer.
|
|
108
|
+
*
|
|
109
|
+
* @private
|
|
110
|
+
*/
|
|
111
|
+
private flush;
|
|
112
|
+
/**
|
|
113
|
+
* Closes the socket and underlying transport.
|
|
114
|
+
*/
|
|
115
|
+
close(): void;
|
|
116
|
+
/**
|
|
117
|
+
* Closes the underlying transport.
|
|
118
|
+
*
|
|
119
|
+
* @private
|
|
120
|
+
*/
|
|
121
|
+
private closeTransport;
|
|
122
|
+
}
|
|
123
|
+
export {};
|