chore: migrate to TypeScript

Related: https://github.com/socketio/engine.io/issues/510
This commit is contained in:
Damien Arrachequesne
2021-10-08 14:55:30 +02:00
parent 18a6eb89fb
commit c0d6eaa1ba
19 changed files with 481 additions and 1129 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
node_modules
npm-debug.log
build/

View File

@@ -1,127 +0,0 @@
/**
* Module dependencies.
*/
const http = require("http");
const Server = require("./server");
/**
* Invoking the library as a function delegates to attach if the first argument
* is an `http.Server`.
*
* If there are no arguments or the first argument is an options object, then
* a new Server instance is returned.
*
* @param {http.Server} server (if specified, will be attached to by the new Server instance)
* @param {Object} options
* @return {Server} engine server
* @api public
*/
exports = module.exports = function() {
// backwards compatible use as `.attach`
// if first argument is an http server
if (arguments.length && arguments[0] instanceof http.Server) {
return attach.apply(this, arguments);
}
// if first argument is not an http server, then just make a regular eio server
return new Server(...arguments);
};
/**
* Protocol revision number.
*
* @api public
*/
exports.protocol = 1;
/**
* Expose Server constructor.
*
* @api public
*/
exports.Server = Server;
/**
* Expose Socket constructor.
*
* @api public
*/
exports.Socket = require("./socket");
/**
* Expose Transport constructor.
*
* @api public
*/
exports.Transport = require("./transport");
/**
* Expose mutable list of available transports.
*
* @api public
*/
exports.transports = require("./transports");
/**
* Exports parser.
*
* @api public
*/
exports.parser = require("engine.io-parser");
/**
* Creates an http.Server exclusively used for WS upgrades.
*
* @param {Number} port
* @param {Function} callback
* @param {Object} options
* @return {Server} websocket.io server
* @api public
*/
exports.listen = listen;
function listen(port, options, fn) {
if ("function" === typeof options) {
fn = options;
options = {};
}
const server = http.createServer(function(req, res) {
res.writeHead(501);
res.end("Not Implemented");
});
// create engine server
const engine = exports.attach(server, options);
engine.httpServer = server;
server.listen(port, fn);
return engine;
}
/**
* Captures upgrade requests for a http.Server.
*
* @param {http.Server} server
* @param {Object} options
* @return {Server} engine server
* @api public
*/
exports.attach = attach;
function attach(server, options) {
const engine = new Server(options);
engine.attach(server, options);
return engine;
}

55
lib/engine.io.ts Normal file
View File

@@ -0,0 +1,55 @@
import { createServer } from "http";
import { Server, AttachOptions, ServerOptions } from "./server";
import transports from "./transports/index";
import * as parser from "engine.io-parser";
export { Server, transports, listen, attach, parser };
export { AttachOptions, ServerOptions } from "./server";
export { Socket } from "./socket";
export { Transport } from "./transport";
export const protocol = parser.protocol;
/**
* Creates an http.Server exclusively used for WS upgrades.
*
* @param {Number} port
* @param {Function} callback
* @param {Object} options
* @return {Server} websocket.io server
* @api public
*/
function listen(port, options: AttachOptions & ServerOptions, fn) {
if ("function" === typeof options) {
fn = options;
options = {};
}
const server = createServer(function(req, res) {
res.writeHead(501);
res.end("Not Implemented");
});
// create engine server
const engine = attach(server, options);
engine.httpServer = server;
server.listen(port, fn);
return engine;
}
/**
* Captures upgrade requests for a http.Server.
*
* @param {http.Server} server
* @param {Object} options
* @return {Server} engine server
* @api public
*/
function attach(server, options: AttachOptions & ServerOptions) {
const engine = new Server(options);
engine.attach(server, options);
return engine;
}

View File

@@ -9,7 +9,7 @@ var utf8 = require('./utf8');
/**
* Current protocol version.
*/
exports.protocol = 3;
export const protocol = 3;
const hasBinary = (packets) => {
for (const packet of packets) {
@@ -24,7 +24,7 @@ const hasBinary = (packets) => {
* Packet types.
*/
var packets = exports.packets = {
export const packets = {
open: 0 // non-ws
, close: 1 // non-ws
, ping: 2
@@ -60,7 +60,7 @@ const EMPTY_BUFFER = Buffer.concat([]);
* @api private
*/
exports.encodePacket = function (packet, supportsBinary, utf8encode, callback) {
export function encodePacket (packet, supportsBinary, utf8encode, callback) {
if (typeof supportsBinary === 'function') {
callback = supportsBinary;
supportsBinary = null;
@@ -94,7 +94,7 @@ exports.encodePacket = function (packet, supportsBinary, utf8encode, callback) {
function encodeBuffer(packet, supportsBinary, callback) {
if (!supportsBinary) {
return exports.encodeBase64Packet(packet, callback);
return encodeBase64Packet(packet, callback);
}
var data = packet.data;
@@ -110,7 +110,7 @@ function encodeBuffer(packet, supportsBinary, callback) {
* @return {String} base64 encoded message
*/
exports.encodeBase64Packet = function(packet, callback){
export function encodeBase64Packet (packet, callback){
var data = Buffer.isBuffer(packet.data) ? packet.data : arrayBufferToBuffer(packet.data);
var message = 'b' + packets[packet.type];
message += data.toString('base64');
@@ -124,7 +124,7 @@ exports.encodeBase64Packet = function(packet, callback){
* @api private
*/
exports.decodePacket = function (data, binaryType, utf8decode) {
export function decodePacket (data, binaryType, utf8decode) {
if (data === undefined) {
return err;
}
@@ -137,7 +137,7 @@ exports.decodePacket = function (data, binaryType, utf8decode) {
type = data.charAt(0);
if (type === 'b') {
return exports.decodeBase64Packet(data.substr(1), binaryType);
return decodeBase64Packet(data.substr(1), binaryType);
}
if (utf8decode) {
@@ -189,7 +189,7 @@ function tryDecode(data) {
* @return {Object} with `type` and `data` (if any)
*/
exports.decodeBase64Packet = function(msg, binaryType) {
export function decodeBase64Packet (msg, binaryType) {
var type = packetslist[msg.charAt(0)];
var data = Buffer.from(msg.substr(1), 'base64');
if (binaryType === 'arraybuffer') {
@@ -197,6 +197,7 @@ exports.decodeBase64Packet = function(msg, binaryType) {
for (var i = 0; i < abv.length; i++){
abv[i] = data[i];
}
// @ts-ignore
data = abv.buffer;
}
return { type: type, data: data };
@@ -218,14 +219,14 @@ exports.decodeBase64Packet = function(msg, binaryType) {
* @api private
*/
exports.encodePayload = function (packets, supportsBinary, callback) {
export function encodePayload (packets, supportsBinary, callback) {
if (typeof supportsBinary === 'function') {
callback = supportsBinary;
supportsBinary = null;
}
if (supportsBinary && hasBinary(packets)) {
return exports.encodePayloadAsBinary(packets, callback);
return encodePayloadAsBinary(packets, callback);
}
if (!packets.length) {
@@ -233,7 +234,7 @@ exports.encodePayload = function (packets, supportsBinary, callback) {
}
function encodeOne(packet, doneCallback) {
exports.encodePacket(packet, supportsBinary, false, function(message) {
encodePacket(packet, supportsBinary, false, function(message) {
doneCallback(null, setLengthHeader(message));
});
}
@@ -273,9 +274,9 @@ function map(ary, each, done) {
* @api public
*/
exports.decodePayload = function (data, binaryType, callback) {
export function decodePayload (data, binaryType, callback) {
if (typeof data !== 'string') {
return exports.decodePayloadAsBinary(data, binaryType, callback);
return decodePayloadAsBinary(data, binaryType, callback);
}
if (typeof binaryType === 'function') {
@@ -298,6 +299,7 @@ exports.decodePayload = function (data, binaryType, callback) {
continue;
}
// @ts-ignore
if (length === '' || (length != (n = Number(length)))) {
// parser error - ignoring payload
return callback(err, 0, 1);
@@ -311,7 +313,7 @@ exports.decodePayload = function (data, binaryType, callback) {
}
if (msg.length) {
packet = exports.decodePacket(msg, binaryType, false);
packet = decodePacket(msg, binaryType, false);
if (err.type === packet.type && err.data === packet.data) {
// parser error in individual packet - ignoring payload
@@ -393,7 +395,7 @@ function arrayBufferToBuffer(data) {
* @api private
*/
exports.encodePayloadAsBinary = function (packets, callback) {
export function encodePayloadAsBinary (packets, callback) {
if (!packets.length) {
return callback(EMPTY_BUFFER);
}
@@ -430,7 +432,7 @@ function encodeOneBinaryPacket(p, doneCallback) {
doneCallback(null, Buffer.concat([sizeBuffer, packet]));
}
exports.encodePacket(p, true, true, onBinaryPacketEncode);
encodePacket(p, true, true, onBinaryPacketEncode);
}
@@ -444,7 +446,7 @@ function encodeOneBinaryPacket(p, doneCallback) {
* @api public
*/
exports.decodePayloadAsBinary = function (data, binaryType, callback) {
export function decodePayloadAsBinary (data, binaryType, callback) {
if (typeof binaryType === 'function') {
callback = binaryType;
binaryType = null;
@@ -478,6 +480,6 @@ exports.decodePayloadAsBinary = function (data, binaryType, callback) {
var total = buffers.length;
for (i = 0; i < total; i++) {
var buffer = buffers[i];
callback(exports.decodePacket(buffer, binaryType, true), i, total);
callback(decodePacket(buffer, binaryType, true), i, total);
}
};

View File

@@ -203,7 +203,7 @@ function utf8decode(byteString, opts) {
return ucs2encode(codePoints);
}
module.exports = {
export default {
version: '2.1.2',
encode: utf8encode,
decode: utf8decode

View File

@@ -1,22 +1,135 @@
const qs = require("querystring");
const parse = require("url").parse;
const base64id = require("base64id");
const transports = require("./transports");
const EventEmitter = require("events").EventEmitter;
const Socket = require("./socket");
const debug = require("debug")("engine");
const cookieMod = require("cookie");
import * as qs from "querystring";
import { parse } from "url";
import * as base64id from "base64id";
import transports from "./transports";
import { EventEmitter } from "events";
import { Socket } from "./socket";
import debugModule from "debug";
import { serialize } from "cookie";
import { Server as DEFAULT_WS_ENGINE } from "ws";
import { IncomingMessage, Server as HttpServer } from "http";
import { CookieSerializeOptions } from "cookie";
import { CorsOptions } from "cors";
const DEFAULT_WS_ENGINE = require("ws").Server;
const debug = debugModule("engine");
type Transport = "polling" | "websocket";
export interface AttachOptions {
/**
* name of the path to capture
* @default "/engine.io"
*/
path?: string;
/**
* destroy unhandled upgrade requests
* @default true
*/
destroyUpgrade?: boolean;
/**
* milliseconds after which unhandled requests are ended
* @default 1000
*/
destroyUpgradeTimeout?: number;
}
export interface ServerOptions {
/**
* how many ms without a pong packet to consider the connection closed
* @default 20000
*/
pingTimeout?: number;
/**
* how many ms before sending a new ping packet
* @default 25000
*/
pingInterval?: number;
/**
* how many ms before an uncompleted transport upgrade is cancelled
* @default 10000
*/
upgradeTimeout?: number;
/**
* how many bytes or characters a message can be, before closing the session (to avoid DoS).
* @default 1e5 (100 KB)
*/
maxHttpBufferSize?: number;
/**
* A function that receives a given handshake or upgrade request as its first parameter,
* and can decide whether to continue or not. The second argument is a function that needs
* to be called with the decided information: fn(err, success), where success is a boolean
* value where false means that the request is rejected, and err is an error code.
*/
allowRequest?: (
req: IncomingMessage,
fn: (err: string | null | undefined, success: boolean) => void
) => void;
/**
* the low-level transports that are enabled
* @default ["polling", "websocket"]
*/
transports?: Transport[];
/**
* whether to allow transport upgrades
* @default true
*/
allowUpgrades?: boolean;
/**
* parameters of the WebSocket permessage-deflate extension (see ws module api docs). Set to false to disable.
* @default false
*/
perMessageDeflate?: boolean | object;
/**
* parameters of the http compression for the polling transports (see zlib api docs). Set to false to disable.
* @default true
*/
httpCompression?: boolean | object;
/**
* what WebSocket server implementation to use. Specified module must
* conform to the ws interface (see ws module api docs).
* An alternative c++ addon is also available by installing eiows module.
*
* @default `require("ws").Server`
*/
wsEngine?: any;
/**
* an optional packet which will be concatenated to the handshake packet emitted by Engine.IO.
*/
initialPacket?: any;
/**
* configuration of the cookie that contains the client sid to send as part of handshake response headers. This cookie
* might be used for sticky-session. Defaults to not sending any cookie.
* @default false
*/
cookie?: (CookieSerializeOptions & { name: string }) | boolean;
/**
* the options that will be forwarded to the cors module
*/
cors?: CorsOptions;
/**
* whether to enable compatibility with Socket.IO v2 clients
* @default false
*/
allowEIO3?: boolean;
}
export class Server extends EventEmitter {
public opts: ServerOptions;
public httpServer?: HttpServer;
private clients: any;
private clientsCount: number;
private ws: any;
private corsMiddleware: Function;
private perMessageDeflate: any;
class Server extends EventEmitter {
/**
* Server constructor.
*
* @param {Object} options
* @param {Object} opts - options
* @api public
*/
constructor(opts = {}) {
constructor(opts: ServerOptions = {}) {
super();
this.clients = {};
@@ -45,6 +158,7 @@ class Server extends EventEmitter {
{
name: "io",
path: "/",
// @ts-ignore
httpOnly: opts.cookie.path !== false,
sameSite: "lax"
},
@@ -73,7 +187,7 @@ class Server extends EventEmitter {
*
* @api private
*/
init() {
private init() {
if (!~this.opts.transports.indexOf("websocket")) return;
if (this.ws) this.ws.close();
@@ -111,7 +225,7 @@ class Server extends EventEmitter {
* @return {Array}
* @api public
*/
upgrades(transport) {
public upgrades(transport) {
if (!this.opts.allowUpgrades) return [];
return transports[transport].upgradesTo || [];
}
@@ -123,7 +237,7 @@ class Server extends EventEmitter {
* @return {Boolean} whether the request is valid
* @api private
*/
verify(req, upgrade, fn) {
private verify(req, upgrade, fn) {
// transport check
const transport = req._query.transport;
if (!~this.opts.transports.indexOf(transport)) {
@@ -189,7 +303,7 @@ class Server extends EventEmitter {
*
* @api private
*/
prepare(req) {
private prepare(req) {
// try to leverage pre-existing `req._query` (e.g: from connect)
if (!req._query) {
req._query = ~req.url.indexOf("?") ? qs.parse(parse(req.url).query) : {};
@@ -201,7 +315,7 @@ class Server extends EventEmitter {
*
* @api public
*/
close() {
public close() {
debug("closing all open clients");
for (let i in this.clients) {
if (this.clients.hasOwnProperty(i)) {
@@ -223,7 +337,7 @@ class Server extends EventEmitter {
* @param {http.ServerResponse|http.OutgoingMessage} response
* @api public
*/
handleRequest(req, res) {
public handleRequest(req, res) {
debug('handling "%s" http request "%s"', req.method, req.url);
this.prepare(req);
req.res = res;
@@ -266,7 +380,7 @@ class Server extends EventEmitter {
* @param {Object} request object
* @api public
*/
generateId(req) {
public generateId(req) {
return base64id.generateId();
}
@@ -279,7 +393,7 @@ class Server extends EventEmitter {
*
* @api private
*/
async handshake(transportName, req, closeConnection) {
private async handshake(transportName, req, closeConnection) {
const protocol = req._query.EIO === "4" ? 4 : 3; // 3rd revision by default
if (protocol === 3 && !this.opts.allowEIO3) {
debug("unsupported protocol version");
@@ -352,7 +466,8 @@ class Server extends EventEmitter {
if (isInitialRequest) {
if (this.opts.cookie) {
headers["Set-Cookie"] = [
cookieMod.serialize(this.opts.cookie.name, id, this.opts.cookie)
// @ts-ignore
serialize(this.opts.cookie.name, id, this.opts.cookie)
];
}
this.emit("initial_headers", headers, req);
@@ -378,7 +493,7 @@ class Server extends EventEmitter {
*
* @api public
*/
handleUpgrade(req, socket, upgradeHead) {
public handleUpgrade(req, socket, upgradeHead) {
this.prepare(req);
this.verify(req, true, (errorCode, errorContext) => {
@@ -409,7 +524,7 @@ class Server extends EventEmitter {
* @param {ws.Socket} websocket
* @api private
*/
onWebSocket(req, socket, websocket) {
private onWebSocket(req, socket, websocket) {
websocket.on("error", onUpgradeError);
if (
@@ -475,7 +590,7 @@ class Server extends EventEmitter {
* @param {Object} options
* @api public
*/
attach(server, options = {}) {
public attach(server, options: AttachOptions = {}) {
let path = (options.path || "/engine.io").replace(/\/$/, "");
const destroyUpgradeTimeout = options.destroyUpgradeTimeout || 1000;
@@ -525,30 +640,30 @@ class Server extends EventEmitter {
});
}
}
/**
* Protocol errors mappings.
*/
static errors = {
UNKNOWN_TRANSPORT: 0,
UNKNOWN_SID: 1,
BAD_HANDSHAKE_METHOD: 2,
BAD_REQUEST: 3,
FORBIDDEN: 4,
UNSUPPORTED_PROTOCOL_VERSION: 5
};
static errorMessages = {
0: "Transport unknown",
1: "Session ID unknown",
2: "Bad handshake method",
3: "Bad request",
4: "Forbidden",
5: "Unsupported protocol version"
};
}
/**
* Protocol errors mappings.
*/
Server.errors = {
UNKNOWN_TRANSPORT: 0,
UNKNOWN_SID: 1,
BAD_HANDSHAKE_METHOD: 2,
BAD_REQUEST: 3,
FORBIDDEN: 4,
UNSUPPORTED_PROTOCOL_VERSION: 5
};
Server.errorMessages = {
0: "Transport unknown",
1: "Session ID unknown",
2: "Bad handshake method",
3: "Bad request",
4: "Forbidden",
5: "Unsupported protocol version"
};
/**
* Close the HTTP long-polling request
*
@@ -585,7 +700,11 @@ function abortRequest(res, errorCode, errorContext) {
* @api private
*/
function abortUpgrade(socket, errorCode, errorContext = {}) {
function abortUpgrade(
socket,
errorCode,
errorContext: { message?: string } = {}
) {
socket.on("error", () => {
debug("ignoring error from closed connection");
});
@@ -606,8 +725,6 @@ function abortUpgrade(socket, errorCode, errorContext = {}) {
socket.destroy();
}
module.exports = Server;
/* eslint-disable */
/**

View File

@@ -1,7 +1,33 @@
const EventEmitter = require("events");
const debug = require("debug")("engine:socket");
import { EventEmitter } from "events";
import debugModule from "debug";
import { IncomingMessage } from "http";
import { Transport } from "./transport";
import { Server } from "./server";
const debug = debugModule("engine:socket");
export class Socket extends EventEmitter {
public readonly protocol: number;
public readonly request: IncomingMessage;
public readonly remoteAddress: string;
public readyState: string;
public transport: Transport;
private server: Server;
private upgrading: boolean;
private upgraded: boolean;
private writeBuffer: any[];
private packetsFn: any[];
private sentCallbackFn: any[];
private cleanupFn: any[];
private checkIntervalTimer;
private upgradeTimeoutTimer;
private pingTimeoutTimer;
private pingIntervalTimer;
private readonly id: string;
class Socket extends EventEmitter {
/**
* Client class (abstract).
*
@@ -42,7 +68,7 @@ class Socket extends EventEmitter {
*
* @api private
*/
onOpen() {
private onOpen() {
this.readyState = "open";
// sends an `open` packet
@@ -80,7 +106,7 @@ class Socket extends EventEmitter {
* @param {Object} packet
* @api private
*/
onPacket(packet) {
private onPacket(packet) {
if ("open" !== this.readyState) {
return debug("packet received with closed socket");
}
@@ -132,7 +158,7 @@ class Socket extends EventEmitter {
* @param {Error} error object
* @api private
*/
onError(err) {
private onError(err) {
debug("transport error");
this.onClose("transport error", err);
}
@@ -143,7 +169,7 @@ class Socket extends EventEmitter {
*
* @api private
*/
schedulePing() {
private schedulePing() {
clearTimeout(this.pingIntervalTimer);
this.pingIntervalTimer = setTimeout(() => {
debug(
@@ -160,7 +186,7 @@ class Socket extends EventEmitter {
*
* @api private
*/
resetPingTimeout(timeout) {
private resetPingTimeout(timeout) {
clearTimeout(this.pingTimeoutTimer);
this.pingTimeoutTimer = setTimeout(() => {
if (this.readyState === "closed") return;
@@ -174,7 +200,7 @@ class Socket extends EventEmitter {
* @param {Transport} transport
* @api private
*/
setTransport(transport) {
private setTransport(transport) {
const onError = this.onError.bind(this);
const onPacket = this.onPacket.bind(this);
const flush = this.flush.bind(this);
@@ -202,7 +228,7 @@ class Socket extends EventEmitter {
* @param {Transport} transport
* @api private
*/
maybeUpgrade(transport) {
private maybeUpgrade(transport) {
debug(
'might upgrade socket transport from "%s" to "%s"',
this.transport.name,
@@ -296,7 +322,7 @@ class Socket extends EventEmitter {
*
* @api private
*/
clearTransport() {
private clearTransport() {
let cleanup;
const toCleanUp = this.cleanupFn.length;
@@ -322,7 +348,7 @@ class Socket extends EventEmitter {
* Possible reasons: `ping timeout`, `client error`, `parse error`,
* `transport error`, `server close`, `transport close`
*/
onClose(reason, description) {
private onClose(reason: string, description?) {
if ("closed" !== this.readyState) {
this.readyState = "closed";
@@ -350,7 +376,7 @@ class Socket extends EventEmitter {
*
* @api private
*/
setupSendCallback() {
private setupSendCallback() {
// the message was sent successfully, execute the callback
const onDrain = () => {
if (this.sentCallbackFn.length > 0) {
@@ -387,12 +413,12 @@ class Socket extends EventEmitter {
* @return {Socket} for chaining
* @api public
*/
send(data, options, callback) {
public send(data, options, callback?) {
this.sendPacket("message", data, options, callback);
return this;
}
write(data, options, callback) {
public write(data, options, callback?) {
this.sendPacket("message", data, options, callback);
return this;
}
@@ -405,7 +431,7 @@ class Socket extends EventEmitter {
* @param {Object} options
* @api private
*/
sendPacket(type, data, options, callback) {
private sendPacket(type, data?, options?, callback?) {
if ("function" === typeof options) {
callback = options;
options = null;
@@ -417,7 +443,7 @@ class Socket extends EventEmitter {
if ("closing" !== this.readyState && "closed" !== this.readyState) {
debug('sending packet "%s" (%s)', type, data);
const packet = {
const packet: any = {
type: type,
options: options
};
@@ -440,7 +466,7 @@ class Socket extends EventEmitter {
*
* @api private
*/
flush() {
private flush() {
if (
"closed" !== this.readyState &&
this.transport.writable &&
@@ -468,7 +494,7 @@ class Socket extends EventEmitter {
*
* @api private
*/
getAvailableUpgrades() {
private getAvailableUpgrades() {
const availableUpgrades = [];
const allUpgrades = this.server.upgrades(this.transport.name);
let i = 0;
@@ -485,11 +511,11 @@ class Socket extends EventEmitter {
/**
* Closes the socket and underlying transport.
*
* @param {Boolean} optional, discard
* @param {Boolean} discard - optional, discard the transport
* @return {Socket} for chaining
* @api public
*/
close(discard) {
public close(discard?: boolean) {
if ("open" !== this.readyState) return;
this.readyState = "closing";
@@ -508,10 +534,8 @@ class Socket extends EventEmitter {
* @param {Boolean} discard
* @api private
*/
closeTransport(discard) {
private closeTransport(discard) {
if (discard) this.transport.discard();
this.transport.close(this.onClose.bind(this, "forced close"));
}
}
module.exports = Socket;

View File

@@ -1,7 +1,10 @@
const EventEmitter = require("events");
const parser_v4 = require("engine.io-parser");
const parser_v3 = require("./parser-v3/index");
const debug = require("debug")("engine:transport");
import { EventEmitter } from "events";
import * as parser_v4 from "engine.io-parser";
import * as parser_v3 from "./parser-v3/index";
import debugModule from "debug";
import { IncomingMessage } from "http";
const debug = debugModule("engine:transport");
/**
* Noop function.
@@ -11,7 +14,17 @@ const debug = require("debug")("engine:transport");
function noop() {}
class Transport extends EventEmitter {
export abstract class Transport extends EventEmitter {
public sid: string;
public writable: boolean;
public protocol: number;
protected readyState: string;
protected discarded: boolean;
protected parser: any;
protected req: IncomingMessage & { cleanup: Function };
protected supportsBinary: boolean;
/**
* Transport constructor.
*
@@ -39,9 +52,9 @@ class Transport extends EventEmitter {
* Called with an incoming HTTP request.
*
* @param {http.IncomingMessage} request
* @api private
* @api protected
*/
onRequest(req) {
protected onRequest(req) {
debug("setting request");
this.req = req;
}
@@ -51,7 +64,7 @@ class Transport extends EventEmitter {
*
* @api private
*/
close(fn) {
close(fn?) {
if ("closed" === this.readyState || "closing" === this.readyState) return;
this.readyState = "closing";
@@ -63,12 +76,14 @@ class Transport extends EventEmitter {
*
* @param {String} message error
* @param {Object} error description
* @api private
* @api protected
*/
onError(msg, desc) {
protected onError(msg: string, desc?) {
if (this.listeners("error").length) {
const err = new Error(msg);
// @ts-ignore
err.type = "TransportError";
// @ts-ignore
err.description = desc;
this.emit("error", err);
} else {
@@ -80,9 +95,9 @@ class Transport extends EventEmitter {
* Called with parsed out a packets from the data stream.
*
* @param {Object} packet
* @api private
* @api protected
*/
onPacket(packet) {
protected onPacket(packet) {
this.emit("packet", packet);
}
@@ -90,21 +105,24 @@ class Transport extends EventEmitter {
* Called with the encoded packet data.
*
* @param {String} data
* @api private
* @api protected
*/
onData(data) {
protected onData(data) {
this.onPacket(this.parser.decodePacket(data));
}
/**
* Called upon transport close.
*
* @api private
* @api protected
*/
onClose() {
protected onClose() {
this.readyState = "closed";
this.emit("close");
}
}
module.exports = Transport;
abstract get supportsFraming();
abstract get name();
abstract send(packets);
abstract doClose(fn?);
}

View File

@@ -1,31 +0,0 @@
const XHR = require("./polling");
const JSONP = require("./polling-jsonp");
/**
* Export transports.
*/
module.exports = exports = {
polling: polling,
websocket: require("./websocket")
};
/**
* Export upgrades map.
*/
exports.polling.upgradesTo = ["websocket"];
/**
* Polling polymorphic constructor.
*
* @api private
*/
function polling(req) {
if ("string" === typeof req._query.j) {
return new JSONP(req);
} else {
return new XHR(req);
}
}

24
lib/transports/index.ts Normal file
View File

@@ -0,0 +1,24 @@
import { Polling as XHR } from "./polling";
import { JSONP } from "./polling-jsonp";
import { WebSocket } from "./websocket";
export default {
polling: polling,
websocket: WebSocket
};
/**
* Polling polymorphic constructor.
*
* @api private
*/
function polling(req) {
if ("string" === typeof req._query.j) {
return new JSONP(req);
} else {
return new XHR(req);
}
}
polling.upgradesTo = ["websocket"];

View File

@@ -1,9 +1,13 @@
const Polling = require("./polling");
const qs = require("querystring");
import { Polling } from "./polling";
import * as qs from "querystring";
const rDoubleSlashes = /\\\\n/g;
const rSlashes = /(\\)?\\n/g;
class JSONP extends Polling {
export class JSONP extends Polling {
private readonly head: string;
private readonly foot: string;
/**
* JSON-P polling transport.
*
@@ -54,5 +58,3 @@ class JSONP extends Polling {
super.doWrite(data, options, callback);
}
}
module.exports = JSONP;

View File

@@ -1,14 +1,27 @@
const Transport = require("../transport");
const zlib = require("zlib");
const accepts = require("accepts");
const debug = require("debug")("engine:polling");
import { Transport } from "../transport";
import { createGzip, createDeflate } from "zlib";
import * as accepts from "accepts";
import debugModule from "debug";
import { IncomingMessage, ServerResponse } from "http";
const debug = debugModule("engine:polling");
const compressionMethods = {
gzip: zlib.createGzip,
deflate: zlib.createDeflate
gzip: createGzip,
deflate: createDeflate
};
class Polling extends Transport {
export class Polling extends Transport {
public maxHttpBufferSize: number;
public httpCompression: any;
private res: ServerResponse;
private dataReq: IncomingMessage;
private dataRes: ServerResponse;
private shouldClose: Function;
private readonly closeTimeout: number;
/**
* HTTP polling constructor.
*
@@ -18,8 +31,6 @@ class Polling extends Transport {
super(req);
this.closeTimeout = 30 * 1000;
this.maxHttpBufferSize = null;
this.httpCompression = null;
}
/**
@@ -31,6 +42,10 @@ class Polling extends Transport {
return "polling";
}
get supportsFraming() {
return false;
}
/**
* Overrides onRequest.
*
@@ -381,5 +396,3 @@ class Polling extends Transport {
return headers;
}
}
module.exports = Polling;

View File

@@ -1,7 +1,12 @@
const Transport = require("../transport");
const debug = require("debug")("engine:ws");
import { Transport } from "../transport";
import debugModule from "debug";
const debug = debugModule("engine:ws");
export class WebSocket extends Transport {
protected perMessageDeflate: any;
private socket: any;
class WebSocket extends Transport {
/**
* WebSocket transport
*
@@ -71,7 +76,7 @@ class WebSocket extends Transport {
}
// always creates a new object since ws modifies it
const opts = {};
const opts: { compress?: boolean } = {};
if (packet.options) {
opts.compress = packet.options.compress;
}
@@ -111,5 +116,3 @@ class WebSocket extends Transport {
fn && fn();
}
}
module.exports = WebSocket;

835
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,8 @@
"name": "engine.io",
"version": "5.2.0",
"description": "The realtime engine behind Socket.IO. Provides the foundation of a bidirectional connection between client and server",
"main": "lib/engine.io.js",
"main": "./build/engine.io.js",
"types": "./build/engine.io.d.ts",
"author": "Guillermo Rauch <guillermo@learnboost.com>",
"homepage": "https://github.com/socketio/engine.io",
"contributors": [
@@ -25,12 +26,15 @@
],
"license": "MIT",
"dependencies": {
"@types/cookie": "^0.4.1",
"@types/cors": "^2.8.12",
"@types/node": ">=10.0.0",
"accepts": "~1.3.4",
"base64id": "2.0.0",
"cookie": "~0.4.1",
"cors": "~2.8.5",
"debug": "~4.3.1",
"engine.io-parser": "~4.0.0",
"engine.io-parser": "~5.0.0",
"ws": "~7.4.2"
},
"devDependencies": {
@@ -38,26 +42,27 @@
"eiows": "^3.3.0",
"engine.io-client": "5.2.0",
"engine.io-client-v3": "npm:engine.io-client@3.5.0",
"eslint": "^4.19.1",
"eslint-config-prettier": "^6.9.0",
"expect.js": "^0.3.1",
"mocha": "^4.0.1",
"prettier": "^1.19.1",
"rimraf": "^3.0.2",
"s": "0.1.1",
"superagent": "^3.8.1"
"superagent": "^3.8.1",
"typescript": "^4.4.3"
},
"scripts": {
"lint": "eslint lib/ test/ *.js",
"test": "npm run lint && npm run format:check && mocha && EIO_CLIENT=3 mocha && EIO_WS_ENGINE=eiows mocha",
"format:check": "prettier --check 'lib/**/*.js' 'test/**/*.js'",
"format:fix": "prettier --write 'lib/**/*.js' 'test/**/*.js'"
"compile": "rimraf ./build && tsc",
"test": "npm run compile && npm run format:check && mocha --bail && EIO_CLIENT=3 mocha && EIO_WS_ENGINE=eiows mocha",
"format:check": "prettier --check 'lib/**/*.ts' 'test/**/*.js'",
"format:fix": "prettier --write 'lib/**/*.ts' 'test/**/*.js'",
"prepack": "npm run compile"
},
"repository": {
"type": "git",
"url": "git@github.com:socketio/engine.io.git"
},
"files": [
"lib/"
"build/"
],
"engines": {
"node": ">=10.0.0"

View File

@@ -1,4 +1,4 @@
const eio = require("..");
const { listen } = require("..");
const eioc =
process.env.EIO_CLIENT === "3"
? require("engine.io-client-v3")
@@ -20,7 +20,7 @@ exports.listen = (opts, fn) => {
opts.wsEngine = require(process.env.EIO_WS_ENGINE).Server;
}
const e = eio.listen(0, opts, () => {
const e = listen(0, opts, () => {
fn(e.httpServer.address().port);
});

View File

@@ -1,5 +1,5 @@
const net = require("net");
const eio = require("..");
const { Server, protocol, attach } = require("..");
const listen = require("./common").listen;
const expect = require("expect.js");
const request = require("superagent");
@@ -11,7 +11,7 @@ const http = require("http");
describe("engine", () => {
it("should expose protocol number", () => {
expect(eio.protocol).to.be.a("number");
expect(protocol).to.be.a("number");
});
it("should be the same version as client", () => {
@@ -21,13 +21,13 @@ describe("engine", () => {
describe("engine()", () => {
it("should create a Server when require called with no arguments", () => {
const engine = eio();
expect(engine).to.be.an(eio.Server);
const engine = new Server();
expect(engine).to.be.an(Server);
expect(engine.ws).to.be.ok();
});
it("should pass options correctly to the Server", () => {
const engine = eio({ cors: true });
const engine = new Server({ cors: true });
expect(engine.opts).to.have.property("cors", true);
});
});
@@ -47,21 +47,21 @@ describe("engine", () => {
describe("attach()", () => {
it("should work from require()", () => {
const server = http.createServer();
const engine = eio(server);
const engine = new Server(server);
expect(engine).to.be.an(eio.Server);
expect(engine).to.be.an(Server);
});
it("should return an engine.Server", () => {
const server = http.createServer();
const engine = eio.attach(server);
const engine = attach(server);
expect(engine).to.be.an(eio.Server);
expect(engine).to.be.an(Server);
});
it("should attach engine to an http server", done => {
const server = http.createServer();
eio.attach(server);
attach(server);
server.listen(() => {
const uri = "http://localhost:%d/engine.io/default/".s(
@@ -80,7 +80,7 @@ describe("engine", () => {
it("should destroy upgrades not handled by engine", done => {
const server = http.createServer();
eio.attach(server, { destroyUpgradeTimeout: 50 });
attach(server, { destroyUpgradeTimeout: 50 });
server.listen(() => {
const client = net.createConnection(server.address().port);
@@ -108,7 +108,7 @@ describe("engine", () => {
it("should not destroy unhandled upgrades with destroyUpgrade:false", done => {
const server = http.createServer();
eio.attach(server, { destroyUpgrade: false, destroyUpgradeTimeout: 50 });
attach(server, { destroyUpgrade: false, destroyUpgradeTimeout: 50 });
server.listen(() => {
const client = net.createConnection(server.address().port);
@@ -140,7 +140,7 @@ describe("engine", () => {
it("should destroy unhandled upgrades with after a timeout", done => {
const server = http.createServer();
eio.attach(server, { destroyUpgradeTimeout: 200 });
attach(server, { destroyUpgradeTimeout: 200 });
server.listen(() => {
const client = net.createConnection(server.address().port);
@@ -174,7 +174,7 @@ describe("engine", () => {
it("should not destroy handled upgrades with after a timeout", done => {
const server = http.createServer();
eio.attach(server, { destroyUpgradeTimeout: 100 });
attach(server, { destroyUpgradeTimeout: 100 });
// write to the socket to keep engine.io from closing it by writing before the timeout
server.on("upgrade", (req, socket) => {
@@ -226,7 +226,7 @@ describe("engine", () => {
listeners++;
});
eio.attach(server);
attach(server);
server.listen(() => {
const port = server.address().port;

View File

@@ -6,7 +6,7 @@ const fs = require("fs");
const path = require("path");
const exec = require("child_process").exec;
const zlib = require("zlib");
const eio = require("..");
const { Server, Socket, attach } = require("..");
const { eioc, listen, createPartialDone } = require("./common");
const expect = require("expect.js");
const request = require("superagent");
@@ -163,8 +163,8 @@ describe("server", () => {
it("should send the io cookie", done => {
listen({ cookie: true }, port => {
request
.get("http://localhost:%d/engine.io/default/".s(port))
.query({ transport: "polling", b64: 1 })
.get("http://localhost:%d/engine.io/".s(port))
.query({ transport: "polling", EIO: 4 })
.end((err, res) => {
expect(err).to.be(null);
// hack-obtain sid
@@ -428,7 +428,7 @@ describe("server", () => {
const engine = listen({ allowUpgrades: false }, port => {
eioc("ws://localhost:%d".s(port));
engine.on("connection", socket => {
expect(socket).to.be.an(eio.Socket);
expect(socket).to.be.an(Socket);
done();
});
});
@@ -606,7 +606,7 @@ describe("server", () => {
// we can't send an invalid header through request.get
// so add an invalid char here
engine.prepare = function(req) {
eio.Server.prototype.prepare.call(engine, req);
Server.prototype.prepare.call(engine, req);
req.headers.origin += "\n";
};
@@ -662,7 +662,7 @@ describe("server", () => {
const partialDone = createPartialDone(done, 2);
const httpServer = http.createServer();
const engine = eio({ allowEIO3: false });
const engine = new Server({ allowEIO3: false });
engine.attach(httpServer);
httpServer.listen(() => {
const port = httpServer.address().port;
@@ -2061,7 +2061,7 @@ describe("server", () => {
res.end("hello world\n");
});
const engine = eio({
const engine = new Server({
transports: ["polling"],
allowUpgrades: false,
allowEIO3: true
@@ -2103,7 +2103,7 @@ describe("server", () => {
res.end("hello world\n");
});
const engine = eio({
const engine = new Server({
transports: ["polling"],
allowUpgrades: false,
allowEIO3: true
@@ -2147,7 +2147,7 @@ describe("server", () => {
res.end("hello world\n");
});
const engine = eio({
const engine = new Server({
transports: ["websocket"],
allowUpgrades: false,
allowEIO3: true
@@ -2190,7 +2190,7 @@ describe("server", () => {
res.end("hello world\n");
});
const engine = eio({
const engine = new Server({
transports: ["polling"],
allowUpgrades: false,
allowEIO3: true
@@ -2233,7 +2233,7 @@ describe("server", () => {
res.end("hello world\n");
});
const engine = eio({
const engine = new Server({
transports: ["websocket"],
allowUpgrades: false,
allowEIO3: true
@@ -2856,7 +2856,7 @@ describe("server", () => {
});
// attach another engine to make sure it doesn't break upgrades
eio.attach(engine.httpServer, { path: "/foo" });
attach(engine.httpServer, { path: "/foo" });
});
});

11
tsconfig.json Normal file
View File

@@ -0,0 +1,11 @@
{
"compilerOptions": {
"outDir": "build/",
"target": "es2018", // Node.js 10 (https://github.com/microsoft/TypeScript/wiki/Node-Target-Mapping)
"module": "commonjs",
"declaration": true
},
"include": [
"./lib/**/*"
]
}