Skip to content

Commit

Permalink
fix: fix cookie management with WebSocket (Node.js only)
Browse files Browse the repository at this point in the history
Before this commit, the cookies were only sent with the HTTP
long-polling transport, and not when upgrading to WebSocket.

See also: 5fc88a6
  • Loading branch information
darrachequesne committed Jun 3, 2024
1 parent 3f66478 commit e105551
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 42 deletions.
34 changes: 18 additions & 16 deletions lib/globals.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export function parse(setCookieString: string): Cookie {
}

export class CookieJar {
private cookies = new Map<string, Cookie>();
private _cookies = new Map<string, Cookie>();

public parseCookies(values: string[]) {
if (!values) {
Expand All @@ -77,21 +77,27 @@ export class CookieJar {
values.forEach((value) => {
const parsed = parse(value);
if (parsed) {
this.cookies.set(parsed.name, parsed);
this._cookies.set(parsed.name, parsed);
}
});
}

get cookies() {
const now = Date.now();
this._cookies.forEach((cookie, name) => {
if (cookie.expires?.getTime() < now) {
this._cookies.delete(name);
}
});
return this._cookies.entries();
}

public addCookies(xhr: any) {
const cookies = [];

this.cookies.forEach((cookie, name) => {
if (cookie.expires?.getTime() < Date.now()) {
this.cookies.delete(name);
} else {
cookies.push(`${name}=${cookie.value}`);
}
});
for (const [name, cookie] of this.cookies) {
cookies.push(`${name}=${cookie.value}`);
}

if (cookies.length) {
xhr.setDisableHeaderCheck(true);
Expand All @@ -100,12 +106,8 @@ export class CookieJar {
}

public appendCookies(headers: Headers) {
this.cookies.forEach((cookie, name) => {
if (cookie.expires?.getTime() < Date.now()) {
this.cookies.delete(name);
} else {
headers.append("cookie", `${name}=${cookie.value}`);
}
});
for (const [name, cookie] of this.cookies) {
headers.append("cookie", `${name}=${cookie.value}`);
}
}
}
14 changes: 13 additions & 1 deletion lib/socket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import { Emitter } from "@socket.io/component-emitter";
import { protocol } from "engine.io-parser";
import type { Packet, BinaryType, PacketType, RawData } from "engine.io-parser";
import { CloseDetails, Transport } from "./transport.js";
import { defaultBinaryType } from "./globals.node.js";
import {
CookieJar,
createCookieJar,
defaultBinaryType,
} from "./globals.node.js";
import debugModule from "debug"; // debug()

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

static priorWebsocketSuccess: boolean;
static protocol = protocol;
Expand Down Expand Up @@ -444,6 +452,10 @@ export class SocketWithoutUpgrade extends Emitter<
}
}

if (this.opts.withCredentials) {
this._cookieJar = createCookieJar();
}

this.open();
}

Expand Down
6 changes: 3 additions & 3 deletions lib/transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { decodePacket } from "engine.io-parser";
import type { Packet, RawData } from "engine.io-parser";
import { Emitter } from "@socket.io/component-emitter";
import { installTimerFunctions } from "./util.js";
import debugModule from "debug"; // debug()
import { SocketOptions } from "./socket.js";
import type { Socket, SocketOptions } from "./socket.js";
import { encode } from "./contrib/parseqs.js";
import debugModule from "debug"; // debug()

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

Expand Down Expand Up @@ -48,7 +48,7 @@ export abstract class Transport extends Emitter<
protected opts: SocketOptions;
protected supportsBinary: boolean;
protected readyState: TransportState;
protected socket: any;
protected socket: Socket;
protected setTimeoutFn: typeof setTimeout;

/**
Expand Down
18 changes: 3 additions & 15 deletions lib/transports/polling-fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,6 @@ import { CookieJar, createCookieJar } from "../globals.node.js";
* @see https://caniuse.com/fetch
*/
export class Fetch extends Polling {
private readonly cookieJar?: CookieJar;

constructor(opts) {
super(opts);

if (this.opts.withCredentials) {
this.cookieJar = createCookieJar();
}
}

override doPoll() {
this._fetch()
.then((res) => {
Expand Down Expand Up @@ -56,18 +46,16 @@ export class Fetch extends Polling {
headers.set("content-type", "text/plain;charset=UTF-8");
}

this.cookieJar?.appendCookies(headers);
this.socket._cookieJar?.appendCookies(headers);

return fetch(this.uri(), {
method: isPost ? "POST" : "GET",
body: isPost ? data : null,
headers,
credentials: this.opts.withCredentials ? "include" : "omit",
}).then((res) => {
if (this.cookieJar) {
// @ts-ignore getSetCookie() was added in Node.js v19.7.0
this.cookieJar.parseCookies(res.headers.getSetCookie());
}
// @ts-ignore getSetCookie() was added in Node.js v19.7.0
this.socket._cookieJar?.parseCookies(res.headers.getSetCookie());

return res;
});
Expand Down
6 changes: 5 additions & 1 deletion lib/transports/polling-xhr.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ const XMLHttpRequest = XMLHttpRequestModule.default || XMLHttpRequestModule;
*/
export class XHR extends BaseXHR {
request(opts: Record<string, any> = {}) {
Object.assign(opts, { xd: this.xd, cookieJar: this.cookieJar }, this.opts);
Object.assign(
opts,
{ xd: this.xd, cookieJar: this.socket?._cookieJar },
this.opts
);
return new Request(
(opts) => new XMLHttpRequest(opts),
this.uri(),
Expand Down
7 changes: 1 addition & 6 deletions lib/transports/polling-xhr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ function empty() {}

export abstract class BaseXHR extends Polling {
protected readonly xd: boolean;
protected readonly cookieJar?: CookieJar;

private pollXhr: any;

Expand All @@ -44,10 +43,6 @@ export abstract class BaseXHR extends Polling {
opts.hostname !== location.hostname) ||
port !== opts.port;
}

if (this.opts.withCredentials) {
this.cookieJar = createCookieJar();
}
}

/**
Expand Down Expand Up @@ -344,7 +339,7 @@ export class XHR extends BaseXHR {
}

request(opts: Record<string, any> = {}) {
Object.assign(opts, { xd: this.xd, cookieJar: this.cookieJar }, this.opts);
Object.assign(opts, { xd: this.xd }, this.opts);
return new Request(newRequest, this.uri(), opts as RequestOptions);
}
}
Expand Down
11 changes: 11 additions & 0 deletions lib/transports/websocket.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@ export class WS extends BaseWS {
protocols: string | string[] | undefined,
opts: Record<string, any>
) {
if (this.socket?._cookieJar) {
opts.headers = opts.headers || {};

opts.headers.cookie =
typeof opts.headers.cookie === "string"
? [opts.headers.cookie]
: opts.headers.cookie || [];
for (const [name, cookie] of this.socket._cookieJar.cookies) {
opts.headers.cookie.push(`${name}=${cookie.value}`);
}
}
return new WebSocket(uri, protocols, opts);
}

Expand Down

0 comments on commit e105551

Please sign in to comment.