From f2596b34de972db0c979651173ab9dc37d3ad1a6 Mon Sep 17 00:00:00 2001 From: Espen Hovlandsdal Date: Mon, 27 Jan 2025 16:51:36 -0800 Subject: [PATCH] fix: include `message` and `code` on errors when logging in node.js and deno --- src/EventSource.ts | 10 +++---- src/errors.ts | 72 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 74 insertions(+), 8 deletions(-) diff --git a/src/EventSource.ts b/src/EventSource.ts index 476b3b5..b3b55a2 100644 --- a/src/EventSource.ts +++ b/src/EventSource.ts @@ -517,7 +517,7 @@ export class EventSource extends EventTarget { * @param code - The HTTP status code, if available * @internal */ - #failConnection(error?: string, code?: number) { + #failConnection(message?: string, code?: number) { // [spec] …if the readyState attribute is set to a value other than CLOSED, // [spec] sets the readyState attribute to CLOSED… if (this.#readyState !== this.CLOSED) { @@ -530,7 +530,7 @@ export class EventSource extends EventTarget { // [spec] > to their development consoles whenever an error event is fired, since little // [spec] > to no information can be made available in the events themselves. // Printing to console is not very programatically helpful, though, so we emit a custom event. - const errorEvent = new ErrorEvent('error', code, error) + const errorEvent = new ErrorEvent('error', {code, message}) this.#onError?.(errorEvent) this.dispatchEvent(errorEvent) @@ -539,11 +539,11 @@ export class EventSource extends EventTarget { /** * Schedules a reconnection attempt against the EventSource endpoint. * - * @param error - The error causing the connection to fail + * @param message - The error causing the connection to fail * @param code - The HTTP status code, if available * @internal */ - #scheduleReconnect(error?: string, code?: number) { + #scheduleReconnect(message?: string, code?: number) { // [spec] If the readyState attribute is set to CLOSED, abort the task. if (this.#readyState === this.CLOSED) { return @@ -553,7 +553,7 @@ export class EventSource extends EventTarget { this.#readyState = this.CONNECTING // [spec] Fire an event named `error` at the EventSource object. - const errorEvent = new ErrorEvent('error', code, error) + const errorEvent = new ErrorEvent('error', {code, message}) this.#onError?.(errorEvent) this.dispatchEvent(errorEvent) diff --git a/src/errors.ts b/src/errors.ts index 18a7ec0..a39f75d 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -22,10 +22,58 @@ export class ErrorEvent extends Event { */ public message?: string | undefined - constructor(type: string, code?: number, message?: string) { + /** + * Constructs a new `ErrorEvent` instance. This is typically not called directly, + * but rather emitted by the `EventSource` object when an error occurs. + * + * @param type - The type of the event (should be "error") + * @param errorEventInitDict - Optional properties to include in the error event + */ + constructor( + type: string, + errorEventInitDict?: {message?: string | undefined; code?: number | undefined}, + ) { super(type) - this.code = code ?? undefined - this.message = message ?? undefined + this.code = errorEventInitDict?.code ?? undefined + this.message = errorEventInitDict?.message ?? undefined + } + + /** + * Node.js "hides" the `message` and `code` properties of the `ErrorEvent` instance, + * when it is `console.log`'ed. This makes it harder to debug errors. To ease debugging, + * we explicitly include the properties in the `inspect` method. + * + * This is automatically called by Node.js when you `console.log` an instance of this class. + * + * @param _depth - The current depth + * @param options - The options passed to `util.inspect` + * @param inspect - The inspect function to use (prevents having to import it from `util`) + * @returns A string representation of the error + */ + [Symbol.for('nodejs.util.inspect.custom')]( + _depth: number, + options: {colors: boolean}, + inspect: (obj: unknown, inspectOptions: {colors: boolean}) => string, + ): string { + return inspect(inspectableError(this), options) + } + + /** + * Deno "hides" the `message` and `code` properties of the `ErrorEvent` instance, + * when it is `console.log`'ed. This makes it harder to debug errors. To ease debugging, + * we explicitly include the properties in the `inspect` method. + * + * This is automatically called by Deno when you `console.log` an instance of this class. + * + * @param inspect - The inspect function to use (prevents having to import it from `util`) + * @param options - The options passed to `Deno.inspect` + * @returns A string representation of the error + */ + [Symbol.for('Deno.customInspect')]( + inspect: (obj: unknown, inspectOptions: {colors: boolean}) => string, + options: {colors: boolean}, + ): string { + return inspect(inspectableError(this), options) } } @@ -73,3 +121,21 @@ export function flattenError(err: unknown): string { return err.message } + +/** + * Convert an `ErrorEvent` instance into a plain object for inspection. + * + * @param err - The `ErrorEvent` instance to inspect + * @returns A plain object representation of the error + * @internal + */ +function inspectableError(err: ErrorEvent) { + return { + type: err.type, + message: err.message, + code: err.code, + defaultPrevented: err.defaultPrevented, + cancelable: err.cancelable, + timeStamp: err.timeStamp, + } +}