server: remove electron

This commit is contained in:
Koushik Dutta
2024-08-24 18:11:39 -07:00
parent fd2d7e9485
commit 4290eb0abb
12 changed files with 2 additions and 972 deletions

View File

@@ -1,158 +0,0 @@
import fs from 'fs';
import path from 'path';
import child_process from 'child_process';
import net from "net";
import { RpcMessage, RpcPeer } from "../../rpc";
import { SidebandSocketSerializer } from "../socket-serializer";
import { ChildProcessWorker } from "./child-process-worker";
import { RuntimeWorkerOptions } from "./runtime-worker";
import type { ScryptedRuntime } from '../../runtime';
import { electronBin } from '../../../bin/electron-get';
export class ElectronForkWorker extends ChildProcessWorker {
static allocatedDisplays = new Set<number>();
allocatedDisplay: number;
constructor(_mainFilename: string, pluginId: string, options: RuntimeWorkerOptions, runtime: ScryptedRuntime, mode: 'default' | 'webgl' | 'webgpu') {
super(pluginId, options);
const { env } = options;
fs.chmodSync(electronBin, 0o755);
const enabledFeatures = ['SharedArrayBuffer', 'VaapiVideoDecodeLinuxGL'];
const args = [
electronBin,
'--disable-features=BlockInsecurePrivateNetworkRequests,PrivateNetworkAccessSendPreflights',
];
if (mode !== 'default') {
args.push(
'--ignore-gpu-blocklist',
'--use-gl=angle',
'--use-angle=gl-egl',
);
}
if (process.platform === 'linux') {
// crappy but should work.
for (let i = 50; i < 100; i++) {
if (!ElectronForkWorker.allocatedDisplays.has(i)) {
this.allocatedDisplay = i;
break;
}
}
if (!this.allocatedDisplay)
throw new Error('unable to allocate DISPLAY for xvfb-run');
ElectronForkWorker.allocatedDisplays.add(this.allocatedDisplay);
// requires xvfb-run as electron does not support the chrome --headless flag.
// dummy up a DISPLAY env variable. this value numerical because of the way it is.
args.unshift('xvfb-run', '-n', this.allocatedDisplay.toString());
// https://github.com/gpuweb/gpuweb/wiki/Implementation-Status#chromium-chrome-edge-etc
args.push(
'--no-sandbox',
);
if (mode === 'webgpu') {
args.push(
'--enable-unsafe-webgpu',
'--disable-vulkan-surface',
);
enabledFeatures.push('Vulkan');
}
}
args.push(
`--enable-features=${enabledFeatures.join(",")}`,
)
if (process.platform === 'darwin') {
// Electron plist must be modified with this to hide dock icon before start. app.dock.hide flashes the dock before program starts.
// <key>LSUIElement</key>
// <string>1</string>
}
if (options?.pluginDebug) {
args.push(`--remote-debugging-port=${options?.pluginDebug.inspectPort}`);
}
args.push(
path.join(__dirname, 'electron', 'electron-plugin-remote.js'),
'--', 'child', this.pluginId);
const bin = args.shift();
this.worker = child_process.spawn(bin, args, {
stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
env: Object.assign({}, process.env, env),
serialization: 'advanced',
});
this.worker.on('exit', () => {
});
if (options?.pluginDebug?.waitDebug) {
options.pluginDebug.waitDebug.catch(() => { });
options.pluginDebug.waitDebug = Promise.resolve(undefined);
}
this.worker.send({
pluginId,
options: {
...options,
pluginDebug: options?.pluginDebug ? {
...options.pluginDebug,
// dont want to send/serialize this.
waitDebug: null,
} : undefined,
},
});
this.setupWorker();
}
kill(): void {
if (this.worker) {
ElectronForkWorker.allocatedDisplays.delete(this.allocatedDisplay);
this.worker.disconnect();
}
super.kill();
}
setupRpcPeer(peer: RpcPeer): void {
this.worker.on('message', (message, sendHandle) => {
if ((message as any).type && sendHandle) {
peer.handleMessage(message as any, {
sendHandle,
});
}
else if (sendHandle) {
this.emit('rpc', message, sendHandle);
}
else {
peer.handleMessage(message as any);
}
});
peer.transportSafeArgumentTypes.add(Buffer.name);
peer.transportSafeArgumentTypes.add(Uint8Array.name);
peer.addSerializer(net.Socket, net.Socket.name, new SidebandSocketSerializer());
}
send(message: RpcMessage, reject?: (e: Error) => void, serializationContext?: any): void {
try {
if (!this.worker)
throw new Error('fork worker has been killed');
this.worker.send(message, serializationContext?.sendHandle, e => {
if (e && reject)
reject(e);
});
}
catch (e) {
reject?.(e);
}
}
get pid() {
return this.worker?.pid;
}
}

View File

@@ -1,70 +0,0 @@
import { startPluginRemote } from "../../plugin-remote-worker";
import { ipcRenderer } from 'electron';
// import { bufferWrapUint8Array } from "./buffer-wrap";
// import { setNpmExecFunctionElectron } from "./set-npm-exec";
import { PassThrough } from "stream";
import { Console } from "console";
import type { RuntimeWorkerOptions } from "../runtime-worker";
import { pipeWorkerConsole } from "../../plugin-console";
// setNpmExecFunctionElectron();
ipcRenderer.on('scrypted-init', (e, initMessage: { pluginId: string, options: RuntimeWorkerOptions }) => {
const { pluginId, options } = initMessage;
for (const [k, v] of Object.entries(options.env || {})) {
process.env[k] = v?.toString();
}
const originalConsole = console;
const stdout = new PassThrough();
const stderr = new PassThrough();
pipeWorkerConsole({ stdout, stderr }, originalConsole);
stdout.on('data', d => {
ipcRenderer.send('scrypted-stdout', d);
});
stderr.on('data', d => {
ipcRenderer.send('scrypted-stderr', d);
});
const pluginConsole = new Console(stdout, stderr);
(globalThis as any).foo = 3;
global.console = pluginConsole;
(global as any).ss = originalConsole;
const peer = startPluginRemote('', pluginId, (message, reject, serializationContext) => {
try {
ipcRenderer.send('scrypted', message);
}
catch (e) {
reject?.(e);
}
}, {
sourceURL(filename) {
return `scrypted-electron://${filename}`;
}
});
// const evalLocal = peer.evalLocal.bind(peer);
// peer.evalLocal = (script, filename, params) => {
// // at some point vscode or chromes source map pathing got confused by
// // file paths and no longer mapped them. by using a custom protocol,
// // the source map paths get properly resolved.
// return evalLocal(script, `scrypted-electron://${filename}`, params);
// }
peer.transportSafeArgumentTypes.add(Buffer.name);
peer.transportSafeArgumentTypes.add(Uint8Array.name);
// const deserialize = peer.deserialize;
// peer.deserialize = (value, deserializationContext) => {
// const ret = deserialize.call(peer, value, deserializationContext);
// if (ret instanceof Uint8Array)
// return bufferWrapUint8Array(ret);
// return ret;
// }
ipcRenderer.on('scrypted', (_, data) => {
peer.handleMessage(data);
})
});

View File

@@ -1,91 +0,0 @@
import { app, BrowserWindow } from 'electron';
import path from 'path';
import { Deferred } from '../../../deferred';
import { RuntimeWorkerOptions } from '../runtime-worker';
if (process.platform === 'darwin') {
// Electron plist must be modified with this to hide dock icon before start. app.dock.hide flashes the dock before program starts.
// <key>LSUIElement</key>
// <string>1</string>
app.dock.hide();
}
let win: BrowserWindow;
const winQueue: any[] = [];
const createWindow = (firstMessage: { plugindId: string, options: RuntimeWorkerOptions }) => {
const message: { plugindId: string, options: RuntimeWorkerOptions } = firstMessage;
win = new BrowserWindow({
width: 800,
height: 600,
show: false,
webPreferences: {
backgroundThrottling: false,
preload: path.join(__dirname, 'electron-plugin-preload.js'),
nodeIntegration: true,
webSecurity: false,
allowRunningInsecureContent: true,
}
});
win.webContents.send('scrypted-init', message);
// win.loadURL('https://webglsamples.org/aquarium/aquarium.html');
console.log(__dirname);
const html = path.join(__dirname, '../../../../electron', 'electron-plugin.html');
win.loadFile(html);
win.webContents.openDevTools();
win.webContents.ipc.on('scrypted', (e, message) => {
process.send(message);
});
win.webContents.ipc.on('scrypted-stdout', (e, message) => {
process.stdout.write(message);
});
win.webContents.ipc.on('scrypted-stderr', (e, message) => {
process.stderr.write(message);
});
while (winQueue.length) {
processMessage(winQueue.shift());
}
function kill() {
process.exit();
}
win.webContents.on('destroyed', kill);
win.webContents.on('plugin-crashed', kill);
win.on('close', kill);
}
const firstMessage = new Deferred<any>;
function processMessage(message: any) {
win.webContents.send('scrypted', message);
}
process.on('message', (message) => {
if (!firstMessage.finished) {
firstMessage.resolve(message);
return;
}
if (win)
processMessage(message);
else
winQueue.push(message);
});
process.on('disconnect', () => {
console.error('peer host disconnected, exiting.');
process.exit(1);
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})
app.whenReady().then(async () => {
const message: { plugindId: string, options: RuntimeWorkerOptions } = await firstMessage.promise;
createWindow(message)
});

View File

@@ -1,6 +1,5 @@
import type { ScryptedRuntime } from "../../runtime";
import { CustomRuntimeWorker } from "./custom-worker";
import { ElectronForkWorker } from "./electron-worker";
import { NodeForkWorker } from "./node-fork-worker";
import { PythonRuntimeWorker } from "./python-worker";
import type { RuntimeWorker, RuntimeWorkerOptions } from "./runtime-worker";
@@ -14,12 +13,5 @@ export function getBuiltinRuntimeHosts() {
pluginHosts.set('python', (_, pluginId, options) => new PythonRuntimeWorker(pluginId, options));
pluginHosts.set('node', (mainFilename, pluginId, options) => new NodeForkWorker(mainFilename, pluginId, options));
// safe
pluginHosts.set('electron', (mainFilename, pluginId, options, runtime) => new ElectronForkWorker(mainFilename, pluginId, options, runtime, 'default'));
// mostly safe
pluginHosts.set('electron-webgl', (mainFilename, pluginId, options, runtime) => new ElectronForkWorker(mainFilename, pluginId, options, runtime, 'webgl'));
// there be dragons
pluginHosts.set('electron-webgpu', (mainFilename, pluginId, options, runtime) => new ElectronForkWorker(mainFilename, pluginId, options, runtime, 'webgpu'));
return pluginHosts;
}