Path: blob/main/src/vs/base/parts/ipc/electron-main/ipcMain.ts
4780 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import electron from 'electron';6import { onUnexpectedError } from '../../../common/errors.js';7import { Event } from '../../../common/event.js';8import { VSCODE_AUTHORITY } from '../../../common/network.js';910type ipcMainListener = (event: electron.IpcMainEvent, ...args: any[]) => void;1112class ValidatedIpcMain implements Event.NodeEventEmitter {1314// We need to keep a map of original listener to the wrapped variant in order15// to properly implement `removeListener`. We use a `WeakMap` because we do16// not want to prevent the `key` of the map to get garbage collected.17private readonly mapListenerToWrapper = new WeakMap<ipcMainListener, ipcMainListener>();1819/**20* Listens to `channel`, when a new message arrives `listener` would be called with21* `listener(event, args...)`.22*/23on(channel: string, listener: ipcMainListener): this {2425// Remember the wrapped listener so that later we can26// properly implement `removeListener`.27const wrappedListener = (event: electron.IpcMainEvent, ...args: any[]) => {28if (this.validateEvent(channel, event)) {29listener(event, ...args);30}31};3233this.mapListenerToWrapper.set(listener, wrappedListener);3435electron.ipcMain.on(channel, wrappedListener);3637return this;38}3940/**41* Adds a one time `listener` function for the event. This `listener` is invoked42* only the next time a message is sent to `channel`, after which it is removed.43*/44once(channel: string, listener: ipcMainListener): this {45electron.ipcMain.once(channel, (event: electron.IpcMainEvent, ...args: any[]) => {46if (this.validateEvent(channel, event)) {47listener(event, ...args);48}49});5051return this;52}5354/**55* Adds a handler for an `invoke`able IPC. This handler will be called whenever a56* renderer calls `ipcRenderer.invoke(channel, ...args)`.57*58* If `listener` returns a Promise, the eventual result of the promise will be59* returned as a reply to the remote caller. Otherwise, the return value of the60* listener will be used as the value of the reply.61*62* The `event` that is passed as the first argument to the handler is the same as63* that passed to a regular event listener. It includes information about which64* WebContents is the source of the invoke request.65*66* Errors thrown through `handle` in the main process are not transparent as they67* are serialized and only the `message` property from the original error is68* provided to the renderer process. Please refer to #24427 for details.69*/70handle(channel: string, listener: (event: electron.IpcMainInvokeEvent, ...args: any[]) => Promise<unknown>): this {71electron.ipcMain.handle(channel, (event: electron.IpcMainInvokeEvent, ...args: any[]) => {72if (this.validateEvent(channel, event)) {73return listener(event, ...args);74}7576return Promise.reject(`Invalid channel '${channel}' or sender for ipcMain.handle() usage.`);77});7879return this;80}8182/**83* Removes any handler for `channel`, if present.84*/85removeHandler(channel: string): this {86electron.ipcMain.removeHandler(channel);8788return this;89}9091/**92* Removes the specified `listener` from the listener array for the specified93* `channel`.94*/95removeListener(channel: string, listener: ipcMainListener): this {96const wrappedListener = this.mapListenerToWrapper.get(listener);97if (wrappedListener) {98electron.ipcMain.removeListener(channel, wrappedListener);99this.mapListenerToWrapper.delete(listener);100}101102return this;103}104105private validateEvent(channel: string, event: electron.IpcMainEvent | electron.IpcMainInvokeEvent): boolean {106if (!channel?.startsWith('vscode:')) {107onUnexpectedError(`Refused to handle ipcMain event for channel '${channel}' because the channel is unknown.`);108return false; // unexpected channel109}110111const sender = event.senderFrame;112113const url = sender?.url;114// `url` can be `undefined` when running tests from playwright https://github.com/microsoft/vscode/issues/147301115// and `url` can be `about:blank` when reloading the window116// from performance tab of devtools https://github.com/electron/electron/issues/39427.117// It is fine to skip the checks in these cases.118if (!url || url === 'about:blank') {119return true;120}121122let host = 'unknown';123try {124host = new URL(url).host;125} catch (error) {126onUnexpectedError(`Refused to handle ipcMain event for channel '${channel}' because of a malformed URL '${url}'.`);127return false; // unexpected URL128}129130if (process.env.VSCODE_DEV) {131if (url === process.env.DEV_WINDOW_SRC && (host === 'localhost' || host.startsWith('localhost:'))) {132return true; // development support where the window is served from localhost133}134}135136if (host !== VSCODE_AUTHORITY) {137onUnexpectedError(`Refused to handle ipcMain event for channel '${channel}' because of a bad origin of '${host}'.`);138return false; // unexpected sender139}140141if (sender?.parent !== null) {142onUnexpectedError(`Refused to handle ipcMain event for channel '${channel}' because sender of origin '${host}' is not a main frame.`);143return false; // unexpected frame144}145146return true;147}148}149150/**151* A drop-in replacement of `ipcMain` that validates the sender of a message152* according to https://github.com/electron/electron/blob/main/docs/tutorial/security.md153*154* @deprecated direct use of Electron IPC is not encouraged. We have utilities in place155* to create services on top of IPC, see `ProxyChannel` for more information.156*/157export const validatedIpcMain = new ValidatedIpcMain();158159160