@gleamkit/engine.io 6.6.3

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.
@@ -0,0 +1,180 @@
1
+ import { EventEmitter } from "events";
2
+ import type { IncomingMessage } from "http";
3
+ import type { EngineRequest, Transport } from "./transport";
4
+ import type { BaseServer } from "./server";
5
+ import type { RawData } from "engine.io-parser";
6
+ export interface SendOptions {
7
+ compress?: boolean;
8
+ }
9
+ type ReadyState = "opening" | "open" | "closing" | "closed";
10
+ type SendCallback = (transport: Transport) => void;
11
+ export declare class Socket extends EventEmitter {
12
+ /**
13
+ * The revision of the protocol:
14
+ *
15
+ * - 3rd is used in Engine.IO v3 / Socket.IO v2
16
+ * - 4th is used in Engine.IO v4 and above / Socket.IO v3 and above
17
+ *
18
+ * It is found in the `EIO` query parameters of the HTTP requests.
19
+ *
20
+ * @see https://github.com/socketio/engine.io-protocol
21
+ */
22
+ readonly protocol: number;
23
+ /**
24
+ * A reference to the first HTTP request of the session
25
+ *
26
+ * TODO for the next major release: remove it
27
+ */
28
+ request: IncomingMessage;
29
+ /**
30
+ * The IP address of the client.
31
+ */
32
+ readonly remoteAddress: string;
33
+ /**
34
+ * The current state of the socket.
35
+ */
36
+ _readyState: ReadyState;
37
+ /**
38
+ * The current low-level transport.
39
+ */
40
+ transport: Transport;
41
+ private server;
42
+ upgrading: boolean;
43
+ upgraded: boolean;
44
+ private writeBuffer;
45
+ private packetsFn;
46
+ private sentCallbackFn;
47
+ private cleanupFn;
48
+ private pingTimeoutTimer;
49
+ private pingIntervalTimer;
50
+ /**
51
+ * This is the session identifier that the client will use in the subsequent HTTP requests. It must not be shared with
52
+ * others parties, as it might lead to session hijacking.
53
+ *
54
+ * @private
55
+ */
56
+ private readonly id;
57
+ get readyState(): ReadyState;
58
+ set readyState(state: ReadyState);
59
+ constructor(id: string, server: BaseServer, transport: Transport, req: EngineRequest, protocol: number);
60
+ /**
61
+ * Called upon transport considered open.
62
+ *
63
+ * @private
64
+ */
65
+ private onOpen;
66
+ /**
67
+ * Called upon transport packet.
68
+ *
69
+ * @param {Object} packet
70
+ * @private
71
+ */
72
+ private onPacket;
73
+ /**
74
+ * Called upon transport error.
75
+ *
76
+ * @param {Error} err - error object
77
+ * @private
78
+ */
79
+ private onError;
80
+ /**
81
+ * Pings client every `this.pingInterval` and expects response
82
+ * within `this.pingTimeout` or closes connection.
83
+ *
84
+ * @private
85
+ */
86
+ private schedulePing;
87
+ /**
88
+ * Resets ping timeout.
89
+ *
90
+ * @private
91
+ */
92
+ private resetPingTimeout;
93
+ /**
94
+ * Attaches handlers for the given transport.
95
+ *
96
+ * @param {Transport} transport
97
+ * @private
98
+ */
99
+ private setTransport;
100
+ /**
101
+ * Upon transport "drain" event
102
+ *
103
+ * @private
104
+ */
105
+ private onDrain;
106
+ /**
107
+ * Upgrades socket to the given transport
108
+ *
109
+ * @param {Transport} transport
110
+ * @private
111
+ */
112
+ _maybeUpgrade(transport: Transport): void;
113
+ /**
114
+ * Clears listeners and timers associated with current transport.
115
+ *
116
+ * @private
117
+ */
118
+ private clearTransport;
119
+ /**
120
+ * Called upon transport considered closed.
121
+ * Possible reasons: `ping timeout`, `client error`, `parse error`,
122
+ * `transport error`, `server close`, `transport close`
123
+ */
124
+ private onClose;
125
+ /**
126
+ * Sends a message packet.
127
+ *
128
+ * @param {Object} data
129
+ * @param {Object} options
130
+ * @param {Function} callback
131
+ * @return {Socket} for chaining
132
+ */
133
+ send(data: RawData, options?: SendOptions, callback?: SendCallback): this;
134
+ /**
135
+ * Alias of {@link send}.
136
+ *
137
+ * @param data
138
+ * @param options
139
+ * @param callback
140
+ */
141
+ write(data: RawData, options?: SendOptions, callback?: SendCallback): this;
142
+ /**
143
+ * Sends a packet.
144
+ *
145
+ * @param {String} type - packet type
146
+ * @param {String} data
147
+ * @param {Object} options
148
+ * @param {Function} callback
149
+ *
150
+ * @private
151
+ */
152
+ private sendPacket;
153
+ /**
154
+ * Attempts to flush the packets buffer.
155
+ *
156
+ * @private
157
+ */
158
+ private flush;
159
+ /**
160
+ * Get available upgrades for this socket.
161
+ *
162
+ * @private
163
+ */
164
+ private getAvailableUpgrades;
165
+ /**
166
+ * Closes the socket and underlying transport.
167
+ *
168
+ * @param {Boolean} discard - optional, discard the transport
169
+ * @return {Socket} for chaining
170
+ */
171
+ close(discard?: boolean): void;
172
+ /**
173
+ * Closes the underlying transport.
174
+ *
175
+ * @param {Boolean} discard
176
+ * @private
177
+ */
178
+ private closeTransport;
179
+ }
180
+ export {};
@@ -0,0 +1,460 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Socket = void 0;
4
+ const events_1 = require("events");
5
+ const debug_1 = require("debug");
6
+ const timers_1 = require("timers");
7
+ const debug = (0, debug_1.default)("engine:socket");
8
+ class Socket extends events_1.EventEmitter {
9
+ get readyState() {
10
+ return this._readyState;
11
+ }
12
+ set readyState(state) {
13
+ debug("readyState updated from %s to %s", this._readyState, state);
14
+ this._readyState = state;
15
+ }
16
+ constructor(id, server, transport, req, protocol) {
17
+ super();
18
+ /**
19
+ * The current state of the socket.
20
+ */
21
+ this._readyState = "opening";
22
+ /* private */ this.upgrading = false;
23
+ /* private */ this.upgraded = false;
24
+ this.writeBuffer = [];
25
+ this.packetsFn = [];
26
+ this.sentCallbackFn = [];
27
+ this.cleanupFn = [];
28
+ this.id = id;
29
+ this.server = server;
30
+ this.request = req;
31
+ this.protocol = protocol;
32
+ // Cache IP since it might not be in the req later
33
+ if (req) {
34
+ if (req.websocket && req.websocket._socket) {
35
+ this.remoteAddress = req.websocket._socket.remoteAddress;
36
+ }
37
+ else {
38
+ this.remoteAddress = req.connection.remoteAddress;
39
+ }
40
+ }
41
+ else {
42
+ // TODO there is currently no way to get the IP address of the client when it connects with WebTransport
43
+ // see https://github.com/fails-components/webtransport/issues/114
44
+ }
45
+ this.pingTimeoutTimer = null;
46
+ this.pingIntervalTimer = null;
47
+ this.setTransport(transport);
48
+ this.onOpen();
49
+ }
50
+ /**
51
+ * Called upon transport considered open.
52
+ *
53
+ * @private
54
+ */
55
+ onOpen() {
56
+ this.readyState = "open";
57
+ // sends an `open` packet
58
+ this.transport.sid = this.id;
59
+ this.sendPacket("open", JSON.stringify({
60
+ sid: this.id,
61
+ upgrades: this.getAvailableUpgrades(),
62
+ pingInterval: this.server.opts.pingInterval,
63
+ pingTimeout: this.server.opts.pingTimeout,
64
+ maxPayload: this.server.opts.maxHttpBufferSize,
65
+ }));
66
+ if (this.server.opts.initialPacket) {
67
+ this.sendPacket("message", this.server.opts.initialPacket);
68
+ }
69
+ this.emit("open");
70
+ if (this.protocol === 3) {
71
+ // in protocol v3, the client sends a ping, and the server answers with a pong
72
+ this.resetPingTimeout();
73
+ }
74
+ else {
75
+ // in protocol v4, the server sends a ping, and the client answers with a pong
76
+ this.schedulePing();
77
+ }
78
+ }
79
+ /**
80
+ * Called upon transport packet.
81
+ *
82
+ * @param {Object} packet
83
+ * @private
84
+ */
85
+ onPacket(packet) {
86
+ if ("open" !== this.readyState) {
87
+ return debug("packet received with closed socket");
88
+ }
89
+ // export packet event
90
+ debug(`received packet ${packet.type}`);
91
+ this.emit("packet", packet);
92
+ switch (packet.type) {
93
+ case "ping":
94
+ if (this.transport.protocol !== 3) {
95
+ this.onError(new Error("invalid heartbeat direction"));
96
+ return;
97
+ }
98
+ debug("got ping");
99
+ this.pingTimeoutTimer.refresh();
100
+ this.sendPacket("pong");
101
+ this.emit("heartbeat");
102
+ break;
103
+ case "pong":
104
+ if (this.transport.protocol === 3) {
105
+ this.onError(new Error("invalid heartbeat direction"));
106
+ return;
107
+ }
108
+ debug("got pong");
109
+ (0, timers_1.clearTimeout)(this.pingTimeoutTimer);
110
+ this.pingIntervalTimer.refresh();
111
+ this.emit("heartbeat");
112
+ break;
113
+ case "error":
114
+ this.onClose("parse error");
115
+ break;
116
+ case "message":
117
+ this.emit("data", packet.data);
118
+ this.emit("message", packet.data);
119
+ break;
120
+ }
121
+ }
122
+ /**
123
+ * Called upon transport error.
124
+ *
125
+ * @param {Error} err - error object
126
+ * @private
127
+ */
128
+ onError(err) {
129
+ debug("transport error");
130
+ this.onClose("transport error", err);
131
+ }
132
+ /**
133
+ * Pings client every `this.pingInterval` and expects response
134
+ * within `this.pingTimeout` or closes connection.
135
+ *
136
+ * @private
137
+ */
138
+ schedulePing() {
139
+ this.pingIntervalTimer = (0, timers_1.setTimeout)(() => {
140
+ debug("writing ping packet - expecting pong within %sms", this.server.opts.pingTimeout);
141
+ this.sendPacket("ping");
142
+ this.resetPingTimeout();
143
+ }, this.server.opts.pingInterval);
144
+ }
145
+ /**
146
+ * Resets ping timeout.
147
+ *
148
+ * @private
149
+ */
150
+ resetPingTimeout() {
151
+ (0, timers_1.clearTimeout)(this.pingTimeoutTimer);
152
+ this.pingTimeoutTimer = (0, timers_1.setTimeout)(() => {
153
+ if (this.readyState === "closed")
154
+ return;
155
+ this.onClose("ping timeout");
156
+ }, this.protocol === 3
157
+ ? this.server.opts.pingInterval + this.server.opts.pingTimeout
158
+ : this.server.opts.pingTimeout);
159
+ }
160
+ /**
161
+ * Attaches handlers for the given transport.
162
+ *
163
+ * @param {Transport} transport
164
+ * @private
165
+ */
166
+ setTransport(transport) {
167
+ const onError = this.onError.bind(this);
168
+ const onReady = () => this.flush();
169
+ const onPacket = this.onPacket.bind(this);
170
+ const onDrain = this.onDrain.bind(this);
171
+ const onClose = this.onClose.bind(this, "transport close");
172
+ this.transport = transport;
173
+ this.transport.once("error", onError);
174
+ this.transport.on("ready", onReady);
175
+ this.transport.on("packet", onPacket);
176
+ this.transport.on("drain", onDrain);
177
+ this.transport.once("close", onClose);
178
+ this.cleanupFn.push(function () {
179
+ transport.removeListener("error", onError);
180
+ transport.removeListener("ready", onReady);
181
+ transport.removeListener("packet", onPacket);
182
+ transport.removeListener("drain", onDrain);
183
+ transport.removeListener("close", onClose);
184
+ });
185
+ }
186
+ /**
187
+ * Upon transport "drain" event
188
+ *
189
+ * @private
190
+ */
191
+ onDrain() {
192
+ if (this.sentCallbackFn.length > 0) {
193
+ debug("executing batch send callback");
194
+ const seqFn = this.sentCallbackFn.shift();
195
+ if (seqFn) {
196
+ for (let i = 0; i < seqFn.length; i++) {
197
+ seqFn[i](this.transport);
198
+ }
199
+ }
200
+ }
201
+ }
202
+ /**
203
+ * Upgrades socket to the given transport
204
+ *
205
+ * @param {Transport} transport
206
+ * @private
207
+ */
208
+ /* private */ _maybeUpgrade(transport) {
209
+ debug('might upgrade socket transport from "%s" to "%s"', this.transport.name, transport.name);
210
+ this.upgrading = true;
211
+ // set transport upgrade timer
212
+ const upgradeTimeoutTimer = (0, timers_1.setTimeout)(() => {
213
+ debug("client did not complete upgrade - closing transport");
214
+ cleanup();
215
+ if ("open" === transport.readyState) {
216
+ transport.close();
217
+ }
218
+ }, this.server.opts.upgradeTimeout);
219
+ let checkIntervalTimer;
220
+ const onPacket = (packet) => {
221
+ if ("ping" === packet.type && "probe" === packet.data) {
222
+ debug("got probe ping packet, sending pong");
223
+ transport.send([{ type: "pong", data: "probe" }]);
224
+ this.emit("upgrading", transport);
225
+ clearInterval(checkIntervalTimer);
226
+ checkIntervalTimer = setInterval(check, 100);
227
+ }
228
+ else if ("upgrade" === packet.type && this.readyState !== "closed") {
229
+ debug("got upgrade packet - upgrading");
230
+ cleanup();
231
+ this.transport.discard();
232
+ this.upgraded = true;
233
+ this.clearTransport();
234
+ this.setTransport(transport);
235
+ this.emit("upgrade", transport);
236
+ this.flush();
237
+ if (this.readyState === "closing") {
238
+ transport.close(() => {
239
+ this.onClose("forced close");
240
+ });
241
+ }
242
+ }
243
+ else {
244
+ cleanup();
245
+ transport.close();
246
+ }
247
+ };
248
+ // we force a polling cycle to ensure a fast upgrade
249
+ const check = () => {
250
+ if ("polling" === this.transport.name && this.transport.writable) {
251
+ debug("writing a noop packet to polling for fast upgrade");
252
+ this.transport.send([{ type: "noop" }]);
253
+ }
254
+ };
255
+ const cleanup = () => {
256
+ this.upgrading = false;
257
+ clearInterval(checkIntervalTimer);
258
+ (0, timers_1.clearTimeout)(upgradeTimeoutTimer);
259
+ transport.removeListener("packet", onPacket);
260
+ transport.removeListener("close", onTransportClose);
261
+ transport.removeListener("error", onError);
262
+ this.removeListener("close", onClose);
263
+ };
264
+ const onError = (err) => {
265
+ debug("client did not complete upgrade - %s", err);
266
+ cleanup();
267
+ transport.close();
268
+ transport = null;
269
+ };
270
+ const onTransportClose = () => {
271
+ onError("transport closed");
272
+ };
273
+ const onClose = () => {
274
+ onError("socket closed");
275
+ };
276
+ transport.on("packet", onPacket);
277
+ transport.once("close", onTransportClose);
278
+ transport.once("error", onError);
279
+ this.once("close", onClose);
280
+ }
281
+ /**
282
+ * Clears listeners and timers associated with current transport.
283
+ *
284
+ * @private
285
+ */
286
+ clearTransport() {
287
+ let cleanup;
288
+ const toCleanUp = this.cleanupFn.length;
289
+ for (let i = 0; i < toCleanUp; i++) {
290
+ cleanup = this.cleanupFn.shift();
291
+ cleanup();
292
+ }
293
+ // silence further transport errors and prevent uncaught exceptions
294
+ this.transport.on("error", function () {
295
+ debug("error triggered by discarded transport");
296
+ });
297
+ // ensure transport won't stay open
298
+ this.transport.close();
299
+ (0, timers_1.clearTimeout)(this.pingTimeoutTimer);
300
+ }
301
+ /**
302
+ * Called upon transport considered closed.
303
+ * Possible reasons: `ping timeout`, `client error`, `parse error`,
304
+ * `transport error`, `server close`, `transport close`
305
+ */
306
+ onClose(reason, description) {
307
+ if ("closed" !== this.readyState) {
308
+ this.readyState = "closed";
309
+ // clear timers
310
+ (0, timers_1.clearTimeout)(this.pingIntervalTimer);
311
+ (0, timers_1.clearTimeout)(this.pingTimeoutTimer);
312
+ // clean writeBuffer in next tick, so developers can still
313
+ // grab the writeBuffer on 'close' event
314
+ process.nextTick(() => {
315
+ this.writeBuffer = [];
316
+ });
317
+ this.packetsFn = [];
318
+ this.sentCallbackFn = [];
319
+ this.clearTransport();
320
+ this.emit("close", reason, description);
321
+ }
322
+ }
323
+ /**
324
+ * Sends a message packet.
325
+ *
326
+ * @param {Object} data
327
+ * @param {Object} options
328
+ * @param {Function} callback
329
+ * @return {Socket} for chaining
330
+ */
331
+ send(data, options, callback) {
332
+ this.sendPacket("message", data, options, callback);
333
+ return this;
334
+ }
335
+ /**
336
+ * Alias of {@link send}.
337
+ *
338
+ * @param data
339
+ * @param options
340
+ * @param callback
341
+ */
342
+ write(data, options, callback) {
343
+ this.sendPacket("message", data, options, callback);
344
+ return this;
345
+ }
346
+ /**
347
+ * Sends a packet.
348
+ *
349
+ * @param {String} type - packet type
350
+ * @param {String} data
351
+ * @param {Object} options
352
+ * @param {Function} callback
353
+ *
354
+ * @private
355
+ */
356
+ sendPacket(type, data, options = {}, callback) {
357
+ if ("function" === typeof options) {
358
+ callback = options;
359
+ options = {};
360
+ }
361
+ if ("closing" !== this.readyState && "closed" !== this.readyState) {
362
+ debug('sending packet "%s" (%s)', type, data);
363
+ // compression is enabled by default
364
+ options.compress = options.compress !== false;
365
+ const packet = {
366
+ type,
367
+ options: options,
368
+ };
369
+ if (data)
370
+ packet.data = data;
371
+ // exports packetCreate event
372
+ this.emit("packetCreate", packet);
373
+ this.writeBuffer.push(packet);
374
+ // add send callback to object, if defined
375
+ if ("function" === typeof callback)
376
+ this.packetsFn.push(callback);
377
+ this.flush();
378
+ }
379
+ }
380
+ /**
381
+ * Attempts to flush the packets buffer.
382
+ *
383
+ * @private
384
+ */
385
+ flush() {
386
+ if ("closed" !== this.readyState &&
387
+ this.transport.writable &&
388
+ this.writeBuffer.length) {
389
+ debug("flushing buffer to transport");
390
+ this.emit("flush", this.writeBuffer);
391
+ this.server.emit("flush", this, this.writeBuffer);
392
+ const wbuf = this.writeBuffer;
393
+ this.writeBuffer = [];
394
+ if (this.packetsFn.length) {
395
+ this.sentCallbackFn.push(this.packetsFn);
396
+ this.packetsFn = [];
397
+ }
398
+ else {
399
+ this.sentCallbackFn.push(null);
400
+ }
401
+ this.transport.send(wbuf);
402
+ this.emit("drain");
403
+ this.server.emit("drain", this);
404
+ }
405
+ }
406
+ /**
407
+ * Get available upgrades for this socket.
408
+ *
409
+ * @private
410
+ */
411
+ getAvailableUpgrades() {
412
+ const availableUpgrades = [];
413
+ const allUpgrades = this.server.upgrades(this.transport.name);
414
+ for (let i = 0; i < allUpgrades.length; ++i) {
415
+ const upg = allUpgrades[i];
416
+ if (this.server.opts.transports.indexOf(upg) !== -1) {
417
+ availableUpgrades.push(upg);
418
+ }
419
+ }
420
+ return availableUpgrades;
421
+ }
422
+ /**
423
+ * Closes the socket and underlying transport.
424
+ *
425
+ * @param {Boolean} discard - optional, discard the transport
426
+ * @return {Socket} for chaining
427
+ */
428
+ close(discard) {
429
+ if (discard &&
430
+ (this.readyState === "open" || this.readyState === "closing")) {
431
+ return this.closeTransport(discard);
432
+ }
433
+ if ("open" !== this.readyState)
434
+ return;
435
+ this.readyState = "closing";
436
+ if (this.writeBuffer.length) {
437
+ debug("there are %d remaining packets in the buffer, waiting for the 'drain' event", this.writeBuffer.length);
438
+ this.once("drain", () => {
439
+ debug("all packets have been sent, closing the transport");
440
+ this.closeTransport(discard);
441
+ });
442
+ return;
443
+ }
444
+ debug("the buffer is empty, closing the transport right away");
445
+ this.closeTransport(discard);
446
+ }
447
+ /**
448
+ * Closes the underlying transport.
449
+ *
450
+ * @param {Boolean} discard
451
+ * @private
452
+ */
453
+ closeTransport(discard) {
454
+ debug("closing the transport (discard? %s)", !!discard);
455
+ if (discard)
456
+ this.transport.discard();
457
+ this.transport.close(this.onClose.bind(this, "forced close"));
458
+ }
459
+ }
460
+ exports.Socket = Socket;