mirror of
https://github.com/Vencord/Vesktop.git
synced 2025-02-22 21:35:08 +00:00
add main to renderer command API
All checks were successful
test / test (push) Successful in 10m41s
All checks were successful
test / test (push) Successful in 10m41s
This commit is contained in:
parent
eddbe27c4d
commit
c9be618164
9 changed files with 176 additions and 49 deletions
|
@ -5,9 +5,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Server from "arrpc";
|
import Server from "arrpc";
|
||||||
import { IpcEvents } from "shared/IpcEvents";
|
import { IpcCommands } from "shared/IpcEvents";
|
||||||
|
|
||||||
import { mainWin } from "./mainWindow";
|
import { sendRendererCommand } from "./ipcCommands";
|
||||||
import { Settings } from "./settings";
|
import { Settings } from "./settings";
|
||||||
|
|
||||||
let server: any;
|
let server: any;
|
||||||
|
@ -19,16 +19,12 @@ export async function initArRPC() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
server = await new Server();
|
server = await new Server();
|
||||||
server.on("activity", (data: any) => mainWin.webContents.send(IpcEvents.ARRPC_ACTIVITY, JSON.stringify(data)));
|
server.on("activity", (data: any) => sendRendererCommand(IpcCommands.RPC_ACTIVITY, JSON.stringify(data)));
|
||||||
server.on("invite", (invite: string, callback: (valid: boolean) => void) => {
|
server.on("invite", async (invite: string, callback: (valid: boolean) => void) => {
|
||||||
invite = String(invite);
|
invite = String(invite);
|
||||||
if (!inviteCodeRegex.test(invite)) return callback(false);
|
if (!inviteCodeRegex.test(invite)) return callback(false);
|
||||||
|
|
||||||
mainWin.webContents
|
await sendRendererCommand(IpcCommands.RPC_INVITE, invite).then(callback);
|
||||||
// Safety: Result of JSON.stringify should always be safe to equal
|
|
||||||
// Also, just to be super super safe, invite is regex validated above
|
|
||||||
.executeJavaScript(`Vesktop.openInviteModal(${JSON.stringify(invite)})`)
|
|
||||||
.then(callback);
|
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to start arRPC server", e);
|
console.error("Failed to start arRPC server", e);
|
||||||
|
|
|
@ -23,6 +23,8 @@ if (IS_DEV) {
|
||||||
autoUpdater.checkForUpdatesAndNotify();
|
autoUpdater.checkForUpdatesAndNotify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("Vesktop v" + app.getVersion());
|
||||||
|
|
||||||
// Make the Vencord files use our DATA_DIR
|
// Make the Vencord files use our DATA_DIR
|
||||||
process.env.VENCORD_USER_DATA_DIR = DATA_DIR;
|
process.env.VENCORD_USER_DATA_DIR = DATA_DIR;
|
||||||
|
|
||||||
|
|
56
src/main/ipcCommands.ts
Normal file
56
src/main/ipcCommands.ts
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0
|
||||||
|
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||||
|
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { randomUUID } from "crypto";
|
||||||
|
import { ipcMain } from "electron";
|
||||||
|
import { IpcEvents } from "shared/IpcEvents";
|
||||||
|
|
||||||
|
import { mainWin } from "./mainWindow";
|
||||||
|
|
||||||
|
const resolvers = new Map<string, Record<"resolve" | "reject", (data: any) => void>>();
|
||||||
|
|
||||||
|
export interface IpcMessage {
|
||||||
|
nonce: string;
|
||||||
|
message: string;
|
||||||
|
data?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IpcResponse {
|
||||||
|
nonce: string;
|
||||||
|
ok: boolean;
|
||||||
|
data?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a message to the renderer process and waits for a response.
|
||||||
|
* `data` must be serializable as it will be sent over IPC.
|
||||||
|
*
|
||||||
|
* You must add a handler for the message in the renderer process.
|
||||||
|
*/
|
||||||
|
export function sendRendererCommand<T = any>(message: string, data?: any) {
|
||||||
|
const nonce = randomUUID();
|
||||||
|
|
||||||
|
const promise = new Promise<T>((resolve, reject) => {
|
||||||
|
resolvers.set(nonce, { resolve, reject });
|
||||||
|
});
|
||||||
|
|
||||||
|
mainWin.webContents.send(IpcEvents.IPC_COMMAND, { nonce, message, data });
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcMain.on(IpcEvents.IPC_COMMAND, (_event, { nonce, ok, data }: IpcResponse) => {
|
||||||
|
const resolver = resolvers.get(nonce);
|
||||||
|
if (!resolver) throw new Error(`Unknown message: ${nonce}`);
|
||||||
|
|
||||||
|
if (ok) {
|
||||||
|
resolver.resolve(data);
|
||||||
|
} else {
|
||||||
|
resolver.reject(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolvers.delete(nonce);
|
||||||
|
});
|
|
@ -18,7 +18,7 @@ import {
|
||||||
} from "electron";
|
} from "electron";
|
||||||
import { rm } from "fs/promises";
|
import { rm } from "fs/promises";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { IpcEvents } from "shared/IpcEvents";
|
import { IpcCommands, IpcEvents } from "shared/IpcEvents";
|
||||||
import { isTruthy } from "shared/utils/guards";
|
import { isTruthy } from "shared/utils/guards";
|
||||||
import { once } from "shared/utils/once";
|
import { once } from "shared/utils/once";
|
||||||
import type { SettingsStore } from "shared/utils/SettingsStore";
|
import type { SettingsStore } from "shared/utils/SettingsStore";
|
||||||
|
@ -36,6 +36,7 @@ import {
|
||||||
MIN_WIDTH,
|
MIN_WIDTH,
|
||||||
VENCORD_FILES_DIR
|
VENCORD_FILES_DIR
|
||||||
} from "./constants";
|
} from "./constants";
|
||||||
|
import { sendRendererCommand } from "./ipcCommands";
|
||||||
import { Settings, State, VencordSettings } from "./settings";
|
import { Settings, State, VencordSettings } from "./settings";
|
||||||
import { createSplashWindow } from "./splash";
|
import { createSplashWindow } from "./splash";
|
||||||
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
|
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
|
||||||
|
@ -198,9 +199,7 @@ function initMenuBar(win: BrowserWindow) {
|
||||||
label: "Settings",
|
label: "Settings",
|
||||||
accelerator: "CmdOrCtrl+,",
|
accelerator: "CmdOrCtrl+,",
|
||||||
async click() {
|
async click() {
|
||||||
mainWin.webContents.executeJavaScript(
|
sendRendererCommand(IpcCommands.NAVIGATE_SETTINGS);
|
||||||
"Vencord.Webpack.Common.SettingsRouter.open('My Account')"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -366,7 +365,7 @@ function initSettingsListeners(win: BrowserWindow) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initSpellCheckLanguages(win: BrowserWindow, languages?: string[]) {
|
async function initSpellCheckLanguages(win: BrowserWindow, languages?: string[]) {
|
||||||
languages ??= await win.webContents.executeJavaScript("[...new Set(navigator.languages)]").catch(() => []);
|
languages ??= await sendRendererCommand(IpcCommands.GET_LANGUAGES);
|
||||||
if (!languages) return;
|
if (!languages) return;
|
||||||
|
|
||||||
const ses = session.defaultSession;
|
const ses = session.defaultSession;
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
import { Node } from "@vencord/venmic";
|
import { Node } from "@vencord/venmic";
|
||||||
import { ipcRenderer } from "electron";
|
import { ipcRenderer } from "electron";
|
||||||
|
import { IpcMessage, IpcResponse } from "main/ipcCommands";
|
||||||
import type { Settings } from "shared/settings";
|
import type { Settings } from "shared/settings";
|
||||||
|
|
||||||
import { IpcEvents } from "../shared/IpcEvents";
|
import { IpcEvents } from "../shared/IpcEvents";
|
||||||
|
@ -70,11 +71,6 @@ export const VesktopNative = {
|
||||||
startSystem: (exclude: Node[]) => invoke<void>(IpcEvents.VIRT_MIC_START_SYSTEM, exclude),
|
startSystem: (exclude: Node[]) => invoke<void>(IpcEvents.VIRT_MIC_START_SYSTEM, exclude),
|
||||||
stop: () => invoke<void>(IpcEvents.VIRT_MIC_STOP)
|
stop: () => invoke<void>(IpcEvents.VIRT_MIC_STOP)
|
||||||
},
|
},
|
||||||
arrpc: {
|
|
||||||
onActivity(cb: (data: string) => void) {
|
|
||||||
ipcRenderer.on(IpcEvents.ARRPC_ACTIVITY, (_, data: string) => cb(data));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
clipboard: {
|
clipboard: {
|
||||||
copyImage: (imageBuffer: Uint8Array, imageSrc: string) =>
|
copyImage: (imageBuffer: Uint8Array, imageSrc: string) =>
|
||||||
invoke<void>(IpcEvents.CLIPBOARD_COPY_IMAGE, imageBuffer, imageSrc)
|
invoke<void>(IpcEvents.CLIPBOARD_COPY_IMAGE, imageBuffer, imageSrc)
|
||||||
|
@ -82,5 +78,11 @@ export const VesktopNative = {
|
||||||
debug: {
|
debug: {
|
||||||
launchGpu: () => invoke<void>(IpcEvents.DEBUG_LAUNCH_GPU),
|
launchGpu: () => invoke<void>(IpcEvents.DEBUG_LAUNCH_GPU),
|
||||||
launchWebrtcInternals: () => invoke<void>(IpcEvents.DEBUG_LAUNCH_WEBRTC_INTERNALS)
|
launchWebrtcInternals: () => invoke<void>(IpcEvents.DEBUG_LAUNCH_WEBRTC_INTERNALS)
|
||||||
|
},
|
||||||
|
commands: {
|
||||||
|
onCommand(cb: (message: IpcMessage) => void) {
|
||||||
|
ipcRenderer.on(IpcEvents.IPC_COMMAND, (_, message) => cb(message));
|
||||||
|
},
|
||||||
|
respond: (response: IpcResponse) => ipcRenderer.send(IpcEvents.IPC_COMMAND, response)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
40
src/renderer/arrpc.ts
Normal file
40
src/renderer/arrpc.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0
|
||||||
|
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||||
|
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { onceReady } from "@vencord/types/webpack";
|
||||||
|
import { FluxDispatcher, InviteActions } from "@vencord/types/webpack/common";
|
||||||
|
import { IpcCommands } from "shared/IpcEvents";
|
||||||
|
|
||||||
|
import { onIpcCommand } from "./ipcCommands";
|
||||||
|
import { Settings } from "./settings";
|
||||||
|
|
||||||
|
const arRPC = Vencord.Plugins.plugins["WebRichPresence (arRPC)"] as any as {
|
||||||
|
handleEvent(e: MessageEvent): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
onIpcCommand(IpcCommands.RPC_ACTIVITY, async data => {
|
||||||
|
if (!Settings.store.arRPC) return;
|
||||||
|
|
||||||
|
await onceReady;
|
||||||
|
|
||||||
|
arRPC.handleEvent(new MessageEvent("message", { data }));
|
||||||
|
});
|
||||||
|
|
||||||
|
onIpcCommand(IpcCommands.RPC_INVITE, async code => {
|
||||||
|
const { invite } = await InviteActions.resolveInvite(code, "Desktop Modal");
|
||||||
|
if (!invite) return false;
|
||||||
|
|
||||||
|
VesktopNative.win.focus();
|
||||||
|
|
||||||
|
FluxDispatcher.dispatch({
|
||||||
|
type: "INVITE_MODAL_OPEN",
|
||||||
|
invite,
|
||||||
|
code,
|
||||||
|
context: "APP"
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
|
@ -8,12 +8,14 @@ import "./fixes";
|
||||||
import "./appBadge";
|
import "./appBadge";
|
||||||
import "./patches";
|
import "./patches";
|
||||||
import "./themedSplash";
|
import "./themedSplash";
|
||||||
|
import "./ipcCommands";
|
||||||
|
import "./arrpc";
|
||||||
|
|
||||||
console.log("read if cute :3");
|
console.log("read if cute :3");
|
||||||
|
|
||||||
export * as Components from "./components";
|
export * as Components from "./components";
|
||||||
import { findByPropsLazy, onceReady } from "@vencord/types/webpack";
|
import { findByPropsLazy, onceReady } from "@vencord/types/webpack";
|
||||||
import { Alerts, FluxDispatcher } from "@vencord/types/webpack/common";
|
import { Alerts } from "@vencord/types/webpack/common";
|
||||||
|
|
||||||
import SettingsUi from "./components/settings/Settings";
|
import SettingsUi from "./components/settings/Settings";
|
||||||
import { Settings } from "./settings";
|
import { Settings } from "./settings";
|
||||||
|
@ -21,22 +23,6 @@ export { Settings };
|
||||||
|
|
||||||
const InviteActions = findByPropsLazy("resolveInvite");
|
const InviteActions = findByPropsLazy("resolveInvite");
|
||||||
|
|
||||||
export async function openInviteModal(code: string) {
|
|
||||||
const { invite } = await InviteActions.resolveInvite(code, "Desktop Modal");
|
|
||||||
if (!invite) return false;
|
|
||||||
|
|
||||||
VesktopNative.win.focus();
|
|
||||||
|
|
||||||
FluxDispatcher.dispatch({
|
|
||||||
type: "INVITE_MODAL_OPEN",
|
|
||||||
invite,
|
|
||||||
code,
|
|
||||||
context: "APP"
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const customSettingsSections = (
|
const customSettingsSections = (
|
||||||
Vencord.Plugins.plugins.Settings as any as { customSections: ((ID: Record<string, unknown>) => any)[] }
|
Vencord.Plugins.plugins.Settings as any as { customSections: ((ID: Record<string, unknown>) => any)[] }
|
||||||
).customSections;
|
).customSections;
|
||||||
|
@ -48,18 +34,6 @@ customSettingsSections.push(() => ({
|
||||||
className: "vc-vesktop-settings"
|
className: "vc-vesktop-settings"
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const arRPC = Vencord.Plugins.plugins["WebRichPresence (arRPC)"] as any as {
|
|
||||||
handleEvent(e: MessageEvent): void;
|
|
||||||
};
|
|
||||||
|
|
||||||
VesktopNative.arrpc.onActivity(async data => {
|
|
||||||
if (!Settings.store.arRPC) return;
|
|
||||||
|
|
||||||
await onceReady;
|
|
||||||
|
|
||||||
arRPC.handleEvent(new MessageEvent("message", { data }));
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: remove soon
|
// TODO: remove soon
|
||||||
const vencordDir = "vencordDir" as keyof typeof Settings.store;
|
const vencordDir = "vencordDir" as keyof typeof Settings.store;
|
||||||
if (Settings.store[vencordDir]) {
|
if (Settings.store[vencordDir]) {
|
||||||
|
|
49
src/renderer/ipcCommands.ts
Normal file
49
src/renderer/ipcCommands.ts
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0
|
||||||
|
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||||
|
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { SettingsRouter } from "@vencord/types/webpack/common";
|
||||||
|
import { IpcCommands } from "shared/IpcEvents";
|
||||||
|
|
||||||
|
type IpcCommandHandler = (data: any) => any;
|
||||||
|
|
||||||
|
const handlers = new Map<string, IpcCommandHandler>();
|
||||||
|
|
||||||
|
function respond(nonce: string, ok: boolean, data: any) {
|
||||||
|
VesktopNative.commands.respond({ nonce, ok, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
VesktopNative.commands.onCommand(async ({ message, nonce, data }) => {
|
||||||
|
const handler = handlers.get(message);
|
||||||
|
if (!handler) {
|
||||||
|
return respond(nonce, false, `No handler for message: ${message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await handler(data);
|
||||||
|
respond(nonce, true, result);
|
||||||
|
} catch (err) {
|
||||||
|
respond(nonce, false, String(err));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export function onIpcCommand(channel: string, handler: IpcCommandHandler) {
|
||||||
|
if (handlers.has(channel)) {
|
||||||
|
throw new Error(`Handler for message ${channel} already exists`);
|
||||||
|
}
|
||||||
|
|
||||||
|
handlers.set(channel, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function offIpcCommand(channel: string) {
|
||||||
|
handlers.delete(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Generic Handlers */
|
||||||
|
|
||||||
|
onIpcCommand(IpcCommands.NAVIGATE_SETTINGS, () => {
|
||||||
|
SettingsRouter.open("My Account");
|
||||||
|
});
|
||||||
|
onIpcCommand(IpcCommands.GET_LANGUAGES, () => navigator.languages);
|
|
@ -53,5 +53,14 @@ export const enum IpcEvents {
|
||||||
CLIPBOARD_COPY_IMAGE = "VCD_CLIPBOARD_COPY_IMAGE",
|
CLIPBOARD_COPY_IMAGE = "VCD_CLIPBOARD_COPY_IMAGE",
|
||||||
|
|
||||||
DEBUG_LAUNCH_GPU = "VCD_DEBUG_LAUNCH_GPU",
|
DEBUG_LAUNCH_GPU = "VCD_DEBUG_LAUNCH_GPU",
|
||||||
DEBUG_LAUNCH_WEBRTC_INTERNALS = "VCD_DEBUG_LAUNCH_WEBRTC"
|
DEBUG_LAUNCH_WEBRTC_INTERNALS = "VCD_DEBUG_LAUNCH_WEBRTC",
|
||||||
|
|
||||||
|
IPC_COMMAND = "VCD_IPC_COMMAND"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const enum IpcCommands {
|
||||||
|
RPC_ACTIVITY = "rpc:activity",
|
||||||
|
RPC_INVITE = "rpc:invite",
|
||||||
|
NAVIGATE_SETTINGS = "navigate:settings",
|
||||||
|
GET_LANGUAGES = "navigator.languages"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue