@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/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-present Guillermo Rauch and Socket.IO contributors
|
|
4
|
+
Copyright (c) 2026 RVNcom
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# @rvncom/socket-bun-engine
|
|
2
|
+
|
|
3
|
+
Engine.IO server implementation for the Bun runtime. Provides native WebSocket and HTTP long-polling transports for [Socket.IO](https://socket.io/).
|
|
4
|
+
|
|
5
|
+
Fork of `@socket.io/bun-engine` with bug fixes, improved API, and active maintenance.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bun add @rvn/bun-engine
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { Server as Engine } from "@rvn/bun-engine";
|
|
17
|
+
import { Server } from "socket.io";
|
|
18
|
+
|
|
19
|
+
const engine = new Engine({
|
|
20
|
+
path: "/socket.io/",
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const io = new Server();
|
|
24
|
+
io.bind(engine);
|
|
25
|
+
|
|
26
|
+
io.on("connection", (socket) => {
|
|
27
|
+
// ...
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export default {
|
|
31
|
+
port: 3000,
|
|
32
|
+
...engine.handler(),
|
|
33
|
+
};
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
You can also use `engine.handleRequest()` directly for custom routing:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
Bun.serve({
|
|
40
|
+
port: 3000,
|
|
41
|
+
|
|
42
|
+
fetch(req, server) {
|
|
43
|
+
const url = new URL(req.url);
|
|
44
|
+
|
|
45
|
+
if (url.pathname === "/health") {
|
|
46
|
+
return new Response(JSON.stringify({ status: "ok", connections: engine.clientsCount }), {
|
|
47
|
+
headers: { "Content-Type": "application/json" },
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return engine.handleRequest(req, server);
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
websocket: engine.handler().websocket,
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Options
|
|
59
|
+
|
|
60
|
+
### `path`
|
|
61
|
+
|
|
62
|
+
Default: `/engine.io/`
|
|
63
|
+
|
|
64
|
+
The path to handle on the server side. Must match the client configuration.
|
|
65
|
+
|
|
66
|
+
### `pingTimeout`
|
|
67
|
+
|
|
68
|
+
Default: `20000`
|
|
69
|
+
|
|
70
|
+
Milliseconds without a pong packet before considering the connection closed.
|
|
71
|
+
|
|
72
|
+
### `pingInterval`
|
|
73
|
+
|
|
74
|
+
Default: `25000`
|
|
75
|
+
|
|
76
|
+
Milliseconds between ping packets sent by the server.
|
|
77
|
+
|
|
78
|
+
### `upgradeTimeout`
|
|
79
|
+
|
|
80
|
+
Default: `10000`
|
|
81
|
+
|
|
82
|
+
Milliseconds before an uncompleted transport upgrade is cancelled.
|
|
83
|
+
|
|
84
|
+
### `maxHttpBufferSize`
|
|
85
|
+
|
|
86
|
+
Default: `1e6` (1 MB)
|
|
87
|
+
|
|
88
|
+
Maximum message size in bytes before closing the session.
|
|
89
|
+
|
|
90
|
+
### `maxClients`
|
|
91
|
+
|
|
92
|
+
Default: `0` (unlimited)
|
|
93
|
+
|
|
94
|
+
Maximum number of concurrent clients. New connections are rejected with HTTP 503 when the limit is reached.
|
|
95
|
+
|
|
96
|
+
### `allowRequest`
|
|
97
|
+
|
|
98
|
+
A function that receives the handshake/upgrade request and can reject it:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
const engine = new Engine({
|
|
102
|
+
allowRequest: (req, server) => {
|
|
103
|
+
return Promise.reject("not allowed");
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### `cors`
|
|
109
|
+
|
|
110
|
+
Cross-Origin Resource Sharing options:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
const engine = new Engine({
|
|
114
|
+
cors: {
|
|
115
|
+
origin: ["https://example.com"],
|
|
116
|
+
allowedHeaders: ["my-header"],
|
|
117
|
+
credentials: true,
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### `editHandshakeHeaders`
|
|
123
|
+
|
|
124
|
+
Edit response headers for the handshake request:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
const engine = new Engine({
|
|
128
|
+
editHandshakeHeaders: (responseHeaders, req, server) => {
|
|
129
|
+
responseHeaders.set("set-cookie", "sid=1234");
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### `editResponseHeaders`
|
|
135
|
+
|
|
136
|
+
Edit response headers for all requests:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
const engine = new Engine({
|
|
140
|
+
editResponseHeaders: (responseHeaders, req, server) => {
|
|
141
|
+
responseHeaders.set("my-header", "abcd");
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## License
|
|
147
|
+
|
|
148
|
+
[MIT](/LICENSE)
|
package/dist/cors.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type OriginOption = boolean | string | RegExp | (string | RegExp)[];
|
|
2
|
+
export interface CorsOptions {
|
|
3
|
+
origin?: OriginOption;
|
|
4
|
+
methods?: string | string[];
|
|
5
|
+
allowedHeaders?: string | string[];
|
|
6
|
+
exposedHeaders?: string | string[];
|
|
7
|
+
credentials?: boolean;
|
|
8
|
+
maxAge?: number;
|
|
9
|
+
}
|
|
10
|
+
export declare function addCorsHeaders(headers: Headers, opts: CorsOptions, req: Request): void;
|
|
11
|
+
export {};
|
package/dist/cors.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export function addCorsHeaders(headers, opts, req) {
|
|
2
|
+
addOrigin(opts, headers, req);
|
|
3
|
+
addCredentials(opts, headers);
|
|
4
|
+
addExposedHeaders(opts, headers);
|
|
5
|
+
if (req.method === "OPTIONS") {
|
|
6
|
+
addMethods(opts, headers);
|
|
7
|
+
addAllowedHeaders(opts, headers, req);
|
|
8
|
+
addMaxAge(opts, headers);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function join(arg) {
|
|
12
|
+
return Array.isArray(arg) ? arg.join(",") : arg;
|
|
13
|
+
}
|
|
14
|
+
function isOriginAllowed(allowedOrigin, origin) {
|
|
15
|
+
if (Array.isArray(allowedOrigin)) {
|
|
16
|
+
for (let i = 0; i < allowedOrigin.length; i++) {
|
|
17
|
+
if (isOriginAllowed(allowedOrigin[i], origin)) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
else if (typeof allowedOrigin === "string") {
|
|
24
|
+
return allowedOrigin === origin;
|
|
25
|
+
}
|
|
26
|
+
else if (allowedOrigin instanceof RegExp) {
|
|
27
|
+
return allowedOrigin.test(origin);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
return !!allowedOrigin;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function addOrigin(opts, headers, req) {
|
|
34
|
+
const origin = req.headers.get("origin");
|
|
35
|
+
const allowedOrigin = opts.origin;
|
|
36
|
+
if (!allowedOrigin || allowedOrigin === "*") {
|
|
37
|
+
headers.set("Access-Control-Allow-Origin", "*");
|
|
38
|
+
}
|
|
39
|
+
else if (typeof allowedOrigin === "string") {
|
|
40
|
+
headers.set("Access-Control-Allow-Origin", allowedOrigin);
|
|
41
|
+
headers.append("Vary", "Origin");
|
|
42
|
+
}
|
|
43
|
+
else if (origin) {
|
|
44
|
+
const isAllowed = isOriginAllowed(allowedOrigin, origin);
|
|
45
|
+
headers.set("Access-Control-Allow-Origin", isAllowed ? origin : "false");
|
|
46
|
+
headers.append("Vary", "Origin");
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
headers.set("Access-Control-Allow-Origin", "false");
|
|
50
|
+
headers.append("Vary", "Origin");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function addMethods(opts, headers) {
|
|
54
|
+
if (opts.methods) {
|
|
55
|
+
headers.set("Access-Control-Allow-Methods", join(opts.methods));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function addAllowedHeaders(opts, headers, req) {
|
|
59
|
+
if (opts.allowedHeaders) {
|
|
60
|
+
headers.set("Access-Control-Allow-Headers", join(opts.allowedHeaders));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const requestedHeaders = req.headers.get("access-control-request-headers");
|
|
64
|
+
if (requestedHeaders) {
|
|
65
|
+
headers.append("Vary", "Access-Control-Request-Headers");
|
|
66
|
+
headers.set("Access-Control-Allow-Headers", requestedHeaders);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function addExposedHeaders(opts, headers) {
|
|
70
|
+
if (opts.exposedHeaders) {
|
|
71
|
+
headers.set("Access-Control-Expose-Headers", join(opts.exposedHeaders));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function addCredentials(opts, headers) {
|
|
75
|
+
if (opts.credentials) {
|
|
76
|
+
headers.set("Access-Control-Allow-Credentials", "true");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function addMaxAge(opts, headers) {
|
|
80
|
+
if (opts.maxAge) {
|
|
81
|
+
headers.set("Access-Control-Max-Age", opts.maxAge.toString());
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An events map is an interface that maps event names to their value, which represents the type of the `on` listener.
|
|
3
|
+
*/
|
|
4
|
+
export interface EventsMap {
|
|
5
|
+
[event: string]: any;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The default events map, used if no EventsMap is given. Using this EventsMap is equivalent to accepting all event
|
|
9
|
+
* names, and any data.
|
|
10
|
+
*/
|
|
11
|
+
export interface DefaultEventsMap {
|
|
12
|
+
[event: string]: (...args: any[]) => void;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Returns a union type containing all the keys of an event map.
|
|
16
|
+
*/
|
|
17
|
+
export type EventNames<Map extends EventsMap> = keyof Map & (string | symbol);
|
|
18
|
+
/** The tuple type representing the parameters of an event listener */
|
|
19
|
+
export type EventParams<Map extends EventsMap, Ev extends EventNames<Map>> = Parameters<Map[Ev]>;
|
|
20
|
+
/**
|
|
21
|
+
* The event names that are either in ReservedEvents or in UserEvents
|
|
22
|
+
*/
|
|
23
|
+
export type ReservedOrUserEventNames<ReservedEventsMap extends EventsMap, UserEvents extends EventsMap> = EventNames<ReservedEventsMap> | EventNames<UserEvents>;
|
|
24
|
+
/**
|
|
25
|
+
* Type of a listener of a user event or a reserved event. If `Ev` is in `ReservedEvents`, the reserved event listener
|
|
26
|
+
* is returned.
|
|
27
|
+
*/
|
|
28
|
+
export type ReservedOrUserListener<ReservedEvents extends EventsMap, UserEvents extends EventsMap, Ev extends ReservedOrUserEventNames<ReservedEvents, UserEvents>> = FallbackToUntypedListener<Ev extends EventNames<ReservedEvents> ? ReservedEvents[Ev] : Ev extends EventNames<UserEvents> ? UserEvents[Ev] : never>;
|
|
29
|
+
/**
|
|
30
|
+
* Returns an untyped listener type if `T` is `never`; otherwise, returns `T`.
|
|
31
|
+
*
|
|
32
|
+
* Needed because of https://github.com/microsoft/TypeScript/issues/41778
|
|
33
|
+
*/
|
|
34
|
+
type FallbackToUntypedListener<T> = [T] extends [never] ? (...args: any[]) => void | Promise<void> : T;
|
|
35
|
+
/**
|
|
36
|
+
* Strictly typed version of an `EventEmitter`. A `TypedEventEmitter` takes type parameters for mappings of event names
|
|
37
|
+
* to event data types, and strictly types method calls to the `EventEmitter` according to these event maps.
|
|
38
|
+
*
|
|
39
|
+
* @typeParam ListenEvents - `EventsMap` of user-defined events that can be listened to with `on` or `once`
|
|
40
|
+
* @typeParam EmitEvents - `EventsMap` of user-defined events that can be emitted with `emit`
|
|
41
|
+
* @typeParam ReservedEvents - `EventsMap` of reserved events, that can be emitted with `emitReserved`, and can be
|
|
42
|
+
* listened to with `listen`.
|
|
43
|
+
*/
|
|
44
|
+
declare abstract class BaseEventEmitter<ListenEvents extends EventsMap, EmitEvents extends EventsMap, ReservedEvents extends EventsMap = never> {
|
|
45
|
+
private _listeners;
|
|
46
|
+
/**
|
|
47
|
+
* Adds the `listener` function as an event listener for `ev`.
|
|
48
|
+
*
|
|
49
|
+
* @param event - Name of the event
|
|
50
|
+
* @param listener - Callback function
|
|
51
|
+
*/
|
|
52
|
+
on<Ev extends ReservedOrUserEventNames<ReservedEvents, ListenEvents>>(event: Ev, listener: ReservedOrUserListener<ReservedEvents, ListenEvents, Ev>): this;
|
|
53
|
+
/**
|
|
54
|
+
* Adds a one-time `listener` function as an event listener for `ev`.
|
|
55
|
+
*
|
|
56
|
+
* @param event - Name of the event
|
|
57
|
+
* @param listener - Callback function
|
|
58
|
+
*/
|
|
59
|
+
once<Ev extends ReservedOrUserEventNames<ReservedEvents, ListenEvents>>(event: Ev, listener: ReservedOrUserListener<ReservedEvents, ListenEvents, Ev>): this;
|
|
60
|
+
/**
|
|
61
|
+
* Removes the `listener` function as an event listener for `ev`.
|
|
62
|
+
*
|
|
63
|
+
* @param event - Name of the event
|
|
64
|
+
* @param listener - Callback function
|
|
65
|
+
*/
|
|
66
|
+
off<Ev extends ReservedOrUserEventNames<ReservedEvents, ListenEvents>>(event?: Ev, listener?: ReservedOrUserListener<ReservedEvents, ListenEvents, Ev>): this;
|
|
67
|
+
/**
|
|
68
|
+
* Removes the `listener` function as an event listener for `ev`.
|
|
69
|
+
*
|
|
70
|
+
* @param event - Name of the event
|
|
71
|
+
* @param listener - Callback function
|
|
72
|
+
*/
|
|
73
|
+
removeListener<Ev extends ReservedOrUserEventNames<ReservedEvents, ListenEvents>>(event?: Ev, listener?: ReservedOrUserListener<ReservedEvents, ListenEvents, Ev>): this;
|
|
74
|
+
/**
|
|
75
|
+
* Emits an event.
|
|
76
|
+
*
|
|
77
|
+
* @param event - Name of the event
|
|
78
|
+
* @param args - Values to send to listeners of this event
|
|
79
|
+
*/
|
|
80
|
+
emit<Ev extends EventNames<EmitEvents>>(event: Ev, ...args: EventParams<EmitEvents, Ev>): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Returns the listeners listening to an event.
|
|
83
|
+
*
|
|
84
|
+
* @param event - Event name
|
|
85
|
+
* @returns Array of listeners subscribed to `event`
|
|
86
|
+
*/
|
|
87
|
+
listeners<Ev extends ReservedOrUserEventNames<ReservedEvents, ListenEvents>>(event: Ev): ReservedOrUserListener<ReservedEvents, ListenEvents, Ev>[];
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* This class extends the BaseEventEmitter abstract class, so a class extending `EventEmitter` can override the `emit`
|
|
91
|
+
* method and still call `emitReserved()` (since it uses `super.emit()`)
|
|
92
|
+
*/
|
|
93
|
+
export declare class EventEmitter<ListenEvents extends EventsMap, EmitEvents extends EventsMap, ReservedEvents extends EventsMap = never> extends BaseEventEmitter<ListenEvents, EmitEvents, ReservedEvents> {
|
|
94
|
+
/**
|
|
95
|
+
* Emits a reserved event.
|
|
96
|
+
*
|
|
97
|
+
* This method is `protected`, so that only a class extending `EventEmitter` can emit its own reserved events.
|
|
98
|
+
*
|
|
99
|
+
* @param event - Reserved event name
|
|
100
|
+
* @param args - Arguments to emit along with the event
|
|
101
|
+
* @protected
|
|
102
|
+
*/
|
|
103
|
+
protected emitReserved<Ev extends EventNames<ReservedEvents>>(event: Ev, ...args: EventParams<ReservedEvents, Ev>): boolean;
|
|
104
|
+
}
|
|
105
|
+
export {};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strictly typed version of an `EventEmitter`. A `TypedEventEmitter` takes type parameters for mappings of event names
|
|
3
|
+
* to event data types, and strictly types method calls to the `EventEmitter` according to these event maps.
|
|
4
|
+
*
|
|
5
|
+
* @typeParam ListenEvents - `EventsMap` of user-defined events that can be listened to with `on` or `once`
|
|
6
|
+
* @typeParam EmitEvents - `EventsMap` of user-defined events that can be emitted with `emit`
|
|
7
|
+
* @typeParam ReservedEvents - `EventsMap` of reserved events, that can be emitted with `emitReserved`, and can be
|
|
8
|
+
* listened to with `listen`.
|
|
9
|
+
*/
|
|
10
|
+
class BaseEventEmitter {
|
|
11
|
+
_listeners = new Map();
|
|
12
|
+
/**
|
|
13
|
+
* Adds the `listener` function as an event listener for `ev`.
|
|
14
|
+
*
|
|
15
|
+
* @param event - Name of the event
|
|
16
|
+
* @param listener - Callback function
|
|
17
|
+
*/
|
|
18
|
+
on(event, listener) {
|
|
19
|
+
const listeners = this._listeners.get(event);
|
|
20
|
+
if (listeners) {
|
|
21
|
+
listeners.push(listener);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
this._listeners.set(event, [listener]);
|
|
25
|
+
}
|
|
26
|
+
return this;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Adds a one-time `listener` function as an event listener for `ev`.
|
|
30
|
+
*
|
|
31
|
+
* @param event - Name of the event
|
|
32
|
+
* @param listener - Callback function
|
|
33
|
+
*/
|
|
34
|
+
once(event, listener) {
|
|
35
|
+
const onceListener = ((...args) => {
|
|
36
|
+
this.off(event, onceListener);
|
|
37
|
+
listener.apply(this, args);
|
|
38
|
+
});
|
|
39
|
+
onceListener.fn = listener;
|
|
40
|
+
return this.on(event, onceListener);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Removes the `listener` function as an event listener for `ev`.
|
|
44
|
+
*
|
|
45
|
+
* @param event - Name of the event
|
|
46
|
+
* @param listener - Callback function
|
|
47
|
+
*/
|
|
48
|
+
off(event, listener) {
|
|
49
|
+
if (!event) {
|
|
50
|
+
this._listeners.clear();
|
|
51
|
+
return this;
|
|
52
|
+
}
|
|
53
|
+
if (!listener) {
|
|
54
|
+
this._listeners.delete(event);
|
|
55
|
+
return this;
|
|
56
|
+
}
|
|
57
|
+
const listeners = this._listeners.get(event);
|
|
58
|
+
if (!listeners) {
|
|
59
|
+
return this;
|
|
60
|
+
}
|
|
61
|
+
for (let i = 0; i < listeners.length; i++) {
|
|
62
|
+
if (listeners[i] === listener || listeners[i].fn === listener) {
|
|
63
|
+
listeners.splice(i, 1);
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (listeners.length === 0) {
|
|
68
|
+
this._listeners.delete(event);
|
|
69
|
+
}
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Removes the `listener` function as an event listener for `ev`.
|
|
74
|
+
*
|
|
75
|
+
* @param event - Name of the event
|
|
76
|
+
* @param listener - Callback function
|
|
77
|
+
*/
|
|
78
|
+
removeListener(event, listener) {
|
|
79
|
+
return this.off(event, listener);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Emits an event.
|
|
83
|
+
*
|
|
84
|
+
* @param event - Name of the event
|
|
85
|
+
* @param args - Values to send to listeners of this event
|
|
86
|
+
*/
|
|
87
|
+
emit(event, ...args) {
|
|
88
|
+
const listeners = this._listeners.get(event);
|
|
89
|
+
if (!listeners) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
if (listeners.length === 1) {
|
|
93
|
+
listeners[0].apply(this, args);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
for (const listener of listeners.slice()) {
|
|
97
|
+
listener.apply(this, args);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Returns the listeners listening to an event.
|
|
104
|
+
*
|
|
105
|
+
* @param event - Event name
|
|
106
|
+
* @returns Array of listeners subscribed to `event`
|
|
107
|
+
*/
|
|
108
|
+
listeners(event) {
|
|
109
|
+
return this._listeners.get(event) || [];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* This class extends the BaseEventEmitter abstract class, so a class extending `EventEmitter` can override the `emit`
|
|
114
|
+
* method and still call `emitReserved()` (since it uses `super.emit()`)
|
|
115
|
+
*/
|
|
116
|
+
export class EventEmitter extends BaseEventEmitter {
|
|
117
|
+
/**
|
|
118
|
+
* Emits a reserved event.
|
|
119
|
+
*
|
|
120
|
+
* This method is `protected`, so that only a class extending `EventEmitter` can emit its own reserved events.
|
|
121
|
+
*
|
|
122
|
+
* @param event - Reserved event name
|
|
123
|
+
* @param args - Arguments to emit along with the event
|
|
124
|
+
* @protected
|
|
125
|
+
*/
|
|
126
|
+
emitReserved(event, ...args) {
|
|
127
|
+
return super.emit(event, ...args);
|
|
128
|
+
}
|
|
129
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/parser.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type PacketType = "open" | "close" | "ping" | "pong" | "message" | "upgrade" | "noop" | "error";
|
|
2
|
+
export type RawData = string | Buffer;
|
|
3
|
+
export interface Packet {
|
|
4
|
+
type: PacketType;
|
|
5
|
+
data?: RawData;
|
|
6
|
+
}
|
|
7
|
+
type BinaryType = "arraybuffer" | "blob";
|
|
8
|
+
export declare const Parser: {
|
|
9
|
+
encodePacket({ type, data }: Packet, supportsBinary: boolean): RawData;
|
|
10
|
+
decodePacket(encodedPacket: RawData, _binaryType?: BinaryType): Packet;
|
|
11
|
+
encodePayload(packets: Packet[]): string;
|
|
12
|
+
decodePayload(encodedPayload: string, binaryType?: BinaryType): Packet[];
|
|
13
|
+
};
|
|
14
|
+
export {};
|
package/dist/parser.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const SEPARATOR = String.fromCharCode(30); // see https://en.wikipedia.org/wiki/Delimiter#ASCII_delimited_text
|
|
2
|
+
const PACKET_TYPES = new Map();
|
|
3
|
+
const PACKET_TYPES_REVERSE = new Map();
|
|
4
|
+
[
|
|
5
|
+
"open",
|
|
6
|
+
"close",
|
|
7
|
+
"ping",
|
|
8
|
+
"pong",
|
|
9
|
+
"message",
|
|
10
|
+
"upgrade",
|
|
11
|
+
"noop",
|
|
12
|
+
].forEach((type, index) => {
|
|
13
|
+
PACKET_TYPES.set(type, "" + index);
|
|
14
|
+
PACKET_TYPES_REVERSE.set("" + index, type);
|
|
15
|
+
});
|
|
16
|
+
const ERROR_PACKET = { type: "error", data: "parser error" };
|
|
17
|
+
export const Parser = {
|
|
18
|
+
encodePacket({ type, data }, supportsBinary) {
|
|
19
|
+
if (Buffer.isBuffer(data)) {
|
|
20
|
+
return supportsBinary ? data : "b" + data.toString("base64");
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
return PACKET_TYPES.get(type) + (data || "");
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
decodePacket(encodedPacket, _binaryType) {
|
|
27
|
+
if (typeof encodedPacket !== "string") {
|
|
28
|
+
return {
|
|
29
|
+
type: "message",
|
|
30
|
+
data: encodedPacket,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const typeChar = encodedPacket.charAt(0);
|
|
34
|
+
if (typeChar === "b") {
|
|
35
|
+
const buffer = Buffer.from(encodedPacket.substring(1), "base64");
|
|
36
|
+
return {
|
|
37
|
+
type: "message",
|
|
38
|
+
data: buffer,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if (!PACKET_TYPES_REVERSE.has(typeChar)) {
|
|
42
|
+
return ERROR_PACKET;
|
|
43
|
+
}
|
|
44
|
+
const type = PACKET_TYPES_REVERSE.get(typeChar);
|
|
45
|
+
return encodedPacket.length > 1
|
|
46
|
+
? {
|
|
47
|
+
type,
|
|
48
|
+
data: encodedPacket.substring(1),
|
|
49
|
+
}
|
|
50
|
+
: {
|
|
51
|
+
type,
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
encodePayload(packets) {
|
|
55
|
+
const encodedPackets = [];
|
|
56
|
+
for (const packet of packets) {
|
|
57
|
+
encodedPackets.push(this.encodePacket(packet, false));
|
|
58
|
+
}
|
|
59
|
+
return encodedPackets.join(SEPARATOR);
|
|
60
|
+
},
|
|
61
|
+
decodePayload(encodedPayload, binaryType) {
|
|
62
|
+
const encodedPackets = encodedPayload.split(SEPARATOR);
|
|
63
|
+
const packets = [];
|
|
64
|
+
for (let i = 0; i < encodedPackets.length; i++) {
|
|
65
|
+
const decodedPacket = this.decodePacket(encodedPackets[i], binaryType);
|
|
66
|
+
packets.push(decodedPacket);
|
|
67
|
+
if (decodedPacket.type === "error") {
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return packets;
|
|
72
|
+
},
|
|
73
|
+
};
|