Skip to content

Commit e105551

Browse files
fix: fix cookie management with WebSocket (Node.js only)
Before this commit, the cookies were only sent with the HTTP long-polling transport, and not when upgrading to WebSocket. See also: 5fc88a6
1 parent 3f66478 commit e105551

7 files changed

+54
-42
lines changed

lib/globals.node.ts

+18-16
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export function parse(setCookieString: string): Cookie {
6868
}
6969

7070
export class CookieJar {
71-
private cookies = new Map<string, Cookie>();
71+
private _cookies = new Map<string, Cookie>();
7272

7373
public parseCookies(values: string[]) {
7474
if (!values) {
@@ -77,21 +77,27 @@ export class CookieJar {
7777
values.forEach((value) => {
7878
const parsed = parse(value);
7979
if (parsed) {
80-
this.cookies.set(parsed.name, parsed);
80+
this._cookies.set(parsed.name, parsed);
8181
}
8282
});
8383
}
8484

85+
get cookies() {
86+
const now = Date.now();
87+
this._cookies.forEach((cookie, name) => {
88+
if (cookie.expires?.getTime() < now) {
89+
this._cookies.delete(name);
90+
}
91+
});
92+
return this._cookies.entries();
93+
}
94+
8595
public addCookies(xhr: any) {
8696
const cookies = [];
8797

88-
this.cookies.forEach((cookie, name) => {
89-
if (cookie.expires?.getTime() < Date.now()) {
90-
this.cookies.delete(name);
91-
} else {
92-
cookies.push(`${name}=${cookie.value}`);
93-
}
94-
});
98+
for (const [name, cookie] of this.cookies) {
99+
cookies.push(`${name}=${cookie.value}`);
100+
}
95101

96102
if (cookies.length) {
97103
xhr.setDisableHeaderCheck(true);
@@ -100,12 +106,8 @@ export class CookieJar {
100106
}
101107

102108
public appendCookies(headers: Headers) {
103-
this.cookies.forEach((cookie, name) => {
104-
if (cookie.expires?.getTime() < Date.now()) {
105-
this.cookies.delete(name);
106-
} else {
107-
headers.append("cookie", `${name}=${cookie.value}`);
108-
}
109-
});
109+
for (const [name, cookie] of this.cookies) {
110+
headers.append("cookie", `${name}=${cookie.value}`);
111+
}
110112
}
111113
}

lib/socket.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ import { Emitter } from "@socket.io/component-emitter";
66
import { protocol } from "engine.io-parser";
77
import type { Packet, BinaryType, PacketType, RawData } from "engine.io-parser";
88
import { CloseDetails, Transport } from "./transport.js";
9-
import { defaultBinaryType } from "./globals.node.js";
9+
import {
10+
CookieJar,
11+
createCookieJar,
12+
defaultBinaryType,
13+
} from "./globals.node.js";
1014
import debugModule from "debug"; // debug()
1115

1216
const debug = debugModule("engine.io-client:socket"); // debug()
@@ -322,6 +326,10 @@ export class SocketWithoutUpgrade extends Emitter<
322326
private readonly hostname: string;
323327
private readonly port: string | number;
324328
private readonly transportsByName: Record<string, TransportCtor>;
329+
/**
330+
* The cookie jar will store the cookies sent by the server (Node. js only).
331+
*/
332+
/* private */ readonly _cookieJar: CookieJar;
325333

326334
static priorWebsocketSuccess: boolean;
327335
static protocol = protocol;
@@ -444,6 +452,10 @@ export class SocketWithoutUpgrade extends Emitter<
444452
}
445453
}
446454

455+
if (this.opts.withCredentials) {
456+
this._cookieJar = createCookieJar();
457+
}
458+
447459
this.open();
448460
}
449461

lib/transport.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import { decodePacket } from "engine.io-parser";
22
import type { Packet, RawData } from "engine.io-parser";
33
import { Emitter } from "@socket.io/component-emitter";
44
import { installTimerFunctions } from "./util.js";
5-
import debugModule from "debug"; // debug()
6-
import { SocketOptions } from "./socket.js";
5+
import type { Socket, SocketOptions } from "./socket.js";
76
import { encode } from "./contrib/parseqs.js";
7+
import debugModule from "debug"; // debug()
88

99
const debug = debugModule("engine.io-client:transport"); // debug()
1010

@@ -48,7 +48,7 @@ export abstract class Transport extends Emitter<
4848
protected opts: SocketOptions;
4949
protected supportsBinary: boolean;
5050
protected readyState: TransportState;
51-
protected socket: any;
51+
protected socket: Socket;
5252
protected setTimeoutFn: typeof setTimeout;
5353

5454
/**

lib/transports/polling-fetch.ts

+3-15
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,6 @@ import { CookieJar, createCookieJar } from "../globals.node.js";
1010
* @see https://caniuse.com/fetch
1111
*/
1212
export class Fetch extends Polling {
13-
private readonly cookieJar?: CookieJar;
14-
15-
constructor(opts) {
16-
super(opts);
17-
18-
if (this.opts.withCredentials) {
19-
this.cookieJar = createCookieJar();
20-
}
21-
}
22-
2313
override doPoll() {
2414
this._fetch()
2515
.then((res) => {
@@ -56,18 +46,16 @@ export class Fetch extends Polling {
5646
headers.set("content-type", "text/plain;charset=UTF-8");
5747
}
5848

59-
this.cookieJar?.appendCookies(headers);
49+
this.socket._cookieJar?.appendCookies(headers);
6050

6151
return fetch(this.uri(), {
6252
method: isPost ? "POST" : "GET",
6353
body: isPost ? data : null,
6454
headers,
6555
credentials: this.opts.withCredentials ? "include" : "omit",
6656
}).then((res) => {
67-
if (this.cookieJar) {
68-
// @ts-ignore getSetCookie() was added in Node.js v19.7.0
69-
this.cookieJar.parseCookies(res.headers.getSetCookie());
70-
}
57+
// @ts-ignore getSetCookie() was added in Node.js v19.7.0
58+
this.socket._cookieJar?.parseCookies(res.headers.getSetCookie());
7159

7260
return res;
7361
});

lib/transports/polling-xhr.node.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ const XMLHttpRequest = XMLHttpRequestModule.default || XMLHttpRequestModule;
1212
*/
1313
export class XHR extends BaseXHR {
1414
request(opts: Record<string, any> = {}) {
15-
Object.assign(opts, { xd: this.xd, cookieJar: this.cookieJar }, this.opts);
15+
Object.assign(
16+
opts,
17+
{ xd: this.xd, cookieJar: this.socket?._cookieJar },
18+
this.opts
19+
);
1620
return new Request(
1721
(opts) => new XMLHttpRequest(opts),
1822
this.uri(),

lib/transports/polling-xhr.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ function empty() {}
1717

1818
export abstract class BaseXHR extends Polling {
1919
protected readonly xd: boolean;
20-
protected readonly cookieJar?: CookieJar;
2120

2221
private pollXhr: any;
2322

@@ -44,10 +43,6 @@ export abstract class BaseXHR extends Polling {
4443
opts.hostname !== location.hostname) ||
4544
port !== opts.port;
4645
}
47-
48-
if (this.opts.withCredentials) {
49-
this.cookieJar = createCookieJar();
50-
}
5146
}
5247

5348
/**
@@ -344,7 +339,7 @@ export class XHR extends BaseXHR {
344339
}
345340

346341
request(opts: Record<string, any> = {}) {
347-
Object.assign(opts, { xd: this.xd, cookieJar: this.cookieJar }, this.opts);
342+
Object.assign(opts, { xd: this.xd }, this.opts);
348343
return new Request(newRequest, this.uri(), opts as RequestOptions);
349344
}
350345
}

lib/transports/websocket.node.ts

+11
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,17 @@ export class WS extends BaseWS {
1616
protocols: string | string[] | undefined,
1717
opts: Record<string, any>
1818
) {
19+
if (this.socket?._cookieJar) {
20+
opts.headers = opts.headers || {};
21+
22+
opts.headers.cookie =
23+
typeof opts.headers.cookie === "string"
24+
? [opts.headers.cookie]
25+
: opts.headers.cookie || [];
26+
for (const [name, cookie] of this.socket._cookieJar.cookies) {
27+
opts.headers.cookie.push(`${name}=${cookie.value}`);
28+
}
29+
}
1930
return new WebSocket(uri, protocols, opts);
2031
}
2132

0 commit comments

Comments
 (0)