Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/base/parts/ipc/electron-main/ipcMain.ts
4780 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import electron from 'electron';
7
import { onUnexpectedError } from '../../../common/errors.js';
8
import { Event } from '../../../common/event.js';
9
import { VSCODE_AUTHORITY } from '../../../common/network.js';
10
11
type ipcMainListener = (event: electron.IpcMainEvent, ...args: any[]) => void;
12
13
class ValidatedIpcMain implements Event.NodeEventEmitter {
14
15
// We need to keep a map of original listener to the wrapped variant in order
16
// to properly implement `removeListener`. We use a `WeakMap` because we do
17
// not want to prevent the `key` of the map to get garbage collected.
18
private readonly mapListenerToWrapper = new WeakMap<ipcMainListener, ipcMainListener>();
19
20
/**
21
* Listens to `channel`, when a new message arrives `listener` would be called with
22
* `listener(event, args...)`.
23
*/
24
on(channel: string, listener: ipcMainListener): this {
25
26
// Remember the wrapped listener so that later we can
27
// properly implement `removeListener`.
28
const wrappedListener = (event: electron.IpcMainEvent, ...args: any[]) => {
29
if (this.validateEvent(channel, event)) {
30
listener(event, ...args);
31
}
32
};
33
34
this.mapListenerToWrapper.set(listener, wrappedListener);
35
36
electron.ipcMain.on(channel, wrappedListener);
37
38
return this;
39
}
40
41
/**
42
* Adds a one time `listener` function for the event. This `listener` is invoked
43
* only the next time a message is sent to `channel`, after which it is removed.
44
*/
45
once(channel: string, listener: ipcMainListener): this {
46
electron.ipcMain.once(channel, (event: electron.IpcMainEvent, ...args: any[]) => {
47
if (this.validateEvent(channel, event)) {
48
listener(event, ...args);
49
}
50
});
51
52
return this;
53
}
54
55
/**
56
* Adds a handler for an `invoke`able IPC. This handler will be called whenever a
57
* renderer calls `ipcRenderer.invoke(channel, ...args)`.
58
*
59
* If `listener` returns a Promise, the eventual result of the promise will be
60
* returned as a reply to the remote caller. Otherwise, the return value of the
61
* listener will be used as the value of the reply.
62
*
63
* The `event` that is passed as the first argument to the handler is the same as
64
* that passed to a regular event listener. It includes information about which
65
* WebContents is the source of the invoke request.
66
*
67
* Errors thrown through `handle` in the main process are not transparent as they
68
* are serialized and only the `message` property from the original error is
69
* provided to the renderer process. Please refer to #24427 for details.
70
*/
71
handle(channel: string, listener: (event: electron.IpcMainInvokeEvent, ...args: any[]) => Promise<unknown>): this {
72
electron.ipcMain.handle(channel, (event: electron.IpcMainInvokeEvent, ...args: any[]) => {
73
if (this.validateEvent(channel, event)) {
74
return listener(event, ...args);
75
}
76
77
return Promise.reject(`Invalid channel '${channel}' or sender for ipcMain.handle() usage.`);
78
});
79
80
return this;
81
}
82
83
/**
84
* Removes any handler for `channel`, if present.
85
*/
86
removeHandler(channel: string): this {
87
electron.ipcMain.removeHandler(channel);
88
89
return this;
90
}
91
92
/**
93
* Removes the specified `listener` from the listener array for the specified
94
* `channel`.
95
*/
96
removeListener(channel: string, listener: ipcMainListener): this {
97
const wrappedListener = this.mapListenerToWrapper.get(listener);
98
if (wrappedListener) {
99
electron.ipcMain.removeListener(channel, wrappedListener);
100
this.mapListenerToWrapper.delete(listener);
101
}
102
103
return this;
104
}
105
106
private validateEvent(channel: string, event: electron.IpcMainEvent | electron.IpcMainInvokeEvent): boolean {
107
if (!channel?.startsWith('vscode:')) {
108
onUnexpectedError(`Refused to handle ipcMain event for channel '${channel}' because the channel is unknown.`);
109
return false; // unexpected channel
110
}
111
112
const sender = event.senderFrame;
113
114
const url = sender?.url;
115
// `url` can be `undefined` when running tests from playwright https://github.com/microsoft/vscode/issues/147301
116
// and `url` can be `about:blank` when reloading the window
117
// from performance tab of devtools https://github.com/electron/electron/issues/39427.
118
// It is fine to skip the checks in these cases.
119
if (!url || url === 'about:blank') {
120
return true;
121
}
122
123
let host = 'unknown';
124
try {
125
host = new URL(url).host;
126
} catch (error) {
127
onUnexpectedError(`Refused to handle ipcMain event for channel '${channel}' because of a malformed URL '${url}'.`);
128
return false; // unexpected URL
129
}
130
131
if (process.env.VSCODE_DEV) {
132
if (url === process.env.DEV_WINDOW_SRC && (host === 'localhost' || host.startsWith('localhost:'))) {
133
return true; // development support where the window is served from localhost
134
}
135
}
136
137
if (host !== VSCODE_AUTHORITY) {
138
onUnexpectedError(`Refused to handle ipcMain event for channel '${channel}' because of a bad origin of '${host}'.`);
139
return false; // unexpected sender
140
}
141
142
if (sender?.parent !== null) {
143
onUnexpectedError(`Refused to handle ipcMain event for channel '${channel}' because sender of origin '${host}' is not a main frame.`);
144
return false; // unexpected frame
145
}
146
147
return true;
148
}
149
}
150
151
/**
152
* A drop-in replacement of `ipcMain` that validates the sender of a message
153
* according to https://github.com/electron/electron/blob/main/docs/tutorial/security.md
154
*
155
* @deprecated direct use of Electron IPC is not encouraged. We have utilities in place
156
* to create services on top of IPC, see `ProxyChannel` for more information.
157
*/
158
export const validatedIpcMain = new ValidatedIpcMain();
159
160