geonix 1.30.2 → 1.31.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.
- package/README.md +9 -1
- package/package.json +2 -2
- package/src/Codec.js +1 -1
- package/src/Connection.js +89 -19
- package/src/Crypto.js +23 -9
- package/src/Gateway.js +47 -32
- package/src/LocalBus.js +190 -0
- package/src/Logger.js +16 -7
- package/src/Registry.js +19 -13
- package/src/Remote.js +12 -8
- package/src/Request.js +35 -16
- package/src/Service.js +66 -39
- package/src/Stream.js +12 -6
- package/src/Util.js +70 -56
- package/src/WebServer.js +11 -12
- package/.vscode/settings.json +0 -11
package/src/Service.js
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import { connection } from "./Connection.js";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
picoid,
|
|
4
|
+
sleep,
|
|
5
|
+
hash,
|
|
6
|
+
getSecondsSinceMidnight,
|
|
7
|
+
OverlayObject,
|
|
8
|
+
GeonixVersion,
|
|
9
|
+
getFirstItemFromAsyncIterable,
|
|
10
|
+
getNetworkAddresses,
|
|
11
|
+
deepMerge,
|
|
12
|
+
} from "./Util.js";
|
|
3
13
|
import { webserver } from "./WebServer.js";
|
|
4
14
|
import { createConnection } from "net";
|
|
5
15
|
import { EOL } from "os";
|
|
@@ -18,7 +28,7 @@ const getInactivityTimeout = () => parseInt(process.env.GX_INACTIVITY_TIMEOUT) |
|
|
|
18
28
|
|
|
19
29
|
const protectedMethodNames = ["constructor", "onStart"];
|
|
20
30
|
const endpointMatcher = /^((?<options>.+)\|)?(?<verb>WS|SUB|GET|POST|PATCH|PUT|DELETE|HEAD|OPTIONS|ALL)\s(?<url>.*)/;
|
|
21
|
-
const isEndpointFilter = methodName => endpointMatcher.test(methodName);
|
|
31
|
+
const isEndpointFilter = (methodName) => endpointMatcher.test(methodName);
|
|
22
32
|
const ERROR_BEGIN_DELIMITER = "-".repeat(10);
|
|
23
33
|
const ERROR_END_DELIMITER = "-".repeat(40);
|
|
24
34
|
|
|
@@ -33,9 +43,9 @@ const defaultServiceOptions = {
|
|
|
33
43
|
middleware: {
|
|
34
44
|
json: true,
|
|
35
45
|
raw: true,
|
|
36
|
-
cookies: true
|
|
46
|
+
cookies: true,
|
|
37
47
|
},
|
|
38
|
-
fullBeacon: true
|
|
48
|
+
fullBeacon: true,
|
|
39
49
|
};
|
|
40
50
|
|
|
41
51
|
/**
|
|
@@ -43,7 +53,6 @@ const defaultServiceOptions = {
|
|
|
43
53
|
* the service on the NATS bus, expose HTTP endpoints, and begin sending beacons to the registry.
|
|
44
54
|
*/
|
|
45
55
|
export class Service {
|
|
46
|
-
|
|
47
56
|
/**
|
|
48
57
|
* Creates a new instance of the subclass and starts it on the NATS bus.
|
|
49
58
|
*
|
|
@@ -52,10 +61,10 @@ export class Service {
|
|
|
52
61
|
*/
|
|
53
62
|
static start(options = {}) {
|
|
54
63
|
const instance = new this();
|
|
55
|
-
instance.#start(options).catch(e => logger.error("gx.service.start:", e));
|
|
64
|
+
instance.#start(options).catch((e) => logger.error("gx.service.start:", e));
|
|
56
65
|
}
|
|
57
66
|
|
|
58
|
-
// ---------------------------------------------------------------------------------------------
|
|
67
|
+
// ---------------------------------------------------------------------------------------------
|
|
59
68
|
|
|
60
69
|
#isActive = false;
|
|
61
70
|
#me = {};
|
|
@@ -78,7 +87,10 @@ export class Service {
|
|
|
78
87
|
const lineMap = new Map();
|
|
79
88
|
for (const name of fields) {
|
|
80
89
|
const quoted = JSON.stringify(name);
|
|
81
|
-
lineMap.set(
|
|
90
|
+
lineMap.set(
|
|
91
|
+
name,
|
|
92
|
+
serviceSource.findIndex((line) => line.includes(quoted)),
|
|
93
|
+
);
|
|
82
94
|
}
|
|
83
95
|
fields.sort((a, b) => lineMap.get(a) - lineMap.get(b));
|
|
84
96
|
|
|
@@ -90,15 +102,21 @@ export class Service {
|
|
|
90
102
|
// name
|
|
91
103
|
n: options.name ?? this.constructor.name,
|
|
92
104
|
// version
|
|
93
|
-
v:
|
|
105
|
+
v:
|
|
106
|
+
process.env.GX_VERSION ||
|
|
107
|
+
process.env.VERSION ||
|
|
108
|
+
process.env.version ||
|
|
109
|
+
options?.version ||
|
|
110
|
+
this.version ||
|
|
111
|
+
`999.999.${getSecondsSinceMidnight()}`,
|
|
94
112
|
// methods
|
|
95
113
|
m: fields
|
|
96
|
-
.filter(methodName => !protectedMethodNames.includes(methodName))
|
|
97
|
-
.filter(methodName => !methodName.startsWith("$")),
|
|
114
|
+
.filter((methodName) => !protectedMethodNames.includes(methodName))
|
|
115
|
+
.filter((methodName) => !methodName.startsWith("$")),
|
|
98
116
|
// geonix version
|
|
99
117
|
gx: GeonixVersion,
|
|
100
118
|
// IP addresses
|
|
101
|
-
a: getNetworkAddresses().map(address => `${address}:${webserver.getPort()}`)
|
|
119
|
+
a: getNetworkAddresses().map((address) => `${address}:${webserver.getPort()}`),
|
|
102
120
|
};
|
|
103
121
|
|
|
104
122
|
// check if method takes context as first argument
|
|
@@ -106,21 +124,20 @@ export class Service {
|
|
|
106
124
|
const method = this[methodName];
|
|
107
125
|
this.#methodTakesContext.set(
|
|
108
126
|
method,
|
|
109
|
-
method
|
|
127
|
+
method
|
|
128
|
+
.toString()
|
|
129
|
+
?.match(/\((?<args>.*)\)/)
|
|
130
|
+
?.groups?.args.startsWith("$"),
|
|
110
131
|
);
|
|
111
132
|
}
|
|
112
133
|
|
|
113
|
-
this.#beacon()
|
|
114
|
-
.catch(e => logger.error("gx.beacon:", e));
|
|
134
|
+
this.#beacon().catch((e) => logger.error("gx.beacon:", e));
|
|
115
135
|
|
|
116
|
-
this.#callListener(`${this.#me.n}@${this.#me.v}`)
|
|
117
|
-
.catch(e => logger.error("gx.queueListener:", e));
|
|
136
|
+
this.#callListener(`${this.#me.n}@${this.#me.v}`).catch((e) => logger.error("gx.queueListener:", e));
|
|
118
137
|
|
|
119
|
-
this.#callListener(this.#me.i)
|
|
120
|
-
.catch(e => logger.error("gx.directListener:", e));
|
|
138
|
+
this.#callListener(this.#me.i).catch((e) => logger.error("gx.directListener:", e));
|
|
121
139
|
|
|
122
|
-
this.#webserver()
|
|
123
|
-
.catch(e => logger.error("gx.webserver:", e));
|
|
140
|
+
this.#webserver().catch((e) => logger.error("gx.webserver:", e));
|
|
124
141
|
|
|
125
142
|
logger.info("gx.service.start", this.#me.n, this.#me.v);
|
|
126
143
|
|
|
@@ -141,7 +158,7 @@ export class Service {
|
|
|
141
158
|
async #beacon() {
|
|
142
159
|
while (this.#isActive) {
|
|
143
160
|
const payload = this.#options.fullBeacon ? this.#me : { i: this.#me.i };
|
|
144
|
-
connection.publish("gx2.beacon", payload).catch(e => logger.warn("beacon.publish:", e));
|
|
161
|
+
connection.publish("gx2.beacon", payload).catch((e) => logger.warn("beacon.publish:", e));
|
|
145
162
|
await sleep(BEACON_INTERVAL);
|
|
146
163
|
}
|
|
147
164
|
}
|
|
@@ -168,11 +185,10 @@ export class Service {
|
|
|
168
185
|
|
|
169
186
|
/**
|
|
170
187
|
* Register local endpoints with express instance
|
|
171
|
-
* @returns
|
|
188
|
+
* @returns
|
|
172
189
|
*/
|
|
173
190
|
async #webserver() {
|
|
174
|
-
const endpoints = this.#me.m
|
|
175
|
-
.filter(isEndpointFilter);
|
|
191
|
+
const endpoints = this.#me.m.filter(isEndpointFilter);
|
|
176
192
|
|
|
177
193
|
const router = webserver.router();
|
|
178
194
|
|
|
@@ -181,7 +197,9 @@ export class Service {
|
|
|
181
197
|
router.post(`/!!_gx/rpc/${hash(this.#me.i)}`, raw, async (req, res) => {
|
|
182
198
|
const body = _payloadKey
|
|
183
199
|
? JSON.parse(decryptPayload(req.body))
|
|
184
|
-
:
|
|
200
|
+
: Buffer.isBuffer(req.body)
|
|
201
|
+
? JSON.parse(req.body.toString())
|
|
202
|
+
: req.body;
|
|
185
203
|
await this.#onCall(body, (result) => {
|
|
186
204
|
const payload = Buffer.from(JSON.stringify(result));
|
|
187
205
|
if (_payloadKey) {
|
|
@@ -214,7 +232,7 @@ export class Service {
|
|
|
214
232
|
let { verb, url: uri } = endpointMatcher.exec(endpoint)?.groups || {};
|
|
215
233
|
verb = verb.toLowerCase();
|
|
216
234
|
|
|
217
|
-
let handlers =
|
|
235
|
+
let handlers = Array.isArray(this[endpoint]) ? this[endpoint] : [this[endpoint]];
|
|
218
236
|
|
|
219
237
|
const handlersBefore = this.#options?.handlers?.before ?? [];
|
|
220
238
|
const handlersAfter = this.#options?.handlers?.after ?? [];
|
|
@@ -232,7 +250,14 @@ export class Service {
|
|
|
232
250
|
// handlersBefore run as route-scoped middleware before the upgrade;
|
|
233
251
|
// handlersAfter does not apply to WebSocket connections.
|
|
234
252
|
if (handlersBefore.length > 0) {
|
|
235
|
-
router.use(
|
|
253
|
+
router.use(
|
|
254
|
+
uri,
|
|
255
|
+
...handlersBefore.map(
|
|
256
|
+
(h) =>
|
|
257
|
+
(...args) =>
|
|
258
|
+
h.apply(this, args),
|
|
259
|
+
),
|
|
260
|
+
);
|
|
236
261
|
}
|
|
237
262
|
router.ws(uri, this[endpoint].bind(this));
|
|
238
263
|
break;
|
|
@@ -258,14 +283,14 @@ export class Service {
|
|
|
258
283
|
handler(event.data);
|
|
259
284
|
}
|
|
260
285
|
};
|
|
261
|
-
processor().catch(e => logger.error("$sub.processor:", e));
|
|
286
|
+
processor().catch((e) => logger.error("$sub.processor:", e));
|
|
262
287
|
}
|
|
263
288
|
|
|
264
289
|
/**
|
|
265
290
|
* Handle individual call
|
|
266
|
-
* @param {Object} call
|
|
267
|
-
* @param {Function} respond
|
|
268
|
-
* @returns
|
|
291
|
+
* @param {Object} call
|
|
292
|
+
* @param {Function} respond
|
|
293
|
+
* @returns
|
|
269
294
|
*/
|
|
270
295
|
async #onCall(call, respond) {
|
|
271
296
|
const { m: methodName, a: args, c: context, o: caller } = call;
|
|
@@ -302,7 +327,7 @@ export class Service {
|
|
|
302
327
|
|
|
303
328
|
this.#connections.set(streamId, {
|
|
304
329
|
client,
|
|
305
|
-
sub: ingress
|
|
330
|
+
sub: ingress,
|
|
306
331
|
});
|
|
307
332
|
|
|
308
333
|
const cleanup = () => {
|
|
@@ -338,8 +363,11 @@ export class Service {
|
|
|
338
363
|
cleanup();
|
|
339
364
|
};
|
|
340
365
|
|
|
341
|
-
incomingLoop().catch(e => {
|
|
342
|
-
|
|
366
|
+
incomingLoop().catch((e) => {
|
|
367
|
+
logger.error("$createConnection.incomingLoop:", e);
|
|
368
|
+
cleanup();
|
|
369
|
+
});
|
|
370
|
+
controlLoop().catch((e) => logger.error("$createConnection.controlLoop:", e));
|
|
343
371
|
|
|
344
372
|
return true;
|
|
345
373
|
}
|
|
@@ -350,11 +378,11 @@ export class Service {
|
|
|
350
378
|
node: {
|
|
351
379
|
version: process.version,
|
|
352
380
|
platform: process.platform,
|
|
353
|
-
arch: process.arch
|
|
381
|
+
arch: process.arch,
|
|
354
382
|
},
|
|
355
383
|
mem: process.memoryUsage(),
|
|
356
384
|
rss: process.memoryUsage.rss(),
|
|
357
|
-
cpu: process.cpuUsage()
|
|
385
|
+
cpu: process.cpuUsage(),
|
|
358
386
|
};
|
|
359
387
|
}
|
|
360
388
|
|
|
@@ -369,5 +397,4 @@ export class Service {
|
|
|
369
397
|
$stop() {
|
|
370
398
|
this.#isActive = false;
|
|
371
399
|
}
|
|
372
|
-
|
|
373
|
-
}
|
|
400
|
+
}
|
package/src/Stream.js
CHANGED
|
@@ -52,7 +52,7 @@ export function Stream(data) {
|
|
|
52
52
|
|
|
53
53
|
const event = await Promise.race([
|
|
54
54
|
getFirstItemFromAsyncIterable(control),
|
|
55
|
-
new Promise(resolve => setTimeout(() => resolve(null), getStreamTimeout()))
|
|
55
|
+
new Promise((resolve) => setTimeout(() => resolve(null), getStreamTimeout())),
|
|
56
56
|
]);
|
|
57
57
|
|
|
58
58
|
if (!event) {
|
|
@@ -68,20 +68,22 @@ export function Stream(data) {
|
|
|
68
68
|
delete activeStreams[id];
|
|
69
69
|
|
|
70
70
|
// kickstart the stream
|
|
71
|
-
readable.on("data", chunk => connection.publishRaw(`gx2.stream.${id}.b`, chunk));
|
|
71
|
+
readable.on("data", (chunk) => connection.publishRaw(`gx2.stream.${id}.b`, chunk));
|
|
72
72
|
readable.on("close", () => {
|
|
73
73
|
connection.publishRaw(`gx2.stream.${id}.b`);
|
|
74
74
|
});
|
|
75
75
|
}
|
|
76
|
-
})().catch(e => logger.error("stream.nats.handler:", e));
|
|
76
|
+
})().catch((e) => logger.error("stream.nats.handler:", e));
|
|
77
77
|
|
|
78
78
|
const result = {
|
|
79
79
|
$: "stream",
|
|
80
|
-
id
|
|
80
|
+
id,
|
|
81
81
|
};
|
|
82
82
|
|
|
83
83
|
// get the port and addresses of the webserver
|
|
84
|
-
const addresses = webserver.getPort()
|
|
84
|
+
const addresses = webserver.getPort()
|
|
85
|
+
? getNetworkAddresses().map((address) => `${address}:${webserver.getPort()}`)
|
|
86
|
+
: undefined;
|
|
85
87
|
if (addresses) {
|
|
86
88
|
result.a = addresses;
|
|
87
89
|
}
|
|
@@ -151,7 +153,11 @@ export async function getReadable(object) {
|
|
|
151
153
|
}
|
|
152
154
|
}
|
|
153
155
|
};
|
|
154
|
-
dataHandler().catch(e => {
|
|
156
|
+
dataHandler().catch((e) => {
|
|
157
|
+
logger.error("stream.dataHandler:", e);
|
|
158
|
+
subscription.unsubscribe();
|
|
159
|
+
readable.destroy();
|
|
160
|
+
});
|
|
155
161
|
|
|
156
162
|
// kickstart remote stream with a blank message
|
|
157
163
|
await connection.publishRaw(`gx2.stream.${object.id}.a`);
|
package/src/Util.js
CHANGED
|
@@ -14,22 +14,22 @@ import { tmpdir } from "os";
|
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Wait for {delay} ms
|
|
17
|
-
* @param {number} delay
|
|
18
|
-
* @returns
|
|
17
|
+
* @param {number} delay
|
|
18
|
+
* @returns
|
|
19
19
|
*/
|
|
20
|
-
export const sleep = delay => new Promise(resolve => setTimeout(resolve, delay));
|
|
20
|
+
export const sleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay));
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Wait for next tick
|
|
24
|
-
*
|
|
25
|
-
* @returns
|
|
24
|
+
*
|
|
25
|
+
* @returns
|
|
26
26
|
*/
|
|
27
|
-
export const yieldToEventLoop = () => new Promise(resolve => setImmediate(resolve));
|
|
27
|
+
export const yieldToEventLoop = () => new Promise((resolve) => setImmediate(resolve));
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* Parse nats:// URL
|
|
31
|
-
* @param {string} url
|
|
32
|
-
* @returns
|
|
31
|
+
* @param {string} url
|
|
32
|
+
* @returns
|
|
33
33
|
*/
|
|
34
34
|
export function parseURL(url) {
|
|
35
35
|
const parsed = new URL(url);
|
|
@@ -38,12 +38,12 @@ export function parseURL(url) {
|
|
|
38
38
|
servers: `${parsed.hostname}:${parsed.port || 4222}`,
|
|
39
39
|
user: parsed.password ? parsed.username : "",
|
|
40
40
|
pass: parsed.password,
|
|
41
|
-
token: parsed.username && !parsed.password ? parsed.username : undefined
|
|
41
|
+
token: parsed.username && !parsed.password ? parsed.username : undefined,
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
return {
|
|
45
45
|
...basic,
|
|
46
|
-
...Object.fromEntries(parsed.searchParams)
|
|
46
|
+
...Object.fromEntries(parsed.searchParams),
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
49
|
|
|
@@ -51,7 +51,9 @@ const BASE62 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
|
51
51
|
const LOG256_LOG62 = Math.log(256) / Math.log(62); // ≈ 1.3437
|
|
52
52
|
|
|
53
53
|
export function encodeBase62(buffer) {
|
|
54
|
-
if (buffer.length === 0) {
|
|
54
|
+
if (buffer.length === 0) {
|
|
55
|
+
return "";
|
|
56
|
+
}
|
|
55
57
|
const len = Math.ceil(buffer.length * LOG256_LOG62);
|
|
56
58
|
let n = BigInt("0x" + buffer.toString("hex"));
|
|
57
59
|
const chars = new Array(len);
|
|
@@ -73,17 +75,17 @@ export const picoid = (size = 16) => encodeBase62(randomBytes(size));
|
|
|
73
75
|
|
|
74
76
|
/**
|
|
75
77
|
* Get SHA256 hash of a string or a buffer
|
|
76
|
-
* @param {string|Buffer} data
|
|
77
|
-
* @returns
|
|
78
|
+
* @param {string|Buffer} data
|
|
79
|
+
* @returns
|
|
78
80
|
*/
|
|
79
81
|
export const hash = (data) => createHash("sha256").update(data).digest("hex");
|
|
80
82
|
|
|
81
83
|
/**
|
|
82
84
|
* Create TCP or HTTP server at specified port
|
|
83
|
-
* @param {number} port
|
|
84
|
-
* @param {Object} pkg
|
|
85
|
-
* @param {Function} handler
|
|
86
|
-
* @returns
|
|
85
|
+
* @param {number} port
|
|
86
|
+
* @param {Object} pkg
|
|
87
|
+
* @param {Function} handler
|
|
88
|
+
* @returns
|
|
87
89
|
*/
|
|
88
90
|
export const createServerAtPort = (port, pkg, handler) =>
|
|
89
91
|
new Promise((resolve) => {
|
|
@@ -131,7 +133,7 @@ export const proxyHttp = (target, req, res) =>
|
|
|
131
133
|
const remoteTarget = `${target}${req.originalUrl}`;
|
|
132
134
|
const options = {
|
|
133
135
|
method: req.method,
|
|
134
|
-
headers: req.headers
|
|
136
|
+
headers: req.headers,
|
|
135
137
|
};
|
|
136
138
|
|
|
137
139
|
const protocol = req.protocol === "https" ? https : http;
|
|
@@ -151,11 +153,12 @@ export const proxyHttp = (target, req, res) =>
|
|
|
151
153
|
|
|
152
154
|
/**
|
|
153
155
|
* Create a object proxy that overlays overlay object
|
|
154
|
-
* @param {*} object
|
|
155
|
-
* @param {*} overlay
|
|
156
|
-
* @returns
|
|
156
|
+
* @param {*} object
|
|
157
|
+
* @param {*} overlay
|
|
158
|
+
* @returns
|
|
157
159
|
*/
|
|
158
|
-
export const OverlayObject = (object, overlay) =>
|
|
160
|
+
export const OverlayObject = (object, overlay) =>
|
|
161
|
+
new Proxy(object, { get: (t, p) => (overlay[p] !== undefined ? overlay[p] : t[p]) });
|
|
159
162
|
|
|
160
163
|
/**
|
|
161
164
|
* The version string of the currently installed Geonix package, read from `package.json` at
|
|
@@ -173,24 +176,25 @@ export const GeonixVersion = (() => {
|
|
|
173
176
|
|
|
174
177
|
/**
|
|
175
178
|
* Chunk a stream into smaller chunks
|
|
176
|
-
*
|
|
177
|
-
* @param {*} chunkSize
|
|
178
|
-
* @returns
|
|
179
|
+
*
|
|
180
|
+
* @param {*} chunkSize
|
|
181
|
+
* @returns
|
|
179
182
|
*/
|
|
180
|
-
export const StreamChunker = (chunkSize = 65536) =>
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
done
|
|
192
|
-
|
|
193
|
-
}
|
|
183
|
+
export const StreamChunker = (chunkSize = 65536) =>
|
|
184
|
+
new Transform({
|
|
185
|
+
transform(chunk, _encoding, done) {
|
|
186
|
+
let offset = 0;
|
|
187
|
+
while (offset < chunk.length) {
|
|
188
|
+
const sliceSize = Math.min(chunkSize, chunk.length - offset);
|
|
189
|
+
this.push(chunk.slice(offset, offset + sliceSize));
|
|
190
|
+
offset += sliceSize;
|
|
191
|
+
}
|
|
192
|
+
done();
|
|
193
|
+
},
|
|
194
|
+
flush(done) {
|
|
195
|
+
done();
|
|
196
|
+
},
|
|
197
|
+
});
|
|
194
198
|
|
|
195
199
|
export async function getFirstItemFromAsyncIterable(asyncIterable) {
|
|
196
200
|
const iterator = asyncIterable[Symbol.asyncIterator]();
|
|
@@ -199,24 +203,31 @@ export async function getFirstItemFromAsyncIterable(asyncIterable) {
|
|
|
199
203
|
}
|
|
200
204
|
|
|
201
205
|
export function getNetworkAddresses() {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
206
|
+
// Loopback entries are seeded first so same-host callers prefer them; the OS-reported
|
|
207
|
+
// duplicates are filtered out below to keep the prepended ones canonical.
|
|
208
|
+
const list = ["127.0.0.1", "[::1]"];
|
|
209
|
+
const interfaces = networkInterfaces() ?? {};
|
|
210
|
+
|
|
211
|
+
for (const interfaceAddresses of Object.values(interfaces)) {
|
|
212
|
+
if (!interfaceAddresses) { continue; }
|
|
213
|
+
for (const addressObject of interfaceAddresses) {
|
|
214
|
+
const addr = addressObject.family === "IPv4"
|
|
215
|
+
? addressObject.address
|
|
216
|
+
: addressObject.family === "IPv6"
|
|
217
|
+
? `[${addressObject.address}]`
|
|
218
|
+
: null;
|
|
219
|
+
if (!addr) { continue; }
|
|
220
|
+
if (addr === "127.0.0.1" || addr === "[::1]") { continue; }
|
|
221
|
+
list.push(addr);
|
|
214
222
|
}
|
|
215
223
|
}
|
|
216
224
|
|
|
217
225
|
return list;
|
|
218
226
|
}
|
|
219
227
|
|
|
228
|
+
export const isLoopbackAddress = (addressWithPort) =>
|
|
229
|
+
addressWithPort.startsWith("127.0.0.1:") || addressWithPort.startsWith("[::1]:");
|
|
230
|
+
|
|
220
231
|
export function isIterable(obj) {
|
|
221
232
|
return obj && (typeof obj[Symbol.iterator] === "function" || typeof obj[Symbol.asyncIterator] === "function");
|
|
222
233
|
}
|
|
@@ -246,7 +257,7 @@ export async function parseMultipart(req, _options) {
|
|
|
246
257
|
const END_OF_HEADERS = Buffer.from("\r\n\r\n");
|
|
247
258
|
const options = {
|
|
248
259
|
useMemory: false,
|
|
249
|
-
..._options
|
|
260
|
+
..._options,
|
|
250
261
|
};
|
|
251
262
|
const parts = [];
|
|
252
263
|
let stream = req;
|
|
@@ -308,7 +319,7 @@ export async function parseMultipart(req, _options) {
|
|
|
308
319
|
headers: {},
|
|
309
320
|
bodyFile: options.useMemory ? undefined : bodyFile,
|
|
310
321
|
body: options.useMemory ? [] : createWriteStream(bodyFile, { flags: "wx" }),
|
|
311
|
-
size: 0
|
|
322
|
+
size: 0,
|
|
312
323
|
};
|
|
313
324
|
parts.push(activePart);
|
|
314
325
|
};
|
|
@@ -341,7 +352,9 @@ export async function parseMultipart(req, _options) {
|
|
|
341
352
|
break;
|
|
342
353
|
}
|
|
343
354
|
|
|
344
|
-
const isLastBoundary =
|
|
355
|
+
const isLastBoundary =
|
|
356
|
+
combined[boundaryIndex + boundary.length] === 45 &&
|
|
357
|
+
combined[boundaryIndex + boundary.length + 1] === 45;
|
|
345
358
|
|
|
346
359
|
if (boundaryIndex > 0) {
|
|
347
360
|
write(combined.subarray(0, boundaryIndex));
|
|
@@ -360,7 +373,8 @@ export async function parseMultipart(req, _options) {
|
|
|
360
373
|
}
|
|
361
374
|
|
|
362
375
|
activePart.headers = combined
|
|
363
|
-
.subarray(boundaryIndex + boundary.length + 2, endOfHeaders)
|
|
376
|
+
.subarray(boundaryIndex + boundary.length + 2, endOfHeaders)
|
|
377
|
+
.toString()
|
|
364
378
|
.split("\r\n")
|
|
365
379
|
.reduce((acc, val) => {
|
|
366
380
|
const [header, value] = val.split(": ");
|
|
@@ -404,7 +418,7 @@ export async function parseMultipart(req, _options) {
|
|
|
404
418
|
try {
|
|
405
419
|
await unlink(part.bodyFile);
|
|
406
420
|
} catch {
|
|
407
|
-
// ignore errors
|
|
421
|
+
// ignore errors
|
|
408
422
|
}
|
|
409
423
|
});
|
|
410
424
|
}
|
package/src/WebServer.js
CHANGED
|
@@ -50,7 +50,6 @@ export const ServeStatic = (root, options = {}) => {
|
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
class WebServer {
|
|
53
|
-
|
|
54
53
|
#app = express();
|
|
55
54
|
#server;
|
|
56
55
|
#port;
|
|
@@ -60,7 +59,9 @@ class WebServer {
|
|
|
60
59
|
#routers = [];
|
|
61
60
|
|
|
62
61
|
async start() {
|
|
63
|
-
if (this.#started) {
|
|
62
|
+
if (this.#started) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
64
65
|
|
|
65
66
|
this.#started = true;
|
|
66
67
|
|
|
@@ -116,15 +117,14 @@ class WebServer {
|
|
|
116
117
|
router = await new Promise((resolve, reject) => {
|
|
117
118
|
currentResolve = resolve;
|
|
118
119
|
|
|
119
|
-
router(req, res,
|
|
120
|
-
(error
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
120
|
+
router(req, res, (error, _req, _res, _next) => {
|
|
121
|
+
if (error) {
|
|
122
|
+
return reject(error);
|
|
123
|
+
}
|
|
124
124
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
125
|
+
// go to next router
|
|
126
|
+
resolve(routers.shift());
|
|
127
|
+
});
|
|
128
128
|
});
|
|
129
129
|
}
|
|
130
130
|
|
|
@@ -138,7 +138,7 @@ class WebServer {
|
|
|
138
138
|
this.#app.all("*", (req, res) => {
|
|
139
139
|
res.status(404).send({
|
|
140
140
|
error: 404,
|
|
141
|
-
source: "ws"
|
|
141
|
+
source: "ws",
|
|
142
142
|
});
|
|
143
143
|
});
|
|
144
144
|
|
|
@@ -175,7 +175,6 @@ class WebServer {
|
|
|
175
175
|
logger.info("gx.webserver.stop");
|
|
176
176
|
}
|
|
177
177
|
}
|
|
178
|
-
|
|
179
178
|
}
|
|
180
179
|
|
|
181
180
|
export const webserver = new WebServer();
|