From 7fb0b5ae226d4daed4a74e38ae17cf27ea399206 Mon Sep 17 00:00:00 2001 From: Aryan Jassal Date: Thu, 7 Nov 2024 10:38:13 +1100 Subject: [PATCH 1/4] feat: properly defined message for non-Error errors --- src/RPCServer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/RPCServer.ts b/src/RPCServer.ts index 0d65799..24a6199 100644 --- a/src/RPCServer.ts +++ b/src/RPCServer.ts @@ -327,7 +327,7 @@ class RPCServer { try { const rpcError: JSONRPCResponseError = { code: errors.JSONRPCResponseErrorCode.RPCRemote, - message: e.message, + message: e.message ?? e.constructor.name, data: this.fromError(e), }; const rpcErrorMessage: JSONRPCResponseFailed = { @@ -599,7 +599,7 @@ class RPCServer { try { const rpcError: JSONRPCResponseError = { code: errors.JSONRPCResponseErrorCode.RPCRemote, - message: e.message, + message: e.message ?? e.constructor.name, data: this.fromError(e), }; const rpcErrorMessage: JSONRPCResponseFailed = { From 0ddd329cb12beaff6fdff3ebb4d6adb3461e283a Mon Sep 17 00:00:00 2001 From: Aryan Jassal Date: Thu, 7 Nov 2024 12:08:13 +1100 Subject: [PATCH 2/4] chore: added a function to better construct the message --- src/RPCServer.ts | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/RPCServer.ts b/src/RPCServer.ts index 24a6199..8ba36c0 100644 --- a/src/RPCServer.ts +++ b/src/RPCServer.ts @@ -37,6 +37,30 @@ import * as events from './events'; const cleanupReason = Symbol('CleanupReason'); +function composeMessage(error: unknown): string { + if ( + typeof error === 'boolean' || + typeof error === 'number' || + typeof error === 'string' || + typeof error === 'bigint' || + typeof error === 'symbol' + ) { + return `Non-error literal ${String(error)} was thrown`; + } + if ( + typeof error === 'object' && + error != null && + 'message' in error && + error.message != null + ) { + return String(error.message); + } + if (typeof error === 'object' && error?.constructor?.name != null) { + return `Non-error object ${error.constructor.name} was thrown`; + } + return `Non-error value ${error} was thrown`; +} + /** * You must provide a error handler `addEventListener('error')`. * Otherwise errors will just be ignored. @@ -327,7 +351,7 @@ class RPCServer { try { const rpcError: JSONRPCResponseError = { code: errors.JSONRPCResponseErrorCode.RPCRemote, - message: e.message ?? e.constructor.name, + message: composeMessage(e), data: this.fromError(e), }; const rpcErrorMessage: JSONRPCResponseFailed = { @@ -599,7 +623,7 @@ class RPCServer { try { const rpcError: JSONRPCResponseError = { code: errors.JSONRPCResponseErrorCode.RPCRemote, - message: e.message ?? e.constructor.name, + message: composeMessage(e), data: this.fromError(e), }; const rpcErrorMessage: JSONRPCResponseFailed = { From d779d8b568febdd764ddb7fd4d8d54fdcb739a62 Mon Sep 17 00:00:00 2001 From: Aryan Jassal Date: Thu, 7 Nov 2024 12:45:18 +1100 Subject: [PATCH 3/4] fix: dealing with edge case where error cannot be serialised --- src/RPCServer.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/RPCServer.ts b/src/RPCServer.ts index 8ba36c0..4a75143 100644 --- a/src/RPCServer.ts +++ b/src/RPCServer.ts @@ -58,7 +58,14 @@ function composeMessage(error: unknown): string { if (typeof error === 'object' && error?.constructor?.name != null) { return `Non-error object ${error.constructor.name} was thrown`; } - return `Non-error value ${error} was thrown`; + // If an object is not serialisable (for example, Object.create(null)), then + // an error would be raised when trying to convert it to a string. In that + // case, simply avoid serialising the error. + try { + return `Non-error value ${error} was thrown`; + } catch (e) { + return 'Non-error value was thrown' + } } /** From 66a2a1582d3cb1cec462b0967d77be5c2ed0abf0 Mon Sep 17 00:00:00 2001 From: Aryan Jassal Date: Thu, 7 Nov 2024 14:35:50 +1100 Subject: [PATCH 4/4] chore: cleaned up and renamed function to compose messages --- src/RPCServer.ts | 56 +++++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/src/RPCServer.ts b/src/RPCServer.ts index 4a75143..1b183d7 100644 --- a/src/RPCServer.ts +++ b/src/RPCServer.ts @@ -37,34 +37,36 @@ import * as events from './events'; const cleanupReason = Symbol('CleanupReason'); -function composeMessage(error: unknown): string { - if ( - typeof error === 'boolean' || - typeof error === 'number' || - typeof error === 'string' || - typeof error === 'bigint' || - typeof error === 'symbol' - ) { - return `Non-error literal ${String(error)} was thrown`; - } - if ( - typeof error === 'object' && - error != null && - 'message' in error && - error.message != null - ) { - return String(error.message); - } - if (typeof error === 'object' && error?.constructor?.name != null) { - return `Non-error object ${error.constructor.name} was thrown`; +function composeErrorMessage(error: unknown): string { + switch (typeof error) { + case 'boolean': + case 'number': + case 'string': + case 'bigint': + case 'symbol': + return `Non-error literal ${String(error)} was thrown`; + case 'object': + // Let the fallback handler catch null values. + if (error == null) break; + // If we have an error message defined, then return that. + if ('message' in error && typeof error.message === 'string') { + return error.message; + } + // If present, mention the constructor name in the message. + if (error.constructor?.name != null) { + return `Non-error object ${error.constructor.name} was thrown`; + } + // Any other values should be handled by the fallback handler. + break; } - // If an object is not serialisable (for example, Object.create(null)), then - // an error would be raised when trying to convert it to a string. In that - // case, simply avoid serialising the error. + + // Handle cases where the error is not serialisable, like objects created + // using Object.create(null). Trying to serialise this throws a TypeError. try { - return `Non-error value ${error} was thrown`; + return `Non-error value ${String(error)} was thrown`; } catch (e) { - return 'Non-error value was thrown' + if (e instanceof TypeError) return 'Non-error value was thrown'; + else throw e; } } @@ -358,7 +360,7 @@ class RPCServer { try { const rpcError: JSONRPCResponseError = { code: errors.JSONRPCResponseErrorCode.RPCRemote, - message: composeMessage(e), + message: composeErrorMessage(e), data: this.fromError(e), }; const rpcErrorMessage: JSONRPCResponseFailed = { @@ -630,7 +632,7 @@ class RPCServer { try { const rpcError: JSONRPCResponseError = { code: errors.JSONRPCResponseErrorCode.RPCRemote, - message: composeMessage(e), + message: composeErrorMessage(e), data: this.fromError(e), }; const rpcErrorMessage: JSONRPCResponseFailed = {