039012a58fef8f3d42ea4d0ddc562df201392e29
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

1) declare var setInterval: any;
2) 
3) 
4) type type_conf =
5) (
6) 	null
7) 	|
8) 	{
9) 		port: int;
10) 		verbosity: int;
11) 		cleaning:
12) 		{
13) 			timeout_in_seconds: int;
14) 			worker_interval_in_seconds: int;
15) 		};
16) 	}
17) );
18) 
19) 
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

20) type type_event =
21) {
Christian Fraß [mod] allow multiple connec...

Christian Fraß authored 2 years ago

22) 	timestamp: int;
23) 	kind: string;
24) 	data: any;
25) };
26) 
27) 
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

28) type type_user =
29) {
Christian Fraß [mod] allow multiple connec...

Christian Fraß authored 2 years ago

30) 	name: string;
31) 	role: string;
32) };
33) 
34) 
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

35) type type_connection =
36) {
Christian Fraß [mod] allow multiple connec...

Christian Fraß authored 2 years ago

37) 	client: any;
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

38) 	eventqueue: Array<type_event>;
39) 	termination: (null | int);
40) };
41) 
42) 
43) type type_id = string;
44) 
45) 
46) type type_model =
47) {
48) 	counter: int;
49) 	connections: Record<type_id, type_connection>;
Christian Fraß [mod] allow multiple connec...

Christian Fraß authored 2 years ago

50) };
51) 
52) 
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

53) type type_internal_request =
Christian Fraß [ini]

Christian Fraß authored 2 years ago

54) {
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

55) 	id: (null | type_id);
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

56) 	action: string;
57) 	data: any;
58) };
59) 
Christian Fraß [ini]

Christian Fraß authored 2 years ago

60) 
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

61) type type_internal_response = any;
Christian Fraß [mod] allow multiple connec...

Christian Fraß authored 2 years ago

62) 
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

63) 
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

64) /**
65)  * the node module "irc"
66)  */
67) var nm_irc: any;
68) 
69) 
70) /**
71)  * gets the current UNIX timestamp
72)  */
73) function get_timestamp
74) (
75) ): int
Christian Fraß [mod] allow multiple connec...

Christian Fraß authored 2 years ago

76) {
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

77) 	return Math.floor(Date.now()/1000);
Christian Fraß [mod] allow multiple connec...

Christian Fraß authored 2 years ago

78) }
79) 
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

80) 
81) /**
82)  * generates a unique 8 digit long string
83)  */
84) function generate_id
85) (
86) 	model: type_model
87) ): type_id
Christian Fraß [mod] allow multiple connec...

Christian Fraß authored 2 years ago

88) {
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

89) 	model.counter += 1;
90) 	return model.counter.toFixed(0).padStart(8, '0');
Christian Fraß [mod] allow multiple connec...

Christian Fraß authored 2 years ago

91) }
92) 
93) 
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

94) /**
95)  * writes a message to stderr
96)  */
97) function log
98) (
99) 	conf: type_conf,
100) 	level: int,
101) 	incident: string,
102) 	details: Record<string, any> = {}
103) ): void
104) {
105) 	if (level <= conf.verbosity)
106) 	{
107) 		process.stderr.write(`-- ${incident} | ${lib_json.encode(details)}\n`);
108) 	}
109) 	else
110) 	{
111) 		// do nothing
112) 	}
113) }
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

114) 
115) 
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

116) /**
117)  * updates the termination timestamp of a connection, prolonging its lifetime
118)  */
119) function connection_touch
120) (
121) 	conf: type_conf,
122) 	connection: type_connection
123) ): void
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

124) {
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

125) 	if (conf.cleaning.timeout_in_seconds !== null)
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

126) 	{
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

127) 		connection.termination = (get_timestamp() + conf.cleaning.timeout_in_seconds);
128) 	}
129) 	else
130) 	{
131) 		connection.termination = null;
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

132) 	}
133) }
Christian Fraß [mod] allow multiple connec...

Christian Fraß authored 2 years ago

134) 
135) 
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

136) /**
137)  * gets a connection by its ID
138)  */
139) function get_connection
140) (
141) 	conf: type_conf,
142) 	model: type_model,
143) 	id: type_id
144) ): type_connection
Christian Fraß [mod] allow multiple connec...

Christian Fraß authored 2 years ago

145) {
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

146) 	if (! model.connections.hasOwnProperty(id))
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

147) 	{
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

148) 		throw (new Error(`no connection for ID '${id}'`));
Christian Fraß [mod] allow multiple connec...

Christian Fraß authored 2 years ago

149) 	}
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

150) 	else
151) 	{
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

152) 		const connection: type_connection = model.connections[id];
153) 		connection_touch(conf, connection);
154) 		return connection;
Christian Fraß [mod] allow multiple connec...

Christian Fraß authored 2 years ago

155) 	}
156) }
157) 
158) 
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

159) /**
160)  * executes a request
161)  */
162) async function execute
163) (
164) 	conf: type_conf,
165) 	model: type_model,
166) 	internal_request: type_internal_request,
167) 	ip_address: string
168) ): Promise<type_internal_response>
Christian Fraß [ini]

Christian Fraß authored 2 years ago

169) {
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

170) 	switch (internal_request.action)
171) 	{
172) 		default:
173) 		{
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

174) 			throw (new Error(`unhandled action '${internal_request.action}'`));
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

175) 			break;
176) 		}
177) 		case "connect":
178) 		{
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

179) 			if (model.connections.hasOwnProperty(internal_request.id))
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

180) 			{
181) 				throw (new Error("already connected"));
182) 			}
183) 			else
184) 			{
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

185) 				const id: type_id = generate_id(model);
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

186) 				const client = new nm_irc.Client
187) 				(
188) 					internal_request.data["server"],
189) 					internal_request.data["nickname"],
190) 					{
Christian Fraß [mod] "say" -> "send"

Christian Fraß authored 2 years ago

191) 						"realName": "webirc",
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

192) 						"userName": lib_sha256.get(ip_address).slice(0, 8),
193) 						"channels": internal_request.data["channels"],
194) 						"showErrors": true,
195) 						"autoConnect": false,
Christian Fraß [mod] allow multiple connec...

Christian Fraß authored 2 years ago

196) 					}
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

197) 				);
Christian Fraß [mod] "say" -> "send"

Christian Fraß authored 2 years ago

198) 				let connection: type_connection =
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

199) 				{
200) 					"client": client,
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

201) 					"eventqueue": [],
202) 					"termination": null,
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

203) 				};
204) 				client.addListener
205) 				(
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

206) 					"message#",
207) 					(nick, to, text, message) =>
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

208) 					{
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

209) 						connection.eventqueue.push
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

210) 						({
211) 							"timestamp": get_timestamp(),
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

212) 							"kind": "message_channel",
213) 							"data": {"channel": to, "sender": nick, "content": text}
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

214) 						});
Christian Fraß [mod] allow multiple connec...

Christian Fraß authored 2 years ago

215) 					}
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

216) 				);
217) 				client.addListener
218) 				(
219) 					"pm",
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

220) 					(nick, text, message) =>
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

221) 					{
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

222) 						connection.eventqueue.push
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

223) 						({
224) 							"timestamp": get_timestamp(),
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

225) 							"kind": "message_query",
226) 							"data": {"user_name": nick, "sender": nick, "content": text}
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

227) 						});
Christian Fraß [mod] allow multiple connec...

Christian Fraß authored 2 years ago

228) 					}
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

229) 				);
230) 				client.addListener
Christian Fraß [add] progation of events "...

Christian Fraß authored 2 years ago

231) 				(
232) 					"selfMessage",
233) 					(to, text) =>
234) 					{
235) 						if (to.charAt(0) === "#")
236) 						{
237) 							connection.eventqueue.push
238) 							({
239) 								"timestamp": get_timestamp(),
240) 								"kind": "message_channel",
241) 								"data": {"channel": to, "sender": null, "content": text}
242) 							});
243) 						}
244) 						else
245) 						{
246) 							connection.eventqueue.push
247) 							({
248) 								"timestamp": get_timestamp(),
249) 								"kind": "message_query",
250) 								"data": {"user_name": to, "sender": null, "content": text}
251) 							});
252) 						}
253) 					}
254) 				);
255) 				client.addListener
256) 				(
257) 					"topic",
258) 					(channel, topic, nick, message) =>
259) 					{
260) 						connection.eventqueue.push
261) 						({
262) 							"timestamp": get_timestamp(),
263) 							"kind": "topic",
264) 							"data": {"channel": channel, "content": topic}
265) 						});
266) 					}
267) 				);
268) 				client.addListener
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

269) 				(
270) 					"names",
271) 					(channel, users) =>
272) 					{
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

273) 						connection.eventqueue.push
274) 						({
275) 							"timestamp": get_timestamp(),
Christian Fraß [mod] event "userlist" rena...

Christian Fraß authored 2 years ago

276) 							"kind": "user_list",
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

277) 							"data": {"channel": channel, "users": Object.entries(users).map(([name, role]) => ({"name": name, "role": role}))}
278) 						});
Christian Fraß [mod] allow multiple connec...

Christian Fraß authored 2 years ago

279) 					}
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

280) 				);
281) 				client.addListener
Christian Fraß [add] progation of events "...

Christian Fraß authored 2 years ago

282) 				(
283) 					"nick",
284) 					(oldnick, newnick, channels, message) =>
285) 					{
286) 						connection.eventqueue.push
287) 						({
288) 							"timestamp": get_timestamp(),
289) 							"kind": "user_renamed",
Christian Fraß [fix] fetch:user_renamed

Christian Fraß authored 2 years ago

290) 							"data": {"user_name_old": oldnick, "user_name_new": newnick}
Christian Fraß [add] progation of events "...

Christian Fraß authored 2 years ago

291) 						});
292) 					}
293) 				);
294) 				client.addListener
Christian Fraß [add] progation of events "...

Christian Fraß authored 2 years ago

295) 				(
296) 					"join",
297) 					(channel, nick, message) =>
298) 					{
299) 						connection.eventqueue.push
300) 						({
301) 							"timestamp": get_timestamp(),
302) 							"kind": "user_joined",
303) 							"data": {"channel": channel, "user_name": nick}
304) 						});
305) 					}
306) 				);
307) 				client.addListener
308) 				(
309) 					"part",
310) 					(channel, nick, reason, message) =>
311) 					{
312) 						connection.eventqueue.push
313) 						({
314) 							"timestamp": get_timestamp(),
315) 							"kind": "user_parted",
316) 							"data": {"channel": channel, "user_name": nick}
317) 						});
318) 					}
319) 				);
320) 				client.addListener
321) 				(
322) 					"kick",
323) 					(channel, nick, by, reason, message) =>
324) 					{
325) 						connection.eventqueue.push
326) 						({
327) 							"timestamp": get_timestamp(),
328) 							"kind": "user_kicked",
329) 							"data": {"channel": channel, "user_name": nick, "op_name": by, "reason": reason}
330) 						});
331) 					}
332) 				);
333) 				client.addListener
334) 				(
335) 					"quit",
336) 					(nick, reason, channels, message) =>
337) 					{
338) 						connection.eventqueue.push
339) 						({
340) 							"timestamp": get_timestamp(),
341) 							"kind": "user_quit",
Christian Fraß [fix] fetch:quit

Christian Fraß authored 2 years ago

342) 							"data": {"user_name": nick, "channels": channels}
Christian Fraß [add] progation of events "...

Christian Fraß authored 2 years ago

343) 						});
344) 					}
345) 				);
346) 				client.addListener
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

347) 				(
348) 					"error",
349) 					(error) =>
350) 					{
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

351) 						log(conf, 0, "irc_error", {"reason": error.message});
Christian Fraß [mod] allow multiple connec...

Christian Fraß authored 2 years ago

352) 					}
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

353) 				);
354) 				client.connect
355) 				(
356) 					3,
357) 					() =>
358) 					{
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

359) 						model.connections[id] = connection;
360) 						connection_touch(conf, connection);
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

361) 					}
362) 				);
363) 				return Promise.resolve<type_internal_response>(id);
364) 			}
365) 			break;
366) 		}
367) 		case "check":
368) 		{
369) 			try
370) 			{
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

371) 				get_connection(conf, model, internal_request.id);
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

372) 				return Promise.resolve<type_internal_response>(true);
373) 			}
374) 			catch (error)
375) 			{
376) 				return Promise.resolve<type_internal_response>(false);
377) 			}
378) 			break;
379) 		}
380) 		case "disconnect":
381) 		{
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

382) 			const connection: type_connection = get_connection(conf, model, internal_request.id);
383) 			delete model.connections[internal_request.id];
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

384) 			connection.client.disconnect("", () => {});
385) 			return Promise.resolve<type_internal_response>(null);
386) 			break;
387) 		}
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

388) 		case "send_channel":
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

389) 		{
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

390) 			const connection: type_connection = get_connection(conf, model, internal_request.id);
391) 			connection.client.say(internal_request.data["channel"], internal_request.data["content"]);
392) 			return Promise.resolve<type_internal_response>(null);
393) 			break;
394) 		}
395) 		case "send_query":
396) 		{
397) 			const connection: type_connection = get_connection(conf, model, internal_request.id);
398) 			connection.client.say(internal_request.data["receiver"], internal_request.data["content"]);
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

399) 			return Promise.resolve<type_internal_response>(null);
400) 			break;
401) 		}
402) 		case "fetch":
403) 		{
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

404) 			const connection: type_connection = get_connection(conf, model, internal_request.id);
405) 			const internal_response: type_internal_response = connection.eventqueue;
406) 			connection.eventqueue = [];
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

407) 			return Promise.resolve<type_internal_response>(internal_response);
408) 			break;
409) 		}
410) 	}
411) }
412) 
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

413) 
414) /**
415)  * sets up the worker for terminating old connections
416)  */
417) function setup_cleaner
418) (
419) 	conf: type_conf,
420) 	model: type_model
421) ): Promise<void>
422) {
423) 	setInterval
424) 	(
425) 		() =>
426) 		{
427) 			const now: int = get_timestamp();
428) 			for (const [id, connection] of Object.entries(model.connections))
429) 			{
430) 				if ((connection.termination !== null) && (now > connection.termination))
431) 				{
432) 					delete model.connections[id];
433) 					connection.client.disconnect("timeout", () => {});
434) 					log(conf, 1, "connection terminated after timeout", {"id": id});
435) 				}
436) 			}
437) 		},
438) 		(conf.cleaning.worker_interval_in_seconds * 1000)
439) 	);
440) 	return Promise.resolve<void>(undefined);
441) }
442) 
443) 
444) /**
445)  * sets up the server, accepting HTTP request
446)  */
447) function setup_server
448) (
449) 	conf: type_conf,
450) 	model: type_model
451) ): Promise<void>
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

452) {
453) 	const server: lib_server.class_server = new lib_server.class_server
454) 	(
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

455) 		conf.port,
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

456) 		async (input: string, metadata?: lib_server.type_metadata): Promise<string> =>
457) 		{
458) 			const http_request: lib_http.type_request = lib_http.decode_request(input);
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

459) 			log(conf, 2, "http_request", http_request);
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

460) 			const internal_request: type_internal_request = lib_json.decode(http_request.body);
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

461) 			log(conf, 1, "internal_request", internal_request);
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

462) 			let internal_response: type_internal_response;
463) 			let error: (null | Error);
464) 			try
465) 			{
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

466) 				internal_response = await execute(conf, model, internal_request, metadata.ip_address);
Christian Fraß [ini]

Christian Fraß authored 2 years ago

467) 				error = null;
468) 			}
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

469) 			catch (error_)
470) 			{
471) 				internal_response = null;
Christian Fraß [ini]

Christian Fraß authored 2 years ago

472) 				error = error_;
473) 			}
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

474) 			let http_response: lib_http.type_response;
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

475) 			if (error !== null)
476) 			{
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

477) 				log(conf, 0, "error_in_execution", {"reason": error.toString()});
478) 				http_response =
479) 				{
Christian Fraß [ini]

Christian Fraß authored 2 years ago

480) 					"statuscode": 500,
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

481) 					"headers": {"Access-Control-Allow-Origin": "*", "Content-Type": "text/plain"},
482) 					"body": "error executing the request; check the server logs for details",
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

483) 				};
484) 			}
485) 			else
486) 			{
487) 				log(conf, 1, "internal_response", {"value": internal_response});
488) 				http_response =
489) 				{
Christian Fraß [ini]

Christian Fraß authored 2 years ago

490) 					"statuscode": 200,
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

491) 					"headers": {"Access-Control-Allow-Origin": "*", "Content-Type": "application/json"},
492) 					"body": lib_json.encode(internal_response)
Christian Fraß [ini]

Christian Fraß authored 2 years ago

493) 				}
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

494) 			}
495) 			log(conf, 2, "http_response", http_response);
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

496) 			const output: string = lib_http.encode_response(http_response);
Christian Fraß [ini]

Christian Fraß authored 2 years ago

497) 			return Promise.resolve<string>(output);
498) 		}
499) 	);
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

500) 	server.start();
501) 	return Promise.resolve<void>(undefined);
502) }
503) 
504) 
505) /**
506)  * initializes and starts the whole system
507)  */
508) async function main
509) (
510) ): Promise<void>
511) {
512) 	nm_irc = require("irc");
513) 	const conf: type_conf = await lib_plankton.file.read("conf.json").then<type_conf>(lib_json.decode);
514) 	let model: type_model =
515) 	{
516) 		"counter": 0,
517) 		"connections": {},
518) 	};
519) 	await Promise.all([
520) 		setup_cleaner(conf, model),
521) 		setup_server(conf, model),
522) 	]);
523) 	return Promise.resolve<void>(undefined);
Christian Fraß [ini]

Christian Fraß authored 2 years ago

524) }
525) 
Christian Fraß [mod] logging [mod] code st...

Christian Fraß authored 2 years ago

526) 
Christian Fraß [mod] stateless

Christian Fraß authored 2 years ago

527) main().then(() => {}).catch((reason) => process.stderr.write(reason.toString()));