@jsbot-tellus/core 2.5.0 → 2.6.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 +8 -5
- package/dist/app/cli.d.ts +10 -0
- package/dist/app/cli.d.ts.map +1 -0
- package/dist/app/cli.js +37 -0
- package/dist/app/cli.js.map +1 -0
- package/dist/app/runtime.d.ts +121 -0
- package/dist/app/runtime.d.ts.map +1 -0
- package/dist/app/runtime.js +296 -0
- package/dist/app/runtime.js.map +1 -0
- package/dist/app.js +63 -273
- package/dist/app.js.map +1 -1
- package/dist/core/plugin/command-params.d.ts +3 -37
- package/dist/core/plugin/command-params.d.ts.map +1 -1
- package/dist/core/plugin/command-params.js +2 -580
- package/dist/core/plugin/command-params.js.map +1 -1
- package/dist/core/plugin/install/paths.d.ts +7 -0
- package/dist/core/plugin/install/paths.d.ts.map +1 -0
- package/dist/core/plugin/install/paths.js +49 -0
- package/dist/core/plugin/install/paths.js.map +1 -0
- package/dist/core/plugin/install/state.d.ts +23 -0
- package/dist/core/plugin/install/state.d.ts.map +1 -0
- package/dist/core/plugin/install/state.js +129 -0
- package/dist/core/plugin/install/state.js.map +1 -0
- package/dist/core/plugin/install/validate-script.d.ts +3 -0
- package/dist/core/plugin/install/validate-script.d.ts.map +1 -0
- package/dist/core/plugin/install/validate-script.js +154 -0
- package/dist/core/plugin/install/validate-script.js.map +1 -0
- package/dist/core/plugin/install-dir.d.ts +3 -28
- package/dist/core/plugin/install-dir.d.ts.map +1 -1
- package/dist/core/plugin/install-dir.js +3 -326
- package/dist/core/plugin/install-dir.js.map +1 -1
- package/dist/core/plugin/installer/checks.d.ts +6 -0
- package/dist/core/plugin/installer/checks.d.ts.map +1 -0
- package/dist/core/plugin/installer/checks.js +99 -0
- package/dist/core/plugin/installer/checks.js.map +1 -0
- package/dist/core/plugin/installer/support.d.ts +37 -0
- package/dist/core/plugin/installer/support.d.ts.map +1 -0
- package/dist/core/plugin/installer/support.js +67 -0
- package/dist/core/plugin/installer/support.js.map +1 -0
- package/dist/core/plugin/installer/warnings.d.ts +5 -0
- package/dist/core/plugin/installer/warnings.d.ts.map +1 -0
- package/dist/core/plugin/installer/warnings.js +28 -0
- package/dist/core/plugin/installer/warnings.js.map +1 -0
- package/dist/core/plugin/installer.d.ts +4 -30
- package/dist/core/plugin/installer.d.ts.map +1 -1
- package/dist/core/plugin/installer.js +21 -202
- package/dist/core/plugin/installer.js.map +1 -1
- package/dist/core/plugin/params/compile.d.ts +3 -0
- package/dist/core/plugin/params/compile.d.ts.map +1 -0
- package/dist/core/plugin/params/compile.js +23 -0
- package/dist/core/plugin/params/compile.js.map +1 -0
- package/dist/core/plugin/params/declaration.d.ts +9 -0
- package/dist/core/plugin/params/declaration.d.ts.map +1 -0
- package/dist/core/plugin/params/declaration.js +286 -0
- package/dist/core/plugin/params/declaration.js.map +1 -0
- package/dist/core/plugin/params/input.d.ts +5 -0
- package/dist/core/plugin/params/input.d.ts.map +1 -0
- package/dist/core/plugin/params/input.js +196 -0
- package/dist/core/plugin/params/input.js.map +1 -0
- package/dist/core/plugin/params/reply.d.ts +3 -0
- package/dist/core/plugin/params/reply.d.ts.map +1 -0
- package/dist/core/plugin/params/reply.js +49 -0
- package/dist/core/plugin/params/reply.js.map +1 -0
- package/dist/core/plugin/params/type-level.d.ts +24 -0
- package/dist/core/plugin/params/type-level.d.ts.map +1 -0
- package/dist/core/plugin/params/type-level.js +2 -0
- package/dist/core/plugin/params/type-level.js.map +1 -0
- package/dist/core/plugin/params/types.d.ts +33 -0
- package/dist/core/plugin/params/types.d.ts.map +1 -0
- package/dist/core/plugin/params/types.js +12 -0
- package/dist/core/plugin/params/types.js.map +1 -0
- package/dist/core/plugin/params/values.d.ts +7 -0
- package/dist/core/plugin/params/values.d.ts.map +1 -0
- package/dist/core/plugin/params/values.js +111 -0
- package/dist/core/plugin/params/values.js.map +1 -0
- package/dist/core/web/api.d.ts +2 -14
- package/dist/core/web/api.d.ts.map +1 -1
- package/dist/core/web/api.js +8 -500
- package/dist/core/web/api.js.map +1 -1
- package/dist/core/web/routes/actions.d.ts +4 -0
- package/dist/core/web/routes/actions.d.ts.map +1 -0
- package/dist/core/web/routes/actions.js +33 -0
- package/dist/core/web/routes/actions.js.map +1 -0
- package/dist/core/web/routes/config.d.ts +5 -0
- package/dist/core/web/routes/config.d.ts.map +1 -0
- package/dist/core/web/routes/config.js +30 -0
- package/dist/core/web/routes/config.js.map +1 -0
- package/dist/core/web/routes/kv.d.ts +6 -0
- package/dist/core/web/routes/kv.d.ts.map +1 -0
- package/dist/core/web/routes/kv.js +80 -0
- package/dist/core/web/routes/kv.js.map +1 -0
- package/dist/core/web/routes/plugins.d.ts +5 -0
- package/dist/core/web/routes/plugins.d.ts.map +1 -0
- package/dist/core/web/routes/plugins.js +98 -0
- package/dist/core/web/routes/plugins.js.map +1 -0
- package/dist/core/web/routes/sessions.d.ts +8 -0
- package/dist/core/web/routes/sessions.d.ts.map +1 -0
- package/dist/core/web/routes/sessions.js +130 -0
- package/dist/core/web/routes/sessions.js.map +1 -0
- package/dist/core/web/routes/shared.d.ts +18 -0
- package/dist/core/web/routes/shared.d.ts.map +1 -0
- package/dist/core/web/routes/shared.js +19 -0
- package/dist/core/web/routes/shared.js.map +1 -0
- package/dist/core/web/routes/status.d.ts +6 -0
- package/dist/core/web/routes/status.d.ts.map +1 -0
- package/dist/core/web/routes/status.js +132 -0
- package/dist/core/web/routes/status.js.map +1 -0
- package/dist/core/web/server/auth.d.ts +17 -0
- package/dist/core/web/server/auth.d.ts.map +1 -0
- package/dist/core/web/server/auth.js +68 -0
- package/dist/core/web/server/auth.js.map +1 -0
- package/dist/core/web/server/csp.d.ts +11 -0
- package/dist/core/web/server/csp.d.ts.map +1 -0
- package/dist/core/web/server/csp.js +31 -0
- package/dist/core/web/server/csp.js.map +1 -0
- package/dist/core/web/server/request.d.ts +6 -0
- package/dist/core/web/server/request.d.ts.map +1 -0
- package/dist/core/web/server/request.js +33 -0
- package/dist/core/web/server/request.js.map +1 -0
- package/dist/core/web/server/security.d.ts +4 -0
- package/dist/core/web/server/security.d.ts.map +1 -0
- package/dist/core/web/server/security.js +12 -0
- package/dist/core/web/server/security.js.map +1 -0
- package/dist/core/web/server/sse.d.ts +18 -0
- package/dist/core/web/server/sse.d.ts.map +1 -0
- package/dist/core/web/server/sse.js +49 -0
- package/dist/core/web/server/sse.js.map +1 -0
- package/dist/core/web/server/static.d.ts +16 -0
- package/dist/core/web/server/static.d.ts.map +1 -0
- package/dist/core/web/server/static.js +80 -0
- package/dist/core/web/server/static.js.map +1 -0
- package/dist/core/web/server.d.ts +0 -7
- package/dist/core/web/server.d.ts.map +1 -1
- package/dist/core/web/server.js +41 -263
- package/dist/core/web/server.js.map +1 -1
- package/docs/architecture.md +28 -11
- package/docs/configuration.md +1 -1
- package/docs/migration.md +2 -2
- package/docs/plugin-api.md +24 -11
- package/docs/plugin-development.md +1 -1
- package/package.json +19 -17
- package/src/app/cli.ts +47 -0
- package/src/app/runtime.ts +522 -0
- package/src/app.ts +87 -395
- package/src/core/plugin/command-params.ts +8 -871
- package/src/core/plugin/install/paths.ts +72 -0
- package/src/core/plugin/install/state.ts +196 -0
- package/src/core/plugin/install/validate-script.ts +154 -0
- package/src/core/plugin/install-dir.ts +19 -427
- package/src/core/plugin/installer/checks.ts +137 -0
- package/src/core/plugin/installer/support.ts +120 -0
- package/src/core/plugin/installer/warnings.ts +52 -0
- package/src/core/plugin/installer.ts +52 -301
- package/src/core/plugin/params/compile.ts +41 -0
- package/src/core/plugin/params/declaration.ts +394 -0
- package/src/core/plugin/params/input.ts +281 -0
- package/src/core/plugin/params/reply.ts +59 -0
- package/src/core/plugin/params/type-level.ts +75 -0
- package/src/core/plugin/params/types.ts +55 -0
- package/src/core/plugin/params/values.ts +150 -0
- package/src/core/web/api.ts +33 -759
- package/src/core/web/routes/actions.ts +43 -0
- package/src/core/web/routes/config.ts +42 -0
- package/src/core/web/routes/kv.ts +122 -0
- package/src/core/web/routes/plugins.ts +151 -0
- package/src/core/web/routes/sessions.ts +188 -0
- package/src/core/web/routes/shared.ts +42 -0
- package/src/core/web/routes/status.ts +191 -0
- package/src/core/web/server/auth.ts +126 -0
- package/src/core/web/server/csp.ts +33 -0
- package/src/core/web/server/request.ts +38 -0
- package/src/core/web/server/security.ts +14 -0
- package/src/core/web/server/sse.ts +74 -0
- package/src/core/web/server/static.ts +112 -0
- package/src/core/web/server.ts +41 -383
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { AuthManager } from '../auth.js';
|
|
2
|
+
import { clearCookie, clientIp, parseCookies, readJsonBody, sendData, sendError, serializeCookie, } from '../http.js';
|
|
3
|
+
import { HttpError } from '../http.js';
|
|
4
|
+
export const SESSION_COOKIE = 'jsbot_webui';
|
|
5
|
+
export function authenticate(auth, req) {
|
|
6
|
+
const cookies = parseCookies(req.headers.cookie);
|
|
7
|
+
return auth.authenticate(cookies[SESSION_COOKIE]);
|
|
8
|
+
}
|
|
9
|
+
export function tokenOf(req) {
|
|
10
|
+
const cookies = parseCookies(req.headers.cookie);
|
|
11
|
+
return cookies[SESSION_COOKIE];
|
|
12
|
+
}
|
|
13
|
+
export function isSecure(deps, req) {
|
|
14
|
+
if (!deps.getWebUiConfig().trustProxy)
|
|
15
|
+
return false;
|
|
16
|
+
const proto = req.headers['x-forwarded-proto'];
|
|
17
|
+
return typeof proto === 'string' && proto.split(',')[0]?.trim() === 'https';
|
|
18
|
+
}
|
|
19
|
+
export async function handleAuth(options) {
|
|
20
|
+
const { req, res, action, auth, deps, bodyLimit } = options;
|
|
21
|
+
const method = req.method ?? 'GET';
|
|
22
|
+
if (action === 'login' && method === 'POST') {
|
|
23
|
+
const body = await readJsonBody(req, bodyLimit);
|
|
24
|
+
const value = typeof body === 'object' && body !== null
|
|
25
|
+
? body
|
|
26
|
+
: {};
|
|
27
|
+
const ip = clientIp(req, deps.getWebUiConfig().trustProxy);
|
|
28
|
+
const result = await auth.login(value['username'], value['password'], ip);
|
|
29
|
+
if (!result.ok || !result.session) {
|
|
30
|
+
const headers = {};
|
|
31
|
+
if (result.retryAfterSeconds !== undefined) {
|
|
32
|
+
headers['retry-after'] = String(result.retryAfterSeconds);
|
|
33
|
+
}
|
|
34
|
+
sendError(res, result.status, result.message ?? 'login failed', undefined, headers);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
res.setHeader('set-cookie', serializeCookie(SESSION_COOKIE, result.session.token, {
|
|
38
|
+
maxAgeSeconds: deps.getWebUiConfig().sessionTtlHours * 3600,
|
|
39
|
+
secure: isSecure(deps, req),
|
|
40
|
+
}));
|
|
41
|
+
sendData(res, result.status, {
|
|
42
|
+
username: result.session.username,
|
|
43
|
+
expiresAt: result.session.expiresAt,
|
|
44
|
+
});
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (action === 'logout' && method === 'POST') {
|
|
48
|
+
const session = authenticate(auth, req);
|
|
49
|
+
auth.logout(tokenOf(req));
|
|
50
|
+
deps.logger.info(`webui logout: ${session?.username ?? 'unknown'}`);
|
|
51
|
+
res.setHeader('set-cookie', clearCookie(SESSION_COOKIE));
|
|
52
|
+
sendData(res, 200, { ok: true });
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (action === 'me' && method === 'GET') {
|
|
56
|
+
const session = authenticate(auth, req);
|
|
57
|
+
if (session === null) {
|
|
58
|
+
throw new HttpError(401, '未登录或会话已过期');
|
|
59
|
+
}
|
|
60
|
+
sendData(res, 200, {
|
|
61
|
+
username: session.username,
|
|
62
|
+
expiresAt: session.expiresAt,
|
|
63
|
+
});
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
throw new HttpError(404, `not found: ${method} /api/auth/${action ?? ''}`);
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../../src/core/web/server/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSP for the WebUI. webui/index.html ships a single authorized inline
|
|
3
|
+
* script (the no-flash appearance bootstrap for the first paint); its exact
|
|
4
|
+
* hash is derived from the built index.html on disk so editing the bootstrap
|
|
5
|
+
* only needs a rebuild, never a source edit. Every other inline script
|
|
6
|
+
* stays blocked.
|
|
7
|
+
*/
|
|
8
|
+
export declare function buildCsp(scriptSrc: string): string;
|
|
9
|
+
/** Extracts the bootstrap script's sha256 from a built index.html. */
|
|
10
|
+
export declare function cspForStaticDir(staticDir: string): string | null;
|
|
11
|
+
//# sourceMappingURL=csp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csp.d.ts","sourceRoot":"","sources":["../../../../src/core/web/server/csp.ts"],"names":[],"mappings":"AAIA;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,sEAAsE;AACtE,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAgBhE"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
/**
|
|
5
|
+
* CSP for the WebUI. webui/index.html ships a single authorized inline
|
|
6
|
+
* script (the no-flash appearance bootstrap for the first paint); its exact
|
|
7
|
+
* hash is derived from the built index.html on disk so editing the bootstrap
|
|
8
|
+
* only needs a rebuild, never a source edit. Every other inline script
|
|
9
|
+
* stays blocked.
|
|
10
|
+
*/
|
|
11
|
+
export function buildCsp(scriptSrc) {
|
|
12
|
+
return `default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src ${scriptSrc}; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'`;
|
|
13
|
+
}
|
|
14
|
+
/** Extracts the bootstrap script's sha256 from a built index.html. */
|
|
15
|
+
export function cspForStaticDir(staticDir) {
|
|
16
|
+
try {
|
|
17
|
+
const html = readFileSync(join(staticDir, 'index.html'), 'utf8');
|
|
18
|
+
const match = /<script>([\s\S]*?)<\/script>/.exec(html);
|
|
19
|
+
const bootstrap = match?.[1];
|
|
20
|
+
if (bootstrap === undefined)
|
|
21
|
+
return null;
|
|
22
|
+
const hash = createHash('sha256')
|
|
23
|
+
.update(bootstrap, 'utf8')
|
|
24
|
+
.digest('base64');
|
|
25
|
+
return buildCsp(`'self' 'sha256-${hash}'`);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=csp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csp.js","sourceRoot":"","sources":["../../../../src/core/web/server/csp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEhC;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAC,SAAiB;IACtC,OAAO,0FAA0F,SAAS,mFAAmF,CAAA;AACjM,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,eAAe,CAAC,SAAiB;IAC7C,IAAI,CAAC;QACD,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,EAAE,MAAM,CAAC,CAAA;QAChE,MAAM,KAAK,GAAG,8BAA8B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACvD,MAAM,SAAS,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,CAAA;QAE5B,IAAI,SAAS,KAAK,SAAS;YAAE,OAAO,IAAI,CAAA;QAExC,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC;aAC5B,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC;aACzB,MAAM,CAAC,QAAQ,CAAC,CAAA;QAErB,OAAO,QAAQ,CAAC,kBAAkB,IAAI,GAAG,CAAC,CAAA;IAC9C,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,IAAI,CAAA;IACf,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { IncomingMessage } from 'node:http';
|
|
2
|
+
import type { URLSearchParamsLike } from '../http.js';
|
|
3
|
+
export declare function parseSearch(req: IncomingMessage): URLSearchParamsLike;
|
|
4
|
+
export declare function segmentsOf(pathname: string): string[];
|
|
5
|
+
export declare function pathnameOf(req: IncomingMessage): string;
|
|
6
|
+
//# sourceMappingURL=request.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../../../src/core/web/server/request.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAErD,wBAAgB,WAAW,CAAC,GAAG,EAAE,eAAe,GAAG,mBAAmB,CAKrE;AAED,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAWrD;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,eAAe,GAAG,MAAM,CAcvD"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export function parseSearch(req) {
|
|
2
|
+
const index = (req.url ?? '').indexOf('?');
|
|
3
|
+
const query = index >= 0 ? (req.url ?? '').slice(index + 1) : '';
|
|
4
|
+
return new URLSearchParams(query);
|
|
5
|
+
}
|
|
6
|
+
export function segmentsOf(pathname) {
|
|
7
|
+
return pathname
|
|
8
|
+
.split('/')
|
|
9
|
+
.filter((segment) => segment.length > 0)
|
|
10
|
+
.map((segment) => {
|
|
11
|
+
try {
|
|
12
|
+
return decodeURIComponent(segment);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return segment;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
export function pathnameOf(req) {
|
|
20
|
+
const url = req.url ?? '/';
|
|
21
|
+
const index = url.indexOf('?');
|
|
22
|
+
const raw = index >= 0 ? url.slice(0, index) : url;
|
|
23
|
+
if (raw.includes('%')) {
|
|
24
|
+
try {
|
|
25
|
+
return decodeURIComponent(raw);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return raw;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return raw;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=request.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request.js","sourceRoot":"","sources":["../../../../src/core/web/server/request.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,WAAW,CAAC,GAAoB;IAC5C,MAAM,KAAK,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAC1C,MAAM,KAAK,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IAEhE,OAAO,IAAI,eAAe,CAAC,KAAK,CAAC,CAAA;AACrC,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,QAAgB;IACvC,OAAO,QAAQ;SACV,KAAK,CAAC,GAAG,CAAC;SACV,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;SACvC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;QACb,IAAI,CAAC;YACD,OAAO,kBAAkB,CAAC,OAAO,CAAC,CAAA;QACtC,CAAC;QAAC,MAAM,CAAC;YACL,OAAO,OAAO,CAAA;QAClB,CAAC;IACL,CAAC,CAAC,CAAA;AACV,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,GAAoB;IAC3C,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,IAAI,GAAG,CAAA;IAC1B,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAC9B,MAAM,GAAG,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAA;IAElD,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACpB,IAAI,CAAC;YACD,OAAO,kBAAkB,CAAC,GAAG,CAAC,CAAA;QAClC,CAAC;QAAC,MAAM,CAAC;YACL,OAAO,GAAG,CAAA;QACd,CAAC;IACL,CAAC;IAED,OAAO,GAAG,CAAA;AACd,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../../../src/core/web/server/security.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAE/C,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAKnD,CAAA;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,cAAc,GAAG,IAAI,CAI9D"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const SECURITY_HEADERS = {
|
|
2
|
+
'x-content-type-options': 'nosniff',
|
|
3
|
+
'x-frame-options': 'DENY',
|
|
4
|
+
'referrer-policy': 'no-referrer',
|
|
5
|
+
'cross-origin-opener-policy': 'same-origin',
|
|
6
|
+
};
|
|
7
|
+
export function applySecurityHeaders(res) {
|
|
8
|
+
for (const [key, value] of Object.entries(SECURITY_HEADERS)) {
|
|
9
|
+
res.setHeader(key, value);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=security.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security.js","sourceRoot":"","sources":["../../../../src/core/web/server/security.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,gBAAgB,GAA2B;IACpD,wBAAwB,EAAE,SAAS;IACnC,iBAAiB,EAAE,MAAM;IACzB,iBAAiB,EAAE,aAAa;IAChC,4BAA4B,EAAE,aAAa;CAC9C,CAAA;AAED,MAAM,UAAU,oBAAoB,CAAC,GAAmB;IACpD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,EAAE,CAAC;QAC1D,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;IAC7B,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
export declare const SSE_HEARTBEAT_MS = 15000;
|
|
3
|
+
export declare const DEFAULT_MAX_SSE_CLIENTS = 20;
|
|
4
|
+
export declare const SSE_TIMEOUT_MS = 60000;
|
|
5
|
+
export interface SseConnection {
|
|
6
|
+
res: ServerResponse;
|
|
7
|
+
close(): void;
|
|
8
|
+
}
|
|
9
|
+
export interface OpenSseOptions {
|
|
10
|
+
req: IncomingMessage;
|
|
11
|
+
res: ServerResponse;
|
|
12
|
+
subscribe: (listener: (entry: unknown) => void) => () => void;
|
|
13
|
+
openedAt: number;
|
|
14
|
+
maxClients: number;
|
|
15
|
+
connections: Set<SseConnection>;
|
|
16
|
+
}
|
|
17
|
+
export declare function openSse(options: OpenSseOptions): void;
|
|
18
|
+
//# sourceMappingURL=sse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../../../../src/core/web/server/sse.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAGhE,eAAO,MAAM,gBAAgB,QAAS,CAAA;AACtC,eAAO,MAAM,uBAAuB,KAAK,CAAA;AACzC,eAAO,MAAM,cAAc,QAAS,CAAA;AAEpC,MAAM,WAAW,aAAa;IAC1B,GAAG,EAAE,cAAc,CAAA;IACnB,KAAK,IAAI,IAAI,CAAA;CAChB;AAED,MAAM,WAAW,cAAc;IAC3B,GAAG,EAAE,eAAe,CAAA;IACpB,GAAG,EAAE,cAAc,CAAA;IACnB,SAAS,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,KAAK,MAAM,IAAI,CAAA;IAC7D,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,GAAG,CAAC,aAAa,CAAC,CAAA;CAClC;AAED,wBAAgB,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,CAoDrD"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { HttpError, safeStringify } from '../http.js';
|
|
2
|
+
export const SSE_HEARTBEAT_MS = 15_000;
|
|
3
|
+
export const DEFAULT_MAX_SSE_CLIENTS = 20;
|
|
4
|
+
export const SSE_TIMEOUT_MS = 60_000;
|
|
5
|
+
export function openSse(options) {
|
|
6
|
+
const { req, res, subscribe, openedAt, maxClients, connections } = options;
|
|
7
|
+
if (connections.size >= maxClients) {
|
|
8
|
+
throw new HttpError(503, '实时连接数量已达上限');
|
|
9
|
+
}
|
|
10
|
+
res.writeHead(200, {
|
|
11
|
+
'content-type': 'text/event-stream; charset=utf-8',
|
|
12
|
+
'cache-control': 'no-store, no-transform',
|
|
13
|
+
connection: 'keep-alive',
|
|
14
|
+
});
|
|
15
|
+
res.write(':ok\n\n');
|
|
16
|
+
const connection = {
|
|
17
|
+
res,
|
|
18
|
+
close: () => {
|
|
19
|
+
unsubscribe();
|
|
20
|
+
clearInterval(heartbeat);
|
|
21
|
+
connections.delete(connection);
|
|
22
|
+
try {
|
|
23
|
+
res.end();
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// Already closed.
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
let closing = false;
|
|
31
|
+
const unsubscribe = subscribe((entry) => {
|
|
32
|
+
if (closing)
|
|
33
|
+
return;
|
|
34
|
+
res.write(`data: ${safeStringify(entry)}\n\n`);
|
|
35
|
+
});
|
|
36
|
+
const heartbeat = setInterval(() => {
|
|
37
|
+
res.write(`:keepalive ${Date.now() - openedAt}\n\n`);
|
|
38
|
+
}, SSE_HEARTBEAT_MS);
|
|
39
|
+
req.on('close', () => {
|
|
40
|
+
if (closing)
|
|
41
|
+
return;
|
|
42
|
+
closing = true;
|
|
43
|
+
unsubscribe();
|
|
44
|
+
clearInterval(heartbeat);
|
|
45
|
+
connections.delete(connection);
|
|
46
|
+
});
|
|
47
|
+
connections.add(connection);
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=sse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse.js","sourceRoot":"","sources":["../../../../src/core/web/server/sse.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAErD,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAM,CAAA;AACtC,MAAM,CAAC,MAAM,uBAAuB,GAAG,EAAE,CAAA;AACzC,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAA;AAgBpC,MAAM,UAAU,OAAO,CAAC,OAAuB;IAC3C,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,GAAG,OAAO,CAAA;IAE1E,IAAI,WAAW,CAAC,IAAI,IAAI,UAAU,EAAE,CAAC;QACjC,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,YAAY,CAAC,CAAA;IAC1C,CAAC;IAED,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;QACf,cAAc,EAAE,kCAAkC;QAClD,eAAe,EAAE,wBAAwB;QACzC,UAAU,EAAE,YAAY;KAC3B,CAAC,CAAA;IACF,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;IAEpB,MAAM,UAAU,GAAkB;QAC9B,GAAG;QACH,KAAK,EAAE,GAAG,EAAE;YACR,WAAW,EAAE,CAAA;YACb,aAAa,CAAC,SAAS,CAAC,CAAA;YACxB,WAAW,CAAC,MAAM,CAAC,UAAU,CAAC,CAAA;YAE9B,IAAI,CAAC;gBACD,GAAG,CAAC,GAAG,EAAE,CAAA;YACb,CAAC;YAAC,MAAM,CAAC;gBACL,kBAAkB;YACtB,CAAC;QACL,CAAC;KACJ,CAAA;IAED,IAAI,OAAO,GAAG,KAAK,CAAA;IAEnB,MAAM,WAAW,GAAG,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE;QACpC,IAAI,OAAO;YAAE,OAAM;QAEnB,GAAG,CAAC,KAAK,CAAC,SAAS,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;IAClD,CAAC,CAAC,CAAA;IAEF,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE;QAC/B,GAAG,CAAC,KAAK,CAAC,cAAc,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,MAAM,CAAC,CAAA;IACxD,CAAC,EAAE,gBAAgB,CAAC,CAAA;IAEpB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;QACjB,IAAI,OAAO;YAAE,OAAM;QAEnB,OAAO,GAAG,IAAI,CAAA;QAEd,WAAW,EAAE,CAAA;QACb,aAAa,CAAC,SAAS,CAAC,CAAA;QACxB,WAAW,CAAC,MAAM,CAAC,UAAU,CAAC,CAAA;IAClC,CAAC,CAAC,CAAA;IAEF,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;AAC/B,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { stat } from 'node:fs/promises';
|
|
2
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
3
|
+
export interface FallbackPageOptions {
|
|
4
|
+
listening: string;
|
|
5
|
+
}
|
|
6
|
+
export declare const FALLBACK_BODY: (options: FallbackPageOptions) => string;
|
|
7
|
+
export interface ServeStaticOptions {
|
|
8
|
+
req: IncomingMessage;
|
|
9
|
+
res: ServerResponse;
|
|
10
|
+
pathname: string;
|
|
11
|
+
staticDir: string | null;
|
|
12
|
+
fallbackBody: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function serveStatic(options: ServeStaticOptions): Promise<void>;
|
|
15
|
+
export declare function statOrNull(filePath: string): Promise<Awaited<ReturnType<typeof stat>> | null>;
|
|
16
|
+
//# sourceMappingURL=static.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"static.d.ts","sourceRoot":"","sources":["../../../../src/core/web/server/static.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAA;AACvC,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAIhE,MAAM,WAAW,mBAAmB;IAChC,SAAS,EAAE,MAAM,CAAA;CACpB;AAED,eAAO,MAAM,aAAa,YACb,mBAAmB,KAC7B,MAWF,CAAA;AAED,MAAM,WAAW,kBAAkB;IAC/B,GAAG,EAAE,eAAe,CAAA;IACpB,GAAG,EAAE,cAAc,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,YAAY,EAAE,MAAM,CAAA;CACvB;AAED,wBAAsB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoE5E;AAED,wBAAsB,UAAU,CAC5B,QAAQ,EAAE,MAAM,GACjB,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAMlD"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { createReadStream } from 'node:fs';
|
|
2
|
+
import { stat } from 'node:fs/promises';
|
|
3
|
+
import { extname, join, resolve, sep } from 'node:path';
|
|
4
|
+
import { HttpError, isImmutableAsset, mimeType, withTimeout } from '../http.js';
|
|
5
|
+
export const FALLBACK_BODY = (options) => `<!doctype html>
|
|
6
|
+
<html lang="zh-CN"><head><meta charset="utf-8">
|
|
7
|
+
<title>JsBot WebUI</title>
|
|
8
|
+
<style>body{font-family:system-ui,sans-serif;max-width:36em;margin:4rem auto;padding:0 1rem;color:#333}</style>
|
|
9
|
+
</head><body>
|
|
10
|
+
<h1>WebUI 前端未构建</h1>
|
|
11
|
+
<p>管理 API 正常运行,但静态资源缺失。请在项目根目录执行:</p>
|
|
12
|
+
<pre><code>npm run build:webui</code></pre>
|
|
13
|
+
<p>然后重启进程,访问 <code>${options.listening}</code>。</p>
|
|
14
|
+
<p>API 仍可通过 <code>/api/health</code>、登录和各接口访问。</p>
|
|
15
|
+
</body></html>
|
|
16
|
+
`;
|
|
17
|
+
export async function serveStatic(options) {
|
|
18
|
+
const { req, res, pathname, staticDir, fallbackBody } = options;
|
|
19
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
20
|
+
throw new HttpError(405, `method not allowed: ${req.method}`);
|
|
21
|
+
}
|
|
22
|
+
if (staticDir === null) {
|
|
23
|
+
res.setHeader('content-type', 'text/html; charset=utf-8');
|
|
24
|
+
res.setHeader('cache-control', 'no-store');
|
|
25
|
+
res.writeHead(503);
|
|
26
|
+
res.end(fallbackBody);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const cleaned = pathname.replace(/\/+$/, '') || '/';
|
|
30
|
+
let filePath = cleaned === '/'
|
|
31
|
+
? join(staticDir, 'index.html')
|
|
32
|
+
: resolve(staticDir, `.${cleaned}`);
|
|
33
|
+
if (!filePath.startsWith(staticDir + sep) && filePath !== staticDir) {
|
|
34
|
+
throw new HttpError(403, 'forbidden');
|
|
35
|
+
}
|
|
36
|
+
let fileStat = await statOrNull(filePath);
|
|
37
|
+
if (fileStat?.isDirectory()) {
|
|
38
|
+
filePath = join(filePath, 'index.html');
|
|
39
|
+
fileStat = await statOrNull(filePath);
|
|
40
|
+
}
|
|
41
|
+
if (fileStat === null) {
|
|
42
|
+
// SPA fallback: only routes without a file extension.
|
|
43
|
+
if (extname(cleaned) === '') {
|
|
44
|
+
filePath = join(staticDir, 'index.html');
|
|
45
|
+
fileStat = await statOrNull(filePath);
|
|
46
|
+
}
|
|
47
|
+
if (fileStat === null)
|
|
48
|
+
throw new HttpError(404, 'not found');
|
|
49
|
+
}
|
|
50
|
+
const cacheControl = isImmutableAsset(cleaned)
|
|
51
|
+
? 'public, max-age=31536000, immutable'
|
|
52
|
+
: 'no-cache';
|
|
53
|
+
res.setHeader('content-type', mimeType(filePath));
|
|
54
|
+
res.setHeader('cache-control', cacheControl);
|
|
55
|
+
res.setHeader('content-length', String(fileStat.size));
|
|
56
|
+
res.writeHead(200);
|
|
57
|
+
if (req.method === 'HEAD') {
|
|
58
|
+
res.end();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const stream = createReadStream(filePath);
|
|
62
|
+
stream.on('error', () => {
|
|
63
|
+
try {
|
|
64
|
+
res.end();
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// Already closed.
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
stream.pipe(res);
|
|
71
|
+
}
|
|
72
|
+
export async function statOrNull(filePath) {
|
|
73
|
+
try {
|
|
74
|
+
return await withTimeout(stat(filePath), 1_000);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=static.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"static.js","sourceRoot":"","sources":["../../../../src/core/web/server/static.ts"],"names":[],"mappings":"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"}
|
|
@@ -19,12 +19,5 @@ export declare class WebUiServer {
|
|
|
19
19
|
invalidateSessions(): void;
|
|
20
20
|
private applySecurityHeaders;
|
|
21
21
|
private handleRequest;
|
|
22
|
-
private authenticate;
|
|
23
|
-
private handleAuth;
|
|
24
|
-
private tokenOf;
|
|
25
|
-
private isSecure;
|
|
26
|
-
private openSse;
|
|
27
|
-
private serveStatic;
|
|
28
|
-
private statOrNull;
|
|
29
22
|
}
|
|
30
23
|
//# sourceMappingURL=server.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/core/web/server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/core/web/server.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,YAAY,EAAa,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAwB7E,qBAAa,WAAW;IACpB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAW;IAChC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAQ;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAQ;IAClC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAoB;IACrD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAoB;IAClD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAa;IAClC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA2B;IAC/C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAQ;IAErC,OAAO,CAAC,UAAU,CAAsB;IACxC,OAAO,CAAC,SAAS,CAA4B;IAC7C,OAAO,CAAC,QAAQ,CAAsB;IAEtC,YAAY,OAAO,EAAE,kBAAkB,EAoBtC;IAED,IAAI,OAAO,IAAI,YAAY,GAAG,IAAI,CAEjC;IAEK,KAAK,CACP,MAAM,EAAE,kBAAkB,CAAC,QAAQ,CAAC,GACrC,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CA0C9B;IAED,IAAI,eAAe,IAAI,OAAO,CAE7B;IAEK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAgB1B;IAED,kBAAkB,IAAI,IAAI,CAEzB;IAED,OAAO,CAAC,oBAAoB;YAgBd,aAAa;CAqG9B"}
|