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 proc.kill("SIGTERM");
60 });
61 }
62 proc.on("exit", () => {
63 console.log("node exited");
64 if (opts.autoQuit) {
65 app.quit();
66 }
67 });
68
69 return {
70 proc,
71 addr: `http://${addr}`,
72 internalAddr: `http://${internalAddr}`,
73 };
74}
75
76const checkService = (
77 url: string,
78 interval = 300,
79 timeout = 10000,
80): Promise<void> => {
81 let attempts = 0;
82 const maxAttempts = timeout / interval;
83
84 return new Promise((resolve, reject) => {
85 const intervalId = setInterval(async () => {
86 attempts++;
87
88 try {
89 const response = await fetch(url);
90 if (response.ok) {
91 // Response status in the range 200-299
92 clearInterval(intervalId);
93 resolve();
94 }
95 } catch (error) {
96 // Fetch failed, continue trying
97 }
98
99 if (attempts >= maxAttempts) {
100 clearInterval(intervalId);
101 reject(new Error("streamplace did not boot up in time"));
102 }
103 }, interval);
104 });
105};