Skip to content

Commit b9252e2

Browse files
feat: add details to the "close" event
The close event will now include additional details to help debugging if anything has gone wrong. Example when a payload is over the maxHttpBufferSize value in HTTP long-polling mode: ```js socket.on("close", (reason, details) => { console.log(reason); // "transport error" // in that case, details is an error object console.log(details.message); "xhr post error" console.log(details.description); // 413 (the HTTP status of the response) // details.context refers to the XMLHttpRequest object console.log(details.context.status); // 413 console.log(details.context.responseText); // "" }); ``` Note: the error object was already included before this commit and is kept for backward compatibility Example when a payload is over the maxHttpBufferSize value with WebSockets: ```js socket.on("close", (reason, details) => { console.log(reason); // "transport close" // in that case, details is a plain object console.log(details.description); // "websocket connection closed" // details.context is a CloseEvent object console.log(details.context.code); // 1009 (which means "Message Too Big") console.log(details.context.reason); // "" }); ``` Example within a cluster without sticky sessions: ```js socket.on("close", (reason, details) => { console.log(details.context.status); // 400 console.log(details.context.responseText); // '{"code":1,"message":"Session ID unknown"}' }); ``` Note: we could also print some warnings in development for the "usual" errors: - CORS error - HTTP 400 with multiple nodes - HTTP 413 with maxHttpBufferSize but that would require an additional step when going to production (i.e. setting NODE_ENV variable to "production"). This is open to discussion! Related: - socketio/socket.io#3946 - socketio/socket.io#1979 - socketio/socket.io-client#1518
1 parent 6e1bbff commit b9252e2

File tree

6 files changed

+181
-46
lines changed

6 files changed

+181
-46
lines changed

lib/socket.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import parseuri from "parseuri";
55
import debugModule from "debug"; // debug()
66
import { Emitter } from "@socket.io/component-emitter";
77
import { protocol } from "engine.io-parser";
8+
import { CloseDetails } from "./transport";
89

910
const debug = debugModule("engine.io-client:socket"); // debug()
1011

@@ -230,7 +231,7 @@ interface SocketReservedEvents {
230231
upgrading: (transport) => void;
231232
upgrade: (transport) => void;
232233
upgradeError: (err: Error) => void;
233-
close: (reason: string, desc?: Error) => void;
234+
close: (reason: string, description?: CloseDetails | Error) => void;
234235
}
235236

236237
export class Socket extends Emitter<{}, {}, SocketReservedEvents> {
@@ -365,7 +366,9 @@ export class Socket extends Emitter<{}, {}, SocketReservedEvents> {
365366
}
366367
if (this.hostname !== "localhost") {
367368
this.offlineEventListener = () => {
368-
this.onClose("transport close");
369+
this.onClose("transport close", {
370+
description: "network connection lost"
371+
});
369372
};
370373
addEventListener("offline", this.offlineEventListener, false);
371374
}
@@ -471,9 +474,7 @@ export class Socket extends Emitter<{}, {}, SocketReservedEvents> {
471474
.on("drain", this.onDrain.bind(this))
472475
.on("packet", this.onPacket.bind(this))
473476
.on("error", this.onError.bind(this))
474-
.on("close", () => {
475-
this.onClose("transport close");
476-
});
477+
.on("close", reason => this.onClose("transport close", reason));
477478
}
478479

479480
/**
@@ -890,7 +891,7 @@ export class Socket extends Emitter<{}, {}, SocketReservedEvents> {
890891
*
891892
* @api private
892893
*/
893-
private onClose(reason: string, desc?) {
894+
private onClose(reason: string, description?: CloseDetails | Error) {
894895
if (
895896
"opening" === this.readyState ||
896897
"open" === this.readyState ||
@@ -921,7 +922,7 @@ export class Socket extends Emitter<{}, {}, SocketReservedEvents> {
921922
this.id = null;
922923

923924
// emit close event
924-
this.emitReserved("close", reason, desc);
925+
this.emitReserved("close", reason, description);
925926

926927
// clean buffers after, so users can still
927928
// grab the buffers on `close` event

lib/transport.ts

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,42 @@
1-
import { decodePacket } from "engine.io-parser";
2-
import { DefaultEventsMap, Emitter } from "@socket.io/component-emitter";
1+
import { decodePacket, Packet, RawData } from "engine.io-parser";
2+
import { Emitter } from "@socket.io/component-emitter";
33
import { installTimerFunctions } from "./util.js";
44
import debugModule from "debug"; // debug()
55
import { SocketOptions } from "./socket.js";
66

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

9+
class TransportError extends Error {
10+
public readonly type = "TransportError";
11+
12+
constructor(
13+
reason: string,
14+
readonly description: any,
15+
readonly context: any
16+
) {
17+
super(reason);
18+
}
19+
}
20+
21+
export interface CloseDetails {
22+
description: string;
23+
context?: CloseEvent | XMLHttpRequest;
24+
}
25+
26+
interface TransportReservedEvents {
27+
open: () => void;
28+
error: (err: TransportError) => void;
29+
packet: (packet: Packet) => void;
30+
close: (details?: CloseDetails) => void;
31+
poll: () => void;
32+
pollComplete: () => void;
33+
drain: () => void;
34+
}
35+
936
export abstract class Transport extends Emitter<
10-
DefaultEventsMap,
11-
DefaultEventsMap
37+
{},
38+
{},
39+
TransportReservedEvents
1240
> {
1341
protected opts: SocketOptions;
1442
protected supportsBinary: boolean;
@@ -37,17 +65,17 @@ export abstract class Transport extends Emitter<
3765
/**
3866
* Emits an error.
3967
*
40-
* @param {String} str
68+
* @param {String} reason
69+
* @param description
70+
* @param context - the error context
4171
* @return {Transport} for chaining
4272
* @api protected
4373
*/
44-
protected onError(msg, desc) {
45-
const err = new Error(msg);
46-
// @ts-ignore
47-
err.type = "TransportError";
48-
// @ts-ignore
49-
err.description = desc;
50-
super.emit("error", err);
74+
protected onError(reason: string, description: any, context?: any) {
75+
super.emitReserved(
76+
"error",
77+
new TransportError(reason, description, context)
78+
);
5179
return this;
5280
}
5381

@@ -102,7 +130,7 @@ export abstract class Transport extends Emitter<
102130
protected onOpen() {
103131
this.readyState = "open";
104132
this.writable = true;
105-
super.emit("open");
133+
super.emitReserved("open");
106134
}
107135

108136
/**
@@ -111,7 +139,7 @@ export abstract class Transport extends Emitter<
111139
* @param {String} data
112140
* @api protected
113141
*/
114-
protected onData(data) {
142+
protected onData(data: RawData) {
115143
const packet = decodePacket(data, this.socket.binaryType);
116144
this.onPacket(packet);
117145
}
@@ -121,18 +149,18 @@ export abstract class Transport extends Emitter<
121149
*
122150
* @api protected
123151
*/
124-
protected onPacket(packet) {
125-
super.emit("packet", packet);
152+
protected onPacket(packet: Packet) {
153+
super.emitReserved("packet", packet);
126154
}
127155

128156
/**
129157
* Called upon close.
130158
*
131159
* @api protected
132160
*/
133-
protected onClose() {
161+
protected onClose(details?: CloseDetails) {
134162
this.readyState = "closed";
135-
super.emit("close");
163+
super.emitReserved("close", details);
136164
}
137165

138166
protected abstract doOpen();

lib/transports/polling-xhr.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { installTimerFunctions, pick } from "../util.js";
77
import { DefaultEventsMap, Emitter } from "@socket.io/component-emitter";
88
import { Polling } from "./polling.js";
99
import { SocketOptions } from "../socket.js";
10+
import { RawData } from "engine.io-parser";
11+
import { CloseDetails } from "../transport";
1012

1113
const debug = debugModule("engine.io-client:polling-xhr"); // debug()
1214

@@ -83,8 +85,8 @@ export class XHR extends Polling {
8385
data: data
8486
});
8587
req.on("success", fn);
86-
req.on("error", err => {
87-
this.onError("xhr post error", err);
88+
req.on("error", (xhrStatus, context) => {
89+
this.onError("xhr post error", xhrStatus, context);
8890
});
8991
}
9092

@@ -97,14 +99,20 @@ export class XHR extends Polling {
9799
debug("xhr poll");
98100
const req = this.request();
99101
req.on("data", this.onData.bind(this));
100-
req.on("error", err => {
101-
this.onError("xhr poll error", err);
102+
req.on("error", (xhrStatus, context) => {
103+
this.onError("xhr poll error", xhrStatus, context);
102104
});
103105
this.pollXhr = req;
104106
}
105107
}
106108

107-
export class Request extends Emitter<DefaultEventsMap, DefaultEventsMap> {
109+
interface RequestReservedEvents {
110+
success: () => void;
111+
data: (data: RawData) => void;
112+
error: (err: number | Error, context: XMLHttpRequest) => void;
113+
}
114+
115+
export class Request extends Emitter<{}, {}, RequestReservedEvents> {
108116
private readonly opts: { xd; xs } & SocketOptions;
109117
private readonly method: string;
110118
private readonly uri: string;
@@ -230,7 +238,7 @@ export class Request extends Emitter<DefaultEventsMap, DefaultEventsMap> {
230238
* @api private
231239
*/
232240
onSuccess() {
233-
this.emit("success");
241+
this.emitReserved("success");
234242
this.cleanup();
235243
}
236244

@@ -240,7 +248,7 @@ export class Request extends Emitter<DefaultEventsMap, DefaultEventsMap> {
240248
* @api private
241249
*/
242250
onData(data) {
243-
this.emit("data", data);
251+
this.emitReserved("data", data);
244252
this.onSuccess();
245253
}
246254

@@ -249,8 +257,8 @@ export class Request extends Emitter<DefaultEventsMap, DefaultEventsMap> {
249257
*
250258
* @api private
251259
*/
252-
onError(err) {
253-
this.emit("error", err);
260+
onError(err: number | Error) {
261+
this.emitReserved("error", err, this.xhr);
254262
this.cleanup(true);
255263
}
256264

lib/transports/polling.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export abstract class Polling extends Transport {
7575
debug("polling");
7676
this.polling = true;
7777
this.doPoll();
78-
this.emit("poll");
78+
this.emitReserved("poll");
7979
}
8080

8181
/**
@@ -93,7 +93,7 @@ export abstract class Polling extends Transport {
9393

9494
// if its a close packet, we close the ongoing requests
9595
if ("close" === packet.type) {
96-
this.onClose();
96+
this.onClose({ description: "transport closed by the server" });
9797
return false;
9898
}
9999

@@ -108,7 +108,7 @@ export abstract class Polling extends Transport {
108108
if ("closed" !== this.readyState) {
109109
// if we got data we're not polling
110110
this.polling = false;
111-
this.emit("pollComplete");
111+
this.emitReserved("pollComplete");
112112

113113
if ("open" === this.readyState) {
114114
this.poll();
@@ -153,7 +153,7 @@ export abstract class Polling extends Transport {
153153
encodePayload(packets, data => {
154154
this.doWrite(data, () => {
155155
this.writable = true;
156-
this.emit("drain");
156+
this.emitReserved("drain");
157157
});
158158
});
159159
}

lib/transports/websocket.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ export class WS extends Transport {
9191
: new WebSocket(uri)
9292
: new WebSocket(uri, protocols, opts);
9393
} catch (err) {
94-
return this.emit("error", err);
94+
return this.emitReserved("error", err);
9595
}
9696

9797
this.ws.binaryType = this.socket.binaryType || defaultBinaryType;
@@ -111,7 +111,11 @@ export class WS extends Transport {
111111
}
112112
this.onOpen();
113113
};
114-
this.ws.onclose = this.onClose.bind(this);
114+
this.ws.onclose = closeEvent =>
115+
this.onClose({
116+
description: "websocket connection closed",
117+
context: closeEvent
118+
});
115119
this.ws.onmessage = ev => this.onData(ev.data);
116120
this.ws.onerror = e => this.onError("websocket error", e);
117121
}
@@ -168,7 +172,7 @@ export class WS extends Transport {
168172
// defer to next tick to allow Socket to clear writeBuffer
169173
nextTick(() => {
170174
this.writable = true;
171-
this.emit("drain");
175+
this.emitReserved("drain");
172176
}, this.setTimeoutFn);
173177
}
174178
});

0 commit comments

Comments
 (0)