Skip to content

Commit 24786e7

Browse files
committedFeb 6, 2023
feat: add support for Express middlewares
This commit implements middlewares at the Engine.IO level, because Socket.IO middlewares are meant for namespace authorization and are not executed during a classic HTTP request/response cycle. A workaround was possible by using the allowRequest option and the "headers" event, but this feels way cleaner and works with upgrade requests too. Syntax: ```js engine.use((req, res, next) => { // do something next(); }); // with express-session import session from "express-session"; engine.use(session({ secret: "keyboard cat", resave: false, saveUninitialized: true, cookie: { secure: true } }); // with helmet import helmet from "helmet"; engine.use(helmet()); ``` Related: - #668 - #651 - socketio/socket.io#4609 - socketio/socket.io#3933 - a lot of other issues asking for compatibility with express-session
1 parent 4d6f454 commit 24786e7

File tree

5 files changed

+723
-122
lines changed

5 files changed

+723
-122
lines changed
 

‎lib/server.ts

+147-32
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,19 @@ import { Socket } from "./socket";
77
import debugModule from "debug";
88
import { serialize } from "cookie";
99
import { Server as DEFAULT_WS_ENGINE } from "ws";
10-
import { IncomingMessage, Server as HttpServer } from "http";
11-
import { CookieSerializeOptions } from "cookie";
12-
import { CorsOptions, CorsOptionsDelegate } from "cors";
10+
import type {
11+
IncomingMessage,
12+
Server as HttpServer,
13+
ServerResponse,
14+
} from "http";
15+
import type { CookieSerializeOptions } from "cookie";
16+
import type { CorsOptions, CorsOptionsDelegate } from "cors";
17+
import type { Duplex } from "stream";
1318

1419
const debug = debugModule("engine");
1520

21+
const kResponseHeaders = Symbol("responseHeaders");
22+
1623
type Transport = "polling" | "websocket";
1724

1825
export interface AttachOptions {
@@ -119,12 +126,26 @@ export interface ServerOptions {
119126
allowEIO3?: boolean;
120127
}
121128

129+
/**
130+
* An Express-compatible middleware.
131+
*
132+
* Middleware functions are functions that have access to the request object (req), the response object (res), and the
133+
* next middleware function in the application’s request-response cycle.
134+
*
135+
* @see https://expressjs.com/en/guide/using-middleware.html
136+
*/
137+
type Middleware = (
138+
req: IncomingMessage,
139+
res: ServerResponse,
140+
next: () => void
141+
) => void;
142+
122143
export abstract class BaseServer extends EventEmitter {
123144
public opts: ServerOptions;
124145

125146
protected clients: any;
126147
private clientsCount: number;
127-
protected corsMiddleware: Function;
148+
protected middlewares: Middleware[] = [];
128149

129150
/**
130151
* Server constructor.
@@ -170,7 +191,7 @@ export abstract class BaseServer extends EventEmitter {
170191
}
171192

172193
if (this.opts.cors) {
173-
this.corsMiddleware = require("cors")(this.opts.cors);
194+
this.use(require("cors")(this.opts.cors));
174195
}
175196

176197
if (opts.perMessageDeflate) {
@@ -289,6 +310,52 @@ export abstract class BaseServer extends EventEmitter {
289310
fn();
290311
}
291312

313+
/**
314+
* Adds a new middleware.
315+
*
316+
* @example
317+
* import helmet from "helmet";
318+
*
319+
* engine.use(helmet());
320+
*
321+
* @param fn
322+
*/
323+
public use(fn: Middleware) {
324+
this.middlewares.push(fn);
325+
}
326+
327+
/**
328+
* Apply the middlewares to the request.
329+
*
330+
* @param req
331+
* @param res
332+
* @param callback
333+
* @protected
334+
*/
335+
protected _applyMiddlewares(
336+
req: IncomingMessage,
337+
res: ServerResponse,
338+
callback: () => void
339+
) {
340+
if (this.middlewares.length === 0) {
341+
debug("no middleware to apply, skipping");
342+
return callback();
343+
}
344+
345+
const apply = (i) => {
346+
debug("applying middleware n°%d", i + 1);
347+
this.middlewares[i](req, res, () => {
348+
if (i + 1 < this.middlewares.length) {
349+
apply(i + 1);
350+
} else {
351+
callback();
352+
}
353+
});
354+
};
355+
356+
apply(0);
357+
}
358+
292359
/**
293360
* Closes all clients.
294361
*
@@ -449,6 +516,40 @@ export abstract class BaseServer extends EventEmitter {
449516
};
450517
}
451518

519+
/**
520+
* Exposes a subset of the http.ServerResponse interface, in order to be able to apply the middlewares to an upgrade
521+
* request.
522+
*
523+
* @see https://nodejs.org/api/http.html#class-httpserverresponse
524+
*/
525+
class WebSocketResponse {
526+
constructor(readonly req, readonly socket: Duplex) {
527+
// temporarily store the response headers on the req object (see the "headers" event)
528+
req[kResponseHeaders] = {};
529+
}
530+
531+
public setHeader(name: string, value: any) {
532+
this.req[kResponseHeaders][name] = value;
533+
}
534+
535+
public getHeader(name: string) {
536+
return this.req[kResponseHeaders][name];
537+
}
538+
539+
public removeHeader(name: string) {
540+
delete this.req[kResponseHeaders][name];
541+
}
542+
543+
public write() {}
544+
545+
public writeHead() {}
546+
547+
public end() {
548+
// we could return a proper error code, but the WebSocket client will emit an "error" event anyway.
549+
this.socket.destroy();
550+
}
551+
}
552+
452553
export class Server extends BaseServer {
453554
public httpServer?: HttpServer;
454555
private ws: any;
@@ -474,7 +575,8 @@ export class Server extends BaseServer {
474575
this.ws.on("headers", (headersArray, req) => {
475576
// note: 'ws' uses an array of headers, while Engine.IO uses an object (response.writeHead() accepts both formats)
476577
// we could also try to parse the array and then sync the values, but that will be error-prone
477-
const additionalHeaders = {};
578+
const additionalHeaders = req[kResponseHeaders] || {};
579+
delete req[kResponseHeaders];
478580

479581
const isInitialRequest = !req._query.sid;
480582
if (isInitialRequest) {
@@ -483,6 +585,7 @@ export class Server extends BaseServer {
483585

484586
this.emit("headers", additionalHeaders, req);
485587

588+
debug("writing headers: %j", additionalHeaders);
486589
Object.keys(additionalHeaders).forEach((key) => {
487590
headersArray.push(`${key}: ${additionalHeaders[key]}`);
488591
});
@@ -517,13 +620,14 @@ export class Server extends BaseServer {
517620
/**
518621
* Handles an Engine.IO HTTP request.
519622
*
520-
* @param {http.IncomingMessage} request
521-
* @param {http.ServerResponse|http.OutgoingMessage} response
623+
* @param {IncomingMessage} req
624+
* @param {ServerResponse} res
522625
* @api public
523626
*/
524-
public handleRequest(req, res) {
627+
public handleRequest(req: IncomingMessage, res: ServerResponse) {
525628
debug('handling "%s" http request "%s"', req.method, req.url);
526629
this.prepare(req);
630+
// @ts-ignore
527631
req.res = res;
528632

529633
const callback = (errorCode, errorContext) => {
@@ -538,51 +642,62 @@ export class Server extends BaseServer {
538642
return;
539643
}
540644

645+
// @ts-ignore
541646
if (req._query.sid) {
542647
debug("setting new request for existing client");
648+
// @ts-ignore
543649
this.clients[req._query.sid].transport.onRequest(req);
544650
} else {
545651
const closeConnection = (errorCode, errorContext) =>
546652
abortRequest(res, errorCode, errorContext);
653+
// @ts-ignore
547654
this.handshake(req._query.transport, req, closeConnection);
548655
}
549656
};
550657

551-
if (this.corsMiddleware) {
552-
this.corsMiddleware.call(null, req, res, () => {
553-
this.verify(req, false, callback);
554-
});
555-
} else {
658+
this._applyMiddlewares(req, res, () => {
556659
this.verify(req, false, callback);
557-
}
660+
});
558661
}
559662

560663
/**
561664
* Handles an Engine.IO HTTP Upgrade.
562665
*
563666
* @api public
564667
*/
565-
public handleUpgrade(req, socket, upgradeHead) {
668+
public handleUpgrade(
669+
req: IncomingMessage,
670+
socket: Duplex,
671+
upgradeHead: Buffer
672+
) {
566673
this.prepare(req);
567674

568-
this.verify(req, true, (errorCode, errorContext) => {
569-
if (errorCode) {
570-
this.emit("connection_error", {
571-
req,
572-
code: errorCode,
573-
message: Server.errorMessages[errorCode],
574-
context: errorContext,
575-
});
576-
abortUpgrade(socket, errorCode, errorContext);
577-
return;
578-
}
675+
const res = new WebSocketResponse(req, socket);
579676

580-
const head = Buffer.from(upgradeHead);
581-
upgradeHead = null;
677+
this._applyMiddlewares(req, res as unknown as ServerResponse, () => {
678+
this.verify(req, true, (errorCode, errorContext) => {
679+
if (errorCode) {
680+
this.emit("connection_error", {
681+
req,
682+
code: errorCode,
683+
message: Server.errorMessages[errorCode],
684+
context: errorContext,
685+
});
686+
abortUpgrade(socket, errorCode, errorContext);
687+
return;
688+
}
582689

583-
// delegate to ws
584-
this.ws.handleUpgrade(req, socket, head, (websocket) => {
585-
this.onWebSocket(req, socket, websocket);
690+
const head = Buffer.from(upgradeHead);
691+
upgradeHead = null;
692+
693+
// some middlewares (like express-session) wait for the writeHead() call to flush their headers
694+
// see https://github.com/expressjs/session/blob/1010fadc2f071ddf2add94235d72224cf65159c6/index.js#L220-L244
695+
res.writeHead();
696+
697+
// delegate to ws
698+
this.ws.handleUpgrade(req, socket, head, (websocket) => {
699+
this.onWebSocket(req, socket, websocket);
700+
});
586701
});
587702
});
588703
}

‎lib/userver.ts

+113-82
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export class uServer extends BaseServer {
3434
*/
3535
private prepare(req, res: HttpResponse) {
3636
req.method = req.getMethod().toUpperCase();
37+
req.url = req.getUrl();
3738

3839
const params = new URLSearchParams(req.getQuery());
3940
req._query = Object.fromEntries(params.entries());
@@ -91,6 +92,23 @@ export class uServer extends BaseServer {
9192
});
9293
}
9394

95+
override _applyMiddlewares(req: any, res: any, callback: () => void): void {
96+
if (this.middlewares.length === 0) {
97+
return callback();
98+
}
99+
100+
// needed to buffer headers until the status is computed
101+
req.res = new ResponseWrapper(res);
102+
103+
super._applyMiddlewares(req, req.res, () => {
104+
// some middlewares (like express-session) wait for the writeHead() call to flush their headers
105+
// see https://github.com/expressjs/session/blob/1010fadc2f071ddf2add94235d72224cf65159c6/index.js#L220-L244
106+
req.res.writeHead();
107+
108+
callback();
109+
});
110+
}
111+
94112
private handleRequest(
95113
res: HttpResponse,
96114
req: HttpRequest & { res: any; _query: any }
@@ -100,104 +118,99 @@ export class uServer extends BaseServer {
100118

101119
req.res = res;
102120

103-
const callback = (errorCode, errorContext) => {
104-
if (errorCode !== undefined) {
105-
this.emit("connection_error", {
106-
req,
107-
code: errorCode,
108-
message: Server.errorMessages[errorCode],
109-
context: errorContext,
110-
});
111-
this.abortRequest(req.res, errorCode, errorContext);
112-
return;
113-
}
114-
115-
if (req._query.sid) {
116-
debug("setting new request for existing client");
117-
this.clients[req._query.sid].transport.onRequest(req);
118-
} else {
119-
const closeConnection = (errorCode, errorContext) =>
120-
this.abortRequest(res, errorCode, errorContext);
121-
this.handshake(req._query.transport, req, closeConnection);
122-
}
123-
};
124-
125-
if (this.corsMiddleware) {
126-
// needed to buffer headers until the status is computed
127-
req.res = new ResponseWrapper(res);
121+
this._applyMiddlewares(req, res, () => {
122+
this.verify(req, false, (errorCode, errorContext) => {
123+
if (errorCode !== undefined) {
124+
this.emit("connection_error", {
125+
req,
126+
code: errorCode,
127+
message: Server.errorMessages[errorCode],
128+
context: errorContext,
129+
});
130+
this.abortRequest(req.res, errorCode, errorContext);
131+
return;
132+
}
128133

129-
this.corsMiddleware.call(null, req, req.res, () => {
130-
this.verify(req, false, callback);
134+
if (req._query.sid) {
135+
debug("setting new request for existing client");
136+
this.clients[req._query.sid].transport.onRequest(req);
137+
} else {
138+
const closeConnection = (errorCode, errorContext) =>
139+
this.abortRequest(res, errorCode, errorContext);
140+
this.handshake(req._query.transport, req, closeConnection);
141+
}
131142
});
132-
} else {
133-
this.verify(req, false, callback);
134-
}
143+
});
135144
}
136145

137146
private handleUpgrade(
138147
res: HttpResponse,
139-
req: HttpRequest & { _query: any },
148+
req: HttpRequest & { res: any; _query: any },
140149
context
141150
) {
142151
debug("on upgrade");
143152

144153
this.prepare(req, res);
145154

146-
// @ts-ignore
147155
req.res = res;
148156

149-
this.verify(req, true, async (errorCode, errorContext) => {
150-
if (errorCode) {
151-
this.emit("connection_error", {
152-
req,
153-
code: errorCode,
154-
message: Server.errorMessages[errorCode],
155-
context: errorContext,
156-
});
157-
this.abortRequest(res, errorCode, errorContext);
158-
return;
159-
}
160-
161-
const id = req._query.sid;
162-
let transport;
163-
164-
if (id) {
165-
const client = this.clients[id];
166-
if (!client) {
167-
debug("upgrade attempt for closed client");
168-
res.close();
169-
} else if (client.upgrading) {
170-
debug("transport has already been trying to upgrade");
171-
res.close();
172-
} else if (client.upgraded) {
173-
debug("transport had already been upgraded");
174-
res.close();
175-
} else {
176-
debug("upgrading existing transport");
177-
transport = this.createTransport(req._query.transport, req);
178-
client.maybeUpgrade(transport);
179-
}
180-
} else {
181-
transport = await this.handshake(
182-
req._query.transport,
183-
req,
184-
(errorCode, errorContext) =>
185-
this.abortRequest(res, errorCode, errorContext)
186-
);
187-
if (!transport) {
157+
this._applyMiddlewares(req, res, () => {
158+
this.verify(req, true, async (errorCode, errorContext) => {
159+
if (errorCode) {
160+
this.emit("connection_error", {
161+
req,
162+
code: errorCode,
163+
message: Server.errorMessages[errorCode],
164+
context: errorContext,
165+
});
166+
this.abortRequest(res, errorCode, errorContext);
188167
return;
189168
}
190-
}
191169

192-
res.upgrade(
193-
{
194-
transport,
195-
},
196-
req.getHeader("sec-websocket-key"),
197-
req.getHeader("sec-websocket-protocol"),
198-
req.getHeader("sec-websocket-extensions"),
199-
context
200-
);
170+
const id = req._query.sid;
171+
let transport;
172+
173+
if (id) {
174+
const client = this.clients[id];
175+
if (!client) {
176+
debug("upgrade attempt for closed client");
177+
res.close();
178+
} else if (client.upgrading) {
179+
debug("transport has already been trying to upgrade");
180+
res.close();
181+
} else if (client.upgraded) {
182+
debug("transport had already been upgraded");
183+
res.close();
184+
} else {
185+
debug("upgrading existing transport");
186+
transport = this.createTransport(req._query.transport, req);
187+
client.maybeUpgrade(transport);
188+
}
189+
} else {
190+
transport = await this.handshake(
191+
req._query.transport,
192+
req,
193+
(errorCode, errorContext) =>
194+
this.abortRequest(res, errorCode, errorContext)
195+
);
196+
if (!transport) {
197+
return;
198+
}
199+
}
200+
201+
// calling writeStatus() triggers the flushing of any header added in a middleware
202+
req.res.writeStatus("101 Switching Protocols");
203+
204+
res.upgrade(
205+
{
206+
transport,
207+
},
208+
req.getHeader("sec-websocket-key"),
209+
req.getHeader("sec-websocket-protocol"),
210+
req.getHeader("sec-websocket-extensions"),
211+
context
212+
);
213+
});
201214
});
202215
}
203216

@@ -233,11 +246,29 @@ class ResponseWrapper {
233246
constructor(readonly res: HttpResponse) {}
234247

235248
public set statusCode(status: number) {
249+
if (!status) {
250+
return;
251+
}
252+
// FIXME: handle all status codes?
236253
this.writeStatus(status === 200 ? "200 OK" : "204 No Content");
237254
}
238255

256+
public writeHead(status: number) {
257+
this.statusCode = status;
258+
}
259+
239260
public setHeader(key, value) {
240-
this.writeHeader(key, value);
261+
if (Array.isArray(value)) {
262+
value.forEach((val) => {
263+
this.writeHeader(key, val);
264+
});
265+
} else {
266+
this.writeHeader(key, value);
267+
}
268+
}
269+
270+
public removeHeader() {
271+
// FIXME: not implemented
241272
}
242273

243274
// needed by vary: https://github.com/jshttp/vary/blob/5d725d059b3871025cf753e9dfa08924d0bcfa8f/index.js#L134

‎package-lock.json

+211-8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+2
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
"engine.io-client": "6.3.0",
4949
"engine.io-client-v3": "npm:engine.io-client@3.5.2",
5050
"expect.js": "^0.3.1",
51+
"express-session": "^1.17.3",
52+
"helmet": "^6.0.1",
5153
"mocha": "^9.1.3",
5254
"prettier": "^2.8.2",
5355
"rimraf": "^3.0.2",

‎test/middlewares.js

+250
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
const listen = require("./common").listen;
2+
const expect = require("expect.js");
3+
const request = require("superagent");
4+
const { WebSocket } = require("ws");
5+
const helmet = require("helmet");
6+
const session = require("express-session");
7+
8+
describe("middlewares", () => {
9+
it("should apply middleware (polling)", (done) => {
10+
const engine = listen((port) => {
11+
engine.use((req, res, next) => {
12+
res.setHeader("foo", "bar");
13+
next();
14+
});
15+
16+
request
17+
.get(`http://localhost:${port}/engine.io/`)
18+
.query({ EIO: 4, transport: "polling" })
19+
.end((err, res) => {
20+
expect(err).to.be(null);
21+
expect(res.status).to.eql(200);
22+
expect(res.headers["foo"]).to.eql("bar");
23+
24+
if (engine.httpServer) {
25+
engine.httpServer.close();
26+
}
27+
done();
28+
});
29+
});
30+
});
31+
32+
it("should apply middleware (websocket)", (done) => {
33+
const engine = listen((port) => {
34+
engine.use((req, res, next) => {
35+
res.setHeader("foo", "bar");
36+
next();
37+
});
38+
39+
const socket = new WebSocket(
40+
`ws://localhost:${port}/engine.io/?EIO=4&transport=websocket`
41+
);
42+
43+
socket.on("upgrade", (res) => {
44+
expect(res.headers["foo"]).to.eql("bar");
45+
46+
if (engine.httpServer) {
47+
engine.httpServer.close();
48+
}
49+
done();
50+
});
51+
52+
socket.on("open", () => {
53+
socket.close();
54+
});
55+
});
56+
});
57+
58+
it("should apply all middlewares in order", (done) => {
59+
const engine = listen((port) => {
60+
let count = 0;
61+
62+
engine.use((req, res, next) => {
63+
expect(++count).to.eql(1);
64+
next();
65+
});
66+
67+
engine.use((req, res, next) => {
68+
expect(++count).to.eql(2);
69+
next();
70+
});
71+
72+
engine.use((req, res, next) => {
73+
expect(++count).to.eql(3);
74+
next();
75+
});
76+
77+
request
78+
.get(`http://localhost:${port}/engine.io/`)
79+
.query({ EIO: 4, transport: "polling" })
80+
.end((err, res) => {
81+
expect(err).to.be(null);
82+
expect(res.status).to.eql(200);
83+
84+
if (engine.httpServer) {
85+
engine.httpServer.close();
86+
}
87+
done();
88+
});
89+
});
90+
});
91+
92+
it("should end the request (polling)", function (done) {
93+
if (process.env.EIO_WS_ENGINE === "uws") {
94+
return this.skip();
95+
}
96+
const engine = listen((port) => {
97+
engine.use((req, res, _next) => {
98+
res.writeHead(503);
99+
res.end();
100+
});
101+
102+
engine.on("connection", () => {
103+
done(new Error("should not happen"));
104+
});
105+
106+
request
107+
.get(`http://localhost:${port}/engine.io/`)
108+
.query({ EIO: 4, transport: "polling" })
109+
.end((err, res) => {
110+
expect(err).to.be.an(Error);
111+
expect(res.status).to.eql(503);
112+
113+
if (engine.httpServer) {
114+
engine.httpServer.close();
115+
}
116+
done();
117+
});
118+
});
119+
});
120+
121+
it("should end the request (websocket)", (done) => {
122+
const engine = listen((port) => {
123+
engine.use((req, res, _next) => {
124+
res.writeHead(503);
125+
res.end();
126+
});
127+
128+
engine.on("connection", () => {
129+
done(new Error("should not happen"));
130+
});
131+
132+
const socket = new WebSocket(
133+
`ws://localhost:${port}/engine.io/?EIO=4&transport=websocket`
134+
);
135+
136+
socket.addEventListener("error", () => {
137+
if (engine.httpServer) {
138+
engine.httpServer.close();
139+
}
140+
done();
141+
});
142+
});
143+
});
144+
145+
it("should work with helmet (polling)", (done) => {
146+
const engine = listen((port) => {
147+
engine.use(helmet());
148+
149+
request
150+
.get(`http://localhost:${port}/engine.io/`)
151+
.query({ EIO: 4, transport: "polling" })
152+
.end((err, res) => {
153+
expect(err).to.be(null);
154+
expect(res.status).to.eql(200);
155+
expect(res.headers["x-download-options"]).to.eql("noopen");
156+
expect(res.headers["x-content-type-options"]).to.eql("nosniff");
157+
158+
if (engine.httpServer) {
159+
engine.httpServer.close();
160+
}
161+
done();
162+
});
163+
});
164+
});
165+
166+
it("should work with helmet (websocket)", (done) => {
167+
const engine = listen((port) => {
168+
engine.use(helmet());
169+
170+
const socket = new WebSocket(
171+
`ws://localhost:${port}/engine.io/?EIO=4&transport=websocket`
172+
);
173+
174+
socket.on("upgrade", (res) => {
175+
expect(res.headers["x-download-options"]).to.eql("noopen");
176+
expect(res.headers["x-content-type-options"]).to.eql("nosniff");
177+
178+
if (engine.httpServer) {
179+
engine.httpServer.close();
180+
}
181+
done();
182+
});
183+
184+
socket.on("open", () => {
185+
socket.close();
186+
});
187+
});
188+
});
189+
190+
it("should work with express-session (polling)", (done) => {
191+
const engine = listen((port) => {
192+
engine.use(
193+
session({
194+
secret: "keyboard cat",
195+
resave: false,
196+
saveUninitialized: true,
197+
cookie: {},
198+
})
199+
);
200+
201+
request
202+
.get(`http://localhost:${port}/engine.io/`)
203+
.query({ EIO: 4, transport: "polling" })
204+
.end((err, res) => {
205+
expect(err).to.be(null);
206+
// expect(res.status).to.eql(200);
207+
expect(res.headers["set-cookie"][0].startsWith("connect.sid=")).to.be(
208+
true
209+
);
210+
211+
if (engine.httpServer) {
212+
engine.httpServer.close();
213+
}
214+
done();
215+
});
216+
});
217+
});
218+
219+
it("should work with express-session (websocket)", (done) => {
220+
const engine = listen((port) => {
221+
engine.use(
222+
session({
223+
secret: "keyboard cat",
224+
resave: false,
225+
saveUninitialized: true,
226+
cookie: {},
227+
})
228+
);
229+
230+
const socket = new WebSocket(
231+
`ws://localhost:${port}/engine.io/?EIO=4&transport=websocket`
232+
);
233+
234+
socket.on("upgrade", (res) => {
235+
expect(res.headers["set-cookie"][0].startsWith("connect.sid=")).to.be(
236+
true
237+
);
238+
239+
if (engine.httpServer) {
240+
engine.httpServer.close();
241+
}
242+
done();
243+
});
244+
245+
socket.on("open", () => {
246+
socket.close();
247+
});
248+
});
249+
});
250+
});

0 commit comments

Comments
 (0)
Please sign in to comment.