diff --git a/packages/core/src/main/spawn-electron.ts b/packages/core/src/main/spawn-electron.ts index 18d3794fd98..c277932cdf2 100644 --- a/packages/core/src/main/spawn-electron.ts +++ b/packages/core/src/main/spawn-electron.ts @@ -29,6 +29,7 @@ import { import windowDefaults, { popupWindowDefaults } from '../webapp/defaults' import ISubwindowPrefs from '../models/SubwindowPrefs' +import { webpackPath } from '../plugins/path' /** * Keep a global reference of the window object, if you don't, the window will @@ -442,10 +443,10 @@ export function createWindow( debug('invoke', message) try { - const mod = await import(message.module) + const mod = await import('@kui-shell/plugin-' + webpackPath(message.module) + '/dist/index.js') debug('invoke got module') - const returnValue = await mod[message.main || 'main'](message.args) + const returnValue = await mod[message.main || 'main'](message.args, event.sender) debug('invoke got returnValue', returnValue) event.sender.send( @@ -600,9 +601,6 @@ export async function initElectron( debug('loading electron') const Electron = await import('electron') app = Electron.app - if (app) { - app.allowRendererProcessReuse = false - } } // deal with multiple processes diff --git a/plugins/plugin-bash-like/src/index.ts b/plugins/plugin-bash-like/src/index.ts index 6f0c1625525..539885cc0e0 100644 --- a/plugins/plugin-bash-like/src/index.ts +++ b/plugins/plugin-bash-like/src/index.ts @@ -20,3 +20,5 @@ export { StdioChannelWebsocketSide } from './pty/stdio-channel' export { getSessionForTab } from './pty/session' export { dispatchToShell as doExecWithPty, doExecWithStdoutViaPty } from './lib/cmds/catchall' export { getTabState } from './tab-state' + +export { initMainPty } from './pty/electron-main-channel' diff --git a/plugins/plugin-bash-like/src/pty/client.ts b/plugins/plugin-bash-like/src/pty/client.ts index 880cb5830e8..c26a57022a5 100644 --- a/plugins/plugin-bash-like/src/pty/client.ts +++ b/plugins/plugin-bash-like/src/pty/client.ts @@ -39,7 +39,8 @@ import Options from './options' import ChannelId from './channel-id' import { cleanupTerminalAfterTermination } from './util' import { getChannelForTab, setChannelForTab, invalidateSession } from './session' -import { Channel, InProcessChannel, WebViewChannelRendererSide } from './channel' +import { Channel, WebViewChannelRendererSide } from './channel' +import ElectronRendererSideChannel from './electron-main-channel' const debug = Debug('plugins/bash-like/pty/client') @@ -682,7 +683,7 @@ const injectFont = (terminal: XTerminal, flush = false) => { } } -type ChannelFactory = (tab: Tab) => Promise +type ChannelFactory = (tab: Tab, execUUID: string) => Promise /** * Create a websocket channel to a remote bash @@ -714,9 +715,9 @@ const remoteChannelFactory: ChannelFactory = async (tab: Tab) => { } } -const electronChannelFactory: ChannelFactory = async () => { - const channel = new InProcessChannel() - channel.init() +const electronChannelFactory: ChannelFactory = async (tab: Tab, execUUID: string) => { + const channel = new ElectronRendererSideChannel() + await channel.init(execUUID) return channel } diff --git a/plugins/plugin-bash-like/src/pty/electron-main-channel.ts b/plugins/plugin-bash-like/src/pty/electron-main-channel.ts new file mode 100644 index 00000000000..ac6c0605c17 --- /dev/null +++ b/plugins/plugin-bash-like/src/pty/electron-main-channel.ts @@ -0,0 +1,155 @@ +/* + * Copyright 2021 The Kubernetes Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This file defines a channel between the Electron renderer process + * and Electron main process. + * + */ + +import Debug from 'debug' +import { EventEmitter } from 'events' +import { IpcMain, IpcRenderer, WebContents } from 'electron' + +import { Channel, ReadyState } from './channel' +import { onConnection, disableBashSessions } from './server' + +interface Args { + execUUID: string +} + +const debugMain = Debug('plugin-bash-like/pty/electron/main') +const debugRenderer = Debug('plugin-bash-like/pty/electron/renderer') + +function messageChannel(execUUID) { + return `/plugin-bash-like/pty/message/${execUUID}` +} + +class ElectronMainSideChannel extends EventEmitter implements Channel { + /** is the channel alive? */ + public readonly isAlive = true + + public readonly readyState = ReadyState.OPEN + + private ipcMain: IpcMain + private otherSide: WebContents + + private messageChannel: string + + private handleMessage = (_, msg: string) => { + // debugMain('got message', msg) + this.emit('message', msg) + } + + public async init(args: Args, otherSide: WebContents) { + debugMain('main side init', args.execUUID) + + const { ipcMain } = await import('electron') + this.ipcMain = ipcMain + this.otherSide = otherSide + + this.messageChannel = messageChannel(args.execUUID) + ipcMain.on(this.messageChannel, this.handleMessage) + + await onConnection(await disableBashSessions())(this) + } + + /** Forcibly close the channel */ + public close() { + this.emit('exit') + this.ipcMain.off(this.messageChannel, this.handleMessage) + } + + /** Send a message over the channel */ + public send(msg: string | Buffer) { + this.otherSide.send(this.messageChannel, msg) + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + public removeEventListener(eventType: string, handler: any) { + this.off(eventType, handler) + } +} + +export default class ElectronRendererSideChannel extends EventEmitter implements Channel { + /** is the channel alive? */ + public readonly isAlive = true + + public readonly readyState = ReadyState.OPEN + + private ipcRenderer: IpcRenderer + private messageChannel: string + + private handleMessage = (_, msg: string) => { + this.emit('message', msg) + } + + public async init(execUUID: string) { + const { ipcRenderer } = await import('electron') + this.ipcRenderer = ipcRenderer + debugRenderer('renderer side init') + + this.messageChannel = messageChannel(execUUID) + + ipcRenderer.once(`/exec/response/${execUUID}`, (_, _msg: string) => { + const msg = JSON.parse(_msg) + debugRenderer('renderer side init exec response', msg) + if (msg.returnValue === true) { + this.ipcRenderer.on(this.messageChannel, this.handleMessage) + this.emit('open') + } + }) + + ipcRenderer.send( + '/exec/invoke', + JSON.stringify({ + module: 'plugin-bash-like', + main: 'initMainPty', + hash: execUUID, + args: { + execUUID + } + }) + ) + } + + /** Forcibly close the channel */ + public close() { + this.send('exit') + this.ipcRenderer.off(this.messageChannel, this.handleMessage) + } + + /** Send a message over the channel */ + public send(msg: string | Buffer) { + this.ipcRenderer.send(this.messageChannel, msg.toString()) + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + public removeEventListener(eventType: string, handler: any) { + this.off(eventType, handler) + } +} + +export async function initMainPty(args: Args, otherSide: WebContents) { + debugMain('initMainPty', args) + try { + await new ElectronMainSideChannel().init(args, otherSide) + return true + } catch (err) { + console.error('Error starting up pty', err) + return false + } +}