diff --git a/packages/vscode-extension/src/common/utils.ts b/packages/vscode-extension/src/common/utils.ts index 1601433f1..43ed9fcbe 100644 --- a/packages/vscode-extension/src/common/utils.ts +++ b/packages/vscode-extension/src/common/utils.ts @@ -14,4 +14,6 @@ export interface UtilsInterface { showDismissableError(errorMessage: string): Promise; openExternalUrl(uriString: string): Promise; + + log(type: "info" | "error" | "warn" | "log", message: string, ...args: any[]): Promise; } diff --git a/packages/vscode-extension/src/panels/WebviewController.ts b/packages/vscode-extension/src/panels/WebviewController.ts index 854e575cb..f4230198a 100644 --- a/packages/vscode-extension/src/panels/WebviewController.ts +++ b/packages/vscode-extension/src/panels/WebviewController.ts @@ -3,7 +3,6 @@ import { DependencyManager } from "../dependency/DependencyManager"; import { DeviceManager } from "../devices/DeviceManager"; import { Project } from "../project/project"; import { Logger } from "../Logger"; -import { extensionContext } from "../utilities/extensionContext"; import { WorkspaceConfigController } from "./WorkspaceConfigController"; import { getTelemetryReporter } from "../utilities/telemetry"; import { Utils } from "../utilities/utils"; @@ -15,10 +14,10 @@ type CallArgs = { method: string; args: unknown[]; }; -export type WebviewEvent = - | { - command: "call"; - } & CallArgs; + +export type WebviewEvent = { + command: "call"; +} & CallArgs; export class WebviewController implements Disposable { private readonly dependencyManager: DependencyManager; @@ -92,15 +91,13 @@ export class WebviewController implements Disposable { private setWebviewMessageListener(webview: Webview) { webview.onDidReceiveMessage( (message: WebviewEvent) => { - const isTouchEvent = message.command === "call" && message.method === "dispatchTouches"; - if (!isTouchEvent) { + // ignore dispatchTouches and log calls from being logged as "Message from webview" + if (message.method !== "dispatchTouches" && message.method !== "log") { Logger.log("Message from webview", message); } - switch (message.command) { - case "call": - this.handleRemoteCall(message); - return; + if (message.command === "call") { + this.handleRemoteCall(message); } }, undefined, @@ -113,27 +110,32 @@ export class WebviewController implements Disposable { const callableObject = this.callableObjects.get(object); if (callableObject && method in callableObject) { const argsWithCallbacks = args.map((arg: any) => { - if (typeof arg === "object" && arg !== null && "__callbackId" in arg) { - const callbackId = arg.__callbackId; - let callback = this.idToCallback.get(callbackId)?.deref(); - if (!callback) { - callback = (...options: any[]) => { - this.webview.postMessage({ - command: "callback", - callbackId, - args: options, - }); - }; - this.idToCallback.set(callbackId, new WeakRef(callback)); - if (this.idToCallback.size > 200) { - Logger.warn("Too many callbacks in memory! Something is wrong!"); + if (typeof arg === "object" && arg !== null) { + if ("__callbackId" in arg) { + const callbackId = arg.__callbackId; + let callback = this.idToCallback.get(callbackId)?.deref(); + if (!callback) { + callback = (...options: any[]) => { + this.webview.postMessage({ + command: "callback", + callbackId, + args: options, + }); + }; + this.idToCallback.set(callbackId, new WeakRef(callback)); + if (this.idToCallback.size > 200) { + Logger.warn("Too many callbacks in memory! Something is wrong!"); + } + this.idToCallbackFinalizationRegistry.register(callback, callbackId); } - this.idToCallbackFinalizationRegistry.register(callback, callbackId); + return callback; + } else if ("__error" in arg) { + const error = new Error(arg.__error.message); + Object.assign(error, arg.__error); + return error; } - return callback; - } else { - return arg; } + return arg; }); // @ts-ignore const result = callableObject[method](...argsWithCallbacks); diff --git a/packages/vscode-extension/src/utilities/utils.ts b/packages/vscode-extension/src/utilities/utils.ts index 7d5405feb..416f6e9b5 100644 --- a/packages/vscode-extension/src/utilities/utils.ts +++ b/packages/vscode-extension/src/utilities/utils.ts @@ -112,4 +112,8 @@ export class Utils implements UtilsInterface { public async openExternalUrl(uriString: string) { env.openExternal(Uri.parse(uriString)); } + + public async log(type: "info" | "error" | "warn" | "log", message: string, ...args: any[]) { + Logger[type]("[WEBVIEW LOG]", message, ...args); + } } diff --git a/packages/vscode-extension/src/webview/index.jsx b/packages/vscode-extension/src/webview/index.jsx index 4f2a60fdb..1e793bec6 100644 --- a/packages/vscode-extension/src/webview/index.jsx +++ b/packages/vscode-extension/src/webview/index.jsx @@ -10,9 +10,11 @@ import AlertProvider from "./providers/AlertProvider"; import WorkspaceConfigProvider from "./providers/WorkspaceConfigProvider"; import "./styles/theme.css"; -import UtilsProvider from "./providers/UtilsProvider"; +import { UtilsProvider, installLogOverrides } from "./providers/UtilsProvider"; import LaunchConfigProvider from "./providers/LaunchConfigProvider"; +installLogOverrides(); + const container = document.getElementById("root"); const root = createRoot(container); diff --git a/packages/vscode-extension/src/webview/providers/UtilsProvider.tsx b/packages/vscode-extension/src/webview/providers/UtilsProvider.tsx index 41702844e..24dbc4051 100644 --- a/packages/vscode-extension/src/webview/providers/UtilsProvider.tsx +++ b/packages/vscode-extension/src/webview/providers/UtilsProvider.tsx @@ -21,7 +21,7 @@ const utils = makeProxy("Utils"); const UtilsContext = createContext(utils); -export default function UtilsProvider({ children }: PropsWithChildren) { +export function UtilsProvider({ children }: PropsWithChildren) { return {children}; } @@ -33,3 +33,29 @@ export function useUtils() { } return context; } + +export function installLogOverrides() { + function wrapConsole(methodName: "log" | "info" | "warn" | "error") { + const consoleMethod = console[methodName]; + console[methodName] = (message: string, ...args: any[]) => { + utils.log(methodName, message, ...args); + consoleMethod(message, ...args); + }; + } + + (["log", "info", "warn", "error"] as const).forEach(wrapConsole); + + // install uncaught exception handler + window.addEventListener("error", (event) => { + utils.log("error", "Uncaught exception", event.error.stack); + // rethrow the error to be caught by the global error handler + throw event.error; + }); + + // install uncaught promise rejection handler + window.addEventListener("unhandledrejection", (event) => { + utils.log("error", "Uncaught promise rejection", event.reason); + // rethrow the error to be caught by the global error handler + throw event.reason; + }); +} diff --git a/packages/vscode-extension/src/webview/utilities/rpc.ts b/packages/vscode-extension/src/webview/utilities/rpc.ts index 576d1fcc7..d1725ce7e 100644 --- a/packages/vscode-extension/src/webview/utilities/rpc.ts +++ b/packages/vscode-extension/src/webview/utilities/rpc.ts @@ -68,7 +68,15 @@ export function makeProxy(objectName: string) { return (...args: any[]) => { const currentCallId = `${instanceToken}:${globalCallCounter++}`; let argsWithCallbacks = args.map((arg) => { - if (typeof arg === "function") { + if (arg instanceof Error) { + return { + __error: { + name: arg.name, + message: arg.message, + stack: arg.stack, + }, + }; + } else if (typeof arg === "function") { maybeInitializeCallbackMessageListener(); const callbackId = callbackToID.get(arg) || `${instanceToken}:${globalCallbackCounter++}`;