mirror of
https://github.com/koush/scrypted.git
synced 2026-04-27 02:11:08 +01:00
server: remove electron
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
})
|
||||
});
|
||||
|
||||
@@ -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)
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user