@zhin.js/host-http 1.0.3 → 1.0.5
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/console-rest-pages.js +8 -3
- package/lib/json-body.d.ts +2 -12
- package/lib/json-body.js +2 -45
- package/package.json +5 -4
- package/src/console-rest-pages.ts +10 -3
- package/src/json-body.ts +2 -47
|
@@ -17,8 +17,7 @@ export function registerConsoleRestPages(host, ctx, options = {}) {
|
|
|
17
17
|
const base = normalizeBase(options.apiBase ?? '/api');
|
|
18
18
|
const fetchFn = options.fetchFn ?? fetch;
|
|
19
19
|
const pluginRegistryUrl = options.pluginRegistryUrl ?? 'https://zhin.js.org/plugins.json';
|
|
20
|
-
const npmRegistryUrl = (options.npmRegistryUrl ?? 'https://registry.npmmirror.com')
|
|
21
|
-
.replace(/\/+$/u, '');
|
|
20
|
+
const npmRegistryUrl = trimTrailingSlashes(options.npmRegistryUrl ?? 'https://registry.npmmirror.com');
|
|
22
21
|
const disposers = [];
|
|
23
22
|
const route = (method, path, handler, meta) => {
|
|
24
23
|
const dispose = host.route(method, path, handler, meta);
|
|
@@ -672,10 +671,16 @@ function decodePathParam(raw) {
|
|
|
672
671
|
return raw;
|
|
673
672
|
}
|
|
674
673
|
}
|
|
674
|
+
function trimTrailingSlashes(s) {
|
|
675
|
+
let i = s.length;
|
|
676
|
+
while (i > 0 && s[i - 1] === '/')
|
|
677
|
+
i--;
|
|
678
|
+
return s.slice(0, i);
|
|
679
|
+
}
|
|
675
680
|
function normalizeBase(value) {
|
|
676
681
|
if (!value.startsWith('/'))
|
|
677
682
|
return `/${value}`;
|
|
678
|
-
return value
|
|
683
|
+
return trimTrailingSlashes(value) || '/api';
|
|
679
684
|
}
|
|
680
685
|
function writeJson(response, status, body) {
|
|
681
686
|
const payload = JSON.stringify(body);
|
package/lib/json-body.d.ts
CHANGED
|
@@ -1,12 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
readonly statusCode: number;
|
|
4
|
-
constructor(message: string, statusCode?: number);
|
|
5
|
-
}
|
|
6
|
-
/**
|
|
7
|
-
* Read and parse a JSON request body with a size limit (default 1 MiB).
|
|
8
|
-
* Empty bodies become `undefined`.
|
|
9
|
-
*/
|
|
10
|
-
export declare function readJsonBody<T = unknown>(request: IncomingMessage, options?: {
|
|
11
|
-
readonly limit?: number;
|
|
12
|
-
}): Promise<T | undefined>;
|
|
1
|
+
/** @deprecated Import from @zhin.js/host-http-contract in protocol hosts. */
|
|
2
|
+
export { HttpBodyError, readJsonBody } from '@zhin.js/host-http-contract';
|
package/lib/json-body.js
CHANGED
|
@@ -1,45 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
constructor(message, statusCode = 400) {
|
|
4
|
-
super(message);
|
|
5
|
-
this.statusCode = statusCode;
|
|
6
|
-
this.name = 'HttpBodyError';
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
/**
|
|
10
|
-
* Read and parse a JSON request body with a size limit (default 1 MiB).
|
|
11
|
-
* Empty bodies become `undefined`.
|
|
12
|
-
*/
|
|
13
|
-
export async function readJsonBody(request, options = {}) {
|
|
14
|
-
const limit = options.limit ?? 1_048_576;
|
|
15
|
-
const chunks = [];
|
|
16
|
-
let size = 0;
|
|
17
|
-
let exceeded = false;
|
|
18
|
-
for await (const chunk of request) {
|
|
19
|
-
// 超限后继续 drain 到 end:提前 break 会 destroy 底层 socket,
|
|
20
|
-
// 客户端将永远收不到后面的 413 响应。
|
|
21
|
-
if (exceeded)
|
|
22
|
-
continue;
|
|
23
|
-
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
24
|
-
size += buffer.length;
|
|
25
|
-
if (size > limit) {
|
|
26
|
-
exceeded = true;
|
|
27
|
-
chunks.length = 0;
|
|
28
|
-
continue;
|
|
29
|
-
}
|
|
30
|
-
chunks.push(buffer);
|
|
31
|
-
}
|
|
32
|
-
if (exceeded)
|
|
33
|
-
throw new HttpBodyError(`Request body exceeds ${limit} bytes`, 413);
|
|
34
|
-
if (chunks.length === 0)
|
|
35
|
-
return undefined;
|
|
36
|
-
const raw = Buffer.concat(chunks).toString('utf8').trim();
|
|
37
|
-
if (!raw)
|
|
38
|
-
return undefined;
|
|
39
|
-
try {
|
|
40
|
-
return JSON.parse(raw);
|
|
41
|
-
}
|
|
42
|
-
catch {
|
|
43
|
-
throw new HttpBodyError('Invalid JSON body', 400);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
1
|
+
/** @deprecated Import from @zhin.js/host-http-contract in protocol hosts. */
|
|
2
|
+
export { HttpBodyError, readJsonBody } from '@zhin.js/host-http-contract';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/host-http",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Minimal HTTP and WebSocket host for Plugin Runtime (no Koa)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -17,13 +17,14 @@
|
|
|
17
17
|
"src"
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"ws": "^8.21.
|
|
20
|
+
"ws": "^8.21.1",
|
|
21
21
|
"@zhin.js/console-protocol": "1.1.0",
|
|
22
|
+
"@zhin.js/host-http-contract": "1.0.0",
|
|
22
23
|
"@zhin.js/logger": "1.0.75",
|
|
23
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
24
|
+
"@zhin.js/plugin-runtime": "1.1.2"
|
|
24
25
|
},
|
|
25
26
|
"devDependencies": {
|
|
26
|
-
"@types/node": "^26.1.
|
|
27
|
+
"@types/node": "^26.1.2",
|
|
27
28
|
"@types/ws": "^8.18.1",
|
|
28
29
|
"typescript": "^6.0.3"
|
|
29
30
|
},
|
|
@@ -128,8 +128,9 @@ export function registerConsoleRestPages(
|
|
|
128
128
|
const base = normalizeBase(options.apiBase ?? '/api');
|
|
129
129
|
const fetchFn = options.fetchFn ?? fetch;
|
|
130
130
|
const pluginRegistryUrl = options.pluginRegistryUrl ?? 'https://zhin.js.org/plugins.json';
|
|
131
|
-
const npmRegistryUrl = (
|
|
132
|
-
.
|
|
131
|
+
const npmRegistryUrl = trimTrailingSlashes(
|
|
132
|
+
options.npmRegistryUrl ?? 'https://registry.npmmirror.com',
|
|
133
|
+
);
|
|
133
134
|
const disposers: Array<() => void> = [];
|
|
134
135
|
const route: typeof host.route = (method, path, handler, meta) => {
|
|
135
136
|
const dispose = host.route(method, path, handler, meta);
|
|
@@ -874,9 +875,15 @@ function decodePathParam(raw: string): string {
|
|
|
874
875
|
}
|
|
875
876
|
}
|
|
876
877
|
|
|
878
|
+
function trimTrailingSlashes(s: string): string {
|
|
879
|
+
let i = s.length;
|
|
880
|
+
while (i > 0 && s[i - 1] === '/') i--;
|
|
881
|
+
return s.slice(0, i);
|
|
882
|
+
}
|
|
883
|
+
|
|
877
884
|
function normalizeBase(value: string): string {
|
|
878
885
|
if (!value.startsWith('/')) return `/${value}`;
|
|
879
|
-
return value
|
|
886
|
+
return trimTrailingSlashes(value) || '/api';
|
|
880
887
|
}
|
|
881
888
|
|
|
882
889
|
function writeJson(response: ServerResponse, status: number, body: unknown): void {
|
package/src/json-body.ts
CHANGED
|
@@ -1,47 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export class HttpBodyError extends Error {
|
|
4
|
-
constructor(
|
|
5
|
-
message: string,
|
|
6
|
-
readonly statusCode: number = 400,
|
|
7
|
-
) {
|
|
8
|
-
super(message);
|
|
9
|
-
this.name = 'HttpBodyError';
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Read and parse a JSON request body with a size limit (default 1 MiB).
|
|
15
|
-
* Empty bodies become `undefined`.
|
|
16
|
-
*/
|
|
17
|
-
export async function readJsonBody<T = unknown>(
|
|
18
|
-
request: IncomingMessage,
|
|
19
|
-
options: { readonly limit?: number } = {},
|
|
20
|
-
): Promise<T | undefined> {
|
|
21
|
-
const limit = options.limit ?? 1_048_576;
|
|
22
|
-
const chunks: Buffer[] = [];
|
|
23
|
-
let size = 0;
|
|
24
|
-
let exceeded = false;
|
|
25
|
-
for await (const chunk of request) {
|
|
26
|
-
// 超限后继续 drain 到 end:提前 break 会 destroy 底层 socket,
|
|
27
|
-
// 客户端将永远收不到后面的 413 响应。
|
|
28
|
-
if (exceeded) continue;
|
|
29
|
-
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
30
|
-
size += buffer.length;
|
|
31
|
-
if (size > limit) {
|
|
32
|
-
exceeded = true;
|
|
33
|
-
chunks.length = 0;
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
chunks.push(buffer);
|
|
37
|
-
}
|
|
38
|
-
if (exceeded) throw new HttpBodyError(`Request body exceeds ${limit} bytes`, 413);
|
|
39
|
-
if (chunks.length === 0) return undefined;
|
|
40
|
-
const raw = Buffer.concat(chunks).toString('utf8').trim();
|
|
41
|
-
if (!raw) return undefined;
|
|
42
|
-
try {
|
|
43
|
-
return JSON.parse(raw) as T;
|
|
44
|
-
} catch {
|
|
45
|
-
throw new HttpBodyError('Invalid JSON body', 400);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
1
|
+
/** @deprecated Import from @zhin.js/host-http-contract in protocol hosts. */
|
|
2
|
+
export { HttpBodyError, readJsonBody } from '@zhin.js/host-http-contract';
|