@peerbit/server 1.0.20 → 1.1.1
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.
- package/lib/esm/cli.js +108 -37
- package/lib/esm/cli.js.map +1 -1
- package/lib/esm/client.d.ts +25 -2
- package/lib/esm/client.js +105 -16
- package/lib/esm/client.js.map +1 -1
- package/lib/esm/config.browser.d.ts +0 -0
- package/lib/esm/config.browser.js +3 -0
- package/lib/esm/config.browser.js.map +1 -0
- package/lib/esm/config.d.ts +3 -0
- package/lib/esm/config.js +71 -0
- package/lib/esm/config.js.map +1 -1
- package/lib/esm/index.d.ts +3 -1
- package/lib/esm/index.js +3 -1
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/peerbit.d.ts +5 -0
- package/lib/esm/peerbit.js +26 -0
- package/lib/esm/peerbit.js.map +1 -0
- package/lib/esm/routes.d.ts +9 -0
- package/lib/esm/routes.js +18 -0
- package/lib/esm/routes.js.map +1 -0
- package/lib/esm/server.browser.d.ts +0 -0
- package/lib/esm/server.browser.js +3 -0
- package/lib/esm/server.browser.js.map +1 -0
- package/lib/esm/server.d.ts +15 -0
- package/lib/esm/server.js +356 -0
- package/lib/esm/server.js.map +1 -0
- package/lib/esm/types.d.ts +7 -0
- package/lib/esm/types.js +2 -0
- package/lib/esm/types.js.map +1 -0
- package/lib/ui/assets/config.browser-4ed993c7.js +1 -0
- package/lib/ui/assets/index-a8188422.js +53 -0
- package/lib/ui/index.html +1 -1
- package/package.json +16 -7
- package/src/cli.ts +119 -44
- package/src/client.ts +157 -16
- package/src/config.browser.ts +1 -0
- package/src/config.ts +80 -0
- package/src/index.ts +3 -1
- package/src/peerbit.ts +26 -0
- package/src/routes.ts +20 -0
- package/src/server.browser.ts +1 -0
- package/src/server.ts +430 -0
- package/src/types.ts +7 -0
- package/lib/esm/api.d.ts +0 -33
- package/lib/esm/api.js +0 -370
- package/lib/esm/api.js.map +0 -1
- package/lib/esm/package.json +0 -3
- package/lib/ui/assets/index-40169014.js +0 -80
- package/src/api.ts +0 -436
package/src/server.ts
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import http from "http";
|
|
2
|
+
import { fromBase64 } from "@peerbit/crypto";
|
|
3
|
+
import { deserialize } from "@dao-xyz/borsh";
|
|
4
|
+
import {
|
|
5
|
+
Program,
|
|
6
|
+
ProgramClient,
|
|
7
|
+
getProgramFromVariant,
|
|
8
|
+
getProgramFromVariants,
|
|
9
|
+
} from "@peerbit/program";
|
|
10
|
+
import { waitFor } from "@peerbit/time";
|
|
11
|
+
import { v4 as uuid } from "uuid";
|
|
12
|
+
import {
|
|
13
|
+
checkExistPath,
|
|
14
|
+
getConfigDir,
|
|
15
|
+
getCredentialsPath,
|
|
16
|
+
getPackageName,
|
|
17
|
+
loadPassword,
|
|
18
|
+
NotFoundError,
|
|
19
|
+
} from "./config.js";
|
|
20
|
+
import { setMaxListeners } from "events";
|
|
21
|
+
import { create } from "./peerbit.js";
|
|
22
|
+
import { Peerbit } from "peerbit";
|
|
23
|
+
import { getSchema } from "@dao-xyz/borsh";
|
|
24
|
+
import { StartByBase64, StartByVariant, StartProgram } from "./types.js";
|
|
25
|
+
import {
|
|
26
|
+
ADDRESS_PATH,
|
|
27
|
+
BOOTSTRAP_PATH,
|
|
28
|
+
INSTALL_PATH,
|
|
29
|
+
LOCAL_PORT,
|
|
30
|
+
PEER_ID_PATH,
|
|
31
|
+
PROGRAMS_PATH,
|
|
32
|
+
PROGRAM_PATH,
|
|
33
|
+
} from "./routes.js";
|
|
34
|
+
import { client } from "./client.js";
|
|
35
|
+
|
|
36
|
+
export const createPassword = async (): Promise<string> => {
|
|
37
|
+
const fs = await import("fs");
|
|
38
|
+
const configDir = await getConfigDir();
|
|
39
|
+
const credentialsPath = await getCredentialsPath(configDir);
|
|
40
|
+
if (await checkExistPath(credentialsPath)) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
"Config path for credentials: " + credentialsPath + ", already exist"
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
console.log(`Creating config folder ${configDir}`);
|
|
46
|
+
|
|
47
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
48
|
+
await waitFor(() => fs.existsSync(configDir));
|
|
49
|
+
|
|
50
|
+
console.log(`Created config folder ${configDir}`);
|
|
51
|
+
|
|
52
|
+
const password = uuid();
|
|
53
|
+
fs.writeFileSync(
|
|
54
|
+
credentialsPath,
|
|
55
|
+
JSON.stringify({ username: "admin", password })
|
|
56
|
+
);
|
|
57
|
+
console.log(`Created credentials at ${credentialsPath}`);
|
|
58
|
+
return password;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const loadOrCreatePassword = async (): Promise<string> => {
|
|
62
|
+
try {
|
|
63
|
+
return await loadPassword();
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (error instanceof NotFoundError) {
|
|
66
|
+
return createPassword();
|
|
67
|
+
}
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
export const startServerWithNode = async (properties: {
|
|
72
|
+
directory?: string;
|
|
73
|
+
domain?: string;
|
|
74
|
+
bootstrap?: boolean;
|
|
75
|
+
}) => {
|
|
76
|
+
const peer = await create({
|
|
77
|
+
directory: properties.directory,
|
|
78
|
+
domain: properties.domain,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (properties.bootstrap) {
|
|
82
|
+
await peer.bootstrap();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const server = await startServer(peer);
|
|
86
|
+
const printNodeInfo = async () => {
|
|
87
|
+
console.log("Starting node with address(es): ");
|
|
88
|
+
const id = await (await client()).peer.id.get();
|
|
89
|
+
console.log("id: " + id);
|
|
90
|
+
console.log("Addresses: ");
|
|
91
|
+
for (const a of await (await client()).peer.addresses.get()) {
|
|
92
|
+
console.log(a.toString());
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
await printNodeInfo();
|
|
97
|
+
const shutDownHook = async (
|
|
98
|
+
controller: { stop: () => any },
|
|
99
|
+
server: {
|
|
100
|
+
close: () => void;
|
|
101
|
+
}
|
|
102
|
+
) => {
|
|
103
|
+
const { exit } = await import("process");
|
|
104
|
+
process.on("SIGINT", async () => {
|
|
105
|
+
console.log("Shutting down node");
|
|
106
|
+
await server.close();
|
|
107
|
+
await controller.stop();
|
|
108
|
+
exit();
|
|
109
|
+
});
|
|
110
|
+
};
|
|
111
|
+
await shutDownHook(peer, server);
|
|
112
|
+
return { server, node: peer };
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const getProgramFromPath = (
|
|
116
|
+
client: Peerbit,
|
|
117
|
+
req: http.IncomingMessage,
|
|
118
|
+
pathIndex: number
|
|
119
|
+
): Program | undefined => {
|
|
120
|
+
if (!req.url) {
|
|
121
|
+
throw new Error("Missing url");
|
|
122
|
+
}
|
|
123
|
+
const url = new URL(req.url, "http://localhost:" + 1234);
|
|
124
|
+
const path = url.pathname
|
|
125
|
+
.substring(Math.min(1, url.pathname.length), url.pathname.length)
|
|
126
|
+
.split("/");
|
|
127
|
+
if (path.length <= pathIndex) {
|
|
128
|
+
throw new Error("Invalid path");
|
|
129
|
+
}
|
|
130
|
+
const address = decodeURIComponent(path[pathIndex]);
|
|
131
|
+
return client.handler.items.get(address);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export const startServer = async (
|
|
135
|
+
client: ProgramClient,
|
|
136
|
+
port: number = LOCAL_PORT
|
|
137
|
+
): Promise<http.Server> => {
|
|
138
|
+
const password = await loadOrCreatePassword();
|
|
139
|
+
|
|
140
|
+
const adminACL = (req: http.IncomingMessage): boolean => {
|
|
141
|
+
const auth = req.headers["authorization"];
|
|
142
|
+
if (!auth?.startsWith("Basic ")) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
const credentials = auth?.substring("Basic ".length);
|
|
146
|
+
const username = credentials.split(":")[0];
|
|
147
|
+
if (username !== "admin") {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
if (password !== credentials.substring(username.length + 1)) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
return true;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const getBody = (
|
|
157
|
+
req: http.IncomingMessage,
|
|
158
|
+
callback: (body: string) => Promise<void> | void
|
|
159
|
+
) => {
|
|
160
|
+
let body = "";
|
|
161
|
+
req.on("data", function (d) {
|
|
162
|
+
body += d;
|
|
163
|
+
});
|
|
164
|
+
req.on("end", function () {
|
|
165
|
+
callback(body);
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const e404 = "404";
|
|
170
|
+
|
|
171
|
+
const endpoints = (client: ProgramClient): http.RequestListener => {
|
|
172
|
+
return async (req, res) => {
|
|
173
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
174
|
+
res.setHeader("Access-Control-Request-Method", "*");
|
|
175
|
+
res.setHeader("Access-Control-Allow-Headers", "*");
|
|
176
|
+
res.setHeader("Access-Control-Allow-Methods", "*");
|
|
177
|
+
const r404 = () => {
|
|
178
|
+
res.writeHead(404);
|
|
179
|
+
res.end(e404);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
if (req.url) {
|
|
184
|
+
if (
|
|
185
|
+
!req.url.startsWith(PEER_ID_PATH) &&
|
|
186
|
+
!req.url.startsWith(ADDRESS_PATH) &&
|
|
187
|
+
!(await adminACL(req))
|
|
188
|
+
) {
|
|
189
|
+
res.writeHead(401);
|
|
190
|
+
res.end("Not authorized");
|
|
191
|
+
return;
|
|
192
|
+
} else if (req.url.startsWith(PROGRAMS_PATH)) {
|
|
193
|
+
if (client instanceof Peerbit === false) {
|
|
194
|
+
res.writeHead(400);
|
|
195
|
+
res.end("Server node is not running a native client");
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
switch (req.method) {
|
|
199
|
+
case "GET":
|
|
200
|
+
try {
|
|
201
|
+
const keys = JSON.stringify([
|
|
202
|
+
...(client as Peerbit).handler.items.keys(),
|
|
203
|
+
]);
|
|
204
|
+
res.setHeader("Content-Type", "application/json");
|
|
205
|
+
res.writeHead(200);
|
|
206
|
+
res.end(keys);
|
|
207
|
+
} catch (error: any) {
|
|
208
|
+
res.writeHead(404);
|
|
209
|
+
res.end(error.message);
|
|
210
|
+
}
|
|
211
|
+
break;
|
|
212
|
+
|
|
213
|
+
default:
|
|
214
|
+
r404();
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
} else if (req.url.startsWith(PROGRAM_PATH)) {
|
|
218
|
+
if (client instanceof Peerbit === false) {
|
|
219
|
+
res.writeHead(400);
|
|
220
|
+
res.end("Server node is not running a native client");
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
switch (req.method) {
|
|
224
|
+
case "HEAD":
|
|
225
|
+
try {
|
|
226
|
+
const program = getProgramFromPath(client as Peerbit, req, 1);
|
|
227
|
+
if (program) {
|
|
228
|
+
res.writeHead(200);
|
|
229
|
+
res.end();
|
|
230
|
+
} else {
|
|
231
|
+
res.writeHead(404);
|
|
232
|
+
res.end();
|
|
233
|
+
}
|
|
234
|
+
} catch (error: any) {
|
|
235
|
+
res.writeHead(404);
|
|
236
|
+
res.end(error.message);
|
|
237
|
+
}
|
|
238
|
+
break;
|
|
239
|
+
|
|
240
|
+
case "DELETE":
|
|
241
|
+
try {
|
|
242
|
+
const url = new URL(req.url, "http://localhost:" + 1234);
|
|
243
|
+
const queryData = url.searchParams.get("delete");
|
|
244
|
+
|
|
245
|
+
const program = getProgramFromPath(client as Peerbit, req, 1);
|
|
246
|
+
if (program) {
|
|
247
|
+
if (queryData === "true") {
|
|
248
|
+
await program.drop();
|
|
249
|
+
} else {
|
|
250
|
+
await program.close();
|
|
251
|
+
}
|
|
252
|
+
res.writeHead(200);
|
|
253
|
+
res.end();
|
|
254
|
+
} else {
|
|
255
|
+
res.writeHead(404);
|
|
256
|
+
res.end();
|
|
257
|
+
}
|
|
258
|
+
} catch (error: any) {
|
|
259
|
+
res.writeHead(404);
|
|
260
|
+
res.end(error.message);
|
|
261
|
+
}
|
|
262
|
+
break;
|
|
263
|
+
case "PUT":
|
|
264
|
+
getBody(req, (body) => {
|
|
265
|
+
try {
|
|
266
|
+
const startArguments: StartProgram = JSON.parse(body);
|
|
267
|
+
|
|
268
|
+
let program: Program;
|
|
269
|
+
if ((startArguments as StartByVariant).variant) {
|
|
270
|
+
const P = getProgramFromVariant(
|
|
271
|
+
(startArguments as StartByVariant).variant
|
|
272
|
+
);
|
|
273
|
+
if (!P) {
|
|
274
|
+
res.writeHead(400);
|
|
275
|
+
res.end(
|
|
276
|
+
"Missing program with variant: " +
|
|
277
|
+
(startArguments as StartByVariant).variant
|
|
278
|
+
);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
program = new P();
|
|
282
|
+
} else {
|
|
283
|
+
program = deserialize(
|
|
284
|
+
fromBase64((startArguments as StartByBase64).base64),
|
|
285
|
+
Program
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
client
|
|
289
|
+
.open(program) // TODO all users to pass args
|
|
290
|
+
.then((program) => {
|
|
291
|
+
res.writeHead(200);
|
|
292
|
+
res.end(program.address.toString());
|
|
293
|
+
})
|
|
294
|
+
.catch((error) => {
|
|
295
|
+
res.writeHead(400);
|
|
296
|
+
res.end("Failed to open program: " + error.toString());
|
|
297
|
+
});
|
|
298
|
+
} catch (error: any) {
|
|
299
|
+
res.writeHead(400);
|
|
300
|
+
res.end(error.toString());
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
break;
|
|
304
|
+
|
|
305
|
+
default:
|
|
306
|
+
r404();
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
} else if (req.url.startsWith(INSTALL_PATH)) {
|
|
310
|
+
switch (req.method) {
|
|
311
|
+
case "PUT":
|
|
312
|
+
getBody(req, async (body) => {
|
|
313
|
+
const name = body;
|
|
314
|
+
|
|
315
|
+
let packageName = name;
|
|
316
|
+
if (name.endsWith(".tgz")) {
|
|
317
|
+
packageName = await getPackageName(name);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (!name || name.length === 0) {
|
|
321
|
+
res.writeHead(400);
|
|
322
|
+
res.end("Invalid package: " + name);
|
|
323
|
+
} else {
|
|
324
|
+
const child_process = await import("child_process");
|
|
325
|
+
try {
|
|
326
|
+
child_process.execSync(
|
|
327
|
+
`npm install ${name} --no-save --no-package-lock`
|
|
328
|
+
); // TODO omit=dev ? but this makes breaks the tests after running once?
|
|
329
|
+
} catch (error: any) {
|
|
330
|
+
res.writeHead(400);
|
|
331
|
+
res.end(
|
|
332
|
+
"Failed ot install library: " +
|
|
333
|
+
name +
|
|
334
|
+
". " +
|
|
335
|
+
error.toString()
|
|
336
|
+
);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
try {
|
|
341
|
+
const programsPre = new Set(
|
|
342
|
+
getProgramFromVariants().map(
|
|
343
|
+
(x) => getSchema(x).variant
|
|
344
|
+
)
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
await import(
|
|
348
|
+
/* webpackIgnore: true */ /* @vite-ignore */ packageName
|
|
349
|
+
);
|
|
350
|
+
const programsPost = getProgramFromVariants()?.map((x) =>
|
|
351
|
+
getSchema(x)
|
|
352
|
+
);
|
|
353
|
+
const newPrograms: { variant: string }[] = [];
|
|
354
|
+
for (const p of programsPost) {
|
|
355
|
+
if (!programsPre.has(p.variant)) {
|
|
356
|
+
newPrograms.push(p as { variant: string });
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
res.writeHead(200);
|
|
361
|
+
res.end(
|
|
362
|
+
JSON.stringify(newPrograms.map((x) => x.variant))
|
|
363
|
+
);
|
|
364
|
+
} catch (e: any) {
|
|
365
|
+
res.writeHead(400);
|
|
366
|
+
res.end(e.message.toString?.());
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
break;
|
|
371
|
+
|
|
372
|
+
default:
|
|
373
|
+
r404();
|
|
374
|
+
break;
|
|
375
|
+
}
|
|
376
|
+
} else if (req.url.startsWith(BOOTSTRAP_PATH)) {
|
|
377
|
+
switch (req.method) {
|
|
378
|
+
case "POST":
|
|
379
|
+
if (client instanceof Peerbit === false) {
|
|
380
|
+
res.writeHead(400);
|
|
381
|
+
res.end("Server node is not running a native client");
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
await (client as Peerbit).bootstrap();
|
|
385
|
+
res.writeHead(200);
|
|
386
|
+
res.end();
|
|
387
|
+
break;
|
|
388
|
+
|
|
389
|
+
default:
|
|
390
|
+
r404();
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
} else if (req.url.startsWith(PEER_ID_PATH)) {
|
|
394
|
+
res.writeHead(200);
|
|
395
|
+
res.end(client.peerId.toString());
|
|
396
|
+
} else if (req.url.startsWith(ADDRESS_PATH)) {
|
|
397
|
+
res.setHeader("Content-Type", "application/json");
|
|
398
|
+
res.writeHead(200);
|
|
399
|
+
const addresses = client.getMultiaddrs().map((x) => x.toString());
|
|
400
|
+
res.end(JSON.stringify(addresses));
|
|
401
|
+
} else {
|
|
402
|
+
r404();
|
|
403
|
+
}
|
|
404
|
+
} else {
|
|
405
|
+
r404();
|
|
406
|
+
}
|
|
407
|
+
} catch (error: any) {
|
|
408
|
+
res.writeHead(500);
|
|
409
|
+
console.error(error?.message);
|
|
410
|
+
res.end("Unexpected error");
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
setMaxListeners(Infinity); // TODO make this better (lower and large enough)
|
|
416
|
+
process.setMaxListeners(Infinity); // TODO make this better (lower and large enough)
|
|
417
|
+
|
|
418
|
+
const server = http.createServer(endpoints(client));
|
|
419
|
+
server.listen(port);
|
|
420
|
+
server.on("error", (e) => {
|
|
421
|
+
console.error("Server error: " + e?.message);
|
|
422
|
+
import("fs").then((fs) => {
|
|
423
|
+
fs.writeFile("error.log", JSON.stringify(e.message), function () {
|
|
424
|
+
/* void */ 0;
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
console.log("API available at port", port);
|
|
429
|
+
return server;
|
|
430
|
+
};
|
package/src/types.ts
ADDED
package/lib/esm/api.d.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import http from "http";
|
|
2
|
-
import { Program, Address, ProgramClient } from "@peerbit/program";
|
|
3
|
-
export declare const SSL_PORT = 9002;
|
|
4
|
-
export declare const LOCAL_PORT = 8082;
|
|
5
|
-
export declare const getPort: (protocol: string) => 9002 | 8082;
|
|
6
|
-
export declare const checkExistPath: (path: string) => Promise<boolean>;
|
|
7
|
-
export declare const createPassword: () => Promise<string>;
|
|
8
|
-
export declare const loadPassword: () => Promise<string>;
|
|
9
|
-
export declare const loadOrCreatePassword: () => Promise<string>;
|
|
10
|
-
export declare const startServerWithNode: (directory: string, domain?: string) => Promise<void>;
|
|
11
|
-
export declare const startServer: (client: ProgramClient, port?: number) => Promise<http.Server>;
|
|
12
|
-
export declare const client: (endpoint?: string) => Promise<{
|
|
13
|
-
peer: {
|
|
14
|
-
id: {
|
|
15
|
-
get: () => Promise<any>;
|
|
16
|
-
};
|
|
17
|
-
addresses: {
|
|
18
|
-
get: () => Promise<import("@multiformats/multiaddr").Multiaddr[]>;
|
|
19
|
-
};
|
|
20
|
-
};
|
|
21
|
-
program: {
|
|
22
|
-
get: (address: Address | string) => Promise<Program | undefined>;
|
|
23
|
-
/**
|
|
24
|
-
* @param program Program, or base64 string representation
|
|
25
|
-
* @param topic, topic
|
|
26
|
-
* @returns
|
|
27
|
-
*/
|
|
28
|
-
put: (program: Program | string) => Promise<Address>;
|
|
29
|
-
};
|
|
30
|
-
library: {
|
|
31
|
-
put: (name: string) => Promise<void>;
|
|
32
|
-
};
|
|
33
|
-
}>;
|