Live video on the AT Protocol
1import { spawn } from "child_process";
2import { app } from "electron";
3import { access, constants } from "fs/promises";
4import os from "os";
5import { resolve } from "path";
6import getEnv from "./env";
7
8const findExe = async (): Promise<string> => {
9 const { isDev } = getEnv();
10 let fname = "streamplace";
11 let exe: string;
12 let platform = os.platform() as string;
13 let architecture = os.arch() as string;
14 if (platform === "win32") {
15 platform = "windows";
16 fname += ".exe";
17 }
18 if (architecture === "x64") {
19 architecture = "amd64";
20 }
21 let binfolder = `build-${platform}-${architecture}`;
22 if (isDev) {
23 // theoretically cwd is streamplace/js/desktop:
24 exe = resolve(process.cwd(), "..", "..", binfolder, fname);
25 } else {
26 exe = resolve(process.resourcesPath, fname);
27 }
28 try {
29 await access(exe, constants.F_OK);
30 } catch (e) {
31 throw new Error(
32 `could not find streamplace node binary at ${exe}: ${e.message}`,
33 );
34 }
35 return exe;
36};
37
38export default async function makeNode(opts: {
39 env: { [k: string]: string };
40 autoQuit: boolean;
41}) {
42 const exe = await findExe();
43 const addr = opts.env.SP_HTTP_ADDR ?? "127.0.0.1:38082";
44 const internalAddr = opts.env.SP_HTTP_INTERNAL_ADDR ?? "127.0.0.1:39092";
45 const proc = spawn(exe, [], {
46 stdio: "inherit",
47 env: {
48 ...process.env,
49 SP_HTTP_ADDR: addr,
50 SP_HTTP_INTERNAL_ADDR: internalAddr,
51 ...opts.env,
52 },
53 windowsHide: true,
54 });
55 await checkService(`http://${addr}/api/healthz`);
56
57 if (opts.autoQuit) {
58 app.on("before-quit", () => {
59 console.log("before-quit");
60 proc.kill("SIGTERM");
61 });
62 }
63 proc.on("exit", () => {
64 console.log("node exited");
65 if (opts.autoQuit) {
66 console.log("exiting app");
67 app.quit();
68 }
69 });
70
71 return {
72 proc,
73 addr: `http://${addr}`,
74 internalAddr: `http://${internalAddr}`,
75 };
76}
77
78const checkService = (
79 url: string,
80 interval = 300,
81 timeout = 10000,
82): Promise<void> => {
83 let attempts = 0;
84 const maxAttempts = timeout / interval;
85
86 return new Promise((resolve, reject) => {
87 const intervalId = setInterval(async () => {
88 attempts++;
89
90 try {
91 const response = await fetch(url);
92 if (response.ok) {
93 // Response status in the range 200-299
94 clearInterval(intervalId);
95 resolve();
96 }
97 } catch (error) {
98 // Fetch failed, continue trying
99 }
100
101 if (attempts >= maxAttempts) {
102 clearInterval(intervalId);
103 reject(new Error("streamplace did not boot up in time"));
104 }
105 }, interval);
106 });
107};