Repository: trezor/trezor-suite
Pull request #17845: feat(connect-web): error fallback for core in desktop
Author: login: martykan
Labels: name: connect
description: Connect API related (ie. fee calculation)
name: no-project
description: This label is used to specify that PR doesn't need to be added to a project
Created: 2025-03-21T14:35:13Z | Merged: 2025-03-25T15:42:58Z

## Description

If core in desktop init/call fails due to websocket, fall back to core in popup. 


Diff:
diff --git a/packages/connect-web/src/impl/core-in-suite-desktop.ts b/packages/connect-web/src/impl/core-in-suite-desktop.ts
index 378e7f1db7c2..6a8475b642ca 100644
--- a/packages/connect-web/src/impl/core-in-suite-desktop.ts
+++ b/packages/connect-web/src/impl/core-in-suite-desktop.ts
@@ -19,6 +19,7 @@ import type {
 } from '@trezor/connect/src/types';
 import { Login } from '@trezor/connect/src/types/api/requestLogin';
 import { WebsocketClient } from '@trezor/websocket-client';
+import { WebsocketError } from '@trezor/websocket-client/src/client';
 
 import { parseConnectSettings } from '../connectSettings';
 
@@ -63,15 +64,12 @@ export class CoreInSuiteDesktop implements ConnectFactoryDependencies<ConnectSet
             );
 
             if (!response) {
-                return {
-                    success: false,
-                    payload: { error: 'No response', code: 'Desktop_ConnectionMissing' },
-                };
+                throw ERRORS.TypedError('Desktop_ConnectionMissing', 'No response');
             }
 
             return response;
         } catch (err) {
-            throw new Error('Handshake timed out ' + err.message);
+            throw ERRORS.TypedError('Desktop_ConnectionMissing', err.message);
         }
     }
 
@@ -87,11 +85,17 @@ export class CoreInSuiteDesktop implements ConnectFactoryDependencies<ConnectSet
         }
         this._settings = newSettings;
 
-        this.ws = new WebsocketClient({ url: 'ws://localhost:21335/connect-ws' });
+        try {
+            this.ws = new WebsocketClient({ url: 'ws://localhost:21335/connect-ws' });
 
-        await this.ws.connect();
+            await this.ws.connect();
 
-        return await this.handshake();
+            return await this.handshake();
+        } catch (err) {
+            throw err instanceof WebsocketError
+                ? ERRORS.TypedError('Desktop_ConnectionMissing', err.message)
+                : err;
+        }
     }
 
     public setTransports() {
@@ -103,8 +107,9 @@ export class CoreInSuiteDesktop implements ConnectFactoryDependencies<ConnectSet
         try {
             if (!this.ws) {
                 await this.init();
+            } else {
+                await this.handshake();
             }
-            await this.handshake();
 
             const response = await this.ws?.sendMessage(
                 {
@@ -117,19 +122,18 @@ export class CoreInSuiteDesktop implements ConnectFactoryDependencies<ConnectSet
             );
 
             if (!response) {
-                return {
-                    success: false,
-                    payload: { error: 'No response', code: 'Desktop_ConnectionMissing' },
-                };
+                throw ERRORS.TypedError('Desktop_ConnectionMissing', 'No response');
             }
 
             return response;
         } catch (err) {
             return {
                 success: false,
-                payload: {
-                    error: err.message,
-                },
+                payload: ERRORS.serializeError(
+                    err instanceof WebsocketError
+                        ? ERRORS.TypedError('Desktop_ConnectionMissing', err.message)
+                        : err,
+                ),
             };
         }
     }
diff --git a/packages/connect-web/src/index.ts b/packages/connect-web/src/index.ts
index 5f5047d09b3d..e5ac411749df 100644
--- a/packages/connect-web/src/index.ts
+++ b/packages/connect-web/src/index.ts
@@ -49,6 +49,13 @@ const impl = new TrezorConnectDynamic<
             return 'iframe';
         }
     },
+    handleBeforeCall: async () => {
+        // Always try if desktop is available again
+        const isCoreModeDesktop = impl.lastSettings?.coreMode === 'suite-desktop';
+        if (isCoreModeDesktop) {
+            await impl.switchTarget('core-in-suite-desktop');
+        }
+    },
     handleErrorFallback: async (errorCode: string) => {
         const env = getEnv();
 
@@ -56,6 +63,17 @@ const impl = new TrezorConnectDynamic<
         const isCoreModeAuto =
             impl.lastSettings?.coreMode === 'auto' || impl.lastSettings?.coreMode === undefined;
 
+        // Handle desktop errors
+        if (
+            !isCoreModeDisabled &&
+            impl.getTargetType() === 'core-in-suite-desktop' &&
+            errorCode === 'Desktop_ConnectionMissing'
+        ) {
+            await impl.switchTarget('core-in-popup');
+
+            return true;
+        }
+
         // Handle iframe errors by switching to core-in-popup
         if (!isCoreModeDisabled && isCoreModeAuto && IFRAME_ERRORS.includes(errorCode)) {
             // Check if WebUSB is available and enabled
diff --git a/packages/connect-web/src/module/index.ts b/packages/connect-web/src/module/index.ts
index 02c4d433cb34..fc407add809c 100644
--- a/packages/connect-web/src/module/index.ts
+++ b/packages/connect-web/src/module/index.ts
@@ -42,6 +42,7 @@ const impl = new TrezorConnectDynamic<
         },
     ],
     getInitTarget: () => 'core-in-module',
+    handleBeforeCall: () => new Promise(resolve => resolve()),
     handleErrorFallback: () => new Promise(resolve => resolve(false)),
 });
 
diff --git a/packages/connect/src/constants/errors.ts b/packages/connect/src/constants/errors.ts
index 185d3d6ef296..c45c549a1efa 100644
--- a/packages/connect/src/constants/errors.ts
+++ b/packages/connect/src/constants/errors.ts
@@ -88,8 +88,14 @@ export const serializeError = (payload: any) => {
     if (payload instanceof TrezorError) {
         return { error: payload.message, code: payload.code };
     }
+    if (payload instanceof Error) {
+        return {
+            error: payload.message,
+            code: 'code' in payload ? payload.code : 'Failure_UnknownCode',
+        };
+    }
 
-    return payload;
+    return { ...payload };
 };
 
 // trezord error prefix.
diff --git a/packages/connect/src/impl/dynamic.ts b/packages/connect/src/impl/dynamic.ts
index 617365a6c8a8..82f93c51a026 100644
--- a/packages/connect/src/impl/dynamic.ts
+++ b/packages/connect/src/impl/dynamic.ts
@@ -18,6 +18,7 @@ type TrezorConnectDynamicParams<
         impl: ImplInterface;
     }[];
     getInitTarget: (settings: InitFullSettings<SettingsType>) => ImplType;
+    handleBeforeCall: () => Promise<void>;
     handleErrorFallback: (errorCode: string) => Promise<boolean>;
 };
 
@@ -44,6 +45,11 @@ export class TrezorConnectDynamic<
         SettingsType,
         ImplInterface
     >['getInitTarget'];
+    private handleBeforeCall: TrezorConnectDynamicParams<
+        ImplType,
+        SettingsType,
+        ImplInterface
+    >['handleBeforeCall'];
     private handleErrorFallback: TrezorConnectDynamicParams<
         ImplType,
         SettingsType,
@@ -55,11 +61,13 @@ export class TrezorConnectDynamic<
     public constructor({
         implementations,
         getInitTarget,
+        handleBeforeCall,
         handleErrorFallback,
     }: TrezorConnectDynamicParams<ImplType, SettingsType, ImplInterface>) {
         this.implementations = implementations;
         this.currentTarget = this.implementations[0].type;
         this.getInitTarget = getInitTarget;
+        this.handleBeforeCall = handleBeforeCall;
         this.handleErrorFallback = handleErrorFallback;
         this.eventEmitter = new ProxyEventEmitter(
             this.implementations.map(impl => impl.impl.eventEmitter),
@@ -70,6 +78,10 @@ export class TrezorConnectDynamic<
         return this.implementations.find(impl => impl.type === this.currentTarget)!.impl;
     }
 
+    public getTargetType() {
+        return this.currentTarget;
+    }
+
     public async switchTarget(target: ImplType) {
         if (this.currentTarget === target) {
             return;
@@ -78,9 +90,17 @@ export class TrezorConnectDynamic<
         if (!this.lastSettings) {
             throw ERRORS.TypedError('Init_NotInitialized');
         }
-        await this.getTarget().dispose();
-        this.currentTarget = target;
-        await this.getTarget().init(this.lastSettings);
+
+        // Go back to the old target if the new target fails to initialize
+        const oldTargetType = this.getTargetType();
+        const oldTarget = this.getTarget();
+        try {
+            this.currentTarget = target;
+            await this.getTarget().init(this.lastSettings);
+            await oldTarget.dispose();
+        } catch {
+            this.currentTarget = oldTargetType;
+        }
     }
 
     public manifest(manifest: Manifest) {
@@ -117,6 +137,7 @@ export class TrezorConnectDynamic<
     }
 
     public async call(params: CallMethodPayload) {
+        await this.handleBeforeCall();
         const response = await this.getTarget().call(params);
         if (!response.success) {
             if (await this.handleErrorFallback(response.payload.code)) {
diff --git a/packages/websocket-client/src/client.ts b/packages/websocket-client/src/client.ts
index dad356314c0c..8e377e5d63ea 100644
--- a/packages/websocket-client/src/client.ts
+++ b/packages/websocket-client/src/client.ts
@@ -27,6 +27,8 @@ type WebsocketClientEvents = {
 export type WebsocketRequest = Record<string, any>;
 export type WebsocketResponse = WebSocket.Data;
 
+export class WebsocketError extends Error {}
+
 export class WebsocketClient<Events extends Record<string, any>> extends TypedEmitter<
     Events & WebsocketClientEvents
 > {
@@ -56,7 +58,7 @@ export class WebsocketClient<Events extends Record<string, any>> extends TypedEm
     protected initWebsocket({ url, timeout, headers, agent }: WebsocketOptions) {
         // url validation
         if (typeof url !== 'string') {
-            throw new Error('websocket_no_url');
+            throw new WebsocketError('websocket_no_url');
         }
         if (url.startsWith('http')) {
             url = url.replace('http', 'ws');
@@ -86,7 +88,7 @@ export class WebsocketClient<Events extends Record<string, any>> extends TypedEm
     private onTimeout() {
         const { ws } = this;
         if (!ws) return;
-        this.messages.rejectAll(new Error('websocket_timeout'));
+        this.messages.rejectAll(new WebsocketError('websocket_timeout'));
         ws.close();
     }
 
@@ -96,7 +98,7 @@ export class WebsocketClient<Events extends Record<string, any>> extends TypedEm
 
     sendMessage(message: WebsocketRequest, { timeout }: { timeout?: number } = {}) {
         const { ws } = this;
-        if (!ws || !this.isConnected()) throw new Error('websocket_not_initialized');
+        if (!ws || !this.isConnected()) throw new WebsocketError('websocket_not_initialized');
         const { promiseId, promise } = this.messages.create(timeout);
 
         const req = { id: promiseId.toString(), ...message };
@@ -112,7 +114,7 @@ export class WebsocketClient<Events extends Record<string, any>> extends TypedEm
 
     protected sendRawMessage(message: WebSocket.Data) {
         const { ws } = this;
-        if (!ws || !this.isConnected()) throw new Error('websocket_not_initialized');
+        if (!ws || !this.isConnected()) throw new WebsocketError('websocket_not_initialized');
 
         ws.send(message, {
             binary: typeof message !== 'string',
@@ -168,9 +170,9 @@ export class WebsocketClient<Events extends Record<string, any>> extends TypedEm
         // set connection timeout before WebSocket initialization
         const connectionTimeout = setTimeout(
             () => {
-                ws.emit('error', new Error('websocket_timeout'));
+                this.onClose();
+                dfd.reject(new WebsocketError('websocket_timeout'));
                 try {
-                    ws.once('error', () => {}); // hack; ws throws uncaughtably when there's no error listener
                     ws.close();
                 } catch {
                     // empty
@@ -182,7 +184,7 @@ export class WebsocketClient<Events extends Record<string, any>> extends TypedEm
         ws.once('error', error => {
             clearTimeout(connectionTimeout);
             this.onClose();
-            dfd.reject(new Error(error.message));
+            dfd.reject(new WebsocketError(error.message));
         });
         ws.on('open', () => {
             clearTimeout(connectionTimeout);
@@ -235,7 +237,10 @@ export class WebsocketClient<Events extends Record<string, any>> extends TypedEm
         clearTimeout(this.pingTimeout);
 
         this.ws?.removeAllListeners();
-        this.messages.rejectAll(new Error('Websocket closed unexpectedly'));
+        this.ws?.on('error', () => {
+            // Suppress errors after close
+        });
+        this.messages.rejectAll(new WebsocketError('Websocket closed unexpectedly'));
     }
 
     dispose() {
diff --git a/packages/websocket-client/src/ws-browser.ts b/packages/websocket-client/src/ws-browser.ts
index e4990b1740ad..5d66f090beeb 100644
--- a/packages/websocket-client/src/ws-browser.ts
+++ b/packages/websocket-client/src/ws-browser.ts
@@ -1,5 +1,7 @@
 import { EventEmitter } from 'events';
 
+import { WebsocketError } from './client';
+
 /**
  * Provides `EventEmitter` interface for native browser `WebSocket`,
  * same, as `ws` package provides.
@@ -31,7 +33,10 @@ class WSWrapper extends EventEmitter {
         // fire an event named error at the WebSocket object.
         // https://stackoverflow.com/a/31003057
         this._ws.onerror = _event => {
-            this.emit('error', new Error(`WsWrapper error. Ready state: ${this.readyState}`));
+            this.emit(
+                'error',
+                new WebsocketError(`WsWrapper error. Ready state: ${this.readyState}`),
+            );
         };
 
         this._ws.onmessage = message => {
@@ -47,7 +52,7 @@ class WSWrapper extends EventEmitter {
 
     send(message: any) {
         if (this.readyState !== WSWrapper.OPEN) {
-            throw new Error(`Connection is not open. state: ${this.readyState}`);
+            throw new WebsocketError(`Connection is not open. state: ${this.readyState}`);
         }
         this._ws.send(message);
     }
diff --git a/packages/websocket-client/src/ws-native.ts b/packages/websocket-client/src/ws-native.ts
index f1685456661d..43c335f8a7cc 100644
--- a/packages/websocket-client/src/ws-native.ts
+++ b/packages/websocket-client/src/ws-native.ts
@@ -1,5 +1,7 @@
 import { EventEmitter } from 'events';
 
+import { WebsocketError } from './client';
+
 /**
  * Provides `EventEmitter` interface for React Native `WebSocket`,
  * same, as `ws` package provides.
@@ -37,7 +39,10 @@ class WSWrapper extends EventEmitter {
         // fire an event named error at the WebSocket object.
         // https://stackoverflow.com/a/31003057
         this._ws.onerror = _event => {
-            this.emit('error', new Error(`WsWrapper error. Ready state: ${this.readyState}`));
+            this.emit(
+                'error',
+                new WebsocketError(`WsWrapper error. Ready state: ${this.readyState}`),
+            );
         };
 
         this._ws.onmessage = message => {
@@ -53,7 +58,7 @@ class WSWrapper extends EventEmitter {
 
     send(message: any) {
         if (this.readyState !== WSWrapper.OPEN) {
-            throw new Error(`Connection is not open. state: ${this.readyState}`);
+            throw new WebsocketError(`Connection is not open. state: ${this.readyState}`);
         }
         this._ws.send(message);
     }


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>packages/connect-explorer/src/components/ApiPlayground.tsx (1)</summary><blockquote>

`97-101`: **Performance optimization using deep comparison**

Good addition of `optionsRef` to store and compare previous `options` values. This prevents unnecessary state updates when the options haven't changed.

However, using `JSON.stringify` for deep comparison can be inefficient for large objects. Consider using a dedicated deep equality library for better performance in the future.


Future enhancement could use a library like `lodash.isEqual` or `fast-deep-equal`:

```diff
- if (JSON.stringify(optionsRef.current) === JSON.stringify(options)) return;
+ if (isEqual(optionsRef.current, options)) return;
```

</blockquote></details>
<details>
<summary>packages/connect/src/impl/dynamic.ts (1)</summary><blockquote>

`124-139`: **Improved error handling with try-catch structure.**

The refactored `call` method now properly handles both explicit failures (in response objects) and exceptions through a structured try-catch approach. This implementation aligns well with the PR objective of implementing an error fallback mechanism for core in desktop.

Key improvements:
1. Errors from failed responses are now handled consistently
2. Exceptions during the call are properly caught and managed
3. The fallback mechanism is triggered in both scenarios



Consider adding explicit type information to the caught error:

```diff
-        } catch (error) {
+        } catch (error: unknown) {
+            if (error && typeof error === 'object' && 'code' in error && 
              await this.handleErrorFallback(error.code)) {
```

This would make the error handling more type-safe, particularly if TypeScript's strict mode is enabled.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between eb307371a49ee71a147e4e598311f6f0ef42fbdd and 838f663771bdd1b9e3146bd7d8de60dbeaceceac.

</details>

<details>
<summary>📒 Files selected for processing (5)</summary>

* `packages/connect-explorer/src/components/ApiPlayground.tsx` (2 hunks)
* `packages/connect-web/src/impl/core-in-suite-desktop.ts` (2 hunks)
* `packages/connect-web/src/index.ts` (1 hunks)
* `packages/connect/src/constants/errors.ts` (1 hunks)
* `packages/connect/src/impl/dynamic.ts` (2 hunks)

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (3)</summary>

* GitHub Check: methods / core_in_popup
* GitHub Check: methods / web
* GitHub Check: run_android_e2e_tests

</details>

<details>
<summary>🔇 Additional comments (5)</summary><blockquote>

<details>
<summary>packages/connect/src/constants/errors.ts (1)</summary>

`10-10`: **Good addition of Desktop_ConnectionMissing error code**

This new error code aligns well with the PR objective of implementing a fallback mechanism for core functionality in desktop environments when WebSocket connections fail.

</details>
<details>
<summary>packages/connect-web/src/impl/core-in-suite-desktop.ts (2)</summary>

`91-92`: **Improved error handling with specific error type**

Good improvement replacing generic errors with the specific `Desktop_ConnectionMissing` error type. This change provides more precise error information when WebSocket connections fail.

---

`103-104`: **Consistent error handling for WebSocket close events**

Good improvement applying the same error handling pattern to the close event. This ensures consistent error reporting across all WebSocket failure scenarios.

</details>
<details>
<summary>packages/connect-web/src/index.ts (1)</summary>

`59-68`: **Excellent implementation of desktop fallback mechanism**

This code correctly implements the core fallback mechanism described in the PR objectives. If the core initialization or WebSocket connection fails in desktop mode, the system will automatically fall back to using core-in-popup mode.

The conditions are properly checked:
1. Core mode is not disabled
2. Current target is desktop
3. The specific Desktop_ConnectionMissing error occurred

This implementation enhances reliability by providing users with a fallback option when desktop connectivity fails.

</details>
<details>
<summary>packages/connect/src/impl/dynamic.ts (1)</summary>

`73-75`: **Well-designed accessor method.**

The new `getTargetType()` method provides a clean way to access the current target type without exposing the private property directly. This is good encapsulation practice.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-21T15:59:54Z
user: login: mroz22
body: looks ok. what about some kind of "upgrade logic" when suite-deskop comes online again?
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T08:00:53Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>packages/connect/src/impl/dynamic.ts (1)</summary><blockquote>

`94-103`: **Enhanced error handling for target switching**

If initialization of the new target fails, the system now reverts to the previous target, providing better resilience.


Consider adding error logging and ensuring the old target is still operational:

```diff
try {
    this.currentTarget = target;
    await this.getTarget().init(this.lastSettings);
    await oldTarget.dispose();
} catch (error) {
+    console.error('Failed to initialize new target, reverting to previous target', error);
    this.currentTarget = oldTargetType;
+    // Consider checking if old target needs re-initialization
}
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 50fbb8d7c51fb1ea9a15c4c3fc09bb136d9ebaa4 and 410949a289e0b40d761a373cde8b6fb04124b560.

</details>

<details>
<summary>📒 Files selected for processing (3)</summary>

* `packages/connect-web/src/index.ts` (1 hunks)
* `packages/connect-web/src/module/index.ts` (1 hunks)
* `packages/connect/src/impl/dynamic.ts` (6 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* packages/connect-web/src/index.ts

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (8)</summary>

* GitHub Check: PR-check   / node-authorizeCoinjoin cancelCoinjoinAuthorization passphrase unlockPath setBusy checkFirmwareAuthenticity keepSession cancel.test info.test resetDevice-api
* GitHub Check: PR-check   / web-authorizeCoinjoin cancelCoinjoinAuthorization passphrase unlockPath setBusy checkFirmwareAuthenticity keepSession cancel.test info.test resetDevice-api
* GitHub Check: PR-check   / node-override init-api-flaky
* GitHub Check: PR-check   / web-override init-api-flaky
* GitHub Check: build-deploy
* GitHub Check: prepare_android_test_app
* GitHub Check: Setup and Cache Dependencies
* GitHub Check: Analyze with CodeQL (javascript)

</details>

<details>
<summary>🔇 Additional comments (6)</summary><blockquote>

<details>
<summary>packages/connect-web/src/module/index.ts (1)</summary>

`45-45`: **Added hook for pre-call operations**

This introduces a hook for operations that need to run before each call to the connect API. Currently, it immediately resolves, but this will likely be used for reconnection attempts as mentioned in the PR comments.


Consider a more concise implementation:
```diff
-handleBeforeCall: () => new Promise(resolve => resolve()),
+handleBeforeCall: () => Promise.resolve(),
```

</details>
<details>
<summary>packages/connect/src/impl/dynamic.ts (5)</summary>

`21-21`: **Added new parameter to support pre-call operations**

This new parameter will allow implementations to execute logic before each call to the core, supporting the error fallback mechanism.

---

`48-52`: **Added private field for the handleBeforeCall function**

This stores the function passed in the constructor, which will be called before each method invocation.

---

`64-65`: **Updated constructor to accept and store the handleBeforeCall parameter**

Properly initializes the handleBeforeCall handler from constructor parameters.



Also applies to: 70-70

---

`81-83`: **Added getter for current target type**

This public method exposes the current target type, which will be useful for monitoring the fallback state and making decisions based on the current implementation in use.

---

`140-140`: **Added pre-call hook execution**

This ensures the `handleBeforeCall` function is executed before each method call, allowing for reconnection attempts or other pre-call operations.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-24T16:23:42Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>packages/connect/src/impl/dynamic.ts (1)</summary><blockquote>

`94-103`: **Consider enhancing error handling in switchTarget method**

The implementation correctly tries to handle initialization errors by reverting to the previous target, but there are a few improvements that could be made:

1. The error is being swallowed without logging, which might hide the root cause of issues.
2. There's no verification that the old target is still valid after reverting.


Consider enhancing the error handling with logging and validation:

```diff
 // Go back to the old target if the new target fails to initialize
 const oldTargetType = this.getTargetType();
 const oldTarget = this.getTarget();
 try {
     this.currentTarget = target;
     await this.getTarget().init(this.lastSettings);
     await oldTarget.dispose();
-} catch {
+} catch (error) {
+    console.error('Failed to initialize new target, reverting to previous target', error);
     this.currentTarget = oldTargetType;
+    // Optional: Check if old target is still valid
+    try {
+        await this.getTarget().init(this.lastSettings);
+    } catch (revertError) {
+        console.error('Failed to revert to previous target', revertError);
+        throw ERRORS.TypedError('Init_Failed');
+    }
 }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 410949a289e0b40d761a373cde8b6fb04124b560 and 281c125146c6e29ad423856022e04df8ea2fabcc.

</details>

<details>
<summary>📒 Files selected for processing (4)</summary>

* `packages/connect-web/src/impl/core-in-suite-desktop.ts` (3 hunks)
* `packages/connect-web/src/index.ts` (1 hunks)
* `packages/connect-web/src/module/index.ts` (1 hunks)
* `packages/connect/src/impl/dynamic.ts` (6 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (3)</summary>

* packages/connect-web/src/module/index.ts
* packages/connect-web/src/index.ts
* packages/connect-web/src/impl/core-in-suite-desktop.ts

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (8)</summary>

* GitHub Check: PR-check   / node-authorizeCoinjoin cancelCoinjoinAuthorization passphrase unlockPath setBusy checkFirmwareAuthenticity keepSession cancel.test info.test resetDevice-api
* GitHub Check: PR-check   / node-override init-api-flaky
* GitHub Check: PR-check   / web-authorizeCoinjoin cancelCoinjoinAuthorization passphrase unlockPath setBusy checkFirmwareAuthenticity keepSession cancel.test info.test resetDevice-api
* GitHub Check: PR-check   / web-override init-api-flaky
* GitHub Check: build-deploy
* GitHub Check: Setup and Cache Dependencies
* GitHub Check: prepare_android_test_app
* GitHub Check: Analyze with CodeQL (javascript)

</details>

<details>
<summary>🔇 Additional comments (6)</summary><blockquote>

<details>
<summary>packages/connect/src/impl/dynamic.ts (6)</summary>

`21-21`: **Good addition of the `handleBeforeCall` parameter**

This new parameter allows for performing operations before each call to the core, which aligns with the PR objectives of implementing an error fallback mechanism.

---

`48-52`: **LGTM - Private field properly typed**

The private field for `handleBeforeCall` is correctly typed and aligned with the parameter definition.

---

`64-64`: **LGTM - Constructor parameter addition**

The `handleBeforeCall` parameter is correctly added to the constructor.

---

`70-70`: **LGTM - Proper initialization of handleBeforeCall**

The `handleBeforeCall` field is correctly initialized with the parameter value.

---

`81-83`: **Good addition of the getTargetType method**

This public method provides access to the current target type, which is useful for the error fallback mechanism and makes the implementation more robust.

---

`140-140`: **LGTM - handleBeforeCall is correctly invoked**

The `handleBeforeCall` function is properly awaited before executing the target's call method, allowing for pre-call operations like reconnection attempts.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-24T17:55:28Z
user: login: mroz22
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T10:15:16Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>packages/connect/src/impl/dynamic.ts (1)</summary><blockquote>

`94-103`: **Well-implemented error handling fallback**

The try-catch block correctly reverts to the previous target if initialization of the new target fails, creating a robust fallback mechanism.

Consider adding error logging to help with debugging when target switching fails.

```diff
 try {
     this.currentTarget = target;
     await this.getTarget().init(this.lastSettings);
     await oldTarget.dispose();
 } catch (error) {
+    console.error('Failed to initialize new target, reverting to previous target:', error);
     this.currentTarget = oldTargetType;
 }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 281c125146c6e29ad423856022e04df8ea2fabcc and 5d4ba2a4b43b469fd2f62b0b70ce6f0804c8cce1.

</details>

<details>
<summary>📒 Files selected for processing (8)</summary>

* `packages/connect-web/src/impl/core-in-suite-desktop.ts` (5 hunks)
* `packages/connect-web/src/index.ts` (1 hunks)
* `packages/connect-web/src/module/index.ts` (1 hunks)
* `packages/connect/src/constants/errors.ts` (1 hunks)
* `packages/connect/src/impl/dynamic.ts` (6 hunks)
* `packages/websocket-client/src/client.ts` (8 hunks)
* `packages/websocket-client/src/ws-browser.ts` (3 hunks)
* `packages/websocket-client/src/ws-native.ts` (3 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (2)</summary>

* packages/connect-web/src/module/index.ts
* packages/connect-web/src/impl/core-in-suite-desktop.ts

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Definitions (2)</summary>

<details>
<summary>packages/websocket-client/src/ws-native.ts (1)</summary><blockquote>

<details>
<summary>packages/websocket-client/src/client.ts (1)</summary>

* `WebsocketError` (30-30)

</details>

</blockquote></details>
<details>
<summary>packages/websocket-client/src/ws-browser.ts (1)</summary><blockquote>

<details>
<summary>packages/websocket-client/src/client.ts (1)</summary>

* `WebsocketError` (30-30)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (11)</summary>

* GitHub Check: PR-check   / node-authorizeCoinjoin cancelCoinjoinAuthorization passphrase unlockPath setBusy checkFirmwareAuthenticity keepSession cancel.test info.test resetDevice-api
* GitHub Check: PR-check   / web-override init-api-flaky
* GitHub Check: PR-check   / node-override init-api-flaky
* GitHub Check: PR-check   / web-authorizeCoinjoin cancelCoinjoinAuthorization passphrase unlockPath setBusy checkFirmwareAuthenticity keepSession cancel.test info.test resetDevice-api
* GitHub Check: build-deploy
* GitHub Check: build-app
* GitHub Check: prepare_android_test_app
* GitHub Check: connect-web-e2e
* GitHub Check: Setup and Cache Dependencies
* GitHub Check: Analyze with CodeQL (javascript)
* GitHub Check: build-web

</details>

<details>
<summary>🔇 Additional comments (23)</summary><blockquote>

<details>
<summary>packages/websocket-client/src/ws-native.ts (3)</summary>

`3-4`: **Custom error type provides better error identification**

Adding the `WebsocketError` import allows for more specific error typing, which will help with error handling and debugging by clearly distinguishing WebSocket-related errors from other types.

---

`42-45`: **Consistent error typing in WebSocket error handler**

Replacing the generic `Error` with the more specific `WebsocketError` class improves error consistency and helps with error classification throughout the application.

---

`61-61`: **Consistent error typing in send method**

Using `WebsocketError` instead of a generic `Error` when the connection is not open provides better error specificity and consistency with the rest of the WebSocket error handling.

</details>
<details>
<summary>packages/connect/src/constants/errors.ts (2)</summary>

`91-96`: **Enhanced error handling for standard Error instances**

This addition properly handles standard JavaScript `Error` instances by extracting their message and code (if available) or using a default code. This is a good enhancement to ensure consistent error serialization across different error types.

---

`98-98`: **Preserve additional error properties**

Spreading the payload properties ensures that any additional information in the error object is preserved in the serialized result, which improves the completeness of error reporting.

</details>
<details>
<summary>packages/websocket-client/src/ws-browser.ts (3)</summary>

`3-4`: **Custom error type provides better error identification**

Adding the `WebsocketError` import allows for more specific error typing, which helps with error handling and debugging by clearly distinguishing WebSocket-related errors from other types.

---

`36-39`: **Consistent error typing in WebSocket error handler**

Replacing the generic `Error` with the more specific `WebsocketError` class improves error consistency and helps with error classification throughout the application.

---

`55-55`: **Consistent error typing in send method**

Using `WebsocketError` instead of a generic `Error` when the connection is not open provides better error specificity and consistency with the rest of the WebSocket error handling.

</details>
<details>
<summary>packages/connect-web/src/index.ts (3)</summary>

`52-58`: **Proactive desktop connection verification**

The new `handleBeforeCall` method attempts to reconnect to the desktop mode before each call if the core mode is set to 'suite-desktop'. This proactive approach ensures that the desktop mode is available before making a call, improving the reliability of the application.

---

`66-75`: **Enhanced desktop error fallback mechanism**

This new error handling logic implements the core feature of this PR by providing a specific fallback path for desktop connection issues. When a desktop connection is missing, the system will now automatically switch to using the core in a popup, improving the user experience by ensuring service continuity.

---

`69-69`: **Update on `getTargetType()` Documentation in TrezorConnectDynamic**

The `getTargetType()` method is used here to determine the current target type, but as noted in a previous review, it lacks proper documentation.

</details>
<details>
<summary>packages/connect/src/impl/dynamic.ts (5)</summary>

`21-21`: **Good addition of the handleBeforeCall parameter**

Adding this parameter allows for pre-processing or validation before executing a call, which increases flexibility in the dynamic connect implementation.

---

`48-52`: **LGTM: Well-typed private property**

The private property is properly typed and follows the class's generic structure consistently.

---

`64-64`: **Proper constructor implementation**

The constructor parameter and property initialization follow TypeScript best practices.



Also applies to: 70-70

---

`81-83`: **Good accessor method addition**

Adding the `getTargetType()` method provides a clean way to access the current target type without exposing the private property directly.

---

`139-141`: **Good implementation of call preprocessing**

The `handleBeforeCall` is now properly invoked before each call, implementing the error fallback feature as specified in the PR objectives.

</details>
<details>
<summary>packages/websocket-client/src/client.ts (7)</summary>

`30-31`: **Good addition of specialized error class**

Creating a dedicated `WebsocketError` class allows for more specific error handling throughout the codebase, making it easier to identify and handle websocket-related errors.

---

`61-61`: **Improved error specificity**

Replacing generic errors with the specialized `WebsocketError` provides better error handling capabilities.

---

`91-91`: **Enhanced error handling in timeout scenario**

Using `WebsocketError` for timeout scenario improves error specificity and consistency.

---

`101-101`: **Consistent error handling for uninitialized websocket**

Both message sending methods now consistently use `WebsocketError` when the websocket is not initialized, which improves error handling uniformity.



Also applies to: 117-117

---

`173-174`: **Better error handling in connection timeout**

The connection timeout error now properly uses `WebsocketError` and ensures cleanup with `onClose()`.

---

`187-187`: **Enhanced websocket error propagation**

Error propagation from the websocket now uses the specialized error class, improving error handling consistency.

---

`240-243`: **Good defensive error handling after close**

Adding an error event listener to suppress errors after the websocket has closed is a good defensive measure to prevent unexpected errors from propagating.

The error message for unexpected closure is also appropriately using the specialized error class.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-25T12:03:16Z
user: login: mroz22
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T15:42:50Z

Review comments:
diff_hunk: @@ -56,6 +56,17 @@ const impl = new TrezorConnectDynamic<
         const isCoreModeAuto =
             impl.lastSettings?.coreMode === 'auto' || impl.lastSettings?.coreMode === undefined;
 
+        // Handle desktop errors
+        if (
+            !isCoreModeDisabled &&
+            impl.getTargetType() === 'core-in-suite-desktop' &&
path: packages/connect-web/src/index.ts
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Consider adding `getTargetType()` method documentation**

The `getTargetType()` method appears to be used to determine the current target type, but I don't see it defined in the provided code. Ensure this method is properly defined and documented in the `TrezorConnectDynamic` class.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if getTargetType() is properly defined in the TrezorConnectDynamic class
rg -A 5 "getTargetType\(\)" --type ts
```

Length of output: 844

---



</details>

**Update on `getTargetType()` Documentation in TrezorConnectDynamic**

The `getTargetType()` method is indeed defined in the implementation (see `packages/connect/src/impl/dynamic.ts`), so the definition is in place. However, it currently lacks accompanying documentation. Please add a proper JSDoc comment to `getTargetType()` to explain its purpose, the nature of the returned value (e.g., indicating target types such as `'core-in-suite-desktop'`), and any relevant context. This will improve clarity and maintainability for future developers.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-21T15:59:54Z
author_association: NONE
diff_hunk: @@ -117,19 +115,14 @@ export class CoreInSuiteDesktop implements ConnectFactoryDependencies<ConnectSet
             );
 
             if (!response) {
-                return {
-                    success: false,
-                    payload: { error: 'No response', code: 'Desktop_ConnectionMissing' },
-                };
+                throw ERRORS.TypedError('Desktop_ConnectionMissing', 'No response');
             }
 
             return response;
         } catch (err) {
             return {
                 success: false,
-                payload: {
-                    error: err.message,
-                },
+                payload: ERRORS.serializeError(err),
path: packages/connect-web/src/impl/core-in-suite-desktop.ts
user: login: mroz22
body: I got this 
<img width="264" alt="image" src="https://github.com/user-attachments/assets/90a0ab12-c159-4ebc-916e-c56a9e5d2aa6" />
 
<img width="554" alt="image" src="https://github.com/user-attachments/assets/27f1312d-72c8-4a27-8440-307b00dceb87" />

which does not look serialized 🤔 
created_at: 2025-03-25T10:15:16Z
author_association: CONTRIBUTOR

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-03-21T15:59:51Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

The changes update the error handling and control flow in the connection modules. In the `CoreInSuiteDesktop` class, the `handshake` and `call` methods now throw a typed error ("Desktop_ConnectionMissing") when there is no response, replacing previous success/error flag logic. The `call` method verifies the initialization of a WebSocket client before proceeding. In the `TrezorConnectDynamic` class, a new public method to retrieve the current target type is introduced, and the constructor now accepts a pre-call handler. The `call` method is modified to execute this handler before proceeding with operations. Additionally, the `switchTarget` method incorporates error handling to revert to the previous target if the new target fails to initialize. A corresponding stub for `handleBeforeCall` is added to the configuration object, and the error fallback method is updated to specifically handle desktop connection issues. The `WebsocketError` class is introduced to enhance error specificity across WebSocket operations.

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=trezor/trezor-suite&utm_content=17845):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai plan` to trigger planning for file edits and PR creation.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: martykan
created_at: 2025-03-24T08:10:45Z
body: Good point, we could probably try again before each call
author_association: MEMBER