import { ChildProcess, spawn } from "child_process"; import electron = require("electron"); export interface IElectronWindowParams { url: string; browserWindowOptions?: electron.BrowserWindowConstructorOptions; loadURLOptions?: electron.LoadURLOptions; openDevTools?: boolean; userDataDir?: string; proxy?: Partial; proxyAuth?: { login: string; password: string; }; } export class ElectronWindow { public static async create(params: IElectronWindowParams) { const child = spawn(electron as any, [__dirname + "/runner.js", "--user-data-dir", params.userDataDir || ""], { stdio: ["inherit", "inherit", "inherit", "ipc"], }); await new Promise((resolve, reject) => { let isRejected = false; const onExit = (err: any) => { if (!isRejected) { isRejected = true; reject("Unexpected exit " + err); } }; const onMessage = (msg: any) => { if (msg === "ready") { child.send({ params, type: "loadURL", }); return; } if (msg === "loaded") { resolve(); return; } if (msg.type === "error") { onExit(msg.error); return; } }; child.on("message", onMessage); child.once("exit", onExit); child.once("error", onExit); }); child.removeAllListeners(); return new ElectronWindow({ child }); } public child: ChildProcess; protected isClosed = false; protected listeners: Array<{ resolve: (msg: any) => void; reject: (err: any) => void; }> = []; constructor(config: { child: ChildProcess }) { this.child = config.child; this.child.on("close", (code) => { this.isClosed = true; if (code !== 0) { this.error("Unexpected close"); } }); this.child.on("message", (msg) => { this.listeners.map((l) => l.resolve(msg)); this.listeners = []; }); this.child.on("disconnect", () => this.error("disconnect")); this.child.on("error", this.error); } public send(msg: OUT) { if (this.isClosed) { throw new Error("Window was closed"); } return new Promise(async (resolve, reject) => { // await new Promise((r) => setTimeout(r, 10)); this.child.send(msg, (err) => { if (err) { reject(err); return; } resolve(); }); }); } public read() { if (this.isClosed) { return Promise.reject("Closed"); } return new Promise((resolve, reject) => { this.listeners.push({ resolve, reject, }); }); } public async close() { this.isClosed = true; await new Promise((resolve) => { this.child.send("quit", resolve); }); // TODO? this.child.kill(); } protected error = (err: string) => { if (this.isClosed) { return; } this.isClosed = true; this.listeners.map((l) => l.reject(err)); }; } export default ElectronWindow;