@thi.ng/server 0.2.0 → 0.3.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/CHANGELOG.md +8 -1
- package/api.d.ts +2 -2
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/interceptors/auth-route.js +1 -1
- package/package.json +5 -2
- package/server.d.ts +20 -8
- package/server.js +51 -26
- package/session/session.d.ts +2 -1
- package/session/session.js +14 -7
- package/static.js +2 -2
- package/utils/host.d.ts +42 -0
- package/utils/host.js +40 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
-
- **Last updated**: 2025-
|
|
3
|
+
- **Last updated**: 2025-02-02T22:46:17Z
|
|
4
4
|
- **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
|
|
5
5
|
|
|
6
6
|
All notable changes to this project will be documented in this file.
|
|
@@ -11,6 +11,13 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
|
|
|
11
11
|
**Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
|
|
12
12
|
and/or version bumps of transitive dependencies.
|
|
13
13
|
|
|
14
|
+
## [0.3.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/server@0.3.0) (2025-02-02)
|
|
15
|
+
|
|
16
|
+
#### 🚀 Features
|
|
17
|
+
|
|
18
|
+
- add more HTTP error response methods ([5731ff3](https://github.com/thi-ng/umbrella/commit/5731ff3))
|
|
19
|
+
- add ServerResponse, IPv6 support ([22f64c5](https://github.com/thi-ng/umbrella/commit/22f64c5))
|
|
20
|
+
|
|
14
21
|
## [0.2.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/server@0.2.0) (2025-01-30)
|
|
15
22
|
|
|
16
23
|
#### 🚀 Features
|
package/api.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Fn, Maybe, MaybePromise } from "@thi.ng/api";
|
|
2
2
|
import type { ILogger } from "@thi.ng/logger";
|
|
3
3
|
import type { Route, RouteMatch } from "@thi.ng/router";
|
|
4
|
-
import type { IncomingMessage
|
|
5
|
-
import type { Server } from "./server.js";
|
|
4
|
+
import type { IncomingMessage } from "node:http";
|
|
5
|
+
import type { ServerResponse, Server } from "./server.js";
|
|
6
6
|
export type Method = "get" | "put" | "post" | "delete" | "head" | "options" | "patch";
|
|
7
7
|
export interface ServerOpts<CTX extends RequestCtx = RequestCtx> {
|
|
8
8
|
logger: ILogger;
|
package/index.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export * from "./session/session.js";
|
|
|
13
13
|
export * from "./session/memory.js";
|
|
14
14
|
export * from "./utils/cookies.js";
|
|
15
15
|
export * from "./utils/cache.js";
|
|
16
|
+
export * from "./utils/host.js";
|
|
16
17
|
export * from "./utils/formdata.js";
|
|
17
18
|
export * from "./utils/multipart.js";
|
|
18
19
|
//# sourceMappingURL=index.d.ts.map
|
package/index.js
CHANGED
|
@@ -13,5 +13,6 @@ export * from "./session/session.js";
|
|
|
13
13
|
export * from "./session/memory.js";
|
|
14
14
|
export * from "./utils/cookies.js";
|
|
15
15
|
export * from "./utils/cache.js";
|
|
16
|
+
export * from "./utils/host.js";
|
|
16
17
|
export * from "./utils/formdata.js";
|
|
17
18
|
export * from "./utils/multipart.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thi.ng/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Minimal HTTP server with declarative routing, static file serving and freely extensible via pre/post interceptors",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./index.js",
|
|
@@ -145,6 +145,9 @@
|
|
|
145
145
|
"./utils/formdata": {
|
|
146
146
|
"default": "./utils/formdata.js"
|
|
147
147
|
},
|
|
148
|
+
"./utils/host": {
|
|
149
|
+
"default": "./utils/host.js"
|
|
150
|
+
},
|
|
148
151
|
"./utils/multipart": {
|
|
149
152
|
"default": "./utils/multipart.js"
|
|
150
153
|
}
|
|
@@ -153,5 +156,5 @@
|
|
|
153
156
|
"status": "alpha",
|
|
154
157
|
"year": 2024
|
|
155
158
|
},
|
|
156
|
-
"gitHead": "
|
|
159
|
+
"gitHead": "fa1407b41ef907a5523d30bcb28691a5aed6e85c\n"
|
|
157
160
|
}
|
package/server.d.ts
CHANGED
|
@@ -7,21 +7,33 @@ export declare class Server<CTX extends RequestCtx = RequestCtx> {
|
|
|
7
7
|
opts: Partial<ServerOpts<CTX>>;
|
|
8
8
|
logger: ILogger;
|
|
9
9
|
router: Router<CompiledServerRoute<CTX>>;
|
|
10
|
-
server: http.Server
|
|
10
|
+
server: http.Server<typeof http.IncomingMessage, typeof ServerResponse>;
|
|
11
|
+
host: string;
|
|
11
12
|
protected augmentCtx: Fn<RequestCtx, CTX>;
|
|
12
13
|
constructor(opts?: Partial<ServerOpts<CTX>>);
|
|
13
14
|
start(): Promise<boolean>;
|
|
14
15
|
stop(): Promise<boolean>;
|
|
15
|
-
protected listener(req: http.IncomingMessage, res:
|
|
16
|
+
protected listener(req: http.IncomingMessage, res: ServerResponse): Promise<void>;
|
|
16
17
|
protected runHandler({ fn, pre, post }: CompiledHandler, ctx: CTX): Promise<void>;
|
|
17
18
|
protected compileRoute(route: ServerRoute<CTX>): CompiledServerRoute<CTX>;
|
|
18
|
-
addRoutes(routes: ServerRoute[]): void;
|
|
19
|
+
addRoutes(routes: ServerRoute<CTX>[]): void;
|
|
19
20
|
sendFile({ req, res }: RequestCtx, path: string, headers?: http.OutgoingHttpHeaders, compress?: boolean): Promise<void>;
|
|
20
|
-
|
|
21
|
-
unmodified(res: http.ServerResponse): void;
|
|
22
|
-
missing(res: http.ServerResponse): void;
|
|
23
|
-
redirectTo(res: http.ServerResponse, location: string): void;
|
|
24
|
-
redirectToRoute(res: http.ServerResponse, route: RouteMatch): void;
|
|
21
|
+
redirectToRoute(res: ServerResponse, route: RouteMatch): void;
|
|
25
22
|
}
|
|
26
23
|
export declare const server: <CTX extends RequestCtx>(opts?: Partial<ServerOpts<CTX>>) => Server<CTX>;
|
|
24
|
+
/**
|
|
25
|
+
* Extended version of the default NodeJS ServerResponse with additional methods
|
|
26
|
+
* for various commonly used HTTP statuses/errors.
|
|
27
|
+
*/
|
|
28
|
+
export declare class ServerResponse extends http.ServerResponse<http.IncomingMessage> {
|
|
29
|
+
noContent(headers?: http.OutgoingHttpHeaders): void;
|
|
30
|
+
redirectTo(location: string, headers?: http.OutgoingHttpHeaders): void;
|
|
31
|
+
seeOther(location: string, headers?: http.OutgoingHttpHeaders): void;
|
|
32
|
+
unmodified(headers?: http.OutgoingHttpHeaders): void;
|
|
33
|
+
unauthorized(headers?: http.OutgoingHttpHeaders, body?: any): void;
|
|
34
|
+
forbidden(headers?: http.OutgoingHttpHeaders, body?: any): void;
|
|
35
|
+
missing(headers?: http.OutgoingHttpHeaders, body?: any): void;
|
|
36
|
+
notAllowed(headers?: http.OutgoingHttpHeaders, body?: any): void;
|
|
37
|
+
notAcceptable(headers?: http.OutgoingHttpHeaders, body?: any): void;
|
|
38
|
+
}
|
|
27
39
|
//# sourceMappingURL=server.d.ts.map
|
package/server.js
CHANGED
|
@@ -4,26 +4,30 @@ import { readText } from "@thi.ng/file-io";
|
|
|
4
4
|
import { ConsoleLogger } from "@thi.ng/logger";
|
|
5
5
|
import { preferredTypeForPath } from "@thi.ng/mime";
|
|
6
6
|
import { Router } from "@thi.ng/router";
|
|
7
|
+
import { upper } from "@thi.ng/strings";
|
|
7
8
|
import { createReadStream } from "node:fs";
|
|
8
9
|
import * as http from "node:http";
|
|
9
10
|
import * as https from "node:https";
|
|
11
|
+
import { isIPv6 } from "node:net";
|
|
10
12
|
import { pipeline, Transform } from "node:stream";
|
|
11
13
|
import { createBrotliCompress, createDeflate, createGzip } from "node:zlib";
|
|
12
14
|
import { parseCoookies } from "./utils/cookies.js";
|
|
13
15
|
import { parseSearchParams } from "./utils/formdata.js";
|
|
14
|
-
import {
|
|
16
|
+
import { isMatchingHost, normalizeIPv6Address } from "./utils/host.js";
|
|
15
17
|
const MISSING = "__missing";
|
|
16
18
|
class Server {
|
|
17
19
|
constructor(opts = {}) {
|
|
18
20
|
this.opts = opts;
|
|
19
21
|
this.logger = opts.logger ?? new ConsoleLogger("server");
|
|
22
|
+
this.host = opts.host ?? "localhost";
|
|
23
|
+
if (isIPv6(this.host)) this.host = normalizeIPv6Address(this.host);
|
|
20
24
|
this.augmentCtx = opts.context ?? identity;
|
|
21
25
|
const routes = [
|
|
22
26
|
{
|
|
23
27
|
id: MISSING,
|
|
24
28
|
match: ["__404__"],
|
|
25
29
|
handlers: {
|
|
26
|
-
get: async ({ res }) =>
|
|
30
|
+
get: async ({ res }) => res.missing()
|
|
27
31
|
}
|
|
28
32
|
},
|
|
29
33
|
...this.opts.routes ?? []
|
|
@@ -38,19 +42,22 @@ class Server {
|
|
|
38
42
|
logger;
|
|
39
43
|
router;
|
|
40
44
|
server;
|
|
45
|
+
host;
|
|
41
46
|
augmentCtx;
|
|
42
47
|
async start() {
|
|
43
|
-
const ssl = this.opts
|
|
44
|
-
const port = this.opts.port ?? (ssl ? 443 : 8080);
|
|
45
|
-
const host = this.opts.host ?? "localhost";
|
|
48
|
+
const { ssl, host = "localhost", port = ssl ? 443 : 8080 } = this.opts;
|
|
46
49
|
try {
|
|
47
50
|
this.server = ssl ? https.createServer(
|
|
48
51
|
{
|
|
49
52
|
key: readText(ssl.key, this.logger),
|
|
50
|
-
cert: readText(ssl.cert, this.logger)
|
|
53
|
+
cert: readText(ssl.cert, this.logger),
|
|
54
|
+
ServerResponse
|
|
51
55
|
},
|
|
52
56
|
this.listener.bind(this)
|
|
53
|
-
) : http.createServer(
|
|
57
|
+
) : http.createServer(
|
|
58
|
+
{ ServerResponse },
|
|
59
|
+
this.listener.bind(this)
|
|
60
|
+
);
|
|
54
61
|
this.server.listen(port, host, void 0, () => {
|
|
55
62
|
this.logger.info(
|
|
56
63
|
`starting server: http${ssl ? "s" : ""}://${host}:${port}`
|
|
@@ -70,13 +77,13 @@ class Server {
|
|
|
70
77
|
return true;
|
|
71
78
|
}
|
|
72
79
|
async listener(req, res) {
|
|
73
|
-
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
74
|
-
if (this.opts.host && this.opts.host !== url.host) {
|
|
75
|
-
res.writeHead(503).end();
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
const path = decodeURIComponent(url.pathname);
|
|
79
80
|
try {
|
|
81
|
+
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
82
|
+
if (this.opts.host && !isMatchingHost(url.hostname, this.opts.host)) {
|
|
83
|
+
res.writeHead(503).end();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const path = decodeURIComponent(url.pathname);
|
|
80
87
|
const query = parseSearchParams(url.searchParams);
|
|
81
88
|
const match = this.router.route(path);
|
|
82
89
|
const route = this.router.routeForID(match.id).spec;
|
|
@@ -112,7 +119,7 @@ class Server {
|
|
|
112
119
|
if (handler) {
|
|
113
120
|
this.runHandler(handler, ctx);
|
|
114
121
|
} else {
|
|
115
|
-
res.
|
|
122
|
+
res.notAllowed();
|
|
116
123
|
}
|
|
117
124
|
} catch (e) {
|
|
118
125
|
this.logger.warn("error:", e.message);
|
|
@@ -196,29 +203,47 @@ class Server {
|
|
|
196
203
|
encoding ? pipeline(src, encoding.tx(), res, finalize) : pipeline(src, res, finalize);
|
|
197
204
|
} catch (e) {
|
|
198
205
|
this.logger.warn(e.message);
|
|
199
|
-
|
|
206
|
+
res.missing();
|
|
200
207
|
resolve();
|
|
201
208
|
}
|
|
202
209
|
});
|
|
203
210
|
}
|
|
204
|
-
|
|
205
|
-
res.
|
|
211
|
+
redirectToRoute(res, route) {
|
|
212
|
+
res.redirectTo(this.router.format(route));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const server = (opts) => new Server(opts);
|
|
216
|
+
class ServerResponse extends http.ServerResponse {
|
|
217
|
+
noContent(headers) {
|
|
218
|
+
this.writeHead(204, headers).end();
|
|
206
219
|
}
|
|
207
|
-
|
|
208
|
-
|
|
220
|
+
redirectTo(location, headers) {
|
|
221
|
+
this.writeHead(302, { ...headers, location }).end();
|
|
209
222
|
}
|
|
210
|
-
|
|
211
|
-
|
|
223
|
+
seeOther(location, headers) {
|
|
224
|
+
this.writeHead(303, { ...headers, location }).end();
|
|
212
225
|
}
|
|
213
|
-
|
|
214
|
-
|
|
226
|
+
unmodified(headers) {
|
|
227
|
+
this.writeHead(304, headers).end();
|
|
215
228
|
}
|
|
216
|
-
|
|
217
|
-
this.
|
|
229
|
+
unauthorized(headers, body) {
|
|
230
|
+
this.writeHead(401, headers).end(body);
|
|
231
|
+
}
|
|
232
|
+
forbidden(headers, body) {
|
|
233
|
+
this.writeHead(403, headers).end(body);
|
|
234
|
+
}
|
|
235
|
+
missing(headers, body) {
|
|
236
|
+
this.writeHead(404, headers).end(body);
|
|
237
|
+
}
|
|
238
|
+
notAllowed(headers, body) {
|
|
239
|
+
this.writeHead(405, headers).end(body);
|
|
240
|
+
}
|
|
241
|
+
notAcceptable(headers, body) {
|
|
242
|
+
this.writeHead(406, headers).end(body);
|
|
218
243
|
}
|
|
219
244
|
}
|
|
220
|
-
const server = (opts) => new Server(opts);
|
|
221
245
|
export {
|
|
222
246
|
Server,
|
|
247
|
+
ServerResponse,
|
|
223
248
|
server
|
|
224
249
|
};
|
package/session/session.d.ts
CHANGED
|
@@ -32,7 +32,8 @@ export declare class SessionInterceptor<CTX extends RequestCtx = RequestCtx, SES
|
|
|
32
32
|
constructor({ store, factory, cookieName, cookieOpts, }?: Partial<SessionOpts<CTX, SESSION>>);
|
|
33
33
|
pre(ctx: CTX): Promise<boolean>;
|
|
34
34
|
delete(ctx: CTX, sessionID: string): Promise<void>;
|
|
35
|
-
|
|
35
|
+
newSession(ctx: CTX): Promise<SESSION | undefined>;
|
|
36
|
+
withSession(res: ServerResponse, sessionID: string): ServerResponse<import("http").IncomingMessage>;
|
|
36
37
|
}
|
|
37
38
|
/**
|
|
38
39
|
* Factory function to create a new {@link SessionInterceptor} instance.
|
package/session/session.js
CHANGED
|
@@ -18,16 +18,14 @@ class SessionInterceptor {
|
|
|
18
18
|
this.cookieOpts = cookieOpts;
|
|
19
19
|
}
|
|
20
20
|
async pre(ctx) {
|
|
21
|
-
const
|
|
22
|
-
const id = cookies?.[this.cookieName];
|
|
21
|
+
const id = ctx.cookies?.[this.cookieName];
|
|
23
22
|
let session = id ? await this.store.get(id) : void 0;
|
|
24
23
|
if (!session) {
|
|
25
|
-
session = this.
|
|
26
|
-
|
|
27
|
-
this.store.set(session);
|
|
24
|
+
session = await this.newSession(ctx);
|
|
25
|
+
if (!session) return false;
|
|
28
26
|
}
|
|
29
27
|
ctx.session = session;
|
|
30
|
-
this.withSession(res, session.id);
|
|
28
|
+
this.withSession(ctx.res, session.id);
|
|
31
29
|
return true;
|
|
32
30
|
}
|
|
33
31
|
async delete(ctx, sessionID) {
|
|
@@ -40,8 +38,17 @@ class SessionInterceptor {
|
|
|
40
38
|
);
|
|
41
39
|
}
|
|
42
40
|
}
|
|
41
|
+
async newSession(ctx) {
|
|
42
|
+
const session = this.factory(ctx);
|
|
43
|
+
ctx.logger.info("new session:", session.id);
|
|
44
|
+
if (!await this.store.set(session)) {
|
|
45
|
+
ctx.logger.warn("could not store session...");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
return session;
|
|
49
|
+
}
|
|
43
50
|
withSession(res, sessionID) {
|
|
44
|
-
res.appendHeader(
|
|
51
|
+
return res.appendHeader(
|
|
45
52
|
"set-cookie",
|
|
46
53
|
`${this.cookieName}=${sessionID};Max-Age=${this.store.ttl};${this.cookieOpts}`
|
|
47
54
|
);
|
package/static.js
CHANGED
|
@@ -54,11 +54,11 @@ const staticFiles = ({
|
|
|
54
54
|
});
|
|
55
55
|
const __fileHeaders = async (path, ctx, filter, etag, headers) => {
|
|
56
56
|
if (!(existsSync(path) && filter(path))) {
|
|
57
|
-
return ctx.
|
|
57
|
+
return ctx.res.missing();
|
|
58
58
|
}
|
|
59
59
|
if (etag) {
|
|
60
60
|
const etagValue = await etag(path);
|
|
61
|
-
return isUnmodified(etagValue, ctx.req.headers["if-none-match"]) ? ctx.
|
|
61
|
+
return isUnmodified(etagValue, ctx.req.headers["if-none-match"]) ? ctx.res.unmodified() : { ...headers, etag: etagValue };
|
|
62
62
|
}
|
|
63
63
|
return { ...headers };
|
|
64
64
|
};
|
package/utils/host.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Takes a hostname or IPv6 address `test` and compares it against `expected`,
|
|
3
|
+
* returns true if matching.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* If `test` is an IPv6 address in the format `[...]`, it will be compared
|
|
7
|
+
* without surrounding square brackets. If `expected` is an IPv6 address it MUST
|
|
8
|
+
* have been pre-normalized using {@link normalizeIPv6Address}. For performance
|
|
9
|
+
* reasons only `test` will be automatically normalized, i.e. `::1` will be
|
|
10
|
+
* normalized as `0:0:0:0:0:0:0:1`.
|
|
11
|
+
*
|
|
12
|
+
* @param test
|
|
13
|
+
* @param expected
|
|
14
|
+
*
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
export declare const isMatchingHost: (test: string, expected: string) => boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Parses given IPv6 address into an 8-tuple of 16bit uints. Throws error if
|
|
20
|
+
* address is invalid.
|
|
21
|
+
*
|
|
22
|
+
* @remarks
|
|
23
|
+
* https://en.wikipedia.org/wiki/IPv6_address
|
|
24
|
+
*
|
|
25
|
+
* @param addr
|
|
26
|
+
*/
|
|
27
|
+
export declare const parseIPv6Address: (addr: string) => number[];
|
|
28
|
+
/**
|
|
29
|
+
* Returns normalized version of given IPv6 address, i.e. expanding `::`
|
|
30
|
+
* sections, removing leading zeroes and performing other syntactic validations.
|
|
31
|
+
* The returned address always has 8 parts. Throws error if address is invalid.
|
|
32
|
+
*
|
|
33
|
+
* @remarks
|
|
34
|
+
* Internally uses {@link parseIPv6Address}.
|
|
35
|
+
*
|
|
36
|
+
* Reference:
|
|
37
|
+
* https://en.wikipedia.org/wiki/IPv6_address
|
|
38
|
+
*
|
|
39
|
+
* @param addr
|
|
40
|
+
*/
|
|
41
|
+
export declare const normalizeIPv6Address: (addr: string) => string;
|
|
42
|
+
//# sourceMappingURL=host.d.ts.map
|
package/utils/host.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { illegalArgs } from "@thi.ng/errors";
|
|
2
|
+
import { HEX } from "@thi.ng/strings";
|
|
3
|
+
const isMatchingHost = (test, expected) => /^\[[0-9a-f:]+\]$/.test(test) ? normalizeIPv6Address(test.substring(1, test.length - 1)) === expected : test === expected;
|
|
4
|
+
const parseIPv6Address = (addr) => {
|
|
5
|
+
if (addr == "::") return [0, 0, 0, 0, 0, 0, 0, 0];
|
|
6
|
+
if (addr == "::1") return [0, 0, 0, 0, 0, 0, 0, 1];
|
|
7
|
+
const n = addr.length - 1;
|
|
8
|
+
if (n > 38) invalidIPv6(addr);
|
|
9
|
+
const parts = [];
|
|
10
|
+
let curr = 0;
|
|
11
|
+
let zeroes = -1;
|
|
12
|
+
for (let i = 0; i <= n; i++) {
|
|
13
|
+
const ch = addr[i];
|
|
14
|
+
if (i === n && ch == ":") illegalArgs(addr);
|
|
15
|
+
if (i === n || ch === ":") {
|
|
16
|
+
if (parts.length >= (zeroes >= 0 ? 6 : 8)) invalidIPv6(addr);
|
|
17
|
+
const end = i === n ? n + 1 : i > curr ? i : invalidIPv6(addr);
|
|
18
|
+
if (end - curr > 4) invalidIPv6(addr);
|
|
19
|
+
parts.push(parseInt(addr.substring(curr, end), 16));
|
|
20
|
+
if (addr[i + 1] === ":") {
|
|
21
|
+
if (zeroes >= 0) invalidIPv6(addr);
|
|
22
|
+
zeroes = parts.length;
|
|
23
|
+
i++;
|
|
24
|
+
}
|
|
25
|
+
curr = i + 1;
|
|
26
|
+
} else if (!HEX[ch]) invalidIPv6(addr);
|
|
27
|
+
}
|
|
28
|
+
if (zeroes >= 0) {
|
|
29
|
+
parts.splice(zeroes, 0, ...new Array(8 - parts.length).fill(0));
|
|
30
|
+
}
|
|
31
|
+
if (parts.length !== 8) invalidIPv6(addr);
|
|
32
|
+
return parts;
|
|
33
|
+
};
|
|
34
|
+
const normalizeIPv6Address = (addr) => parseIPv6Address(addr).map((x) => x.toString(16)).join(":");
|
|
35
|
+
const invalidIPv6 = (addr) => illegalArgs("invalid IPv6 address: " + addr);
|
|
36
|
+
export {
|
|
37
|
+
isMatchingHost,
|
|
38
|
+
normalizeIPv6Address,
|
|
39
|
+
parseIPv6Address
|
|
40
|
+
};
|