Skip to content

Commit 744453b

Browse files
committed
chore: Stop using node-pty in Electron renderer process
part of kubernetes-sigs#7456
1 parent f706ad0 commit 744453b

File tree

4 files changed

+170
-12
lines changed

4 files changed

+170
-12
lines changed

packages/core/src/main/spawn-electron.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929

3030
import windowDefaults, { popupWindowDefaults } from '../webapp/defaults'
3131
import ISubwindowPrefs from '../models/SubwindowPrefs'
32+
import { webpackPath } from '../plugins/path'
3233

3334
/**
3435
* Keep a global reference of the window object, if you don't, the window will
@@ -442,10 +443,10 @@ export function createWindow(
442443
debug('invoke', message)
443444

444445
try {
445-
const mod = await import(message.module)
446+
const mod = await import('@kui-shell/plugin-' + webpackPath(message.module) + '/dist/index.js')
446447
debug('invoke got module')
447448

448-
const returnValue = await mod[message.main || 'main'](message.args)
449+
const returnValue = await mod[message.main || 'main'](message.args, event.sender)
449450
debug('invoke got returnValue', returnValue)
450451

451452
event.sender.send(
@@ -600,9 +601,6 @@ export async function initElectron(
600601
debug('loading electron')
601602
const Electron = await import('electron')
602603
app = Electron.app
603-
if (app) {
604-
app.allowRendererProcessReuse = false
605-
}
606604
}
607605

608606
// deal with multiple processes

plugins/plugin-bash-like/src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,5 @@ export { StdioChannelWebsocketSide } from './pty/stdio-channel'
2020
export { getSessionForTab } from './pty/session'
2121
export { dispatchToShell as doExecWithPty, doExecWithStdoutViaPty } from './lib/cmds/catchall'
2222
export { getTabState } from './tab-state'
23+
24+
export { initMainPty } from './pty/electron-main-channel'

plugins/plugin-bash-like/src/pty/client.ts

+10-7
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ import Options from './options'
3939
import ChannelId from './channel-id'
4040
import { cleanupTerminalAfterTermination } from './util'
4141
import { getChannelForTab, invalidateSession } from './session'
42-
import { Channel, InProcessChannel, WebViewChannelRendererSide } from './channel'
42+
import { Channel, WebViewChannelRendererSide } from './channel'
43+
import ElectronRendererSideChannel from './electron-main-channel'
4344

4445
const debug = Debug('plugins/bash-like/pty/client')
4546

@@ -679,7 +680,7 @@ const injectFont = (terminal: XTerminal, flush = false) => {
679680
}
680681
}
681682

682-
type ChannelFactory = (tab: Tab) => Promise<Channel>
683+
type ChannelFactory = (tab: Tab, execUUID: string) => Promise<Channel>
683684

684685
/**
685686
* Create a websocket channel to a remote bash
@@ -711,9 +712,9 @@ const remoteChannelFactory: ChannelFactory = async (tab: Tab) => {
711712
}
712713
}
713714

714-
const electronChannelFactory: ChannelFactory = async () => {
715-
const channel = new InProcessChannel()
716-
channel.init()
715+
const electronChannelFactory: ChannelFactory = async (tab: Tab, execUUID: string) => {
716+
const channel = new ElectronRendererSideChannel()
717+
await channel.init(execUUID)
717718
return channel
718719
}
719720

@@ -772,8 +773,10 @@ const getOrCreateChannel = async (
772773

773774
if (!cachedws || cachedws.readyState === WebSocket.CLOSING || cachedws.readyState === WebSocket.CLOSED) {
774775
// allocating new channel
775-
const ws = await channelFactory(tab)
776-
tab['ws'] = ws
776+
const ws = await channelFactory(tab, uuid)
777+
if (inBrowser()) {
778+
tab['ws'] = ws
779+
}
777780

778781
// when the websocket is ready, handle any queued input; only then
779782
// do we focus the terminal (till then, the CLI module will handle
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/*
2+
* Copyright 2021 The Kubernetes Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* This file defines a channel between the Electron renderer process
19+
* and Electron main process.
20+
*
21+
*/
22+
23+
import Debug from 'debug'
24+
import { EventEmitter } from 'events'
25+
import { IpcMain, IpcRenderer, WebContents } from 'electron'
26+
27+
import { Channel, ReadyState } from './channel'
28+
import { onConnection, disableBashSessions } from './server'
29+
30+
interface Args {
31+
execUUID: string
32+
}
33+
34+
const debugMain = Debug('plugin-bash-like/pty/electron/main')
35+
const debugRenderer = Debug('plugin-bash-like/pty/electron/renderer')
36+
37+
function messageChannel(execUUID) {
38+
return `/plugin-bash-like/pty/message/${execUUID}`
39+
}
40+
41+
class ElectronMainSideChannel extends EventEmitter implements Channel {
42+
/** is the channel alive? */
43+
public readonly isAlive = true
44+
45+
public readonly readyState = ReadyState.OPEN
46+
47+
private ipcMain: IpcMain
48+
private otherSide: WebContents
49+
50+
private messageChannel: string
51+
52+
private handleMessage = (_, msg: string) => {
53+
// debugMain('got message', msg)
54+
this.emit('message', msg)
55+
}
56+
57+
public async init(args: Args, otherSide: WebContents) {
58+
debugMain('main side init', args.execUUID)
59+
60+
const { ipcMain } = await import('electron')
61+
this.ipcMain = ipcMain
62+
this.otherSide = otherSide
63+
64+
this.messageChannel = messageChannel(args.execUUID)
65+
ipcMain.on(this.messageChannel, this.handleMessage)
66+
67+
await onConnection(await disableBashSessions())(this)
68+
}
69+
70+
/** Forcibly close the channel */
71+
public close() {
72+
this.emit('exit')
73+
this.ipcMain.off(this.messageChannel, this.handleMessage)
74+
}
75+
76+
/** Send a message over the channel */
77+
public send(msg: string | Buffer) {
78+
this.otherSide.send(this.messageChannel, msg)
79+
}
80+
81+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
82+
public removeEventListener(eventType: string, handler: any) {
83+
this.off(eventType, handler)
84+
}
85+
}
86+
87+
export default class ElectronRendererSideChannel extends EventEmitter implements Channel {
88+
/** is the channel alive? */
89+
public readonly isAlive = true
90+
91+
public readonly readyState = ReadyState.OPEN
92+
93+
private ipcRenderer: IpcRenderer
94+
private messageChannel: string
95+
96+
private handleMessage = (_, msg: string) => {
97+
this.emit('message', msg)
98+
}
99+
100+
public async init(execUUID: string) {
101+
const { ipcRenderer } = await import('electron')
102+
this.ipcRenderer = ipcRenderer
103+
debugRenderer('renderer side init')
104+
105+
this.messageChannel = messageChannel(execUUID)
106+
107+
ipcRenderer.once(`/exec/response/${execUUID}`, (_, _msg: string) => {
108+
const msg = JSON.parse(_msg)
109+
debugRenderer('renderer side init exec response', msg)
110+
if (msg.returnValue === true) {
111+
this.ipcRenderer.on(this.messageChannel, this.handleMessage)
112+
this.emit('open')
113+
}
114+
})
115+
116+
ipcRenderer.send(
117+
'/exec/invoke',
118+
JSON.stringify({
119+
module: 'plugin-bash-like',
120+
main: 'initMainPty',
121+
hash: execUUID,
122+
args: {
123+
execUUID
124+
}
125+
})
126+
)
127+
}
128+
129+
/** Forcibly close the channel */
130+
public close() {
131+
this.send('exit')
132+
this.ipcRenderer.off(this.messageChannel, this.handleMessage)
133+
}
134+
135+
/** Send a message over the channel */
136+
public send(msg: string | Buffer) {
137+
this.ipcRenderer.send(this.messageChannel, msg.toString())
138+
}
139+
140+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
141+
public removeEventListener(eventType: string, handler: any) {
142+
this.off(eventType, handler)
143+
}
144+
}
145+
146+
export async function initMainPty(args: Args, otherSide: WebContents) {
147+
debugMain('initMainPty', args)
148+
try {
149+
await new ElectronMainSideChannel().init(args, otherSide)
150+
return true
151+
} catch (err) {
152+
console.error('Error starting up pty', err)
153+
return false
154+
}
155+
}

0 commit comments

Comments
 (0)