@peerbit/server 1.1.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/lib/esm/cli.js +518 -144
  2. package/lib/esm/cli.js.map +1 -1
  3. package/lib/esm/client.d.ts +11 -3
  4. package/lib/esm/client.js +61 -27
  5. package/lib/esm/client.js.map +1 -1
  6. package/lib/esm/config.d.ts +8 -5
  7. package/lib/esm/config.js +44 -19
  8. package/lib/esm/config.js.map +1 -1
  9. package/lib/esm/peerbit.d.ts +4 -0
  10. package/lib/esm/peerbit.js +15 -2
  11. package/lib/esm/peerbit.js.map +1 -1
  12. package/lib/esm/remotes.browser.d.ts +0 -0
  13. package/lib/esm/remotes.browser.js +3 -0
  14. package/lib/esm/remotes.browser.js.map +1 -0
  15. package/lib/esm/remotes.d.ts +16 -0
  16. package/lib/esm/remotes.js +51 -0
  17. package/lib/esm/remotes.js.map +1 -0
  18. package/lib/esm/routes.d.ts +3 -0
  19. package/lib/esm/routes.js +3 -0
  20. package/lib/esm/routes.js.map +1 -1
  21. package/lib/esm/server.d.ts +14 -4
  22. package/lib/esm/server.js +297 -144
  23. package/lib/esm/server.js.map +1 -1
  24. package/lib/esm/session.d.ts +19 -0
  25. package/lib/esm/session.js +49 -0
  26. package/lib/esm/session.js.map +1 -0
  27. package/lib/esm/signes-request.d.ts +5 -0
  28. package/lib/esm/signes-request.js +54 -0
  29. package/lib/esm/signes-request.js.map +1 -0
  30. package/lib/esm/trust.browser.d.ts +0 -0
  31. package/lib/esm/trust.browser.js +3 -0
  32. package/lib/esm/trust.browser.js.map +1 -0
  33. package/lib/esm/trust.d.ts +9 -0
  34. package/lib/esm/trust.js +36 -0
  35. package/lib/esm/trust.js.map +1 -0
  36. package/lib/esm/types.d.ts +10 -0
  37. package/lib/ui/assets/index-cac7195d.js +77 -0
  38. package/lib/ui/index.html +1 -1
  39. package/package.json +9 -5
  40. package/src/cli.ts +705 -271
  41. package/src/client.ts +105 -30
  42. package/src/config.ts +52 -25
  43. package/src/peerbit.ts +27 -3
  44. package/src/remotes.browser.ts +1 -0
  45. package/src/remotes.ts +63 -0
  46. package/src/routes.ts +3 -1
  47. package/src/server.ts +381 -190
  48. package/src/session.ts +69 -0
  49. package/src/signes-request.ts +84 -0
  50. package/src/trust.browser.ts +1 -0
  51. package/src/trust.ts +39 -0
  52. package/src/types.ts +13 -0
  53. package/lib/ui/assets/config.browser-4ed993c7.js +0 -1
  54. package/lib/ui/assets/index-a8188422.js +0 -53
package/src/server.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import http from "http";
2
- import { fromBase64 } from "@peerbit/crypto";
2
+ import { Ed25519Keypair, fromBase64, sha256Base64Sync } from "@peerbit/crypto";
3
3
  import { deserialize } from "@dao-xyz/borsh";
4
4
  import {
5
5
  Program,
@@ -10,90 +10,130 @@ import {
10
10
  import { waitFor } from "@peerbit/time";
11
11
  import { v4 as uuid } from "uuid";
12
12
  import {
13
- checkExistPath,
14
13
  getHomeConfigDir,
15
- getCredentialsPath,
16
- getPackageName,
17
- loadPassword,
18
- NotFoundError,
14
+ getNodePath,
15
+ getKeypair,
16
+ getTrustPath,
19
17
  } from "./config.js";
20
18
  import { setMaxListeners } from "events";
21
19
  import { create } from "./peerbit.js";
22
20
  import { Peerbit } from "peerbit";
23
21
  import { getSchema } from "@dao-xyz/borsh";
24
- import { StartByBase64, StartByVariant, StartProgram } from "./types.js";
22
+ import {
23
+ InstallDependency,
24
+ StartByBase64,
25
+ StartByVariant,
26
+ StartProgram,
27
+ } from "./types.js";
25
28
  import {
26
29
  ADDRESS_PATH,
27
30
  BOOTSTRAP_PATH,
31
+ TERMINATE_PATH,
28
32
  INSTALL_PATH,
29
33
  LOCAL_PORT,
30
34
  PEER_ID_PATH,
31
35
  PROGRAMS_PATH,
32
36
  PROGRAM_PATH,
37
+ RESTART_PATH,
38
+ TRUST_PATH,
33
39
  } from "./routes.js";
34
- import { client } from "./client.js";
35
-
40
+ import { Session } from "./session.js";
41
+ import fs from "fs";
42
+ import { exit } from "process";
43
+ import { spawn, fork, execSync } from "child_process";
44
+ import tmp from "tmp";
45
+ import path from "path";
46
+ import { base58btc } from "multiformats/bases/base58";
36
47
  import { dirname } from "path";
37
48
  import { fileURLToPath } from "url";
49
+ import { Level } from "level";
50
+ import { MemoryLevel } from "memory-level";
51
+ import { Trust } from "./trust.js";
52
+ import { getBody, verifyRequest } from "./signes-request.js";
53
+ import { cli } from "./cli.js";
38
54
 
39
55
  const __dirname = dirname(fileURLToPath(import.meta.url));
40
56
 
41
- export const createPassword = async (): Promise<string> => {
42
- const fs = await import("fs");
43
- const configDir = await getHomeConfigDir();
44
- const credentialsPath = await getCredentialsPath(configDir);
45
- if (await checkExistPath(credentialsPath)) {
46
- throw new Error(
47
- "Config path for credentials: " + credentialsPath + ", already exist"
48
- );
49
- }
50
- console.log(`Creating config folder ${configDir}`);
51
-
52
- fs.mkdirSync(configDir, { recursive: true });
53
- await waitFor(() => fs.existsSync(configDir));
54
-
55
- console.log(`Created config folder ${configDir}`);
56
-
57
- const password = uuid();
58
- fs.writeFileSync(
59
- credentialsPath,
60
- JSON.stringify({ username: "admin", password })
61
- );
62
- console.log(`Created credentials at ${credentialsPath}`);
63
- return password;
57
+ export const stopAndWait = (server: http.Server) => {
58
+ let closed = false;
59
+ server.on("close", () => {
60
+ closed = true;
61
+ });
62
+ server.close();
63
+ return waitFor(() => closed);
64
64
  };
65
65
 
66
- export const loadOrCreatePassword = async (): Promise<string> => {
67
- try {
68
- return await loadPassword();
69
- } catch (error) {
70
- if (error instanceof NotFoundError) {
71
- return createPassword();
72
- }
73
- throw error;
74
- }
75
- };
76
66
  export const startServerWithNode = async (properties: {
77
- directory?: string;
67
+ directory: string;
78
68
  domain?: string;
79
69
  bootstrap?: boolean;
70
+ newSession?: boolean;
71
+ ports?: {
72
+ node: number;
73
+ api: number;
74
+ };
75
+ restart?: () => void;
80
76
  }) => {
77
+ if (!fs.existsSync(properties.directory)) {
78
+ fs.mkdirSync(properties.directory, { recursive: true });
79
+ }
80
+ const keypair = await getKeypair(properties.directory);
81
81
  const peer = await create({
82
- directory: properties.directory,
82
+ directory:
83
+ properties.directory != null
84
+ ? getNodePath(properties.directory)
85
+ : undefined,
83
86
  domain: properties.domain,
87
+ listenPort: properties.ports?.node,
88
+ peerId: await keypair.toPeerId(),
84
89
  });
85
90
 
86
91
  if (properties.bootstrap) {
87
92
  await peer.bootstrap();
88
93
  }
94
+ const sessionDirectory =
95
+ properties.directory != null
96
+ ? path.join(properties.directory, "session")
97
+ : undefined;
98
+
99
+ const session = new Session(
100
+ sessionDirectory
101
+ ? new Level<string, Uint8Array>(sessionDirectory, {
102
+ valueEncoding: "view",
103
+ keyEncoding: "utf-8",
104
+ })
105
+ : new MemoryLevel({ valueEncoding: "view", keyEncoding: "utf-8" })
106
+ );
107
+ if (!properties.newSession) {
108
+ for (const [string] of await session.imports.all()) {
109
+ await import(string);
110
+ }
111
+ for (const [address] of await session.programs.all()) {
112
+ // TODO args
113
+ try {
114
+ await peer.open(address, { timeout: 3000 });
115
+ } catch (error) {
116
+ console.error(error);
117
+ }
118
+ }
119
+ } else {
120
+ await session.clear();
121
+ }
89
122
 
90
- const server = await startServer(peer);
123
+ const server = await startApiServer(peer, {
124
+ port: properties.ports?.api,
125
+ configDirectory:
126
+ properties.directory != null
127
+ ? path.join(properties.directory, "server")
128
+ : undefined || getHomeConfigDir(),
129
+ session,
130
+ });
91
131
  const printNodeInfo = async () => {
92
132
  console.log("Starting node with address(es): ");
93
- const id = await (await client()).peer.id.get();
133
+ const id = peer.peerId.toString();
94
134
  console.log("id: " + id);
95
135
  console.log("Addresses: ");
96
- for (const a of await (await client()).peer.addresses.get()) {
136
+ for (const a of peer.getMultiaddrs()) {
97
137
  console.log(a.toString());
98
138
  }
99
139
  };
@@ -101,27 +141,33 @@ export const startServerWithNode = async (properties: {
101
141
  await printNodeInfo();
102
142
  const shutDownHook = async (
103
143
  controller: { stop: () => any },
104
- server: {
105
- close: () => void;
106
- }
144
+ server: http.Server
107
145
  ) => {
108
- const { exit } = await import("process");
109
- process.on("SIGINT", async () => {
110
- console.log("Shutting down node");
111
- await server.close();
112
- await controller.stop();
113
- exit();
146
+ ["SIGTERM", "SIGINT", "SIGUSR1", "SIGUSR2"].forEach((code) => {
147
+ process.on(code, async () => {
148
+ if (server.listening) {
149
+ console.log("Shutting down node");
150
+ await stopAndWait(server);
151
+ await waitFor(() => closed);
152
+ await controller.stop();
153
+ }
154
+ exit();
155
+ });
156
+ });
157
+ process.on("exit", async () => {
158
+ if (server.listening) {
159
+ console.log("Shutting down node");
160
+ await stopAndWait(server);
161
+ await waitFor(() => closed);
162
+ await controller.stop();
163
+ }
114
164
  });
115
165
  };
116
166
  await shutDownHook(peer, server);
117
167
  return { server, node: peer };
118
168
  };
119
169
 
120
- const getProgramFromPath = (
121
- client: Peerbit,
122
- req: http.IncomingMessage,
123
- pathIndex: number
124
- ): Program | undefined => {
170
+ const getPathValue = (req: http.IncomingMessage, pathIndex: number): string => {
125
171
  if (!req.url) {
126
172
  throw new Error("Missing url");
127
173
  }
@@ -133,42 +179,90 @@ const getProgramFromPath = (
133
179
  throw new Error("Invalid path");
134
180
  }
135
181
  const address = decodeURIComponent(path[pathIndex]);
136
- return client.handler.items.get(address);
182
+ return address;
137
183
  };
184
+ function findPeerbitProgramFolder(inputDirectory: string): string | null {
185
+ let currentDir = path.resolve(inputDirectory);
186
+
187
+ while (currentDir !== "/") {
188
+ // Stop at the root directory
189
+ const nodeModulesPath = path.join(currentDir, "node_modules");
190
+ const packageJsonPath = path.join(
191
+ nodeModulesPath,
192
+ "@peerbit",
193
+ "program",
194
+ "package.json"
195
+ );
196
+
197
+ if (fs.existsSync(packageJsonPath)) {
198
+ const packageJsonContent = fs.readFileSync(packageJsonPath, "utf-8");
199
+ const packageData = JSON.parse(packageJsonContent);
200
+
201
+ if (packageData.name === "@peerbit/program") {
202
+ return currentDir;
203
+ }
204
+ }
205
+
206
+ currentDir = path.dirname(currentDir);
207
+ }
208
+
209
+ return null;
210
+ }
138
211
 
139
- export const startServer = async (
212
+ export const startApiServer = async (
140
213
  client: ProgramClient,
141
- port: number = LOCAL_PORT
214
+ properties: {
215
+ configDirectory: string;
216
+ session?: Session;
217
+ port?: number;
218
+ }
142
219
  ): Promise<http.Server> => {
143
- const password = await loadOrCreatePassword();
220
+ const port = properties?.port ?? LOCAL_PORT;
221
+ if (!fs.existsSync(properties.configDirectory)) {
222
+ fs.mkdirSync(properties.configDirectory, { recursive: true });
223
+ }
144
224
 
145
- const adminACL = (req: http.IncomingMessage): boolean => {
146
- const auth = req.headers["authorization"];
147
- if (!auth?.startsWith("Basic ")) {
148
- return false;
149
- }
150
- const credentials = auth?.substring("Basic ".length);
151
- const username = credentials.split(":")[0];
152
- if (username !== "admin") {
153
- return false;
154
- }
155
- if (password !== credentials.substring(username.length + 1)) {
156
- return false;
157
- }
158
- return true;
225
+ const trust = new Trust(getTrustPath(properties.configDirectory));
226
+
227
+ const restart = async () => {
228
+ await client.stop();
229
+ await stopAndWait(server);
230
+
231
+ // We filter out the reset command, since restarting means that we want to resume something
232
+ spawn(
233
+ process.argv.shift()!,
234
+ [
235
+ ...process.execArgv,
236
+ ...process.argv.filter((x) => x !== "--reset" && x !== "-r"),
237
+ ],
238
+ {
239
+ cwd: process.cwd(),
240
+ detached: true,
241
+ stdio: "inherit",
242
+ gid: process.getgid!(),
243
+ }
244
+ );
245
+ process.exit(0);
159
246
  };
247
+ if (!client.peerId.equals(await client.identity.publicKey.toPeerId())) {
248
+ throw new Error("Expecting node identity to equal peerId");
249
+ }
160
250
 
161
- const getBody = (
162
- req: http.IncomingMessage,
163
- callback: (body: string) => Promise<void> | void
164
- ) => {
165
- let body = "";
166
- req.on("data", function (d) {
167
- body += d;
168
- });
169
- req.on("end", function () {
170
- callback(body);
171
- });
251
+ const getVerifiedBody = async (req: http.IncomingMessage) => {
252
+ const body = await getBody(req);
253
+ const result = await verifyRequest(
254
+ req.headers,
255
+ req.method!,
256
+ req.url!,
257
+ body
258
+ );
259
+ if (result.equals(client.identity.publicKey)) {
260
+ return body;
261
+ }
262
+ if (trust.isTrusted(result.hashcode())) {
263
+ return body;
264
+ }
265
+ throw new Error("Not trusted");
172
266
  };
173
267
 
174
268
  const e404 = "404";
@@ -186,15 +280,20 @@ export const startServer = async (
186
280
 
187
281
  try {
188
282
  if (req.url) {
189
- if (
190
- !req.url.startsWith(PEER_ID_PATH) &&
191
- !req.url.startsWith(ADDRESS_PATH) &&
192
- !(await adminACL(req))
193
- ) {
283
+ let body: string;
284
+ try {
285
+ body =
286
+ req.url.startsWith(PEER_ID_PATH) ||
287
+ req.url.startsWith(ADDRESS_PATH)
288
+ ? await getBody(req)
289
+ : await getVerifiedBody(req);
290
+ } catch (error: any) {
194
291
  res.writeHead(401);
195
- res.end("Not authorized");
292
+ res.end("Not authorized: " + error.toString());
196
293
  return;
197
- } else if (req.url.startsWith(PROGRAMS_PATH)) {
294
+ }
295
+
296
+ if (req.url.startsWith(PROGRAMS_PATH)) {
198
297
  if (client instanceof Peerbit === false) {
199
298
  res.writeHead(400);
200
299
  res.end("Server node is not running a native client");
@@ -203,9 +302,8 @@ export const startServer = async (
203
302
  switch (req.method) {
204
303
  case "GET":
205
304
  try {
206
- const keys = JSON.stringify([
207
- ...(client as Peerbit).handler.items.keys(),
208
- ]);
305
+ const ref = (client as Peerbit).handler?.items?.keys() || [];
306
+ const keys = JSON.stringify([...ref]);
209
307
  res.setHeader("Content-Type", "application/json");
210
308
  res.writeHead(200);
211
309
  res.end(keys);
@@ -228,7 +326,9 @@ export const startServer = async (
228
326
  switch (req.method) {
229
327
  case "HEAD":
230
328
  try {
231
- const program = getProgramFromPath(client as Peerbit, req, 1);
329
+ const program = (client as Peerbit).handler?.items.get(
330
+ getPathValue(req, 1)
331
+ );
232
332
  if (program) {
233
333
  res.writeHead(200);
234
334
  res.end();
@@ -247,13 +347,22 @@ export const startServer = async (
247
347
  const url = new URL(req.url, "http://localhost:" + 1234);
248
348
  const queryData = url.searchParams.get("delete");
249
349
 
250
- const program = getProgramFromPath(client as Peerbit, req, 1);
350
+ const program = (client as Peerbit).handler?.items.get(
351
+ getPathValue(req, 1)
352
+ );
251
353
  if (program) {
354
+ let closed = false;
252
355
  if (queryData === "true") {
253
- await program.drop();
356
+ closed = await program.drop();
254
357
  } else {
255
- await program.close();
358
+ closed = await program.close();
359
+ }
360
+ if (closed) {
361
+ await properties?.session?.programs.remove(
362
+ program.address
363
+ );
256
364
  }
365
+
257
366
  res.writeHead(200);
258
367
  res.end();
259
368
  } else {
@@ -266,45 +375,54 @@ export const startServer = async (
266
375
  }
267
376
  break;
268
377
  case "PUT":
269
- getBody(req, (body) => {
270
- try {
271
- const startArguments: StartProgram = JSON.parse(body);
378
+ try {
379
+ const startArguments: StartProgram = JSON.parse(body);
272
380
 
273
- let program: Program;
274
- if ((startArguments as StartByVariant).variant) {
275
- const P = getProgramFromVariant(
276
- (startArguments as StartByVariant).variant
277
- );
278
- if (!P) {
279
- res.writeHead(400);
280
- res.end(
281
- "Missing program with variant: " +
282
- (startArguments as StartByVariant).variant
283
- );
284
- return;
285
- }
286
- program = new P();
287
- } else {
288
- program = deserialize(
289
- fromBase64((startArguments as StartByBase64).base64),
290
- Program
381
+ let program: Program;
382
+ if ((startArguments as StartByVariant).variant) {
383
+ const P = getProgramFromVariant(
384
+ (startArguments as StartByVariant).variant
385
+ );
386
+ if (!P) {
387
+ res.writeHead(400);
388
+ res.end(
389
+ "Missing program with variant: " +
390
+ (startArguments as StartByVariant).variant
291
391
  );
392
+ return;
292
393
  }
293
- client
294
- .open(program) // TODO all users to pass args
295
- .then((program) => {
296
- res.writeHead(200);
297
- res.end(program.address.toString());
298
- })
299
- .catch((error) => {
300
- res.writeHead(400);
301
- res.end("Failed to open program: " + error.toString());
302
- });
303
- } catch (error: any) {
304
- res.writeHead(400);
305
- res.end(error.toString());
394
+ program = new P();
395
+ } else {
396
+ program = deserialize(
397
+ fromBase64((startArguments as StartByBase64).base64),
398
+ Program
399
+ );
306
400
  }
307
- });
401
+ client
402
+ .open(program) // TODO all users to pass args
403
+ .then(async (program) => {
404
+ // TODO what if this is a reopen?
405
+ console.log(
406
+ "OPEN ADDRESS",
407
+ program.address,
408
+ (client as Peerbit).directory,
409
+ await client.services.blocks.has(program.address)
410
+ );
411
+ await properties?.session?.programs.add(
412
+ program.address,
413
+ new Uint8Array()
414
+ );
415
+ res.writeHead(200);
416
+ res.end(program.address.toString());
417
+ })
418
+ .catch((error) => {
419
+ res.writeHead(400);
420
+ res.end("Failed to open program: " + error.toString());
421
+ });
422
+ } catch (error: any) {
423
+ res.writeHead(400);
424
+ res.end(error.toString());
425
+ }
308
426
  break;
309
427
 
310
428
  default:
@@ -313,71 +431,101 @@ export const startServer = async (
313
431
  }
314
432
  } else if (req.url.startsWith(INSTALL_PATH)) {
315
433
  switch (req.method) {
316
- case "PUT":
317
- getBody(req, async (body) => {
318
- const name = body;
434
+ case "PUT": {
435
+ const installArgs: InstallDependency = JSON.parse(body);
319
436
 
320
- let packageName = name;
321
- if (name.endsWith(".tgz")) {
322
- packageName = await getPackageName(name);
323
- }
437
+ const packageName = installArgs.name; // @abc/123
438
+ let installName = installArgs.name; // abc123.tgz or @abc/123 (npm package name)
439
+ let clear: (() => void) | undefined;
440
+ if (installArgs.type === "tgz") {
441
+ const binary = fromBase64(installArgs.base64);
442
+ const tempFile = tmp.fileSync({
443
+ name:
444
+ base58btc.encode(Buffer.from(installName)) +
445
+ uuid() +
446
+ ".tgz",
447
+ });
448
+ fs.writeFileSync(tempFile.fd, binary);
449
+ clear = () => tempFile.removeCallback();
450
+ installName = tempFile.name;
451
+ } else {
452
+ clear = undefined;
453
+ }
324
454
 
325
- if (!name || name.length === 0) {
326
- res.writeHead(400);
327
- res.end("Invalid package: " + name);
328
- } else {
329
- const child_process = await import("child_process");
330
- try {
331
- // TODO do this without sudo. i.e. for servers provide arguments so that this app folder is writeable by default by the user
332
- child_process.execSync(
333
- `sudo npm install ${name} --prefix ${__dirname} --no-save --no-package-lock`
334
- ); // TODO omit=dev ? but this makes breaks the tests after running once?
335
- } catch (error: any) {
455
+ if (!installName || installName.length === 0) {
456
+ res.writeHead(400);
457
+ res.end("Invalid package: " + packageName);
458
+ } else {
459
+ try {
460
+ // TODO do this without sudo. i.e. for servers provide arguments so that this app folder is writeable by default by the user
461
+ const installDir =
462
+ process.env.PEERBIT_MODULES_PATH ||
463
+ findPeerbitProgramFolder(__dirname);
464
+ let permission = "";
465
+ if (!installDir) {
336
466
  res.writeHead(400);
337
- res.end(
338
- "Failed ot install library: " +
339
- name +
340
- ". " +
341
- error.toString()
342
- );
467
+ res.end("Missing installation directory");
343
468
  return;
344
469
  }
345
-
346
470
  try {
347
- const programsPre = new Set(
348
- getProgramFromVariants().map(
349
- (x) => getSchema(x).variant
350
- )
351
- );
471
+ fs.accessSync(installDir, fs.constants.W_OK);
472
+ } catch (error) {
473
+ permission = "sudo";
474
+ }
352
475
 
353
- await import(
354
- /* webpackIgnore: true */ /* @vite-ignore */ packageName
355
- );
356
- const programsPost = getProgramFromVariants()?.map((x) =>
357
- getSchema(x)
358
- );
359
- const newPrograms: { variant: string }[] = [];
360
- for (const p of programsPost) {
361
- if (!programsPre.has(p.variant)) {
362
- newPrograms.push(p as { variant: string });
363
- }
364
- }
476
+ console.log("Installing package: " + installName);
477
+ execSync(
478
+ `${permission} npm install ${installName} --prefix ${installDir} --no-save --no-package-lock`
479
+ ); // TODO omit=dev ? but this makes breaks the tests after running once?
480
+ } catch (error: any) {
481
+ res.writeHead(400);
482
+ res.end(
483
+ "Failed to install library: " +
484
+ packageName +
485
+ ". " +
486
+ error.toString()
487
+ );
488
+ clear?.();
489
+ return;
490
+ }
365
491
 
366
- res.writeHead(200);
367
- res.end(
368
- JSON.stringify(newPrograms.map((x) => x.variant))
369
- );
370
- } catch (e: any) {
371
- res.writeHead(400);
372
- res.end(e.message.toString?.());
492
+ try {
493
+ const programsPre = new Set(
494
+ getProgramFromVariants().map((x) => getSchema(x).variant)
495
+ );
496
+
497
+ await import(
498
+ /* webpackIgnore: true */ /* @vite-ignore */ packageName
499
+ );
500
+ await properties?.session?.imports.add(
501
+ packageName,
502
+ new Uint8Array()
503
+ );
504
+ const programsPost = getProgramFromVariants()?.map((x) =>
505
+ getSchema(x)
506
+ );
507
+ const newPrograms: { variant: string }[] = [];
508
+ for (const p of programsPost) {
509
+ if (!programsPre.has(p.variant)) {
510
+ newPrograms.push(p as { variant: string });
511
+ }
373
512
  }
513
+
514
+ res.writeHead(200);
515
+ res.end(JSON.stringify(newPrograms.map((x) => x.variant)));
516
+ } catch (e: any) {
517
+ res.writeHead(400);
518
+ res.end(e.message.toString?.());
519
+ clear?.();
374
520
  }
375
- });
521
+ }
376
522
  break;
523
+ }
377
524
 
378
- default:
525
+ default: {
379
526
  r404();
380
527
  break;
528
+ }
381
529
  }
382
530
  } else if (req.url.startsWith(BOOTSTRAP_PATH)) {
383
531
  switch (req.method) {
@@ -392,6 +540,48 @@ export const startServer = async (
392
540
  res.end();
393
541
  break;
394
542
 
543
+ default:
544
+ r404();
545
+ break;
546
+ }
547
+ } else if (req.url.startsWith(TRUST_PATH)) {
548
+ switch (req.method) {
549
+ case "PUT": {
550
+ trust.add(getPathValue(req, 1));
551
+ res.writeHead(200);
552
+ res.end();
553
+ break;
554
+ }
555
+ case "DELETE": {
556
+ const removed = trust.remove(getPathValue(req, 1));
557
+ res.writeHead(200);
558
+ res.end(removed);
559
+ break;
560
+ }
561
+ default:
562
+ r404();
563
+ break;
564
+ }
565
+ } else if (req.url.startsWith(RESTART_PATH)) {
566
+ switch (req.method) {
567
+ case "POST":
568
+ res.writeHead(200);
569
+ res.end();
570
+ restart();
571
+ break;
572
+
573
+ default:
574
+ r404();
575
+ break;
576
+ }
577
+ } else if (req.url.startsWith(TERMINATE_PATH)) {
578
+ switch (req.method) {
579
+ case "POST":
580
+ res.writeHead(200);
581
+ res.end();
582
+ process.exit(0);
583
+ break;
584
+
395
585
  default:
396
586
  r404();
397
587
  break;
@@ -431,6 +621,7 @@ export const startServer = async (
431
621
  });
432
622
  });
433
623
  });
624
+ await waitFor(() => server.listening);
434
625
  console.log("API available at port", port);
435
626
  return server;
436
627
  };