Safe send (#421)
This commit is contained in:
@@ -39,6 +39,7 @@ import { startProxy } from "../utils/start_proxy_server";
|
||||
import { Worker } from "worker_threads";
|
||||
import { createFromTemplate } from "./createFromTemplate";
|
||||
import { gitCommit } from "../utils/git_utils";
|
||||
import { safeSend } from "../utils/safe_sender";
|
||||
|
||||
async function copyDir(
|
||||
source: string,
|
||||
@@ -126,7 +127,7 @@ async function executeAppLocalNode({
|
||||
const message = util.stripVTControlCharacters(data.toString());
|
||||
logger.debug(`App ${appId} (PID: ${process.pid}) stdout: ${message}`);
|
||||
|
||||
event.sender.send("app:output", {
|
||||
safeSend(event.sender, "app:output", {
|
||||
type: "stdout",
|
||||
message,
|
||||
appId,
|
||||
@@ -135,7 +136,7 @@ async function executeAppLocalNode({
|
||||
if (urlMatch) {
|
||||
proxyWorker = await startProxy(urlMatch[1], {
|
||||
onStarted: (proxyUrl) => {
|
||||
event.sender.send("app:output", {
|
||||
safeSend(event.sender, "app:output", {
|
||||
type: "stdout",
|
||||
message: `[dyad-proxy-server]started=[${proxyUrl}] original=[${urlMatch[1]}]`,
|
||||
appId,
|
||||
@@ -148,7 +149,7 @@ async function executeAppLocalNode({
|
||||
process.stderr?.on("data", (data) => {
|
||||
const message = util.stripVTControlCharacters(data.toString());
|
||||
logger.error(`App ${appId} (PID: ${process.pid}) stderr: ${message}`);
|
||||
event.sender.send("app:output", {
|
||||
safeSend(event.sender, "app:output", {
|
||||
type: "stderr",
|
||||
message,
|
||||
appId,
|
||||
|
||||
@@ -37,6 +37,8 @@ import { GoogleGenerativeAIProviderOptions } from "@ai-sdk/google";
|
||||
|
||||
import { getExtraProviderOptions } from "../utils/thinking_utils";
|
||||
|
||||
import { safeSend } from "../utils/safe_sender";
|
||||
|
||||
const logger = log.scope("chat_stream_handlers");
|
||||
|
||||
// Track active streams for cancellation
|
||||
@@ -237,7 +239,7 @@ ${componentSnippet}
|
||||
}
|
||||
|
||||
// Send the messages right away so that the loading state is shown for the message.
|
||||
event.sender.send("chat:response:chunk", {
|
||||
safeSend(event.sender, "chat:response:chunk", {
|
||||
chatId: req.chatId,
|
||||
messages: updatedChat.messages,
|
||||
});
|
||||
@@ -540,7 +542,7 @@ This conversation includes one or more image attachments. When the user uploads
|
||||
}
|
||||
|
||||
// Update the assistant message in the database
|
||||
event.sender.send("chat:response:chunk", {
|
||||
safeSend(event.sender, "chat:response:chunk", {
|
||||
chatId: req.chatId,
|
||||
messages: currentMessages,
|
||||
});
|
||||
@@ -622,27 +624,28 @@ This conversation includes one or more image attachments. When the user uploads
|
||||
},
|
||||
});
|
||||
|
||||
event.sender.send("chat:response:chunk", {
|
||||
safeSend(event.sender, "chat:response:chunk", {
|
||||
chatId: req.chatId,
|
||||
messages: chat!.messages,
|
||||
});
|
||||
|
||||
if (status.error) {
|
||||
event.sender.send(
|
||||
safeSend(
|
||||
event.sender,
|
||||
"chat:response:error",
|
||||
`Sorry, there was an error applying the AI's changes: ${status.error}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Signal that the stream has completed
|
||||
event.sender.send("chat:response:end", {
|
||||
safeSend(event.sender, "chat:response:end", {
|
||||
chatId: req.chatId,
|
||||
updatedFiles: status.updatedFiles ?? false,
|
||||
extraFiles: status.extraFiles,
|
||||
extraFilesError: status.extraFilesError,
|
||||
} satisfies ChatResponseEnd);
|
||||
} else {
|
||||
event.sender.send("chat:response:end", {
|
||||
safeSend(event.sender, "chat:response:end", {
|
||||
chatId: req.chatId,
|
||||
updatedFiles: false,
|
||||
} satisfies ChatResponseEnd);
|
||||
@@ -674,7 +677,8 @@ This conversation includes one or more image attachments. When the user uploads
|
||||
return req.chatId;
|
||||
} catch (error) {
|
||||
logger.error("Error calling LLM:", error);
|
||||
event.sender.send(
|
||||
safeSend(
|
||||
event.sender,
|
||||
"chat:response:error",
|
||||
`Sorry, there was an error processing your request: ${error}`,
|
||||
);
|
||||
@@ -698,7 +702,7 @@ This conversation includes one or more image attachments. When the user uploads
|
||||
}
|
||||
|
||||
// Send the end event to the renderer
|
||||
event.sender.send("chat:response:end", {
|
||||
safeSend(event.sender, "chat:response:end", {
|
||||
chatId,
|
||||
updatedFiles: false,
|
||||
} satisfies ChatResponseEnd);
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
createTestOnlyLoggedHandler,
|
||||
} from "./safe_handle";
|
||||
import { handleSupabaseOAuthReturn } from "../../supabase_admin/supabase_return_handler";
|
||||
import { safeSend } from "../utils/safe_sender";
|
||||
|
||||
const logger = log.scope("supabase_handlers");
|
||||
const handle = createLoggedHandler(logger);
|
||||
@@ -70,7 +71,7 @@ export function registerSupabaseHandlers() {
|
||||
);
|
||||
|
||||
// Simulate the deep link event
|
||||
event.sender.send("deep-link-received", {
|
||||
safeSend(event.sender, "deep-link-received", {
|
||||
type: "supabase-oauth-return",
|
||||
url: "https://supabase-oauth.dyad.sh/api/connect-supabase/login",
|
||||
});
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { safeSend } from "../utils/safe_sender";
|
||||
|
||||
// e.g. [dyad-qa=add-dep]
|
||||
// Canned responses for test prompts
|
||||
const TEST_RESPONSES: Record<string, string> = {
|
||||
@@ -64,7 +66,7 @@ export async function streamTestResponse(
|
||||
fullResponse += chunk + " ";
|
||||
|
||||
// Send the current accumulated response
|
||||
event.sender.send("chat:response:chunk", {
|
||||
safeSend(event.sender, "chat:response:chunk", {
|
||||
chatId: chatId,
|
||||
messages: [
|
||||
...updatedChat.messages,
|
||||
|
||||
29
src/ipc/utils/safe_sender.ts
Normal file
29
src/ipc/utils/safe_sender.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { WebContents } from "electron";
|
||||
import log from "electron-log";
|
||||
|
||||
/**
|
||||
* Sends an IPC message to the renderer only if the provided `WebContents` is
|
||||
* still alive. This prevents `Object has been destroyed` errors that can occur
|
||||
* when asynchronous callbacks attempt to communicate after the window has
|
||||
* already been closed (e.g. during e2e test teardown).
|
||||
*/
|
||||
export function safeSend(
|
||||
sender: WebContents | null | undefined,
|
||||
channel: string,
|
||||
...args: unknown[]
|
||||
): void {
|
||||
if (!sender) return;
|
||||
if (sender.isDestroyed()) return;
|
||||
// @ts-ignore – `isCrashed` exists at runtime but is not in the type defs
|
||||
if (typeof sender.isCrashed === "function" && sender.isCrashed()) return;
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore – allow variadic args beyond `data`
|
||||
sender.send(channel, ...args);
|
||||
} catch (error) {
|
||||
log.debug(
|
||||
`safeSend: failed to send on channel "${channel}" because: ${(error as Error).message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user