diff --git a/web/packages/shared/components/DesktopSession/DesktopSession.story.tsx b/web/packages/shared/components/DesktopSession/DesktopSession.story.tsx
index 4259fde7fbf56..4fef60b3badc6 100644
--- a/web/packages/shared/components/DesktopSession/DesktopSession.story.tsx
+++ b/web/packages/shared/components/DesktopSession/DesktopSession.story.tsx
@@ -30,6 +30,7 @@ import {
TdpClient,
TdpClientEvent,
} from 'shared/libs/tdp';
+import { TdpError as RemoteTdpError } from 'shared/libs/tdp/client';
import { DesktopSession, DesktopSessionProps } from './DesktopSession';
@@ -86,7 +87,15 @@ export const FetchError = () => (
export const TdpError = () => {
const client = fakeClient();
client.connect = async () => {
- client.emit(TdpClientEvent.ERROR, new Error('some tdp error'));
+ client.emit(
+ TdpClientEvent.ERROR,
+ new RemoteTdpError(
+ 'RDP client exited with an error: Connection Timed Out.\n\n' +
+ 'Teleport could not connect to the host within the timeout period. This may be due to a firewall blocking the connection, an overloaded system, or network congestion.\n\n' +
+ 'To resolve this issue, ensure that the Teleport agent can reach the Windows host.\n\n' +
+ 'You can use the command "nc -vz HOST 3389" to help diagnose connectivity problems.'
+ )
+ );
};
return ;
@@ -96,10 +105,10 @@ export const Connected = () => {
return ;
};
-export const Disconnected = () => {
+export const DisconnectedWithNoMessage = () => {
const client = fakeClient();
client.connect = async () => {
- client.emit(TdpClientEvent.TRANSPORT_CLOSE, 'session disconnected');
+ client.emit(TdpClientEvent.TRANSPORT_CLOSE);
};
return ;
diff --git a/web/packages/shared/components/DesktopSession/DesktopSession.tsx b/web/packages/shared/components/DesktopSession/DesktopSession.tsx
index fd24992c90f4f..278c5e38ff1d2 100644
--- a/web/packages/shared/components/DesktopSession/DesktopSession.tsx
+++ b/web/packages/shared/components/DesktopSession/DesktopSession.tsx
@@ -37,6 +37,7 @@ import {
TdpClient,
useListener,
} from 'shared/libs/tdp';
+import { TdpError } from 'shared/libs/tdp/client';
import { KeyboardHandler } from './KeyboardHandler';
import TopBar from './TopBar';
@@ -130,7 +131,8 @@ export function DesktopSession({
setClipboardSharingState(defaultClipboardSharingState);
setTdpConnectionStatus({
status: 'disconnected',
- message: error.message || error.toString(),
+ fromTdpError: error instanceof TdpError,
+ message: error?.message || error?.toString(),
});
initialTdpConnectionSucceeded.current = false;
},
@@ -166,8 +168,11 @@ export function DesktopSession({
useListener(
client.onTransportClose,
useCallback(
- statusText => {
- setTdpConnectionStatus({ status: 'disconnected', message: statusText });
+ error => {
+ setTdpConnectionStatus({
+ status: 'disconnected',
+ message: error?.message || error?.toString(),
+ });
initialTdpConnectionSucceeded.current = false;
},
[setTdpConnectionStatus]
@@ -350,7 +355,7 @@ export function DesktopSession({
/>
)}
{screenState.state === 'custom' && screenState.component}
- {screenState.state === 'error' && (
+ {screenState.state === 'disconnected' && (
)}
{screenState.state === 'processing' && }
@@ -447,7 +452,7 @@ function getScreenState(
if (aclAttempt.status === 'error') {
return {
- state: 'error',
+ state: 'disconnected',
message: {
title: 'Could not fetch session details',
details: aclAttempt.statusText,
@@ -456,7 +461,7 @@ function getScreenState(
}
if (anotherDesktopActiveAttempt.status === 'error') {
return {
- state: 'error',
+ state: 'disconnected',
message: {
title: 'Could not fetch session details',
details: anotherDesktopActiveAttempt.statusText,
@@ -465,8 +470,8 @@ function getScreenState(
}
if (tdpConnectionStatus.status === 'disconnected') {
return {
- state: 'error',
- message: { title: tdpConnectionStatus.message },
+ state: 'disconnected',
+ message: { title: tdpConnectionStatus.message || 'Session disconnected' },
};
}
@@ -499,6 +504,7 @@ type TdpConnectionStatus =
*/
| {
status: 'disconnected';
+ fromTdpError?: boolean;
message: string;
};
@@ -508,6 +514,11 @@ type ScreenState =
| { state: 'processing' }
| { state: 'canvas-visible' }
| {
- state: 'error';
- message: { title: string; details?: string };
+ state: 'disconnected';
+ message: DisconnectedMessage;
};
+
+interface DisconnectedMessage {
+ title: string;
+ details?: string;
+}
diff --git a/web/packages/shared/libs/tdp/client.ts b/web/packages/shared/libs/tdp/client.ts
index 122e91ffa40af..fe694939c0154 100644
--- a/web/packages/shared/libs/tdp/client.ts
+++ b/web/packages/shared/libs/tdp/client.ts
@@ -144,7 +144,7 @@ export class TdpClient extends EventEmitter {
this.transportAbortController.signal
);
} catch (error) {
- this.emit(TdpClientEvent.ERROR, error.message);
+ this.emit(TdpClientEvent.ERROR, error);
return;
}
@@ -183,11 +183,11 @@ export class TdpClient extends EventEmitter {
// 'Processing' errors are the most important.
if (processingError) {
- this.emit(TdpClientEvent.ERROR, processingError.message);
+ this.emit(TdpClientEvent.ERROR, processingError);
} else if (connectionError) {
- this.emit(TdpClientEvent.TRANSPORT_CLOSE, connectionError.message);
+ this.emit(TdpClientEvent.TRANSPORT_CLOSE, connectionError);
} else {
- this.emit(TdpClientEvent.TRANSPORT_CLOSE, 'Session disconnected');
+ this.emit(TdpClientEvent.TRANSPORT_CLOSE);
}
this.logger.info('Transport is closed');
@@ -235,7 +235,7 @@ export class TdpClient extends EventEmitter {
return () => this.off(TdpClientEvent.TDP_WARNING, listener);
};
- onTransportClose = (listener: (message: string) => void) => {
+ onTransportClose = (listener: (error?: Error) => void) => {
this.on(TdpClientEvent.TRANSPORT_CLOSE, listener);
return () => this.off(TdpClientEvent.TRANSPORT_CLOSE, listener);
};
@@ -391,7 +391,7 @@ export class TdpClient extends EventEmitter {
handleTdpNotification(buffer: ArrayBuffer) {
const notification = this.codec.decodeNotification(buffer);
if (notification.severity === Severity.Error) {
- throw new Error(notification.message);
+ throw new TdpError(notification.message);
} else if (notification.severity === Severity.Warning) {
this.handleWarning(notification.message, TdpClientEvent.TDP_WARNING);
} else {
@@ -799,3 +799,11 @@ export function useListener(
};
}, [emitter, listener]);
}
+
+/** Represents an alert emitted by the TDP service with "error" severity. */
+export class TdpError extends Error {
+ constructor(message: string) {
+ super(message);
+ this.name = 'TdpError';
+ }
+}
diff --git a/web/packages/teleport/src/lib/tdp/webSocketTransportAdapter.ts b/web/packages/teleport/src/lib/tdp/webSocketTransportAdapter.ts
index 0eed87426a0ad..92c8c153f4aca 100644
--- a/web/packages/teleport/src/lib/tdp/webSocketTransportAdapter.ts
+++ b/web/packages/teleport/src/lib/tdp/webSocketTransportAdapter.ts
@@ -30,6 +30,9 @@ export async function adaptWebSocketToTdpTransport(
socket: WebSocket,
signal: AbortSignal
): Promise {
+ if (signal.aborted) {
+ throw new DOMException('Websocket was aborted.', 'AbortError');
+ }
// WebsocketCloseCode.NORMAL
signal.addEventListener('abort', () => socket.close(1000));
socket.binaryType = 'arraybuffer';