diff --git a/scripts/build/build.mts b/scripts/build/build.mts index 2aa98fc..ee3f7ca 100644 --- a/scripts/build/build.mts +++ b/scripts/build/build.mts @@ -58,6 +58,12 @@ await Promise.all([ outfile: "dist/js/main.js", footer: { js: "//# sourceURL=VCDMain" } }), + createContext({ + ...NodeCommonOpts, + entryPoints: ["src/main/arrpc/worker.ts"], + outfile: "dist/js/arRpcWorker.js", + footer: { js: "//# sourceURL=VCDArRpcWorker" } + }), createContext({ ...NodeCommonOpts, entryPoints: ["src/preload/index.ts"], diff --git a/src/main/arrpc.ts b/src/main/arrpc.ts deleted file mode 100644 index 9f6ee32..0000000 --- a/src/main/arrpc.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Vesktop, a desktop app aiming to give you a snappier Discord Experience - * Copyright (c) 2023 Vendicated and Vencord contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -import Server from "arrpc"; -import { IpcCommands } from "shared/IpcEvents"; - -import { sendRendererCommand } from "./ipcCommands"; -import { Settings } from "./settings"; - -let server: any; - -const inviteCodeRegex = /^(\w|-)+$/; - -export async function initArRPC() { - if (server || !Settings.store.arRPC) return; - - try { - server = await new Server(); - server.on("activity", (data: any) => sendRendererCommand(IpcCommands.RPC_ACTIVITY, JSON.stringify(data))); - server.on("invite", async (invite: string, callback: (valid: boolean) => void) => { - invite = String(invite); - if (!inviteCodeRegex.test(invite)) return callback(false); - - await sendRendererCommand(IpcCommands.RPC_INVITE, invite).then(callback); - }); - server.on("link", async (data: any, deepCallback: (valid: boolean) => void) => { - await sendRendererCommand(IpcCommands.RPC_DEEP_LINK, data).then(deepCallback); - }); - } catch (e) { - console.error("Failed to start arRPC server", e); - } -} - -Settings.addChangeListener("arRPC", initArRPC); diff --git a/src/main/arrpc/index.ts b/src/main/arrpc/index.ts new file mode 100644 index 0000000..d5dd214 --- /dev/null +++ b/src/main/arrpc/index.ts @@ -0,0 +1,83 @@ +/* + * Vesktop, a desktop app aiming to give you a snappier Discord Experience + * Copyright (c) 2023 Vendicated and Vencord contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { resolve } from "path"; +import { IpcCommands } from "shared/IpcEvents"; +import { MessageChannel, Worker } from "worker_threads"; + +import { sendRendererCommand } from "../ipcCommands"; +import { Settings } from "../settings"; +import { ArRpcEvent, ArRpcHostEvent } from "./types"; + +let worker: Worker; + +const inviteCodeRegex = /^(\w|-)+$/; + +export async function initArRPC() { + if (worker || !Settings.store.arRPC) return; + + try { + const { port1: hostPort, port2: workerPort } = new MessageChannel(); + + worker = new Worker(resolve(__dirname, "./arRpcWorker.js"), { + workerData: { + workerPort + }, + transferList: [workerPort] + }); + + hostPort.on("message", async (e: ArRpcEvent) => { + switch (e.type) { + case "activity": { + sendRendererCommand(IpcCommands.RPC_ACTIVITY, e.data); + break; + } + + case "invite": { + const invite = String(e.data); + + const response: ArRpcHostEvent = { + type: "ack-invite", + nonce: e.nonce, + data: false + }; + + if (!inviteCodeRegex.test(invite)) { + return hostPort.postMessage(response); + } + + response.data = await sendRendererCommand(IpcCommands.RPC_INVITE, invite).catch(() => false); + + hostPort.postMessage(response); + break; + } + + case "link": { + const link = String(e.data); + + const response: ArRpcHostEvent = { + type: "ack-link", + nonce: e.nonce, + data: false + }; + + if (!inviteCodeRegex.test(link)) { + return hostPort.postMessage(response); + } + + response.data = await sendRendererCommand(IpcCommands.RPC_DEEP_LINK, link).catch(() => false); + + hostPort.postMessage(response); + break; + } + } + }); + } catch (e) { + console.error("Failed to start arRPC server", e); + } +} + +Settings.addChangeListener("arRPC", initArRPC); diff --git a/src/main/arrpc/types.ts b/src/main/arrpc/types.ts new file mode 100644 index 0000000..51492ed --- /dev/null +++ b/src/main/arrpc/types.ts @@ -0,0 +1,37 @@ +/* + * Vesktop, a desktop app aiming to give you a snappier Discord Experience + * Copyright (c) 2025 Vendicated and Vesktop contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +export type ArRpcEvent = ArRpcActivityEvent | ArRpcInviteEvent | ArRpcLinkEvent; +export type ArRpcHostEvent = ArRpcHostAckInviteEvent | ArRpcHostAckLinkEvent; + +export interface ArRpcActivityEvent { + type: "activity"; + data: string; +} + +export interface ArRpcInviteEvent { + type: "invite"; + nonce: string; + data: string; +} + +export interface ArRpcLinkEvent { + type: "link"; + nonce: string; + data: string; +} + +export interface ArRpcHostAckInviteEvent { + type: "ack-invite"; + nonce: string; + data: boolean; +} + +export interface ArRpcHostAckLinkEvent { + type: "ack-link"; + nonce: string; + data: boolean; +} diff --git a/src/main/arrpc/worker.ts b/src/main/arrpc/worker.ts new file mode 100644 index 0000000..f994c43 --- /dev/null +++ b/src/main/arrpc/worker.ts @@ -0,0 +1,60 @@ +/* + * Vesktop, a desktop app aiming to give you a snappier Discord Experience + * Copyright (c) 2025 Vendicated and Vesktop contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import Server from "arrpc"; +import { randomUUID } from "crypto"; +import { MessagePort, workerData } from "worker_threads"; + +import { ArRpcEvent, ArRpcHostEvent } from "./types"; + +let server: any; + +type InviteCallback = (valid: boolean) => void; +type LinkCallback = InviteCallback; + +const inviteCallbacks = new Map(); +const linkCallbacks = new Map(); + +(async function () { + const { workerPort } = workerData as { workerPort: MessagePort }; + + server = await new Server(); + + server.on("activity", (data: any) => { + const event: ArRpcEvent = { + type: "activity", + data: JSON.stringify(data) + }; + workerPort.postMessage(event); + }); + + server.on("invite", (invite: string, callback: InviteCallback) => { + const nonce = randomUUID(); + inviteCallbacks.set(nonce, callback); + + const event: ArRpcEvent = { + type: "invite", + data: invite, + nonce + }; + workerPort.postMessage(event); + }); + + workerPort.on("message", (e: ArRpcHostEvent) => { + switch (e.type) { + case "ack-invite": { + inviteCallbacks.get(e.nonce)?.(e.data); + inviteCallbacks.delete(e.nonce); + break; + } + case "ack-link": { + linkCallbacks.get(e.nonce)?.(e.data); + linkCallbacks.delete(e.nonce); + break; + } + } + }); +})(); diff --git a/src/shared/IpcEvents.ts b/src/shared/IpcEvents.ts index 1097989..03126d2 100644 --- a/src/shared/IpcEvents.ts +++ b/src/shared/IpcEvents.ts @@ -49,8 +49,6 @@ export const enum IpcEvents { VIRT_MIC_START_SYSTEM = "VCD_VIRT_MIC_START_ALL", VIRT_MIC_STOP = "VCD_VIRT_MIC_STOP", - ARRPC_ACTIVITY = "VCD_ARRPC_ACTIVITY", - CLIPBOARD_COPY_IMAGE = "VCD_CLIPBOARD_COPY_IMAGE", DEBUG_LAUNCH_GPU = "VCD_DEBUG_LAUNCH_GPU",