@aalis/plugin-webui-server 0.14.1 → 0.16.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.
Files changed (118) hide show
  1. package/README.md +5 -1
  2. package/dist/admin-channel.d.ts +10 -0
  3. package/dist/admin-channel.d.ts.map +1 -0
  4. package/dist/admin-channel.js +57 -0
  5. package/dist/admin-channel.js.map +1 -0
  6. package/dist/attention.d.ts +36 -0
  7. package/dist/attention.d.ts.map +1 -0
  8. package/dist/attention.js +74 -0
  9. package/dist/attention.js.map +1 -0
  10. package/dist/auth.d.ts +32 -4
  11. package/dist/auth.d.ts.map +1 -1
  12. package/dist/auth.js +253 -79
  13. package/dist/auth.js.map +1 -1
  14. package/dist/client-discovery.d.ts +7 -3
  15. package/dist/client-discovery.d.ts.map +1 -1
  16. package/dist/client-discovery.js +9 -3
  17. package/dist/client-discovery.js.map +1 -1
  18. package/dist/client-switch-page.d.ts +26 -2
  19. package/dist/client-switch-page.d.ts.map +1 -1
  20. package/dist/client-switch-page.js +76 -45
  21. package/dist/client-switch-page.js.map +1 -1
  22. package/dist/gate.d.ts +3 -3
  23. package/dist/gate.d.ts.map +1 -1
  24. package/dist/gate.js +5 -4
  25. package/dist/gate.js.map +1 -1
  26. package/dist/html.d.ts +15 -0
  27. package/dist/html.d.ts.map +1 -0
  28. package/dist/html.js +38 -0
  29. package/dist/html.js.map +1 -0
  30. package/dist/index.d.ts +24 -26
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +627 -1102
  33. package/dist/index.js.map +1 -1
  34. package/dist/layout.d.ts +19 -0
  35. package/dist/layout.d.ts.map +1 -0
  36. package/dist/layout.js +200 -0
  37. package/dist/layout.js.map +1 -0
  38. package/dist/locales/en.d.ts +191 -0
  39. package/dist/locales/en.d.ts.map +1 -0
  40. package/dist/locales/en.js +191 -0
  41. package/dist/locales/en.js.map +1 -0
  42. package/dist/localize.d.ts +24 -0
  43. package/dist/localize.d.ts.map +1 -0
  44. package/dist/localize.js +68 -0
  45. package/dist/localize.js.map +1 -0
  46. package/dist/logs.d.ts +10 -0
  47. package/dist/logs.d.ts.map +1 -0
  48. package/dist/logs.js +50 -0
  49. package/dist/logs.js.map +1 -0
  50. package/dist/page-check.d.ts +56 -0
  51. package/dist/page-check.d.ts.map +1 -0
  52. package/dist/page-check.js +376 -0
  53. package/dist/page-check.js.map +1 -0
  54. package/dist/page-files.d.ts +13 -0
  55. package/dist/page-files.d.ts.map +1 -0
  56. package/dist/page-files.js +39 -0
  57. package/dist/page-files.js.map +1 -0
  58. package/dist/pages.d.ts +62 -0
  59. package/dist/pages.d.ts.map +1 -0
  60. package/dist/pages.js +486 -0
  61. package/dist/pages.js.map +1 -0
  62. package/dist/route.d.ts +14 -0
  63. package/dist/route.d.ts.map +1 -0
  64. package/dist/route.js +16 -0
  65. package/dist/route.js.map +1 -0
  66. package/dist/routes/files.d.ts +8 -9
  67. package/dist/routes/files.d.ts.map +1 -1
  68. package/dist/routes/files.js +33 -119
  69. package/dist/routes/files.js.map +1 -1
  70. package/dist/routes/frames.d.ts +4 -0
  71. package/dist/routes/frames.d.ts.map +1 -0
  72. package/dist/routes/frames.js +103 -0
  73. package/dist/routes/frames.js.map +1 -0
  74. package/dist/routes/i18n.d.ts +20 -0
  75. package/dist/routes/i18n.d.ts.map +1 -0
  76. package/dist/routes/i18n.js +78 -0
  77. package/dist/routes/i18n.js.map +1 -0
  78. package/dist/routes/marketplace.d.ts +46 -129
  79. package/dist/routes/marketplace.d.ts.map +1 -1
  80. package/dist/routes/marketplace.js +156 -106
  81. package/dist/routes/marketplace.js.map +1 -1
  82. package/dist/routes/media.d.ts +10 -0
  83. package/dist/routes/media.d.ts.map +1 -0
  84. package/dist/routes/media.js +66 -0
  85. package/dist/routes/media.js.map +1 -0
  86. package/dist/routes/pages.d.ts +22 -0
  87. package/dist/routes/pages.d.ts.map +1 -0
  88. package/dist/routes/pages.js +104 -0
  89. package/dist/routes/pages.js.map +1 -0
  90. package/dist/routes/pipe-to-response.d.ts +9 -0
  91. package/dist/routes/pipe-to-response.d.ts.map +1 -0
  92. package/dist/routes/pipe-to-response.js +16 -0
  93. package/dist/routes/pipe-to-response.js.map +1 -0
  94. package/dist/routes/plugins.d.ts +22 -28
  95. package/dist/routes/plugins.d.ts.map +1 -1
  96. package/dist/routes/plugins.js +339 -344
  97. package/dist/routes/plugins.js.map +1 -1
  98. package/dist/routes/proxy.d.ts +3 -2
  99. package/dist/routes/proxy.d.ts.map +1 -1
  100. package/dist/routes/proxy.js +64 -42
  101. package/dist/routes/proxy.js.map +1 -1
  102. package/dist/session-transport.d.ts +6 -0
  103. package/dist/session-transport.d.ts.map +1 -0
  104. package/dist/session-transport.js +32 -0
  105. package/dist/session-transport.js.map +1 -0
  106. package/dist/texts.d.ts +381 -0
  107. package/dist/texts.d.ts.map +1 -0
  108. package/dist/texts.js +194 -0
  109. package/dist/texts.js.map +1 -0
  110. package/package.json +38 -32
  111. package/dist/protocol.d.ts +0 -113
  112. package/dist/protocol.d.ts.map +0 -1
  113. package/dist/protocol.js +0 -59
  114. package/dist/protocol.js.map +0 -1
  115. package/dist/routes/uploaded-files.d.ts +0 -20
  116. package/dist/routes/uploaded-files.d.ts.map +0 -1
  117. package/dist/routes/uploaded-files.js +0 -161
  118. package/dist/routes/uploaded-files.js.map +0 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @aalis/plugin-webui-server
2
2
 
3
- Web 管理界面后端:对话/插件/服务/市场/文件
3
+ Web 管理界面后端:会话连接、管理面推送、插件、服务与市场
4
4
  ## 角色
5
5
 
6
6
  运行时插件
@@ -11,6 +11,10 @@ Web 管理界面后端:对话/插件/服务/市场/文件
11
11
  pnpm add @aalis/plugin-webui-server
12
12
  ```
13
13
 
14
+ ## 宿主路径
15
+
16
+ 以 `optional(hostPaths)` 声明 `host-paths`(`@aalis/api-host-paths`),从安装集扫描本机包与前端。宿主不提供时发现不到前端,整个 WebUI 前端不可用,只剩 HTTP 接口,市场与系统组件接口返回 503。Node 宿主 `@aalis/runtime` 总会提供它。
17
+
14
18
  ## 文档
15
19
 
16
20
  详见 [docs/plugins/plugin-webui-server.md](../../docs/plugins/plugin-webui-server.md)。
@@ -0,0 +1,10 @@
1
+ import type { LogFilter, WebuiAdminEvent } from '@aalis/api-webui';
2
+ import type { LogEntry, Logger } from '@aalis/core';
3
+ import { WebSocket } from 'ws';
4
+ export declare const ADMIN_PATH = "/admin";
5
+ /** match 按筛选给出判定日志的函数 */
6
+ export declare function createAdminChannel(logger: Logger, match: (filter: LogFilter) => (entry: LogEntry) => boolean): {
7
+ accept: (ws: WebSocket) => void;
8
+ send: (event: WebuiAdminEvent) => void;
9
+ };
10
+ //# sourceMappingURL=admin-channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-channel.d.ts","sourceRoot":"","sources":["../src/admin-channel.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAG/B,eAAO,MAAM,UAAU,WAAW,CAAC;AAYnC,yBAAyB;AACzB,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,CAAC,KAAK,EAAE,QAAQ,KAAK,OAAO;iBAKvF,SAAS,KAAG,IAAI;kBAgBf,eAAe,KAAG,IAAI;EAY5C"}
@@ -0,0 +1,57 @@
1
+ // ============================================================
2
+ // admin-channel.ts — /admin:管理面事件推送
3
+ //
4
+ // 会话之外的推送(日志、插件与页面登记表的变化、重启、换前端)走这条端点,与会话协议的 /session 分开。
5
+ // 事件类型见 @aalis/api-webui 的 WebuiAdminEvent:日志只推给订阅了、符合它订阅时的筛选的连接,其余事件推给全部连接。
6
+ // 前端发来的只有日志的订阅(可带筛选,再订阅一次换掉筛选)与退订;认不出的消息(含写法不对的筛选)记一行告警后忽略,
7
+ // 连接与之前的订阅照常,较新的前端连上较旧的服务端也不会被断开。
8
+ // ============================================================
9
+ import { WebSocket } from 'ws';
10
+ import { parseLogFilter } from './logs.js';
11
+ export const ADMIN_PATH = '/admin';
12
+ /** 帧按 JSON 读出的对象;不是 JSON 对象时为 undefined */
13
+ function messageOf(text) {
14
+ try {
15
+ const value = JSON.parse(text);
16
+ return typeof value === 'object' && value !== null ? value : undefined;
17
+ }
18
+ catch {
19
+ return undefined;
20
+ }
21
+ }
22
+ /** match 按筛选给出判定日志的函数 */
23
+ export function createAdminChannel(logger, match) {
24
+ const clients = new Set();
25
+ const logSubscribers = new Map();
26
+ /** 接一条已认证的 /admin 连接 */
27
+ function accept(ws) {
28
+ clients.add(ws);
29
+ ws.on('message', data => {
30
+ const message = messageOf(String(data));
31
+ const filter = message?.type === 'log.subscribe' ? parseLogFilter(message.filter) : undefined;
32
+ if (filter)
33
+ logSubscribers.set(ws, match(filter));
34
+ else if (message?.type === 'log.unsubscribe')
35
+ logSubscribers.delete(ws);
36
+ else
37
+ logger.warn('WebUI /admin 收到认不出的消息,已忽略');
38
+ });
39
+ ws.on('close', () => {
40
+ clients.delete(ws);
41
+ logSubscribers.delete(ws);
42
+ });
43
+ }
44
+ /** 推一个事件:日志推给订阅了、符合筛选的连接,其余推给全部连接 */
45
+ function send(event) {
46
+ const json = JSON.stringify(event);
47
+ const targets = event.type === 'log'
48
+ ? [...logSubscribers].flatMap(([ws, accepts]) => (accepts(event.entry) ? [ws] : []))
49
+ : clients;
50
+ for (const ws of targets) {
51
+ if (ws.readyState === WebSocket.OPEN)
52
+ ws.send(json);
53
+ }
54
+ }
55
+ return { accept, send };
56
+ }
57
+ //# sourceMappingURL=admin-channel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-channel.js","sourceRoot":"","sources":["../src/admin-channel.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,oCAAoC;AACpC,EAAE;AACF,0DAA0D;AAC1D,6EAA6E;AAC7E,4DAA4D;AAC5D,kCAAkC;AAClC,+DAA+D;AAI/D,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC/B,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAE3C,MAAM,CAAC,MAAM,UAAU,GAAG,QAAQ,CAAC;AAEnC,2CAA2C;AAC3C,SAAS,SAAS,CAAC,IAAY;IAC7B,IAAI,CAAC;QACH,MAAM,KAAK,GAAY,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACxC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAC,CAAC,CAAE,KAAiC,CAAC,CAAC,CAAC,SAAS,CAAC;IACtG,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,yBAAyB;AACzB,MAAM,UAAU,kBAAkB,CAAC,MAAc,EAAE,KAA0D;IAC3G,MAAM,OAAO,GAAG,IAAI,GAAG,EAAa,CAAC;IACrC,MAAM,cAAc,GAAG,IAAI,GAAG,EAA2C,CAAC;IAE1E,wBAAwB;IACxB,SAAS,MAAM,CAAC,EAAa;QAC3B,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChB,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,IAAI,CAAC,EAAE;YACtB,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;YACxC,MAAM,MAAM,GAAG,OAAO,EAAE,IAAI,KAAK,eAAe,CAAC,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YAC9F,IAAI,MAAM;gBAAE,cAAc,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;iBAC7C,IAAI,OAAO,EAAE,IAAI,KAAK,iBAAiB;gBAAE,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;;gBACnE,MAAM,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC;QAChD,CAAC,CAAC,CAAC;QACH,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YAClB,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACnB,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;IACL,CAAC;IAED,qCAAqC;IACrC,SAAS,IAAI,CAAC,KAAsB;QAClC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACnC,MAAM,OAAO,GACX,KAAK,CAAC,IAAI,KAAK,KAAK;YAClB,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YACpF,CAAC,CAAC,OAAO,CAAC;QACd,KAAK,MAAM,EAAE,IAAI,OAAO,EAAE,CAAC;YACzB,IAAI,EAAE,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI;gBAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtD,CAAC;IACH,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,36 @@
1
+ import type { Approval } from '@aalis/api-approval';
2
+ import type { HostConfig } from '@aalis/api-host-config';
3
+ import { type SessionManagerService } from '@aalis/api-session-manager';
4
+ import type { AttentionItem, Notice, PluginBlockers, WebuiAdminEvent } from '@aalis/api-webui';
5
+ import type { PluginManagerService, PluginStatusEntry, ServiceSelection, Services } from '@aalis/core';
6
+ /** 算插件没起来的原因要读的:插件状态与定义、当前有提供者的服务名、配置文档(宿主不提供时读运行态配置) */
7
+ interface BlockerSources {
8
+ plugins: Pick<PluginManagerService, 'getStatus' | 'getPlugin'>;
9
+ services: Pick<Services, 'names'>;
10
+ doc?: Pick<HostConfig, 'getPluginConfig'>;
11
+ }
12
+ /** 插件状态快照,每个实例带上没起来的原因 */
13
+ export declare function blockedStatus({ plugins, services, doc }: BlockerSources): Array<PluginStatusEntry & PluginBlockers>;
14
+ interface AttentionDeps {
15
+ /** 推出未结的审批;审批服务不在时没有 */
16
+ approvals(): readonly Approval[];
17
+ /** 会话管理:审批所在会话的标题(没有标题时是会话的显示名称) */
18
+ sessions: Pick<ServiceSelection<Pick<SessionManagerService, 'getSession'>>, 'present' | 'use'>;
19
+ /** 插件状态与没起来的原因;插件管理服务不在时为空 */
20
+ plugins(): ReadonlyArray<PluginStatusEntry & PluginBlockers>;
21
+ /** 登记着的提示,带所属实例 */
22
+ notices(): ReadonlyArray<Notice & {
23
+ instance: string;
24
+ }>;
25
+ send(event: WebuiAdminEvent): void;
26
+ }
27
+ export declare function createAttention(deps: AttentionDeps): {
28
+ /** 审批在前,其后是插件故障与提示,各按先后 */
29
+ items: () => AttentionItem[];
30
+ /** 审批推出或结算了 */
31
+ changed: () => void;
32
+ /** 插件登记表重算过:故障(连同链与根)变了才推 */
33
+ pluginsChanged(): void;
34
+ };
35
+ export {};
36
+ //# sourceMappingURL=attention.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attention.d.ts","sourceRoot":"","sources":["../src/attention.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,KAAK,qBAAqB,EAAgB,MAAM,4BAA4B,CAAC;AACtF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAC/F,OAAO,KAAK,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAGvG,yDAAyD;AACzD,UAAU,cAAc;IACtB,OAAO,EAAE,IAAI,CAAC,oBAAoB,EAAE,WAAW,GAAG,WAAW,CAAC,CAAC;IAC/D,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAClC,GAAG,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,iBAAiB,CAAC,CAAC;CAC3C;AAED,0BAA0B;AAC1B,wBAAgB,aAAa,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,EAAE,cAAc,GAAG,KAAK,CAAC,iBAAiB,GAAG,cAAc,CAAC,CAgBnH;AAED,UAAU,aAAa;IACrB,wBAAwB;IACxB,SAAS,IAAI,SAAS,QAAQ,EAAE,CAAC;IACjC,oCAAoC;IACpC,QAAQ,EAAE,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,qBAAqB,EAAE,YAAY,CAAC,CAAC,EAAE,SAAS,GAAG,KAAK,CAAC,CAAC;IAC/F,8BAA8B;IAC9B,OAAO,IAAI,aAAa,CAAC,iBAAiB,GAAG,cAAc,CAAC,CAAC;IAC7D,mBAAmB;IACnB,OAAO,IAAI,aAAa,CAAC,MAAM,GAAG;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxD,IAAI,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAAC;CACpC;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,aAAa;IA2B/C,2BAA2B;iBAChB,aAAa,EAAE;IAQ1B,eAAe;;IAEf,6BAA6B;sBACX,IAAI;EAOzB"}
@@ -0,0 +1,74 @@
1
+ // ============================================================
2
+ // attention.ts — 插件没起来的原因与「需要处理」
3
+ //
4
+ // 插件没起来的原因(缺、在等、待配置)在这里算一份,插件列表与「需要处理」共用:缺与在等按 api-doctor 的共享判据沿依赖链
5
+ // 追到根,与 runtime 的启动告警、诊断同一说法;失败的另按配置 schema 校验它的配置,列出必填却没填的项。
6
+ //
7
+ // 「需要处理」:推出未结的审批、插件故障(失败与缺服务;只在等别的插件启动的不算)与插件的提示汇总成一个列表,每项计 1,
8
+ // 经 GET /api/attention 给出。变了就经 /admin 推 attention.changed:审批推出或结算是变化,提示的登记与撤回由页面登记表推
9
+ // (pages.ts);插件登记表每次重算之后比一次故障(连同链与根),变了才推(重算很频繁,多数与故障无关)。
10
+ // ============================================================
11
+ import { failureText, unmetDependencies } from '@aalis/api-doctor';
12
+ import { sessionLabel } from '@aalis/api-session-manager';
13
+ import { validateConfig } from '@aalis/schema-config';
14
+ /** 插件状态快照,每个实例带上没起来的原因 */
15
+ export function blockedStatus({ plugins, services, doc }) {
16
+ const status = plugins.getStatus();
17
+ const unmet = unmetDependencies(status, services.names());
18
+ /** 必填却没填的配置项:`''` 算没填,密钥引用算填了(validateConfig 的 missing) */
19
+ const unconfigured = (instanceId) => {
20
+ const entry = plugins.getPlugin(instanceId);
21
+ const config = doc ? doc.getPluginConfig(instanceId) : (entry?.config ?? {});
22
+ return validateConfig(entry?.definition.configSchema, config)
23
+ .filter(issue => issue.kind === 'missing')
24
+ .map(issue => issue.path);
25
+ };
26
+ return status.map(entry => ({
27
+ ...entry,
28
+ ...(unmet.get(entry.instanceId) ?? { lacking: [], waiting: [] }),
29
+ unconfigured: entry.state === 'error' ? unconfigured(entry.instanceId) : [],
30
+ }));
31
+ }
32
+ export function createAttention(deps) {
33
+ const faults = () => deps.plugins().flatMap(({ instanceId, state, error, failure, lacking, waiting, unconfigured }) => state === 'error' || (state === 'pending' && lacking.length > 0)
34
+ ? [
35
+ {
36
+ kind: 'plugin',
37
+ instance: instanceId,
38
+ state,
39
+ ...(error !== undefined && { error: failureText({ failure, error }) }),
40
+ lacking,
41
+ waiting,
42
+ unconfigured,
43
+ },
44
+ ]
45
+ : []);
46
+ const titleOf = (sessionId) => {
47
+ const info = deps.sessions.present ? deps.sessions.use.getSession(sessionId) : undefined;
48
+ return info && (info.title || sessionLabel(info));
49
+ };
50
+ let reported = JSON.stringify(faults());
51
+ const changed = () => deps.send({ type: 'attention.changed' });
52
+ return {
53
+ /** 审批在前,其后是插件故障与提示,各按先后 */
54
+ items: () => [
55
+ ...deps.approvals().map(({ id, sessionId, tool }) => {
56
+ const session = titleOf(sessionId);
57
+ return { kind: 'approval', id, sessionId, ...(session !== undefined && { session }), tool };
58
+ }),
59
+ ...faults(),
60
+ ...deps.notices().map(notice => ({ kind: 'notice', ...notice })),
61
+ ],
62
+ /** 审批推出或结算了 */
63
+ changed,
64
+ /** 插件登记表重算过:故障(连同链与根)变了才推 */
65
+ pluginsChanged() {
66
+ const now = JSON.stringify(faults());
67
+ if (now === reported)
68
+ return;
69
+ reported = now;
70
+ changed();
71
+ },
72
+ };
73
+ }
74
+ //# sourceMappingURL=attention.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attention.js","sourceRoot":"","sources":["../src/attention.ts"],"names":[],"mappings":"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"}
package/dist/auth.d.ts CHANGED
@@ -1,10 +1,29 @@
1
1
  import type { IncomingMessage } from 'node:http';
2
2
  import type { UserIdentity } from '@aalis/api-authority';
3
3
  import type { ProcessService } from '@aalis/api-process';
4
- import type { RequestHandler } from 'express';
4
+ import type express from 'express';
5
+ import type { Localizer } from './localize.js';
6
+ /** 失败计数表最多记这么多个地址:满了再记新地址时丢掉最早的那条,内存有界,也不因此变成全局锁定 */
7
+ export declare const LOGIN_FAILURE_ADDRESSES_MAX = 10000;
8
+ /**
9
+ * 配置里的一项是不是主机名(allowedHosts 的写法):可带端口,不带协议、用户名、路径、查询与片段,去掉首尾空白后解析得出。
10
+ * 带协议的照 Host 头的写法拼出来会把协议名当主机名(`https://x` 成了 `https`),真正要认的主机名照旧被拒
11
+ */
12
+ export declare function isHostName(entry: string): boolean;
13
+ /**
14
+ * 请求的来源能不能收,WebSocket 升级与改状态的请求(非 GET)共用。浏览器发起的握手与 POST 一律带 Origin,只收与本服务同源的:
15
+ * Origin 的主机与端口等于请求的 Host。同一主机别的端口上的网页与本服务同站,SameSite cookie 会随它的请求发出,
16
+ * 靠这一关拦下。没有 Origin 的是浏览器之外的客户端(脚本、命令行),不会被别的网页借用户的浏览器发起,交给 token。
17
+ * Host 按 Origin 的协议解析后再比,两边的默认端口都去掉(代理转来的 Host 可能写成 `x:443`)。
18
+ * 经反向代理访问时,代理要把原始的 Host 转给本服务。
19
+ */
20
+ export declare function originAllowed(req: Pick<IncomingMessage, 'headers'>): boolean;
5
21
  export interface AuthSystem {
6
- /** Express 中间件:处理 ?token= 重定向、登录端点、未认证拦截、登录页 */
7
- middleware: RequestHandler;
22
+ /**
23
+ * 挂上认证,要在 JSON 解析之后、其余路由之前:先核 Host,再是改状态的请求核来源,然后是登录页的脚本、`?token=` 一键登录、
24
+ * 登录、登录状态与注销,其余未登录的请求一律拦下(API 回 401,GET 页面回登录页)
25
+ */
26
+ install(expressApp: express.Express): void;
8
27
  /** 校验 WebSocket 升级请求是否已认证(cookie 读取) */
9
28
  verifyWsClient(req: IncomingMessage): boolean;
10
29
  /** 解析调用者身份:单 token 模式 → `webui:console`(owner 级,单人语义);未认证 → undefined。 */
@@ -16,9 +35,18 @@ export interface AuthSystem {
16
35
  * - token 由调用方经 getToken 提供,每次校验现取:persist 模式下 storage 晚上线时,调用方会把
17
36
  * 启动时的临时 token 换成读回的持久化 token(见 index.ts 的 token 解析)
18
37
  * - HttpOnly + SameSite=Strict cookie;`?token=` 一键登录;同源 POST /api/auth/login 提交
38
+ * - 请求的 Host 先按 {@link hostAllowed} 核对,hostNames 是 IP 地址与 localhost 之外认的主机名(监听地址与 allowedHosts);
39
+ * 不认的回 421,不进登录的比对与计数,带着 cookie 也不放行
40
+ * - 改状态的请求(非 GET、HEAD、OPTIONS)按 {@link originAllowed} 校验来源,与 WebSocket 升级同一个判定:SameSite 按站判、
41
+ * 不分端口,同一主机别的端口上的网页发一个简单 POST 就会带上 cookie
42
+ * - `?token=` 与登录接口按来源地址限速:失败计数见 LOGIN_MAX_FAILURES、LOGIN_FAILURE_WINDOW_MS,成功清零。
43
+ * 别的网页借 owner 的浏览器发起的登录(跨源的 POST、别的站点打开的 `?token=`、DNS 重绑定)不比对也不计数,免得别人替 owner 把
44
+ * 地址锁住。带 cookie 的请求不计数:各种模式的 token 都够长(随机生成的 24 字节,固定的至少 32 个字符),
45
+ * 按 cookie 逐个猜猜不中;对 cookie 也锁定的话,共用一个来源地址(同一个反向代理)的已登录页面会被别人一起锁住
19
46
  * - 身份恒为 `webui:console`(owner 级,单人模式语义)
47
+ * - 登录页与回应里的文字按全局层的语言(localizer)
20
48
  */
21
- export declare function createAuthSystem(getToken: () => string): AuthSystem;
49
+ export declare function createAuthSystem(getToken: () => string, localizer: Pick<Localizer, 'render' | 'languages'>, hostNames: readonly string[]): AuthSystem;
22
50
  /**
23
51
  * 跨平台打开默认浏览器到指定 URL(无外部依赖)
24
52
  * 失败静默;此为便利功能,不影响服务启动。
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AA0E9C,MAAM,WAAW,UAAU;IACzB,gDAAgD;IAChD,UAAU,EAAE,cAAc,CAAC;IAC3B,wCAAwC;IACxC,cAAc,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC;IAC9C,0EAA0E;IAC1E,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,CAAC,GAAG,YAAY,GAAG,SAAS,CAAC;CAC3E;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,MAAM,GAAG,UAAU,CAiFnE;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,GAAG,IAAI,CAwBnE"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAEjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAGzD,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AAEnC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAU/C,qDAAqD;AACrD,eAAO,MAAM,2BAA2B,QAAS,CAAC;AAiBlD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEjD;AAgBD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,CAAC,GAAG,OAAO,CAO5E;AA2GD,MAAM,WAAW,UAAU;IACzB;;;OAGG;IACH,OAAO,CAAC,UAAU,EAAE,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAC3C,wCAAwC;IACxC,cAAc,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC;IAC9C,0EAA0E;IAC1E,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,CAAC,GAAG,YAAY,GAAG,SAAS,CAAC;CAC3E;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,MAAM,EACtB,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,QAAQ,GAAG,WAAW,CAAC,EAClD,SAAS,EAAE,SAAS,MAAM,EAAE,GAC3B,UAAU,CA0JZ;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,GAAG,IAAI,CAwBnE"}