-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement backend to electron IPC channels #10698
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
/******************************************************************************** | ||
* Copyright (C) 2022 TypeFox and others. | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Eclipse Public License v. 2.0 which is available at | ||
* http://www.eclipse.org/legal/epl-2.0. | ||
* | ||
* This Source Code may also be made available under the following Secondary | ||
* Licenses when the conditions for such availability set forth in the Eclipse | ||
* Public License v. 2.0 are satisfied: GNU General Public License, version 2 | ||
* with the GNU Classpath Exception which is available at | ||
* https://www.gnu.org/software/classpath/license.html. | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 | ||
********************************************************************************/ | ||
|
||
export const TestConnection = Symbol('TestConnection'); | ||
|
||
export const TEST_CONNECTION_PATH = '/services/test-connection'; | ||
|
||
export interface TestConnection { | ||
runTest(): Promise<string[]>; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
/******************************************************************************** | ||
* Copyright (C) 2022 TypeFox and others. | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Eclipse Public License v. 2.0 which is available at | ||
* http://www.eclipse.org/legal/epl-2.0. | ||
* | ||
* This Source Code may also be made available under the following Secondary | ||
* Licenses when the conditions for such availability set forth in the Eclipse | ||
* Public License v. 2.0 are satisfied: GNU General Public License, version 2 | ||
* with the GNU Classpath Exception which is available at | ||
* https://www.gnu.org/software/classpath/license.html. | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 | ||
********************************************************************************/ | ||
|
||
import { ConnectionHandler } from '../../common/messaging/handler'; | ||
|
||
/** | ||
* Name of the channel used with `process.on/message`. | ||
*/ | ||
export const THEIA_ELECTRON_BACKEND_IPC_CHANNEL_NAME = 'theia-electron-backend-ipc'; | ||
|
||
export interface ElectronBackendMessage { | ||
[THEIA_ELECTRON_BACKEND_IPC_CHANNEL_NAME]: string | ||
} | ||
|
||
export namespace ElectronBackendMessage { | ||
export function is(message: unknown): message is ElectronBackendMessage { | ||
return typeof message === 'object' && !!message && THEIA_ELECTRON_BACKEND_IPC_CHANNEL_NAME in message; | ||
} | ||
export function get(message: ElectronBackendMessage): string { | ||
return message[THEIA_ELECTRON_BACKEND_IPC_CHANNEL_NAME]; | ||
} | ||
export function create(data: string): ElectronBackendMessage { | ||
return { [THEIA_ELECTRON_BACKEND_IPC_CHANNEL_NAME]: data }; | ||
} | ||
} | ||
|
||
/** | ||
* A class capable of piping messaging data from the backend server to the electron main application. | ||
* This should only be used when the app runs in `--no-cluster` mode, since we can't use normal inter-process communication. | ||
*/ | ||
export class ElectronBackendMessagePipe { | ||
|
||
protected electronHandler?: (data: string) => void; | ||
protected backendHandler?: (data: string) => void; | ||
|
||
onMessage(from: 'backend' | 'electron', handler: (data: string) => void): boolean { | ||
if (from === 'backend') { | ||
this.electronHandler = handler; | ||
return !!this.backendHandler; | ||
} else { | ||
this.backendHandler = handler; | ||
return !!this.electronHandler; | ||
} | ||
} | ||
|
||
pushMessage(to: 'backend' | 'electron', data: string): boolean { | ||
if (to === 'backend') { | ||
this.electronHandler?.(data); | ||
return !!this.electronHandler; | ||
} else { | ||
this.backendHandler?.(data); | ||
return !!this.backendHandler; | ||
} | ||
} | ||
|
||
} | ||
|
||
export const ElectronBackendConnectionPipe = new ElectronBackendMessagePipe(); | ||
|
||
/** | ||
* IPC-specific connection handler. | ||
* Use this if you want to establish communication from the electron-main to the backend process. | ||
*/ | ||
export const ElectronMainConnectionHandler = Symbol('ElectronBackendConnectionHandler'); | ||
|
||
export interface ElectronMainConnectionHandler extends ConnectionHandler { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/******************************************************************************** | ||
* Copyright (C) 2022 TypeFox and others. | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Eclipse Public License v. 2.0 which is available at | ||
* http://www.eclipse.org/legal/epl-2.0. | ||
* | ||
* This Source Code may also be made available under the following Secondary | ||
* Licenses when the conditions for such availability set forth in the Eclipse | ||
* Public License v. 2.0 are satisfied: GNU General Public License, version 2 | ||
* with the GNU Classpath Exception which is available at | ||
* https://www.gnu.org/software/classpath/license.html. | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 | ||
********************************************************************************/ | ||
|
||
import { TestConnection } from '../electron-common/electron-test-connection'; | ||
import { BrowserWindow } from '@theia/electron/shared/electron'; | ||
import { injectable } from 'inversify'; | ||
|
||
@injectable() | ||
export class TestConnectionImpl implements TestConnection { | ||
async runTest(): Promise<string[]> { | ||
const titles = BrowserWindow.getAllWindows().map(e => e.getTitle()); | ||
return titles; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
/******************************************************************************** | ||
* Copyright (C) 2022 TypeFox and others. | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Eclipse Public License v. 2.0 which is available at | ||
* http://www.eclipse.org/legal/epl-2.0. | ||
* | ||
* This Source Code may also be made available under the following Secondary | ||
* Licenses when the conditions for such availability set forth in the Eclipse | ||
* Public License v. 2.0 are satisfied: GNU General Public License, version 2 | ||
* with the GNU Classpath Exception which is available at | ||
* https://www.gnu.org/software/classpath/license.html. | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 | ||
********************************************************************************/ | ||
|
||
/* eslint-disable @typescript-eslint/no-explicit-any */ | ||
|
||
import { inject, injectable, named } from 'inversify'; | ||
import { createWebSocketConnection } from 'vscode-ws-jsonrpc/lib/socket/connection'; | ||
import { ContributionProvider } from '../../common/contribution-provider'; | ||
import { WebSocketChannel } from '../../common/messaging/web-socket-channel'; | ||
import { ConsoleLogger } from '../../node/messaging/logger'; | ||
import { ElectronMainConnectionHandler, ElectronBackendMessage, ElectronBackendConnectionPipe } from '../../electron-common/messaging/electron-backend-connection-handler'; | ||
import { MessagingContribution } from '../../node/messaging/messaging-contribution'; | ||
import { ChildProcess } from 'child_process'; | ||
|
||
export interface ElectronMainConnectionOptions { | ||
} | ||
|
||
/** | ||
* This component replicates the role filled by `MessagingContribution` but for connecting the backend with the electron process. | ||
* It is based on process communication using the created backend process. | ||
* Alternatively it uses a simple message pipe object when running in `--no-cluster` mode. | ||
* | ||
* This component allows communication between server process (backend) and electron main process. | ||
*/ | ||
@injectable() | ||
export class ElectronMainMessagingService { | ||
|
||
@inject(ContributionProvider) @named(ElectronMainConnectionHandler) | ||
protected readonly connectionHandlers: ContributionProvider<ElectronMainConnectionHandler>; | ||
|
||
protected readonly channelHandlers = new MessagingContribution.ConnectionHandlers<WebSocketChannel>(); | ||
protected readonly channels = new Map<number, WebSocketChannel>(); | ||
protected backendProcess?: ChildProcess; | ||
|
||
start(backendProcess?: ChildProcess): void { | ||
if (backendProcess) { | ||
this.backendProcess = backendProcess; | ||
this.backendProcess.on('message', message => { | ||
if (ElectronBackendMessage.is(message)) { | ||
this.handleMessage(ElectronBackendMessage.get(message)); | ||
} | ||
}); | ||
this.backendProcess.on('exit', () => { | ||
this.closeChannels(); | ||
}); | ||
} else { | ||
ElectronBackendConnectionPipe.onMessage('electron', message => { | ||
this.handleMessage(message); | ||
}); | ||
} | ||
for (const connectionHandler of this.connectionHandlers.getContributions()) { | ||
this.channelHandlers.push(connectionHandler.path, (params, channel) => { | ||
const connection = createWebSocketConnection(channel, new ConsoleLogger()); | ||
connectionHandler.onConnection(connection); | ||
}); | ||
} | ||
} | ||
|
||
protected closeChannels(): void { | ||
for (const channel of Array.from(this.channels.values())) { | ||
channel.close(undefined, 'Backend exited'); | ||
} | ||
this.channels.clear(); | ||
} | ||
|
||
protected handleMessage(data: string): void { | ||
try { | ||
// Start parsing the message to extract the channel id and route | ||
const message: WebSocketChannel.Message = JSON.parse(data.toString()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One point of the discussion re: performance in our communication layer is that conversions back and forth from string to JSON are relatively expensive. Will all messages be passing through here, or only some, or how will it fit into the refactor of the messaging layer currently underway? Is it expected that this will be one terminus of the messaging system that can delegate the parsed messages to handlers, or will further parsing be necessary? |
||
// Someone wants to open a logical channel | ||
if (message.kind === 'open') { | ||
const { id, path } = message; | ||
const channel = this.createChannel(id); | ||
if (this.channelHandlers.route(path, channel)) { | ||
channel.ready(); | ||
this.channels.set(id, channel); | ||
channel.onClose(() => this.channels.delete(id)); | ||
} else { | ||
console.error('Cannot find a service for the path: ' + path); | ||
} | ||
} else { | ||
const { id } = message; | ||
const channel = this.channels.get(id); | ||
if (channel) { | ||
channel.handleMessage(message); | ||
} else { | ||
console.error('The ipc channel does not exist', id); | ||
} | ||
} | ||
} catch (error) { | ||
console.error('IPC: Failed to handle message', { error, data }); | ||
} | ||
} | ||
|
||
protected createChannel(id: number): WebSocketChannel { | ||
return new WebSocketChannel(id, content => { | ||
if (this.backendProcess) { | ||
if (this.backendProcess.send) { | ||
this.backendProcess.send(ElectronBackendMessage.create(content)); | ||
} | ||
} else { | ||
ElectronBackendConnectionPipe.pushMessage('backend', content); | ||
} | ||
}); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/******************************************************************************** | ||
* Copyright (C) 2022 TypeFox and others. | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Eclipse Public License v. 2.0 which is available at | ||
* http://www.eclipse.org/legal/epl-2.0. | ||
* | ||
* This Source Code may also be made available under the following Secondary | ||
* Licenses when the conditions for such availability set forth in the Eclipse | ||
* Public License v. 2.0 are satisfied: GNU General Public License, version 2 | ||
* with the GNU Classpath Exception which is available at | ||
* https://www.gnu.org/software/classpath/license.html. | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 | ||
********************************************************************************/ | ||
|
||
import { ContainerModule } from 'inversify'; | ||
import { TestConnection, TEST_CONNECTION_PATH } from '../../electron-common/electron-test-connection'; | ||
import { ElectronBackendConnectionProvider } from './electron-backend-connection-provider'; | ||
|
||
export default new ContainerModule(bind => { | ||
bind(ElectronBackendConnectionProvider).toSelf().inSingletonScope(); | ||
bind(TestConnection).toDynamicValue(context => | ||
ElectronBackendConnectionProvider.createProxy(context.container, TEST_CONNECTION_PATH, | ||
() => context.container.get(TestConnection)) | ||
).inSingletonScope(); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should already be iterable.