@zqagent/shared 0.0.0-stage → 0.1.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/LICENSE +190 -0
- package/README.md +21 -2
- package/dist/approval-view.cjs +99 -0
- package/dist/approval-view.cjs.map +1 -0
- package/dist/approval-view.d.cts +39 -0
- package/dist/approval-view.d.ts +39 -0
- package/dist/approval-view.js +69 -0
- package/dist/approval-view.js.map +1 -0
- package/dist/fleet.cjs +263 -0
- package/dist/fleet.cjs.map +1 -0
- package/dist/fleet.d.cts +284 -0
- package/dist/fleet.d.ts +284 -0
- package/dist/fleet.js +214 -0
- package/dist/fleet.js.map +1 -0
- package/dist/hub-api.cjs +901 -0
- package/dist/hub-api.cjs.map +1 -0
- package/dist/hub-api.d.cts +1677 -0
- package/dist/hub-api.d.ts +1677 -0
- package/dist/hub-api.js +776 -0
- package/dist/hub-api.js.map +1 -0
- package/dist/index-DokiNlGJ.d.cts +182 -0
- package/dist/index-DokiNlGJ.d.ts +182 -0
- package/dist/index.cjs +861 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +779 -0
- package/dist/index.js.map +1 -0
- package/dist/llm-presets.cjs +92 -0
- package/dist/llm-presets.cjs.map +1 -0
- package/dist/llm-presets.d.cts +28 -0
- package/dist/llm-presets.d.ts +28 -0
- package/dist/llm-presets.js +66 -0
- package/dist/llm-presets.js.map +1 -0
- package/dist/media.cjs +111 -0
- package/dist/media.cjs.map +1 -0
- package/dist/media.d.cts +59 -0
- package/dist/media.d.ts +59 -0
- package/dist/media.js +79 -0
- package/dist/media.js.map +1 -0
- package/dist/messages-CiXjdR1Z.d.cts +257 -0
- package/dist/messages-CiXjdR1Z.d.ts +257 -0
- package/dist/model-capabilities.cjs +105 -0
- package/dist/model-capabilities.cjs.map +1 -0
- package/dist/model-capabilities.d.cts +46 -0
- package/dist/model-capabilities.d.ts +46 -0
- package/dist/model-capabilities.js +74 -0
- package/dist/model-capabilities.js.map +1 -0
- package/dist/notify/index.cjs +375 -0
- package/dist/notify/index.cjs.map +1 -0
- package/dist/notify/index.d.cts +2 -0
- package/dist/notify/index.d.ts +2 -0
- package/dist/notify/index.js +330 -0
- package/dist/notify/index.js.map +1 -0
- package/dist/secrets.cjs +121 -0
- package/dist/secrets.cjs.map +1 -0
- package/dist/secrets.d.cts +25 -0
- package/dist/secrets.d.ts +25 -0
- package/dist/secrets.js +82 -0
- package/dist/secrets.js.map +1 -0
- package/dist/wire/index.cjs +1163 -0
- package/dist/wire/index.cjs.map +1 -0
- package/dist/wire/index.d.cts +166 -0
- package/dist/wire/index.d.ts +166 -0
- package/dist/wire/index.js +1081 -0
- package/dist/wire/index.js.map +1 -0
- package/dist/wire/node.cjs +224 -0
- package/dist/wire/node.cjs.map +1 -0
- package/dist/wire/node.d.cts +14 -0
- package/dist/wire/node.d.ts +14 -0
- package/dist/wire/node.js +195 -0
- package/dist/wire/node.js.map +1 -0
- package/package.json +99 -3
package/dist/media.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 音视频判定与 HTTP Range 解析(节点 / 控制台 / 网关共用同一套规则)
|
|
3
|
+
*
|
|
4
|
+
* 只收录浏览器原生 `<audio>` / `<video>` 能直接播放的容器。mkv / avi / wmv / flv
|
|
5
|
+
* 等需要转码的格式返回 null,由上层提示「下载后查看」而不是给一个永远播不了的
|
|
6
|
+
* 播放器(错误提示会更难解释)。
|
|
7
|
+
*
|
|
8
|
+
* Range 解析按 RFC 7233:只接受单段(多段请求直接忽略,回整个文件),
|
|
9
|
+
* 语法不合法回 null(调用方忽略 Range),语法合法但区间落在文件外回
|
|
10
|
+
* "unsatisfiable"(调用方回 416)。
|
|
11
|
+
*/
|
|
12
|
+
type MediaKind = "audio" | "video";
|
|
13
|
+
interface MediaInfo {
|
|
14
|
+
/** 响应用 Content-Type */
|
|
15
|
+
mime: string;
|
|
16
|
+
kind: MediaKind;
|
|
17
|
+
}
|
|
18
|
+
/** 文件名 → 可播放媒体的 MIME 与类别;不可(或浏览器不支持)返回 null */
|
|
19
|
+
declare function mediaInfo(fileName: string): MediaInfo | null;
|
|
20
|
+
/** 文件名 → MIME;非可播放媒体返回 null */
|
|
21
|
+
declare function mediaMime(fileName: string): string | null;
|
|
22
|
+
/** 文件名 → audio / video;非可播放媒体返回 null */
|
|
23
|
+
declare function mediaKind(fileName: string): MediaKind | null;
|
|
24
|
+
/** 文件名 → 可内嵌预览的文档 MIME;非文档返回 null */
|
|
25
|
+
declare function docPreviewMime(fileName: string): string | null;
|
|
26
|
+
/**
|
|
27
|
+
* 文件名 → 可用于 **HTTP Range 流式预览**的 MIME(音视频 + 可内嵌文档)。
|
|
28
|
+
*
|
|
29
|
+
* 节点签发文件票据 / `/files/range` 用它决定 `Content-Type`:票据里没有 MIME 时
|
|
30
|
+
* 前端会直接放弃流式通道,退回整文件下载(大 PDF 尤其痛),所以 PDF 必须在这里
|
|
31
|
+
* 被认出来。
|
|
32
|
+
*/
|
|
33
|
+
declare function previewMime(fileName: string): string | null;
|
|
34
|
+
interface ByteRange {
|
|
35
|
+
/** 起始偏移(含) */
|
|
36
|
+
start: number;
|
|
37
|
+
/** 结束偏移(含) */
|
|
38
|
+
end: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 解析 `Range: bytes=...`。
|
|
42
|
+
*
|
|
43
|
+
* @param header 原始头值(可为 null / 空)
|
|
44
|
+
* @param size 文件总字节数
|
|
45
|
+
* @returns 单段区间;`null` = 忽略该头(无 Range / 多段 / 语法不合法);
|
|
46
|
+
* `"unsatisfiable"` = 范围越界,调用方应回 416。
|
|
47
|
+
*/
|
|
48
|
+
declare function parseByteRange(header: string | null | undefined, size: number): ByteRange | "unsatisfiable" | null;
|
|
49
|
+
/** Range 响应的 `Content-Range` 头值 */
|
|
50
|
+
declare function contentRangeHeader(range: ByteRange, size: number): string;
|
|
51
|
+
/**
|
|
52
|
+
* 文件 ETag(强校验)。
|
|
53
|
+
*
|
|
54
|
+
* 由 size + mtime 组成:文件内容变化几乎必然改变其中一个;不读文件本身,
|
|
55
|
+
* 因此 `HEAD` / `304` 可以在完全不触达节点的情况下应答。
|
|
56
|
+
*/
|
|
57
|
+
declare function fileEtag(size: number, mtimeMs: number): string;
|
|
58
|
+
|
|
59
|
+
export { type ByteRange, type MediaInfo, type MediaKind, contentRangeHeader, docPreviewMime, fileEtag, mediaInfo, mediaKind, mediaMime, parseByteRange, previewMime };
|
package/dist/media.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// src/media.ts
|
|
2
|
+
var MEDIA_MIME = {
|
|
3
|
+
// 视频
|
|
4
|
+
mp4: { mime: "video/mp4", kind: "video" },
|
|
5
|
+
m4v: { mime: "video/mp4", kind: "video" },
|
|
6
|
+
webm: { mime: "video/webm", kind: "video" },
|
|
7
|
+
ogv: { mime: "video/ogg", kind: "video" },
|
|
8
|
+
mov: { mime: "video/quicktime", kind: "video" },
|
|
9
|
+
// 音频
|
|
10
|
+
mp3: { mime: "audio/mpeg", kind: "audio" },
|
|
11
|
+
wav: { mime: "audio/wav", kind: "audio" },
|
|
12
|
+
m4a: { mime: "audio/mp4", kind: "audio" },
|
|
13
|
+
aac: { mime: "audio/aac", kind: "audio" },
|
|
14
|
+
flac: { mime: "audio/flac", kind: "audio" },
|
|
15
|
+
ogg: { mime: "audio/ogg", kind: "audio" },
|
|
16
|
+
oga: { mime: "audio/ogg", kind: "audio" },
|
|
17
|
+
opus: { mime: "audio/ogg", kind: "audio" },
|
|
18
|
+
weba: { mime: "audio/webm", kind: "audio" }
|
|
19
|
+
};
|
|
20
|
+
function extOf(fileName) {
|
|
21
|
+
return fileName.toLowerCase().split(".").pop() ?? "";
|
|
22
|
+
}
|
|
23
|
+
function mediaInfo(fileName) {
|
|
24
|
+
return MEDIA_MIME[extOf(fileName)] ?? null;
|
|
25
|
+
}
|
|
26
|
+
function mediaMime(fileName) {
|
|
27
|
+
return MEDIA_MIME[extOf(fileName)]?.mime ?? null;
|
|
28
|
+
}
|
|
29
|
+
function mediaKind(fileName) {
|
|
30
|
+
return MEDIA_MIME[extOf(fileName)]?.kind ?? null;
|
|
31
|
+
}
|
|
32
|
+
var DOC_PREVIEW_MIME = {
|
|
33
|
+
pdf: "application/pdf"
|
|
34
|
+
};
|
|
35
|
+
function docPreviewMime(fileName) {
|
|
36
|
+
return DOC_PREVIEW_MIME[extOf(fileName)] ?? null;
|
|
37
|
+
}
|
|
38
|
+
function previewMime(fileName) {
|
|
39
|
+
return mediaMime(fileName) ?? docPreviewMime(fileName);
|
|
40
|
+
}
|
|
41
|
+
function parseByteRange(header, size) {
|
|
42
|
+
const raw = (header ?? "").trim();
|
|
43
|
+
if (!raw) return null;
|
|
44
|
+
const m = /^bytes=(\d*)-(\d*)$/i.exec(raw);
|
|
45
|
+
if (!m) return null;
|
|
46
|
+
const startRaw = m[1] ?? "";
|
|
47
|
+
const endRaw = m[2] ?? "";
|
|
48
|
+
if (!startRaw && !endRaw) return null;
|
|
49
|
+
if (size <= 0) return "unsatisfiable";
|
|
50
|
+
if (!startRaw) {
|
|
51
|
+
const suffix = Number(endRaw);
|
|
52
|
+
if (!Number.isFinite(suffix) || suffix <= 0) return "unsatisfiable";
|
|
53
|
+
return { start: Math.max(size - suffix, 0), end: size - 1 };
|
|
54
|
+
}
|
|
55
|
+
const start = Number(startRaw);
|
|
56
|
+
if (!Number.isFinite(start) || start < 0) return null;
|
|
57
|
+
if (start >= size) return "unsatisfiable";
|
|
58
|
+
if (!endRaw) return { start, end: size - 1 };
|
|
59
|
+
const end = Number(endRaw);
|
|
60
|
+
if (!Number.isFinite(end) || end < start) return null;
|
|
61
|
+
return { start, end: Math.min(end, size - 1) };
|
|
62
|
+
}
|
|
63
|
+
function contentRangeHeader(range, size) {
|
|
64
|
+
return `bytes ${range.start}-${range.end}/${size}`;
|
|
65
|
+
}
|
|
66
|
+
function fileEtag(size, mtimeMs) {
|
|
67
|
+
return `"${size.toString(16)}-${Math.trunc(mtimeMs).toString(16)}"`;
|
|
68
|
+
}
|
|
69
|
+
export {
|
|
70
|
+
contentRangeHeader,
|
|
71
|
+
docPreviewMime,
|
|
72
|
+
fileEtag,
|
|
73
|
+
mediaInfo,
|
|
74
|
+
mediaKind,
|
|
75
|
+
mediaMime,
|
|
76
|
+
parseByteRange,
|
|
77
|
+
previewMime
|
|
78
|
+
};
|
|
79
|
+
//# sourceMappingURL=media.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/media.ts"],"sourcesContent":["/**\n * 音视频判定与 HTTP Range 解析(节点 / 控制台 / 网关共用同一套规则)\n *\n * 只收录浏览器原生 `<audio>` / `<video>` 能直接播放的容器。mkv / avi / wmv / flv\n * 等需要转码的格式返回 null,由上层提示「下载后查看」而不是给一个永远播不了的\n * 播放器(错误提示会更难解释)。\n *\n * Range 解析按 RFC 7233:只接受单段(多段请求直接忽略,回整个文件),\n * 语法不合法回 null(调用方忽略 Range),语法合法但区间落在文件外回\n * \"unsatisfiable\"(调用方回 416)。\n */\n\nexport type MediaKind = \"audio\" | \"video\";\n\nexport interface MediaInfo {\n /** 响应用 Content-Type */\n mime: string;\n kind: MediaKind;\n}\n\n/**\n * 扩展名 → MIME / 类别。\n *\n * `mov` 只有 Safari 稳定支持;`webm` 在旧 Safari 上不支持。是否真能播由浏览器\n * 决定,前端监听 `<video onError>` 兜底,不在这里做设备探测。\n */\nconst MEDIA_MIME: Record<string, MediaInfo> = {\n // 视频\n mp4: { mime: \"video/mp4\", kind: \"video\" },\n m4v: { mime: \"video/mp4\", kind: \"video\" },\n webm: { mime: \"video/webm\", kind: \"video\" },\n ogv: { mime: \"video/ogg\", kind: \"video\" },\n mov: { mime: \"video/quicktime\", kind: \"video\" },\n // 音频\n mp3: { mime: \"audio/mpeg\", kind: \"audio\" },\n wav: { mime: \"audio/wav\", kind: \"audio\" },\n m4a: { mime: \"audio/mp4\", kind: \"audio\" },\n aac: { mime: \"audio/aac\", kind: \"audio\" },\n flac: { mime: \"audio/flac\", kind: \"audio\" },\n ogg: { mime: \"audio/ogg\", kind: \"audio\" },\n oga: { mime: \"audio/ogg\", kind: \"audio\" },\n opus: { mime: \"audio/ogg\", kind: \"audio\" },\n weba: { mime: \"audio/webm\", kind: \"audio\" },\n};\n\nfunction extOf(fileName: string): string {\n return fileName.toLowerCase().split(\".\").pop() ?? \"\";\n}\n\n/** 文件名 → 可播放媒体的 MIME 与类别;不可(或浏览器不支持)返回 null */\nexport function mediaInfo(fileName: string): MediaInfo | null {\n return MEDIA_MIME[extOf(fileName)] ?? null;\n}\n\n/** 文件名 → MIME;非可播放媒体返回 null */\nexport function mediaMime(fileName: string): string | null {\n return MEDIA_MIME[extOf(fileName)]?.mime ?? null;\n}\n\n/** 文件名 → audio / video;非可播放媒体返回 null */\nexport function mediaKind(fileName: string): MediaKind | null {\n return MEDIA_MIME[extOf(fileName)]?.kind ?? null;\n}\n\n/**\n * 浏览器可**内嵌**渲染的文档 MIME。\n *\n * 与 `MEDIA_MIME` 分开维护:那张表的语义是「`<video>` / `<audio>` 能直接播放」,\n * PDF 不是音视频,混进去会让 `mediaKind()` 返回一个不存在的 kind。这里只声明\n * 「浏览器自带查看器能显示」,由前端决定用 `<iframe>` 还是保底提示。\n *\n * 注意浏览器支持面不是重点:Chrome / Edge / Firefox / Safari / WebView2 都有\n * 内置 PDF 查看器,WebKitGTK(Linux 桌面壳)没有——前端必须能优雅退化。\n */\nconst DOC_PREVIEW_MIME: Record<string, string> = {\n pdf: \"application/pdf\",\n};\n\n/** 文件名 → 可内嵌预览的文档 MIME;非文档返回 null */\nexport function docPreviewMime(fileName: string): string | null {\n return DOC_PREVIEW_MIME[extOf(fileName)] ?? null;\n}\n\n/**\n * 文件名 → 可用于 **HTTP Range 流式预览**的 MIME(音视频 + 可内嵌文档)。\n *\n * 节点签发文件票据 / `/files/range` 用它决定 `Content-Type`:票据里没有 MIME 时\n * 前端会直接放弃流式通道,退回整文件下载(大 PDF 尤其痛),所以 PDF 必须在这里\n * 被认出来。\n */\nexport function previewMime(fileName: string): string | null {\n return mediaMime(fileName) ?? docPreviewMime(fileName);\n}\n\nexport interface ByteRange {\n /** 起始偏移(含) */\n start: number;\n /** 结束偏移(含) */\n end: number;\n}\n\n/**\n * 解析 `Range: bytes=...`。\n *\n * @param header 原始头值(可为 null / 空)\n * @param size 文件总字节数\n * @returns 单段区间;`null` = 忽略该头(无 Range / 多段 / 语法不合法);\n * `\"unsatisfiable\"` = 范围越界,调用方应回 416。\n */\nexport function parseByteRange(\n header: string | null | undefined,\n size: number,\n): ByteRange | \"unsatisfiable\" | null {\n const raw = (header ?? \"\").trim();\n if (!raw) return null;\n const m = /^bytes=(\\d*)-(\\d*)$/i.exec(raw);\n if (!m) return null; // 多段与其它单位一律忽略\n\n const startRaw = m[1] ?? \"\";\n const endRaw = m[2] ?? \"\";\n if (!startRaw && !endRaw) return null; // \"bytes=-\"\n if (size <= 0) return \"unsatisfiable\";\n\n // 后缀形式:bytes=-N → 最后 N 字节\n if (!startRaw) {\n const suffix = Number(endRaw);\n if (!Number.isFinite(suffix) || suffix <= 0) return \"unsatisfiable\";\n return { start: Math.max(size - suffix, 0), end: size - 1 };\n }\n\n const start = Number(startRaw);\n if (!Number.isFinite(start) || start < 0) return null;\n if (start >= size) return \"unsatisfiable\";\n\n // 省略结束:bytes=N- → 到文件末尾\n if (!endRaw) return { start, end: size - 1 };\n\n const end = Number(endRaw);\n if (!Number.isFinite(end) || end < start) return null;\n return { start, end: Math.min(end, size - 1) };\n}\n\n/** Range 响应的 `Content-Range` 头值 */\nexport function contentRangeHeader(range: ByteRange, size: number): string {\n return `bytes ${range.start}-${range.end}/${size}`;\n}\n\n/**\n * 文件 ETag(强校验)。\n *\n * 由 size + mtime 组成:文件内容变化几乎必然改变其中一个;不读文件本身,\n * 因此 `HEAD` / `304` 可以在完全不触达节点的情况下应答。\n */\nexport function fileEtag(size: number, mtimeMs: number): string {\n return `\"${size.toString(16)}-${Math.trunc(mtimeMs).toString(16)}\"`;\n}\n"],"mappings":";AA0BA,IAAM,aAAwC;AAAA;AAAA,EAE5C,KAAK,EAAE,MAAM,aAAa,MAAM,QAAQ;AAAA,EACxC,KAAK,EAAE,MAAM,aAAa,MAAM,QAAQ;AAAA,EACxC,MAAM,EAAE,MAAM,cAAc,MAAM,QAAQ;AAAA,EAC1C,KAAK,EAAE,MAAM,aAAa,MAAM,QAAQ;AAAA,EACxC,KAAK,EAAE,MAAM,mBAAmB,MAAM,QAAQ;AAAA;AAAA,EAE9C,KAAK,EAAE,MAAM,cAAc,MAAM,QAAQ;AAAA,EACzC,KAAK,EAAE,MAAM,aAAa,MAAM,QAAQ;AAAA,EACxC,KAAK,EAAE,MAAM,aAAa,MAAM,QAAQ;AAAA,EACxC,KAAK,EAAE,MAAM,aAAa,MAAM,QAAQ;AAAA,EACxC,MAAM,EAAE,MAAM,cAAc,MAAM,QAAQ;AAAA,EAC1C,KAAK,EAAE,MAAM,aAAa,MAAM,QAAQ;AAAA,EACxC,KAAK,EAAE,MAAM,aAAa,MAAM,QAAQ;AAAA,EACxC,MAAM,EAAE,MAAM,aAAa,MAAM,QAAQ;AAAA,EACzC,MAAM,EAAE,MAAM,cAAc,MAAM,QAAQ;AAC5C;AAEA,SAAS,MAAM,UAA0B;AACvC,SAAO,SAAS,YAAY,EAAE,MAAM,GAAG,EAAE,IAAI,KAAK;AACpD;AAGO,SAAS,UAAU,UAAoC;AAC5D,SAAO,WAAW,MAAM,QAAQ,CAAC,KAAK;AACxC;AAGO,SAAS,UAAU,UAAiC;AACzD,SAAO,WAAW,MAAM,QAAQ,CAAC,GAAG,QAAQ;AAC9C;AAGO,SAAS,UAAU,UAAoC;AAC5D,SAAO,WAAW,MAAM,QAAQ,CAAC,GAAG,QAAQ;AAC9C;AAYA,IAAM,mBAA2C;AAAA,EAC/C,KAAK;AACP;AAGO,SAAS,eAAe,UAAiC;AAC9D,SAAO,iBAAiB,MAAM,QAAQ,CAAC,KAAK;AAC9C;AASO,SAAS,YAAY,UAAiC;AAC3D,SAAO,UAAU,QAAQ,KAAK,eAAe,QAAQ;AACvD;AAiBO,SAAS,eACd,QACA,MACoC;AACpC,QAAM,OAAO,UAAU,IAAI,KAAK;AAChC,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,IAAI,uBAAuB,KAAK,GAAG;AACzC,MAAI,CAAC,EAAG,QAAO;AAEf,QAAM,WAAW,EAAE,CAAC,KAAK;AACzB,QAAM,SAAS,EAAE,CAAC,KAAK;AACvB,MAAI,CAAC,YAAY,CAAC,OAAQ,QAAO;AACjC,MAAI,QAAQ,EAAG,QAAO;AAGtB,MAAI,CAAC,UAAU;AACb,UAAM,SAAS,OAAO,MAAM;AAC5B,QAAI,CAAC,OAAO,SAAS,MAAM,KAAK,UAAU,EAAG,QAAO;AACpD,WAAO,EAAE,OAAO,KAAK,IAAI,OAAO,QAAQ,CAAC,GAAG,KAAK,OAAO,EAAE;AAAA,EAC5D;AAEA,QAAM,QAAQ,OAAO,QAAQ;AAC7B,MAAI,CAAC,OAAO,SAAS,KAAK,KAAK,QAAQ,EAAG,QAAO;AACjD,MAAI,SAAS,KAAM,QAAO;AAG1B,MAAI,CAAC,OAAQ,QAAO,EAAE,OAAO,KAAK,OAAO,EAAE;AAE3C,QAAM,MAAM,OAAO,MAAM;AACzB,MAAI,CAAC,OAAO,SAAS,GAAG,KAAK,MAAM,MAAO,QAAO;AACjD,SAAO,EAAE,OAAO,KAAK,KAAK,IAAI,KAAK,OAAO,CAAC,EAAE;AAC/C;AAGO,SAAS,mBAAmB,OAAkB,MAAsB;AACzE,SAAO,SAAS,MAAM,KAAK,IAAI,MAAM,GAAG,IAAI,IAAI;AAClD;AAQO,SAAS,SAAS,MAAc,SAAyB;AAC9D,SAAO,IAAI,KAAK,SAAS,EAAE,CAAC,IAAI,KAAK,MAAM,OAAO,EAAE,SAAS,EAAE,CAAC;AAClE;","names":[]}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 消息编解码(Envelope / Ack / Status / Event)
|
|
3
|
+
*
|
|
4
|
+
* 字段定义见 proto/fleet.proto。手写编解码,规则与 prost 对齐(proto3 默认值省略、
|
|
5
|
+
* 字段号升序、string 严格 UTF-8),未知字段按 wire type 跳过以支持前向兼容。
|
|
6
|
+
*
|
|
7
|
+
* 签名:`signableBytes()` 给出「不含 sig」的编码字节;`sig` 是 10 号字段(最后),
|
|
8
|
+
* 因此 `encode(env, sig 置空) === signableBytes(env)` 恒成立,校验方可重编码比对。
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** 与现有 FleetCmdEnvelope(packages/shared/src/fleet.ts:159-178)语义一致 */
|
|
12
|
+
interface Envelope {
|
|
13
|
+
/** 毫秒时间戳 */
|
|
14
|
+
t: number;
|
|
15
|
+
nonce: string;
|
|
16
|
+
reqId: string;
|
|
17
|
+
type: string;
|
|
18
|
+
clientId: string;
|
|
19
|
+
nodeId: string;
|
|
20
|
+
projectId?: string;
|
|
21
|
+
sessionId?: string;
|
|
22
|
+
/** 载荷字节(按 type 选择强类型消息或 JSON 字节) */
|
|
23
|
+
payload?: Uint8Array;
|
|
24
|
+
/**
|
|
25
|
+
* 执行预算(毫秒,可选):由发起方指定,网关据此决定等待回执的上限。
|
|
26
|
+
* 0/缺省表示按 `type` 的默认分级(见 docs/wire-protocol.md §5.2)。
|
|
27
|
+
*/
|
|
28
|
+
timeoutMs?: number;
|
|
29
|
+
/** HMAC-SHA256 原始 32 字节(**必须是最后一个字段**:其前缀即签名字节) */
|
|
30
|
+
sig?: Uint8Array;
|
|
31
|
+
}
|
|
32
|
+
interface Ack {
|
|
33
|
+
reqId: string;
|
|
34
|
+
type: string;
|
|
35
|
+
ok: boolean;
|
|
36
|
+
error?: string;
|
|
37
|
+
data?: Uint8Array;
|
|
38
|
+
}
|
|
39
|
+
interface Status {
|
|
40
|
+
nodeId: string;
|
|
41
|
+
online: boolean;
|
|
42
|
+
/** 毫秒时间戳(现有 JSON 协议为 ISO 字符串,映射在 M2) */
|
|
43
|
+
ts: number;
|
|
44
|
+
label?: string;
|
|
45
|
+
platform?: string;
|
|
46
|
+
mode?: string;
|
|
47
|
+
version?: string;
|
|
48
|
+
ip?: string;
|
|
49
|
+
}
|
|
50
|
+
interface EventMsg {
|
|
51
|
+
seq: number;
|
|
52
|
+
ts: number;
|
|
53
|
+
type: string;
|
|
54
|
+
projectId?: string;
|
|
55
|
+
sessionId?: string;
|
|
56
|
+
data?: Uint8Array;
|
|
57
|
+
/**
|
|
58
|
+
* 事件来源节点。由**网关**按连接绑定的 nodeId 填充(节点上行时留空)。
|
|
59
|
+
*
|
|
60
|
+
* 终端面一条连接可订阅多个节点,缺了它就无法分辨事件来自哪台节点。
|
|
61
|
+
*/
|
|
62
|
+
nodeId?: string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 命令终端数据(`FrameType.TERMINAL`)。
|
|
66
|
+
*
|
|
67
|
+
* 与 `EventMsg` 完全平行的另一条通道,**不是** Event 的一种:它不参与
|
|
68
|
+
* seq 游标、不入事件环形缓冲、不跨副本广播(理由见 proto 注释)。
|
|
69
|
+
*/
|
|
70
|
+
interface TerminalData {
|
|
71
|
+
/** 目标节点;节点上行时留空,由网关按连接绑定填充 */
|
|
72
|
+
nodeId?: string;
|
|
73
|
+
/** 终端实例 ID(节点侧生成,`t_` 前缀;与会话 ID 不同空间) */
|
|
74
|
+
terminalId: string;
|
|
75
|
+
/**
|
|
76
|
+
* 帧种类:
|
|
77
|
+
* opened | data | exited | error —— 节点 → 网关 → 终端
|
|
78
|
+
* input | resize —— 终端 → 网关 → 节点
|
|
79
|
+
*
|
|
80
|
+
* 开/关终端不走这条通道(见 `terminal_create` / `terminal_close` 指令)。
|
|
81
|
+
*/
|
|
82
|
+
kind: string;
|
|
83
|
+
/** data/input 为原始字节;resize 为 JSON 字节({"cols":N,"rows":N}) */
|
|
84
|
+
data?: Uint8Array;
|
|
85
|
+
/** 终端内局部序号(仅该终端内有效,不参与 resume);文件流中为 stream_id */
|
|
86
|
+
seq?: number;
|
|
87
|
+
/**
|
|
88
|
+
* 通道:0/缺省 = 终端;1 = 文件流(音视频预览)。
|
|
89
|
+
*
|
|
90
|
+
* 文件流复用 TERMINAL 帧以避开协议版本升级(帧类型低 4 位已用尽),
|
|
91
|
+
* 但网关按该字段分流,不会 fanout 给终端连接。
|
|
92
|
+
*/
|
|
93
|
+
channel?: number;
|
|
94
|
+
/**
|
|
95
|
+
* P2P 打洞信令(`channel=2`)的会话标识。
|
|
96
|
+
*
|
|
97
|
+
* 网关按它把信令**回发给发起连接**,不 fanout——否则双方公网 IP 会泄漏给
|
|
98
|
+
* 同一节点的其他协作者。
|
|
99
|
+
*/
|
|
100
|
+
session?: string;
|
|
101
|
+
}
|
|
102
|
+
declare function encodeEnvelope(env: Envelope): Uint8Array;
|
|
103
|
+
/** 参与签名的字节(字段 1..10,不含 sig) */
|
|
104
|
+
declare function signableBytes(env: Envelope): Uint8Array;
|
|
105
|
+
declare function decodeEnvelope(bytes: Uint8Array): Envelope;
|
|
106
|
+
declare function encodeAck(ack: Ack): Uint8Array;
|
|
107
|
+
declare function decodeAck(bytes: Uint8Array): Ack;
|
|
108
|
+
declare function encodeStatus(st: Status): Uint8Array;
|
|
109
|
+
declare function decodeStatus(bytes: Uint8Array): Status;
|
|
110
|
+
declare function encodeEvent(ev: EventMsg): Uint8Array;
|
|
111
|
+
declare function decodeEvent(bytes: Uint8Array): EventMsg;
|
|
112
|
+
|
|
113
|
+
interface Info {
|
|
114
|
+
nodeId: string;
|
|
115
|
+
platform?: string;
|
|
116
|
+
version?: string;
|
|
117
|
+
ip?: string;
|
|
118
|
+
/** 项目数 */
|
|
119
|
+
projects: number;
|
|
120
|
+
/** 会话数 */
|
|
121
|
+
sessions: number;
|
|
122
|
+
/** 运行中的会话数 */
|
|
123
|
+
running: number;
|
|
124
|
+
/** 等待审批/提问的会话数 */
|
|
125
|
+
waiting: number;
|
|
126
|
+
/** 毫秒时间戳 */
|
|
127
|
+
ts: number;
|
|
128
|
+
/**
|
|
129
|
+
* 节点上报的**原始 info JSON**(含 label / 项目明细 / stats / usage)。
|
|
130
|
+
*
|
|
131
|
+
* 5..9 的计数供网关快速聚合;本字段原样承载完整快照,供控制面读取
|
|
132
|
+
* (替代从前经 MQTT retained info 的那条路径),避免为视图反复改 schema。
|
|
133
|
+
*/
|
|
134
|
+
detail?: Uint8Array;
|
|
135
|
+
}
|
|
136
|
+
declare function encodeInfo(info: Info): Uint8Array;
|
|
137
|
+
declare function decodeInfo(bytes: Uint8Array): Info;
|
|
138
|
+
/**
|
|
139
|
+
* 节点通知:审批请求 / 提问 / 回合完成 / 错误。
|
|
140
|
+
*
|
|
141
|
+
* 与 `EventMsg` 的分工:Event 是高频实时进度,Notify 是低频但重要(审批/提问挂起必须
|
|
142
|
+
* 让终端看到)。两者共用网关分配的 `seq` 游标与缓冲,因此 `resume` 时都能回放。
|
|
143
|
+
*/
|
|
144
|
+
interface NotifyMsg {
|
|
145
|
+
/** 由网关分配的游标(节点上报时留 0) */
|
|
146
|
+
seq: number;
|
|
147
|
+
ts: number;
|
|
148
|
+
kind: string;
|
|
149
|
+
projectId?: string;
|
|
150
|
+
sessionId?: string;
|
|
151
|
+
data?: Uint8Array;
|
|
152
|
+
/** 通知来源节点;由网关填充(多路复用终端据此归属到具体节点) */
|
|
153
|
+
nodeId?: string;
|
|
154
|
+
}
|
|
155
|
+
declare function encodeNotify(n: NotifyMsg): Uint8Array;
|
|
156
|
+
declare function decodeNotify(bytes: Uint8Array): NotifyMsg;
|
|
157
|
+
interface Auth {
|
|
158
|
+
/** 节点面:nodeId;终端面:userId */
|
|
159
|
+
id: string;
|
|
160
|
+
/** 毫秒时间戳(±60s 窗口) */
|
|
161
|
+
ts: number;
|
|
162
|
+
nonce: string;
|
|
163
|
+
/** 协议能力版本 */
|
|
164
|
+
proto: number;
|
|
165
|
+
/** "node" | "terminal" */
|
|
166
|
+
kind: string;
|
|
167
|
+
/** 终端载体:"web" | "miniapp" | "app"(节点面留空) */
|
|
168
|
+
client?: string;
|
|
169
|
+
/**
|
|
170
|
+
* 终端面 = HMAC-SHA256(terminalSecret, signableAuthBytes);节点面 = HMAC-SHA256(fleetSecret, …)
|
|
171
|
+
*
|
|
172
|
+
* 注意 `scope`/`expires` 在 `token` **之后**:proto3 按字段号升序编码,因此它们仍在
|
|
173
|
+
* `signableAuthBytes` 内 —— 即**同样受签名完整性保护**(改 scope 会让 token 失效)。
|
|
174
|
+
*/
|
|
175
|
+
token?: Uint8Array;
|
|
176
|
+
/** 终端面被授权的 nodeId 列表(非空时网关严格校验;见 `verifyTerminalAuth`) */
|
|
177
|
+
scope?: string[];
|
|
178
|
+
/** 终端面过期时刻(毫秒);>0 时网关跳过 ±60s 窗口 */
|
|
179
|
+
expires?: number;
|
|
180
|
+
/**
|
|
181
|
+
* 客户端能力位(见 `frame.ts` 的 `CAP_DEFLATE`)。
|
|
182
|
+
*
|
|
183
|
+
* 只用于声明支持;节点只有在 `AuthOk.caps` 也回同样位时才启用压缩上行。
|
|
184
|
+
* 旧网关忽略未知字段,行为不变。
|
|
185
|
+
*/
|
|
186
|
+
caps?: number;
|
|
187
|
+
}
|
|
188
|
+
interface AuthOk {
|
|
189
|
+
ok: boolean;
|
|
190
|
+
error?: string;
|
|
191
|
+
serverTs: number;
|
|
192
|
+
/** 网关能力位(语义同 `Auth.caps`);旧网关不回此字段(视为 0) */
|
|
193
|
+
caps?: number;
|
|
194
|
+
}
|
|
195
|
+
declare function encodeAuth(auth: Auth): Uint8Array;
|
|
196
|
+
/**
|
|
197
|
+
* 参与签名的字节:清空 `token` 后编码。
|
|
198
|
+
*
|
|
199
|
+
* `scope`/`expires` 字段号大于 `token`,但 prost/本实现都按字段号升序编码,
|
|
200
|
+
* 因此它们**仍在签名字节内**(受完整性保护),不是「token 之后的免费字段」。
|
|
201
|
+
*/
|
|
202
|
+
declare function signableAuthBytes(auth: Auth): Uint8Array;
|
|
203
|
+
declare function decodeAuth(bytes: Uint8Array): Auth;
|
|
204
|
+
declare function encodeAuthOk(ok: AuthOk): Uint8Array;
|
|
205
|
+
declare function decodeAuthOk(bytes: Uint8Array): AuthOk;
|
|
206
|
+
declare function encodeTerminalData(td: TerminalData): Uint8Array;
|
|
207
|
+
declare function decodeTerminalData(bytes: Uint8Array): TerminalData;
|
|
208
|
+
interface NodeState {
|
|
209
|
+
nodeId: string;
|
|
210
|
+
online: boolean;
|
|
211
|
+
lastSeen: number;
|
|
212
|
+
platform?: string;
|
|
213
|
+
version?: string;
|
|
214
|
+
ip?: string;
|
|
215
|
+
/**
|
|
216
|
+
* 节点上报的原始 info JSON(`Info.detail` 的转发),供终端快照读取项目/统计。
|
|
217
|
+
*
|
|
218
|
+
* 快照借此**替代 MQTT retained info**:订阅后一次拿到,不必逐节点发指令。
|
|
219
|
+
* 节点从未上报过 INFO 时为空。
|
|
220
|
+
*/
|
|
221
|
+
detail?: Uint8Array;
|
|
222
|
+
}
|
|
223
|
+
interface Snapshot {
|
|
224
|
+
nodes: NodeState[];
|
|
225
|
+
/** 当前事件游标(客户端据此发起 resume) */
|
|
226
|
+
seq: number;
|
|
227
|
+
}
|
|
228
|
+
declare function encodeNodeState(n: NodeState): Uint8Array;
|
|
229
|
+
declare function decodeNodeState(bytes: Uint8Array): NodeState;
|
|
230
|
+
declare function encodeSnapshot(snap: Snapshot): Uint8Array;
|
|
231
|
+
declare function decodeSnapshot(bytes: Uint8Array): Snapshot;
|
|
232
|
+
declare function encodeNodeIds(ids: string[]): Uint8Array;
|
|
233
|
+
declare function decodeNodeIds(bytes: Uint8Array): string[];
|
|
234
|
+
/**
|
|
235
|
+
* 订阅帧:节点列表 + 是否要内容增量。
|
|
236
|
+
*
|
|
237
|
+
* `noDetail` 用反向布尔的 `.proto` 字段(`Subscribe.no_detail`):默认 false
|
|
238
|
+
* 表示「要细节」,与本字段出现前的行为一致。只有双方(控制台/网关)都支持时
|
|
239
|
+
* 才会真正省下带宽;旧网关忽略未知字段,退化为全量推送。
|
|
240
|
+
*/
|
|
241
|
+
declare function encodeSubscribe(ids: string[], noDetail?: boolean): Uint8Array;
|
|
242
|
+
/** 只取节点列表(保持既有调用方与测试的返回类型不变) */
|
|
243
|
+
declare const decodeSubscribe: typeof decodeNodeIds;
|
|
244
|
+
/** 连 `no_detail` 一起解出(网关侧 / 测试需要判定订阅意图时用) */
|
|
245
|
+
declare function decodeSubscribeDetail(bytes: Uint8Array): {
|
|
246
|
+
nodeIds: string[];
|
|
247
|
+
noDetail: boolean;
|
|
248
|
+
};
|
|
249
|
+
declare const encodeUnsubscribe: typeof encodeNodeIds;
|
|
250
|
+
declare const decodeUnsubscribe: typeof decodeNodeIds;
|
|
251
|
+
declare function encodeResume(since: number, nodeIds?: string[]): Uint8Array;
|
|
252
|
+
declare function decodeResume(bytes: Uint8Array): {
|
|
253
|
+
since: number;
|
|
254
|
+
nodeIds: string[];
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
export { type Auth as A, encodeNodeState as B, encodeNotify as C, encodeResume as D, type Envelope as E, encodeSnapshot as F, encodeStatus as G, encodeSubscribe as H, type Info as I, encodeTerminalData as J, encodeUnsubscribe as K, signableAuthBytes as L, signableBytes as M, type NodeState as N, type Snapshot as S, type TerminalData as T, type Ack as a, type AuthOk as b, type EventMsg as c, type NotifyMsg as d, type Status as e, decodeAck as f, decodeAuth as g, decodeAuthOk as h, decodeEnvelope as i, decodeEvent as j, decodeInfo as k, decodeNodeState as l, decodeNotify as m, decodeResume as n, decodeSnapshot as o, decodeStatus as p, decodeSubscribe as q, decodeSubscribeDetail as r, decodeTerminalData as s, decodeUnsubscribe as t, encodeAck as u, encodeAuth as v, encodeAuthOk as w, encodeEnvelope as x, encodeEvent as y, encodeInfo as z };
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 消息编解码(Envelope / Ack / Status / Event)
|
|
3
|
+
*
|
|
4
|
+
* 字段定义见 proto/fleet.proto。手写编解码,规则与 prost 对齐(proto3 默认值省略、
|
|
5
|
+
* 字段号升序、string 严格 UTF-8),未知字段按 wire type 跳过以支持前向兼容。
|
|
6
|
+
*
|
|
7
|
+
* 签名:`signableBytes()` 给出「不含 sig」的编码字节;`sig` 是 10 号字段(最后),
|
|
8
|
+
* 因此 `encode(env, sig 置空) === signableBytes(env)` 恒成立,校验方可重编码比对。
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** 与现有 FleetCmdEnvelope(packages/shared/src/fleet.ts:159-178)语义一致 */
|
|
12
|
+
interface Envelope {
|
|
13
|
+
/** 毫秒时间戳 */
|
|
14
|
+
t: number;
|
|
15
|
+
nonce: string;
|
|
16
|
+
reqId: string;
|
|
17
|
+
type: string;
|
|
18
|
+
clientId: string;
|
|
19
|
+
nodeId: string;
|
|
20
|
+
projectId?: string;
|
|
21
|
+
sessionId?: string;
|
|
22
|
+
/** 载荷字节(按 type 选择强类型消息或 JSON 字节) */
|
|
23
|
+
payload?: Uint8Array;
|
|
24
|
+
/**
|
|
25
|
+
* 执行预算(毫秒,可选):由发起方指定,网关据此决定等待回执的上限。
|
|
26
|
+
* 0/缺省表示按 `type` 的默认分级(见 docs/wire-protocol.md §5.2)。
|
|
27
|
+
*/
|
|
28
|
+
timeoutMs?: number;
|
|
29
|
+
/** HMAC-SHA256 原始 32 字节(**必须是最后一个字段**:其前缀即签名字节) */
|
|
30
|
+
sig?: Uint8Array;
|
|
31
|
+
}
|
|
32
|
+
interface Ack {
|
|
33
|
+
reqId: string;
|
|
34
|
+
type: string;
|
|
35
|
+
ok: boolean;
|
|
36
|
+
error?: string;
|
|
37
|
+
data?: Uint8Array;
|
|
38
|
+
}
|
|
39
|
+
interface Status {
|
|
40
|
+
nodeId: string;
|
|
41
|
+
online: boolean;
|
|
42
|
+
/** 毫秒时间戳(现有 JSON 协议为 ISO 字符串,映射在 M2) */
|
|
43
|
+
ts: number;
|
|
44
|
+
label?: string;
|
|
45
|
+
platform?: string;
|
|
46
|
+
mode?: string;
|
|
47
|
+
version?: string;
|
|
48
|
+
ip?: string;
|
|
49
|
+
}
|
|
50
|
+
interface EventMsg {
|
|
51
|
+
seq: number;
|
|
52
|
+
ts: number;
|
|
53
|
+
type: string;
|
|
54
|
+
projectId?: string;
|
|
55
|
+
sessionId?: string;
|
|
56
|
+
data?: Uint8Array;
|
|
57
|
+
/**
|
|
58
|
+
* 事件来源节点。由**网关**按连接绑定的 nodeId 填充(节点上行时留空)。
|
|
59
|
+
*
|
|
60
|
+
* 终端面一条连接可订阅多个节点,缺了它就无法分辨事件来自哪台节点。
|
|
61
|
+
*/
|
|
62
|
+
nodeId?: string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 命令终端数据(`FrameType.TERMINAL`)。
|
|
66
|
+
*
|
|
67
|
+
* 与 `EventMsg` 完全平行的另一条通道,**不是** Event 的一种:它不参与
|
|
68
|
+
* seq 游标、不入事件环形缓冲、不跨副本广播(理由见 proto 注释)。
|
|
69
|
+
*/
|
|
70
|
+
interface TerminalData {
|
|
71
|
+
/** 目标节点;节点上行时留空,由网关按连接绑定填充 */
|
|
72
|
+
nodeId?: string;
|
|
73
|
+
/** 终端实例 ID(节点侧生成,`t_` 前缀;与会话 ID 不同空间) */
|
|
74
|
+
terminalId: string;
|
|
75
|
+
/**
|
|
76
|
+
* 帧种类:
|
|
77
|
+
* opened | data | exited | error —— 节点 → 网关 → 终端
|
|
78
|
+
* input | resize —— 终端 → 网关 → 节点
|
|
79
|
+
*
|
|
80
|
+
* 开/关终端不走这条通道(见 `terminal_create` / `terminal_close` 指令)。
|
|
81
|
+
*/
|
|
82
|
+
kind: string;
|
|
83
|
+
/** data/input 为原始字节;resize 为 JSON 字节({"cols":N,"rows":N}) */
|
|
84
|
+
data?: Uint8Array;
|
|
85
|
+
/** 终端内局部序号(仅该终端内有效,不参与 resume);文件流中为 stream_id */
|
|
86
|
+
seq?: number;
|
|
87
|
+
/**
|
|
88
|
+
* 通道:0/缺省 = 终端;1 = 文件流(音视频预览)。
|
|
89
|
+
*
|
|
90
|
+
* 文件流复用 TERMINAL 帧以避开协议版本升级(帧类型低 4 位已用尽),
|
|
91
|
+
* 但网关按该字段分流,不会 fanout 给终端连接。
|
|
92
|
+
*/
|
|
93
|
+
channel?: number;
|
|
94
|
+
/**
|
|
95
|
+
* P2P 打洞信令(`channel=2`)的会话标识。
|
|
96
|
+
*
|
|
97
|
+
* 网关按它把信令**回发给发起连接**,不 fanout——否则双方公网 IP 会泄漏给
|
|
98
|
+
* 同一节点的其他协作者。
|
|
99
|
+
*/
|
|
100
|
+
session?: string;
|
|
101
|
+
}
|
|
102
|
+
declare function encodeEnvelope(env: Envelope): Uint8Array;
|
|
103
|
+
/** 参与签名的字节(字段 1..10,不含 sig) */
|
|
104
|
+
declare function signableBytes(env: Envelope): Uint8Array;
|
|
105
|
+
declare function decodeEnvelope(bytes: Uint8Array): Envelope;
|
|
106
|
+
declare function encodeAck(ack: Ack): Uint8Array;
|
|
107
|
+
declare function decodeAck(bytes: Uint8Array): Ack;
|
|
108
|
+
declare function encodeStatus(st: Status): Uint8Array;
|
|
109
|
+
declare function decodeStatus(bytes: Uint8Array): Status;
|
|
110
|
+
declare function encodeEvent(ev: EventMsg): Uint8Array;
|
|
111
|
+
declare function decodeEvent(bytes: Uint8Array): EventMsg;
|
|
112
|
+
|
|
113
|
+
interface Info {
|
|
114
|
+
nodeId: string;
|
|
115
|
+
platform?: string;
|
|
116
|
+
version?: string;
|
|
117
|
+
ip?: string;
|
|
118
|
+
/** 项目数 */
|
|
119
|
+
projects: number;
|
|
120
|
+
/** 会话数 */
|
|
121
|
+
sessions: number;
|
|
122
|
+
/** 运行中的会话数 */
|
|
123
|
+
running: number;
|
|
124
|
+
/** 等待审批/提问的会话数 */
|
|
125
|
+
waiting: number;
|
|
126
|
+
/** 毫秒时间戳 */
|
|
127
|
+
ts: number;
|
|
128
|
+
/**
|
|
129
|
+
* 节点上报的**原始 info JSON**(含 label / 项目明细 / stats / usage)。
|
|
130
|
+
*
|
|
131
|
+
* 5..9 的计数供网关快速聚合;本字段原样承载完整快照,供控制面读取
|
|
132
|
+
* (替代从前经 MQTT retained info 的那条路径),避免为视图反复改 schema。
|
|
133
|
+
*/
|
|
134
|
+
detail?: Uint8Array;
|
|
135
|
+
}
|
|
136
|
+
declare function encodeInfo(info: Info): Uint8Array;
|
|
137
|
+
declare function decodeInfo(bytes: Uint8Array): Info;
|
|
138
|
+
/**
|
|
139
|
+
* 节点通知:审批请求 / 提问 / 回合完成 / 错误。
|
|
140
|
+
*
|
|
141
|
+
* 与 `EventMsg` 的分工:Event 是高频实时进度,Notify 是低频但重要(审批/提问挂起必须
|
|
142
|
+
* 让终端看到)。两者共用网关分配的 `seq` 游标与缓冲,因此 `resume` 时都能回放。
|
|
143
|
+
*/
|
|
144
|
+
interface NotifyMsg {
|
|
145
|
+
/** 由网关分配的游标(节点上报时留 0) */
|
|
146
|
+
seq: number;
|
|
147
|
+
ts: number;
|
|
148
|
+
kind: string;
|
|
149
|
+
projectId?: string;
|
|
150
|
+
sessionId?: string;
|
|
151
|
+
data?: Uint8Array;
|
|
152
|
+
/** 通知来源节点;由网关填充(多路复用终端据此归属到具体节点) */
|
|
153
|
+
nodeId?: string;
|
|
154
|
+
}
|
|
155
|
+
declare function encodeNotify(n: NotifyMsg): Uint8Array;
|
|
156
|
+
declare function decodeNotify(bytes: Uint8Array): NotifyMsg;
|
|
157
|
+
interface Auth {
|
|
158
|
+
/** 节点面:nodeId;终端面:userId */
|
|
159
|
+
id: string;
|
|
160
|
+
/** 毫秒时间戳(±60s 窗口) */
|
|
161
|
+
ts: number;
|
|
162
|
+
nonce: string;
|
|
163
|
+
/** 协议能力版本 */
|
|
164
|
+
proto: number;
|
|
165
|
+
/** "node" | "terminal" */
|
|
166
|
+
kind: string;
|
|
167
|
+
/** 终端载体:"web" | "miniapp" | "app"(节点面留空) */
|
|
168
|
+
client?: string;
|
|
169
|
+
/**
|
|
170
|
+
* 终端面 = HMAC-SHA256(terminalSecret, signableAuthBytes);节点面 = HMAC-SHA256(fleetSecret, …)
|
|
171
|
+
*
|
|
172
|
+
* 注意 `scope`/`expires` 在 `token` **之后**:proto3 按字段号升序编码,因此它们仍在
|
|
173
|
+
* `signableAuthBytes` 内 —— 即**同样受签名完整性保护**(改 scope 会让 token 失效)。
|
|
174
|
+
*/
|
|
175
|
+
token?: Uint8Array;
|
|
176
|
+
/** 终端面被授权的 nodeId 列表(非空时网关严格校验;见 `verifyTerminalAuth`) */
|
|
177
|
+
scope?: string[];
|
|
178
|
+
/** 终端面过期时刻(毫秒);>0 时网关跳过 ±60s 窗口 */
|
|
179
|
+
expires?: number;
|
|
180
|
+
/**
|
|
181
|
+
* 客户端能力位(见 `frame.ts` 的 `CAP_DEFLATE`)。
|
|
182
|
+
*
|
|
183
|
+
* 只用于声明支持;节点只有在 `AuthOk.caps` 也回同样位时才启用压缩上行。
|
|
184
|
+
* 旧网关忽略未知字段,行为不变。
|
|
185
|
+
*/
|
|
186
|
+
caps?: number;
|
|
187
|
+
}
|
|
188
|
+
interface AuthOk {
|
|
189
|
+
ok: boolean;
|
|
190
|
+
error?: string;
|
|
191
|
+
serverTs: number;
|
|
192
|
+
/** 网关能力位(语义同 `Auth.caps`);旧网关不回此字段(视为 0) */
|
|
193
|
+
caps?: number;
|
|
194
|
+
}
|
|
195
|
+
declare function encodeAuth(auth: Auth): Uint8Array;
|
|
196
|
+
/**
|
|
197
|
+
* 参与签名的字节:清空 `token` 后编码。
|
|
198
|
+
*
|
|
199
|
+
* `scope`/`expires` 字段号大于 `token`,但 prost/本实现都按字段号升序编码,
|
|
200
|
+
* 因此它们**仍在签名字节内**(受完整性保护),不是「token 之后的免费字段」。
|
|
201
|
+
*/
|
|
202
|
+
declare function signableAuthBytes(auth: Auth): Uint8Array;
|
|
203
|
+
declare function decodeAuth(bytes: Uint8Array): Auth;
|
|
204
|
+
declare function encodeAuthOk(ok: AuthOk): Uint8Array;
|
|
205
|
+
declare function decodeAuthOk(bytes: Uint8Array): AuthOk;
|
|
206
|
+
declare function encodeTerminalData(td: TerminalData): Uint8Array;
|
|
207
|
+
declare function decodeTerminalData(bytes: Uint8Array): TerminalData;
|
|
208
|
+
interface NodeState {
|
|
209
|
+
nodeId: string;
|
|
210
|
+
online: boolean;
|
|
211
|
+
lastSeen: number;
|
|
212
|
+
platform?: string;
|
|
213
|
+
version?: string;
|
|
214
|
+
ip?: string;
|
|
215
|
+
/**
|
|
216
|
+
* 节点上报的原始 info JSON(`Info.detail` 的转发),供终端快照读取项目/统计。
|
|
217
|
+
*
|
|
218
|
+
* 快照借此**替代 MQTT retained info**:订阅后一次拿到,不必逐节点发指令。
|
|
219
|
+
* 节点从未上报过 INFO 时为空。
|
|
220
|
+
*/
|
|
221
|
+
detail?: Uint8Array;
|
|
222
|
+
}
|
|
223
|
+
interface Snapshot {
|
|
224
|
+
nodes: NodeState[];
|
|
225
|
+
/** 当前事件游标(客户端据此发起 resume) */
|
|
226
|
+
seq: number;
|
|
227
|
+
}
|
|
228
|
+
declare function encodeNodeState(n: NodeState): Uint8Array;
|
|
229
|
+
declare function decodeNodeState(bytes: Uint8Array): NodeState;
|
|
230
|
+
declare function encodeSnapshot(snap: Snapshot): Uint8Array;
|
|
231
|
+
declare function decodeSnapshot(bytes: Uint8Array): Snapshot;
|
|
232
|
+
declare function encodeNodeIds(ids: string[]): Uint8Array;
|
|
233
|
+
declare function decodeNodeIds(bytes: Uint8Array): string[];
|
|
234
|
+
/**
|
|
235
|
+
* 订阅帧:节点列表 + 是否要内容增量。
|
|
236
|
+
*
|
|
237
|
+
* `noDetail` 用反向布尔的 `.proto` 字段(`Subscribe.no_detail`):默认 false
|
|
238
|
+
* 表示「要细节」,与本字段出现前的行为一致。只有双方(控制台/网关)都支持时
|
|
239
|
+
* 才会真正省下带宽;旧网关忽略未知字段,退化为全量推送。
|
|
240
|
+
*/
|
|
241
|
+
declare function encodeSubscribe(ids: string[], noDetail?: boolean): Uint8Array;
|
|
242
|
+
/** 只取节点列表(保持既有调用方与测试的返回类型不变) */
|
|
243
|
+
declare const decodeSubscribe: typeof decodeNodeIds;
|
|
244
|
+
/** 连 `no_detail` 一起解出(网关侧 / 测试需要判定订阅意图时用) */
|
|
245
|
+
declare function decodeSubscribeDetail(bytes: Uint8Array): {
|
|
246
|
+
nodeIds: string[];
|
|
247
|
+
noDetail: boolean;
|
|
248
|
+
};
|
|
249
|
+
declare const encodeUnsubscribe: typeof encodeNodeIds;
|
|
250
|
+
declare const decodeUnsubscribe: typeof decodeNodeIds;
|
|
251
|
+
declare function encodeResume(since: number, nodeIds?: string[]): Uint8Array;
|
|
252
|
+
declare function decodeResume(bytes: Uint8Array): {
|
|
253
|
+
since: number;
|
|
254
|
+
nodeIds: string[];
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
export { type Auth as A, encodeNodeState as B, encodeNotify as C, encodeResume as D, type Envelope as E, encodeSnapshot as F, encodeStatus as G, encodeSubscribe as H, type Info as I, encodeTerminalData as J, encodeUnsubscribe as K, signableAuthBytes as L, signableBytes as M, type NodeState as N, type Snapshot as S, type TerminalData as T, type Ack as a, type AuthOk as b, type EventMsg as c, type NotifyMsg as d, type Status as e, decodeAck as f, decodeAuth as g, decodeAuthOk as h, decodeEnvelope as i, decodeEvent as j, decodeInfo as k, decodeNodeState as l, decodeNotify as m, decodeResume as n, decodeSnapshot as o, decodeStatus as p, decodeSubscribe as q, decodeSubscribeDetail as r, decodeTerminalData as s, decodeUnsubscribe as t, encodeAck as u, encodeAuth as v, encodeAuthOk as w, encodeEnvelope as x, encodeEvent as y, encodeInfo as z };
|