From 4f3fb451d2f2084e5e36d6a2e41a0f6e6a6ef749 Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Fri, 16 May 2025 12:59:59 +0200 Subject: [PATCH 01/10] Add a utility function to convert any input to an Error instance --- web/packages/shared/libs/tdp/client.ts | 5 +- web/packages/shared/utils/error.test.ts | 102 ++++++++++++++++++++++++ web/packages/shared/utils/error.ts | 48 +++++++++++ 3 files changed, 153 insertions(+), 2 deletions(-) create mode 100644 web/packages/shared/utils/error.test.ts create mode 100644 web/packages/shared/utils/error.ts diff --git a/web/packages/shared/libs/tdp/client.ts b/web/packages/shared/libs/tdp/client.ts index f1c6e7fb269e5..92b12b2ff22a8 100644 --- a/web/packages/shared/libs/tdp/client.ts +++ b/web/packages/shared/libs/tdp/client.ts @@ -26,6 +26,7 @@ import init, { } from 'shared/libs/ironrdp/pkg/ironrdp'; import Logger from 'shared/libs/logger'; import { isAbortError } from 'shared/utils/abortError'; +import { ensureError } from 'shared/utils/error'; import Codec, { FileType, @@ -152,7 +153,7 @@ export class TdpClient extends EventEmitter { this.transportAbortController.signal ); } catch (error) { - this.emit(TdpClientEvent.ERROR, error); + this.emit(TdpClientEvent.ERROR, ensureError(error)); return; } @@ -173,7 +174,7 @@ export class TdpClient extends EventEmitter { subscribers.add( this.transport.onMessage(data => { void this.processMessage(data).catch(error => { - processingError = error; + processingError = ensureError(error); unsubscribe(); // All errors are treated as fatal, close the connection. this.transportAbortController.abort(); diff --git a/web/packages/shared/utils/error.test.ts b/web/packages/shared/utils/error.test.ts new file mode 100644 index 0000000000000..9ea0d0c194e86 --- /dev/null +++ b/web/packages/shared/utils/error.test.ts @@ -0,0 +1,102 @@ +/** + * Teleport + * Copyright (C) 2025 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { ensureError } from './error'; + +class CustomErrorClass extends Error { + constructor(message: string) { + super(message); + this.name = 'CustomErrorClass'; + } +} + +describe('ensureError', () => { + const cases = [ + { + input: new Error('already error'), + expectedMessage: 'already error', + expectedName: 'Error', + expectedInstance: Error, + }, + { + input: { message: 'custom message' }, + expectedMessage: 'custom message', + expectedName: 'Error', + expectedInstance: Error, + }, + { + input: { message: '', otherField: '123' }, + expectedMessage: '', + expectedName: 'Error', + expectedInstance: Error, + }, + { + input: { name: 'MyError', message: 'fail' }, + expectedMessage: 'fail', + expectedName: 'MyError', + expectedInstance: Error, + }, + { + input: new CustomErrorClass('fail'), + expectedMessage: 'fail', + expectedName: 'CustomErrorClass', + expectedInstance: CustomErrorClass, + }, + { + input: { foo: 'bar' }, + expectedMessage: '{"foo":"bar"}', + expectedName: 'Error', + expectedInstance: Error, + }, + { + input: 'just a string', + expectedMessage: 'just a string', + expectedName: 'Error', + expectedInstance: Error, + }, + { + input: 42, + expectedMessage: '42', + expectedName: 'Error', + expectedInstance: Error, + }, + { + input: null, + expectedMessage: '', + expectedName: 'Error', + expectedInstance: Error, + }, + { + input: undefined, + expectedMessage: '', + expectedName: 'Error', + expectedInstance: Error, + }, + ]; + + test.each(cases)( + 'converts input "$input" to Error with message "$expectedMessage" and name "$expectedName"', + ({ input, expectedMessage, expectedName, expectedInstance }) => { + const error = ensureError(input); + + expect(error).toBeInstanceOf(expectedInstance); + expect(error.message).toBe(expectedMessage); + expect(error.name).toBe(expectedName); + } + ); +}); diff --git a/web/packages/shared/utils/error.ts b/web/packages/shared/utils/error.ts new file mode 100644 index 0000000000000..6117b05f7534e --- /dev/null +++ b/web/packages/shared/utils/error.ts @@ -0,0 +1,48 @@ +/** + * Teleport + * Copyright (C) 2025 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * Converts any unknown input into an Error instance. + * Preserves message and name if available. + */ +export function ensureError(err: unknown): Error { + if (err instanceof Error) { + return err; + } + + if (err === null || err === undefined) { + return new Error(); + } + + if (typeof err === 'object') { + let message: string; + if ('message' in err) { + message = String(err.message); + } else { + message = JSON.stringify(err); + } + + const error = new Error(message); + if ('name' in err && typeof err.name === 'string') { + error.name = err.name; + } + return error; + } + + return new Error(String(err)); +} From 3872154edbec10acacf672a5c3097f1e8caa054a Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Fri, 16 May 2025 13:01:36 +0200 Subject: [PATCH 02/10] Add types for TdpClient events --- .../DesktopSession/DesktopSession.story.tsx | 2 +- web/packages/shared/libs/tdp/client.ts | 21 +++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/web/packages/shared/components/DesktopSession/DesktopSession.story.tsx b/web/packages/shared/components/DesktopSession/DesktopSession.story.tsx index 53b29c246554f..c076d3530577d 100644 --- a/web/packages/shared/components/DesktopSession/DesktopSession.story.tsx +++ b/web/packages/shared/components/DesktopSession/DesktopSession.story.tsx @@ -109,7 +109,7 @@ export const Connected = () => { export const DisconnectedWithNoMessage = () => { const client = fakeClient(); client.connect = async () => { - client.emit(TdpClientEvent.TRANSPORT_CLOSE); + client.emit(TdpClientEvent.TRANSPORT_CLOSE, undefined); }; return ; diff --git a/web/packages/shared/libs/tdp/client.ts b/web/packages/shared/libs/tdp/client.ts index 92b12b2ff22a8..d3830fe15447c 100644 --- a/web/packages/shared/libs/tdp/client.ts +++ b/web/packages/shared/libs/tdp/client.ts @@ -81,6 +81,23 @@ export enum TdpClientEvent { LATENCY_STATS = 'latency stats', } +type EventMap = { + [TdpClientEvent.TDP_CLIENT_SCREEN_SPEC]: [ClientScreenSpec]; + [TdpClientEvent.TDP_PNG_FRAME]: [PngFrame]; + [TdpClientEvent.TDP_BMP_FRAME]: [BitmapFrame]; + [TdpClientEvent.TDP_CLIPBOARD_DATA]: [ClipboardData]; + [TdpClientEvent.ERROR]: [Error]; + [TdpClientEvent.TDP_WARNING]: [string]; + [TdpClientEvent.CLIENT_WARNING]: [string]; + [TdpClientEvent.TDP_INFO]: [string]; + [TdpClientEvent.TRANSPORT_OPEN]: [void]; + [TdpClientEvent.TRANSPORT_CLOSE]: [Error | undefined]; + [TdpClientEvent.RESET]: [void]; + [TdpClientEvent.POINTER]: [PointerData]; + [TdpClientEvent.LATENCY_STATS]: [LatencyStats]; + 'terminal.webauthn': [string]; +}; + export enum LogType { OFF = 'OFF', ERROR = 'ERROR', @@ -117,7 +134,7 @@ let wasmReady: Promise | undefined; // sending client commands, and receiving and processing server messages. Its creator is responsible for // ensuring the websocket gets closed and all of its event listeners cleaned up when it is no longer in use. // For convenience, this can be done in one fell swoop by calling Client.shutdown(). -export class TdpClient extends EventEmitter { +export class TdpClient extends EventEmitter { protected codec: Codec; protected transport: TdpTransport | undefined; private transportAbortController: AbortController | undefined; @@ -198,7 +215,7 @@ export class TdpClient extends EventEmitter { } else if (connectionError && !isAbortError(connectionError)) { this.emit(TdpClientEvent.TRANSPORT_CLOSE, connectionError); } else { - this.emit(TdpClientEvent.TRANSPORT_CLOSE); + this.emit(TdpClientEvent.TRANSPORT_CLOSE, undefined); } this.logger.info('Transport is closed'); From 230352d908ac7b268e30538b8202028d1f1110f3 Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Fri, 16 May 2025 13:02:12 +0200 Subject: [PATCH 03/10] Always throw Error from `adaptWebSocketToTdpTransport` --- .../teleport/src/lib/tdp/webSocketTransportAdapter.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/web/packages/teleport/src/lib/tdp/webSocketTransportAdapter.ts b/web/packages/teleport/src/lib/tdp/webSocketTransportAdapter.ts index 88fd2caa1dd45..5c789cd6d600d 100644 --- a/web/packages/teleport/src/lib/tdp/webSocketTransportAdapter.ts +++ b/web/packages/teleport/src/lib/tdp/webSocketTransportAdapter.ts @@ -95,7 +95,11 @@ async function waitToOpen(socket: WebSocket): Promise { const handleError = (event: Event) => { cleanup(); - reject(event); + reject( + new Error( + `WebSocket error (type=${event.type}, readyState=${socket.readyState})` + ) + ); }; function cleanup() { From 195c8e9dc6d5ce4502e0f1ebd4a67cbdd8de6e14 Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Fri, 16 May 2025 14:02:41 +0200 Subject: [PATCH 04/10] Improve getting the error message in `getErrMessage` --- web/packages/shared/utils/error.ts | 9 +++++++++ web/packages/shared/utils/errorType.ts | 14 ++++---------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/web/packages/shared/utils/error.ts b/web/packages/shared/utils/error.ts index 6117b05f7534e..266fd3104253b 100644 --- a/web/packages/shared/utils/error.ts +++ b/web/packages/shared/utils/error.ts @@ -46,3 +46,12 @@ export function ensureError(err: unknown): Error { return new Error(String(err)); } + +/** Extracts an error message or returns a default one. */ +export function getErrorMessage(err: unknown): string { + const errorInstance = ensureError(err); + if (errorInstance.message === '') { + return 'something went wrong'; + } + return errorInstance.message; +} diff --git a/web/packages/shared/utils/errorType.ts b/web/packages/shared/utils/errorType.ts index 40d531e9a9b79..fa8b38d54a3ff 100644 --- a/web/packages/shared/utils/errorType.ts +++ b/web/packages/shared/utils/errorType.ts @@ -16,13 +16,7 @@ * along with this program. If not, see . */ -// getErrMessage first checks if the error is of type Error -// before attempting to access the error message field. -// Used with try catch blocks, where the error caught -// may not necessary be of type Error. -export function getErrMessage(err: unknown) { - let message = 'something went wrong'; - if (err instanceof Error) message = err.message; - - return message; -} +export { + /** @deprecated Import `getErrorMessage` from 'shared/utils/error.ts' */ + getErrorMessage as getErrMessage, +} from './error'; From ac71a063bab9d15fa54badade45bf95621373f9d Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Fri, 16 May 2025 14:03:40 +0200 Subject: [PATCH 05/10] Move `isAbortError` to error.ts --- web/packages/shared/utils/abortError.test.ts | 45 -------------------- web/packages/shared/utils/abortError.ts | 16 ++----- web/packages/shared/utils/error.test.ts | 30 ++++++++++++- web/packages/shared/utils/error.ts | 13 ++++++ 4 files changed, 46 insertions(+), 58 deletions(-) delete mode 100644 web/packages/shared/utils/abortError.test.ts diff --git a/web/packages/shared/utils/abortError.test.ts b/web/packages/shared/utils/abortError.test.ts deleted file mode 100644 index 312d328b4a378..0000000000000 --- a/web/packages/shared/utils/abortError.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Teleport - * Copyright (C) 2023 Gravitational, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -import { isAbortError } from 'shared/utils/abortError'; - -describe.each([ - ['DOMException', newDOMAbortError], - ['ApiError', newApiAbortError], - ['gRPC Error', newGrpcAbortError], -])('for error type %s', (_, ErrorType) => { - it('is abort error', async () => { - expect(isAbortError(ErrorType())).toBe(true); - }); -}); - -function newDOMAbortError() { - return new DOMException('Aborted', 'AbortError'); -} - -// mimics ApiError -function newApiAbortError() { - return new Error('The user aborted a request', { - cause: newDOMAbortError(), - }); -} - -// mimics TshdRpcError -function newGrpcAbortError() { - return { code: 'CANCELLED' }; -} diff --git a/web/packages/shared/utils/abortError.ts b/web/packages/shared/utils/abortError.ts index 2bff7ce9769bc..692f7e83bf692 100644 --- a/web/packages/shared/utils/abortError.ts +++ b/web/packages/shared/utils/abortError.ts @@ -16,15 +16,7 @@ * along with this program. If not, see . */ -export const isAbortError = (err: any): boolean => { - // handles Web UI abort error - if ( - (err instanceof DOMException && err.name === 'AbortError') || - (err.cause && isAbortError(err.cause)) - ) { - return true; - } - - // handles Connect abort error (specifically gRPC cancel error), see TshdRpcError - return err?.code === 'CANCELLED'; -}; +export { + /** @deprecated Import `isAbortError` from 'shared/utils/error.ts' */ + isAbortError, +} from './error'; diff --git a/web/packages/shared/utils/error.test.ts b/web/packages/shared/utils/error.test.ts index 9ea0d0c194e86..e7f0ae13d528a 100644 --- a/web/packages/shared/utils/error.test.ts +++ b/web/packages/shared/utils/error.test.ts @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import { ensureError } from './error'; +import { ensureError, isAbortError } from './error'; class CustomErrorClass extends Error { constructor(message: string) { @@ -100,3 +100,31 @@ describe('ensureError', () => { } ); }); + +describe('isAbortError', () => { + describe.each([ + ['DOMException', newDOMAbortError], + ['ApiError', newApiAbortError], + ['gRPC Error', newGrpcAbortError], + ])('for error type %s', (_, ErrorType) => { + it('is abort error', async () => { + expect(isAbortError(ErrorType())).toBe(true); + }); + }); +}); + +function newDOMAbortError() { + return new DOMException('Aborted', 'AbortError'); +} + +// mimics ApiError +function newApiAbortError() { + return new Error('The user aborted a request', { + cause: newDOMAbortError(), + }); +} + +// mimics TshdRpcError +function newGrpcAbortError() { + return { code: 'CANCELLED' }; +} diff --git a/web/packages/shared/utils/error.ts b/web/packages/shared/utils/error.ts index 266fd3104253b..9508e1f23d573 100644 --- a/web/packages/shared/utils/error.ts +++ b/web/packages/shared/utils/error.ts @@ -55,3 +55,16 @@ export function getErrorMessage(err: unknown): string { } return errorInstance.message; } + +export function isAbortError(err: any): boolean { + // handles Web UI abort error + if ( + (err instanceof DOMException && err.name === 'AbortError') || + (err.cause && isAbortError(err.cause)) + ) { + return true; + } + + // handles Connect abort error (specifically gRPC cancel error), see TshdRpcError + return err?.code === 'CANCELLED'; +} From fabcd1ad4ef21e1422c28b0e5aa691329f4d91b5 Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Fri, 16 May 2025 14:03:50 +0200 Subject: [PATCH 06/10] Remove unused `tshd/errors.ts` --- .../teleterm/src/services/tshd/errors.ts | 24 ------------------- 1 file changed, 24 deletions(-) delete mode 100644 web/packages/teleterm/src/services/tshd/errors.ts diff --git a/web/packages/teleterm/src/services/tshd/errors.ts b/web/packages/teleterm/src/services/tshd/errors.ts deleted file mode 100644 index 16660ad9cf2e2..0000000000000 --- a/web/packages/teleterm/src/services/tshd/errors.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Teleport - * Copyright (C) 2023 Gravitational, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -import { isTshdRpcError } from './cloneableClient'; - -/** @deprecated Use `isTshdRpcError(error, 'UNIMPLEMENTED')`. */ -export function isUnimplementedError(error: unknown): boolean { - return isTshdRpcError(error, 'UNIMPLEMENTED'); -} From 3770a6bef1a80eea21cb7b4e678f1eb94b9e05b5 Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Wed, 21 May 2025 09:50:25 +0200 Subject: [PATCH 07/10] Handle `JSON.stringify` error, add `cause` to thrown errors --- web/packages/shared/utils/error.test.ts | 2 ++ web/packages/shared/utils/error.ts | 38 ++++++++++++++----------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/web/packages/shared/utils/error.test.ts b/web/packages/shared/utils/error.test.ts index e7f0ae13d528a..d24b94f7f4a67 100644 --- a/web/packages/shared/utils/error.test.ts +++ b/web/packages/shared/utils/error.test.ts @@ -97,6 +97,8 @@ describe('ensureError', () => { expect(error).toBeInstanceOf(expectedInstance); expect(error.message).toBe(expectedMessage); expect(error.name).toBe(expectedName); + // Non-Error instances should have the original input attached as .cause. + expect(error.cause).toBe(input instanceof Error ? undefined : input); } ); }); diff --git a/web/packages/shared/utils/error.ts b/web/packages/shared/utils/error.ts index 9508e1f23d573..feb693fe8df55 100644 --- a/web/packages/shared/utils/error.ts +++ b/web/packages/shared/utils/error.ts @@ -20,31 +20,35 @@ * Converts any unknown input into an Error instance. * Preserves message and name if available. */ -export function ensureError(err: unknown): Error { - if (err instanceof Error) { - return err; +export function ensureError(input: unknown): Error { + if (input instanceof Error) { + return input; } - if (err === null || err === undefined) { - return new Error(); + if (input === null || input === undefined) { + return new Error('', { cause: input }); } - if (typeof err === 'object') { - let message: string; - if ('message' in err) { - message = String(err.message); - } else { - message = JSON.stringify(err); - } + if (typeof input !== 'object') { + return new Error(String(input), { cause: input }); + } - const error = new Error(message); - if ('name' in err && typeof err.name === 'string') { - error.name = err.name; + let message = ''; + if ('message' in input) { + message = String(input.message); + } else { + try { + message = JSON.stringify(input); + } catch { + message = '[Unable to stringify the thrown value]'; } - return error; } - return new Error(String(err)); + const error = new Error(message, { cause: input }); + if ('name' in input && typeof input.name === 'string') { + error.name = input.name; + } + return error; } /** Extracts an error message or returns a default one. */ From 6907d80ab422bd1f06adaae6e575b9cb02f118a3 Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Wed, 21 May 2025 09:51:19 +0200 Subject: [PATCH 08/10] Remove unnecessary `async` --- web/packages/shared/utils/error.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/packages/shared/utils/error.test.ts b/web/packages/shared/utils/error.test.ts index d24b94f7f4a67..71bbc6f363fdc 100644 --- a/web/packages/shared/utils/error.test.ts +++ b/web/packages/shared/utils/error.test.ts @@ -109,7 +109,7 @@ describe('isAbortError', () => { ['ApiError', newApiAbortError], ['gRPC Error', newGrpcAbortError], ])('for error type %s', (_, ErrorType) => { - it('is abort error', async () => { + it('is abort error', () => { expect(isAbortError(ErrorType())).toBe(true); }); }); From 3eb12d82d53b5e59f3f89389c4b7c38246aea9e3 Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Wed, 21 May 2025 09:53:43 +0200 Subject: [PATCH 09/10] Remove `error.toString` from the callsites --- .../shared/components/DesktopSession/DesktopSession.tsx | 4 ++-- web/packages/shared/libs/tdp/client.ts | 3 +-- web/packages/teleport/src/Player/DesktopPlayer.tsx | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/web/packages/shared/components/DesktopSession/DesktopSession.tsx b/web/packages/shared/components/DesktopSession/DesktopSession.tsx index 251b6803ab393..3799fb716bbaf 100644 --- a/web/packages/shared/components/DesktopSession/DesktopSession.tsx +++ b/web/packages/shared/components/DesktopSession/DesktopSession.tsx @@ -144,7 +144,7 @@ export function DesktopSession({ setTdpConnectionStatus({ status: 'disconnected', fromTdpError: error instanceof TdpError, - message: error.message || error.toString(), + message: error.message, }); initialTdpConnectionSucceeded.current = false; }, @@ -183,7 +183,7 @@ export function DesktopSession({ error => { setTdpConnectionStatus({ status: 'disconnected', - message: error?.message || error?.toString(), + message: error?.message, }); initialTdpConnectionSucceeded.current = false; }, diff --git a/web/packages/shared/libs/tdp/client.ts b/web/packages/shared/libs/tdp/client.ts index d3830fe15447c..f8926ef93f172 100644 --- a/web/packages/shared/libs/tdp/client.ts +++ b/web/packages/shared/libs/tdp/client.ts @@ -25,8 +25,7 @@ import init, { init_wasm_log, } from 'shared/libs/ironrdp/pkg/ironrdp'; import Logger from 'shared/libs/logger'; -import { isAbortError } from 'shared/utils/abortError'; -import { ensureError } from 'shared/utils/error'; +import { ensureError, isAbortError } from 'shared/utils/error'; import Codec, { FileType, diff --git a/web/packages/teleport/src/Player/DesktopPlayer.tsx b/web/packages/teleport/src/Player/DesktopPlayer.tsx index ec2adbdb1c388..c16cd502ea61e 100644 --- a/web/packages/teleport/src/Player/DesktopPlayer.tsx +++ b/web/packages/teleport/src/Player/DesktopPlayer.tsx @@ -144,7 +144,7 @@ const useDesktopPlayer = ({ clusterId, sid }) => { const clientOnError = useCallback((error: Error) => { setPlayerStatus(StatusEnum.ERROR); - setStatusText(error.message || error.toString()); + setStatusText(error.message); }, []); const clientOnTdpInfo = useCallback((info: string) => { From 01b1818880035a3408eda25bfb5bb100df0f2de1 Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Thu, 22 May 2025 09:51:27 +0200 Subject: [PATCH 10/10] Handle `err` being undefined --- web/packages/shared/utils/error.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/packages/shared/utils/error.ts b/web/packages/shared/utils/error.ts index feb693fe8df55..c006cbab0bad5 100644 --- a/web/packages/shared/utils/error.ts +++ b/web/packages/shared/utils/error.ts @@ -64,7 +64,7 @@ export function isAbortError(err: any): boolean { // handles Web UI abort error if ( (err instanceof DOMException && err.name === 'AbortError') || - (err.cause && isAbortError(err.cause)) + (err?.cause && isAbortError(err.cause)) ) { return true; }