Skip to content

Commit

Permalink
chore: Stop using node-pty in Electron renderer process
Browse files Browse the repository at this point in the history
  • Loading branch information
starpit committed May 28, 2021
1 parent 5c339e9 commit c350dbd
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 10 deletions.
8 changes: 3 additions & 5 deletions packages/core/src/main/spawn-electron.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions plugins/plugin-bash-like/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
11 changes: 6 additions & 5 deletions plugins/plugin-bash-like/src/pty/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down Expand Up @@ -682,7 +683,7 @@ const injectFont = (terminal: XTerminal, flush = false) => {
}
}

type ChannelFactory = (tab: Tab) => Promise<Channel>
type ChannelFactory = (tab: Tab, execUUID: string) => Promise<Channel>

/**
* Create a websocket channel to a remote bash
Expand Down Expand Up @@ -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
}

Expand Down
155 changes: 155 additions & 0 deletions plugins/plugin-bash-like/src/pty/electron-main-channel.ts
Original file line number Diff line number Diff line change
@@ -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
}
}

0 comments on commit c350dbd

Please sign in to comment.