From e5f5552e1cc4a292c560731ebc742f205119af42 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Thu, 4 Sep 2025 14:47:18 +0000 Subject: [PATCH 1/2] feat: Make client URL usage more lenient Compensate for URLs with trailing slashes when joining api path --- client/core/ts/src/lib.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/client/core/ts/src/lib.ts b/client/core/ts/src/lib.ts index bb473a72f..279a9c4fa 100644 --- a/client/core/ts/src/lib.ts +++ b/client/core/ts/src/lib.ts @@ -50,6 +50,25 @@ export type ClientState = { secret: string | undefined; }; +/** + * Joins all interim parts with one separator character + * + * @see https://stackoverflow.com/a/55142565/1469797 + */ +const pathJoin = (parts: string[], sep?: string) => { + const separator = sep || '/'; + parts = parts.map((part, index)=>{ + if (index) { + part = part.replace(new RegExp('^' + separator), ''); + } + if (index !== parts.length - 1) { + part = part.replace(new RegExp(separator + '$'), ''); + } + return part; + }) + return parts.join(separator); +} + /** Initialize a new client for Komodo */ export function KomodoClient(url: string, options: InitOptions) { const state: ClientState = { @@ -65,7 +84,7 @@ export function KomodoClient(url: string, options: InitOptions) { ): Promise => new Promise(async (res, rej) => { try { - let response = await fetch(`${url}${path}/${type}`, { + let response = await fetch(pathJoin([url, `${path}/${type}`]), { method: "POST", body: JSON.stringify(params), headers: { From c9050c73c6714fc2c629091ebf347e45f30a05ed Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Thu, 4 Sep 2025 15:13:47 +0000 Subject: [PATCH 2/2] feat: Use proper Error class when throwing client response errors * Uses Error Cause for non-komodo errors * Error message is extracted from Serror, if present * Appends trace, if present, to stack string * Exposes response and SError as class fields --- client/core/ts/src/lib.ts | 62 ++++++++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 17 deletions(-) diff --git a/client/core/ts/src/lib.ts b/client/core/ts/src/lib.ts index 279a9c4fa..2007e8e37 100644 --- a/client/core/ts/src/lib.ts +++ b/client/core/ts/src/lib.ts @@ -24,6 +24,7 @@ import { UserRequest, WriteRequest, WsLoginMessage, + __Serror, } from "./types.js"; export * as Types from "./types.js"; @@ -69,6 +70,46 @@ const pathJoin = (parts: string[], sep?: string) => { return parts.join(separator); } +export const asSError = (e: unknown): e is __Serror => { + return e !== null + && typeof e === 'object' + && 'error' in e + && typeof e.error === 'string' + && 'trace' in e + && Array.isArray(e.trace); +} + +export class KomodoApiError extends Error { + + response?: Response; + sError?: __Serror; + + constructor(kError?: __Serror | string, options: ErrorOptions & {response?: Response} = {}) { + let status = 1; + + const { + response + } = options; + if(response !== undefined) { + status = response.status; + } + super(`(${status}) ${asSError(kError) ? kError.error : kError}`, options); + + this.name = 'KomodoApiError'; + this.response = response; + + let kTrace: string = '(No trace returned from Komodo)'; + + if(asSError(kError)) { + this.sError = kError; + if(kError.trace.length > 0) { + kTrace = `${kError.trace.join('\n')}` + } + } + this.stack = `${this.stack ?? ''}\nKomodo Trace: ${kTrace}`; + } +} + /** Initialize a new client for Komodo */ export function KomodoClient(url: string, options: InitOptions) { const state: ClientState = { @@ -82,6 +123,7 @@ export function KomodoClient(url: string, options: InitOptions) { type: string, params: Params ): Promise => + // deno-lint-ignore no-async-promise-executor new Promise(async (res, rej) => { try { let response = await fetch(pathJoin([url, `${path}/${type}`]), { @@ -107,27 +149,13 @@ export function KomodoClient(url: string, options: InitOptions) { } else { try { const result = await response.json(); - rej({ status: response.status, result }); + rej(new KomodoApiError(result, {response})); } catch (error) { - rej({ - status: response.status, - result: { - error: "Failed to get response body", - trace: [JSON.stringify(error)], - }, - error, - }); + rej(new KomodoApiError('Failed to get response body', {response, cause: error})); } } } catch (error) { - rej({ - status: 1, - result: { - error: "Request failed with error", - trace: [JSON.stringify(error)], - }, - error, - }); + rej(new KomodoApiError('Request failed with error', {cause: error})); } });