ai-fly 0.0.0 → 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/README.md +105 -0
- package/dist/ai-fly.d.ts +1 -0
- package/dist/ai-fly.js +76 -0
- package/dist/common-4VeA_3wo.mjs +347 -0
- package/dist/common-BULbPtYn.mjs +1295 -0
- package/dist/config-CO3WyW2H.mjs +173 -0
- package/dist/errors-BGtxPkM7.mjs +27 -0
- package/dist/forget-BEbNwPip.mjs +22 -0
- package/dist/group-Csy9VmFb.mjs +92 -0
- package/dist/import-BVAFw5Tx.mjs +63 -0
- package/dist/join-B9op0juF.mjs +228 -0
- package/dist/join-E1_-Rt-7.mjs +32 -0
- package/dist/key-Bt4EWiuO.mjs +31 -0
- package/dist/key-CK77IYXp.mjs +67 -0
- package/dist/mux-xHl9Jrbi.mjs +630 -0
- package/dist/ports-BQukx7wS.mjs +46 -0
- package/dist/ports-pI59MtmI.mjs +68 -0
- package/dist/revoke-CyrOzc8t.mjs +34 -0
- package/dist/run-Be8H-IRf.mjs +42 -0
- package/dist/runtime-Dg4en1D8.mjs +1372 -0
- package/dist/sdk-C6xDRbzg.mjs +394 -0
- package/dist/serve-DuyQMeJh.mjs +1665 -0
- package/dist/serve-R03mDBwd.mjs +45 -0
- package/dist/service-Cd4R47n4.mjs +94 -0
- package/dist/share-DGM2TOft.mjs +133 -0
- package/dist/status-DRaKNvqz.mjs +102 -0
- package/dist/status-W2OS7WB6.mjs +57 -0
- package/package.json +64 -6
- package/index.js +0 -1
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
//#region src/wire/frames.ts
|
|
3
|
+
const FRAME_TYPE = {
|
|
4
|
+
AUTH: 1,
|
|
5
|
+
AUTH_OK: 2,
|
|
6
|
+
AUTH_ERR: 3,
|
|
7
|
+
REQ: 4,
|
|
8
|
+
REQ_BODY: 5,
|
|
9
|
+
RESP_META: 6,
|
|
10
|
+
RESP_CHUNK: 7,
|
|
11
|
+
RESP_END: 8,
|
|
12
|
+
ERROR: 9,
|
|
13
|
+
ABORT: 10,
|
|
14
|
+
PING: 11,
|
|
15
|
+
DATA_UP: 12,
|
|
16
|
+
DATA_DOWN: 13,
|
|
17
|
+
CLOSE: 14
|
|
18
|
+
};
|
|
19
|
+
/** 类型号 → 名称(日志与误用报错用)。 */
|
|
20
|
+
const FRAME_TYPE_NAME = {
|
|
21
|
+
[FRAME_TYPE.AUTH]: "AUTH",
|
|
22
|
+
[FRAME_TYPE.AUTH_OK]: "AUTH_OK",
|
|
23
|
+
[FRAME_TYPE.AUTH_ERR]: "AUTH_ERR",
|
|
24
|
+
[FRAME_TYPE.REQ]: "REQ",
|
|
25
|
+
[FRAME_TYPE.REQ_BODY]: "REQ_BODY",
|
|
26
|
+
[FRAME_TYPE.RESP_META]: "RESP_META",
|
|
27
|
+
[FRAME_TYPE.RESP_CHUNK]: "RESP_CHUNK",
|
|
28
|
+
[FRAME_TYPE.RESP_END]: "RESP_END",
|
|
29
|
+
[FRAME_TYPE.ERROR]: "ERROR",
|
|
30
|
+
[FRAME_TYPE.ABORT]: "ABORT",
|
|
31
|
+
[FRAME_TYPE.PING]: "PING",
|
|
32
|
+
[FRAME_TYPE.DATA_UP]: "DATA_UP",
|
|
33
|
+
[FRAME_TYPE.DATA_DOWN]: "DATA_DOWN",
|
|
34
|
+
[FRAME_TYPE.CLOSE]: "CLOSE"
|
|
35
|
+
};
|
|
36
|
+
function isKnownFrameType(type) {
|
|
37
|
+
return type in FRAME_TYPE_NAME;
|
|
38
|
+
}
|
|
39
|
+
const ERROR_CODE = {
|
|
40
|
+
aborted: "aborted",
|
|
41
|
+
buffer_overflow: "buffer_overflow",
|
|
42
|
+
idle_timeout: "idle_timeout",
|
|
43
|
+
unauthorized: "unauthorized",
|
|
44
|
+
key_all_invalid: "key_all_invalid",
|
|
45
|
+
unknown_service: "unknown_service",
|
|
46
|
+
path_not_offered: "path_not_offered",
|
|
47
|
+
upstream_unreachable: "upstream_unreachable",
|
|
48
|
+
upstream_status: "upstream_status",
|
|
49
|
+
secret_missing: "secret_missing",
|
|
50
|
+
body_too_large: "body_too_large",
|
|
51
|
+
rate_limited: "rate_limited",
|
|
52
|
+
quota_exceeded: "quota_exceeded",
|
|
53
|
+
forbidden_method: "forbidden_method",
|
|
54
|
+
forbidden_header: "forbidden_header",
|
|
55
|
+
protocol_version: "protocol_version",
|
|
56
|
+
protocol_seq: "protocol_seq",
|
|
57
|
+
protocol_error: "protocol_error",
|
|
58
|
+
internal: "internal"
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* 仅 AUTH_OK.rejected 载荷码(spec 明确:这两个码不是 ERROR 帧码);
|
|
62
|
+
* 归档注释锚点:key_invalid / key_revoked 不得加入 ERROR_CODE。
|
|
63
|
+
*/
|
|
64
|
+
const REJECTED_CODE = {
|
|
65
|
+
key_invalid: "key_invalid",
|
|
66
|
+
key_revoked: "key_revoked"
|
|
67
|
+
};
|
|
68
|
+
/** 帧资源上限(spec「帧资源上限」:path / headers 项数 / 键值 / JSON 头总长)。 */
|
|
69
|
+
const STRUCT_LIMITS = {
|
|
70
|
+
pathMaxBytes: 4096,
|
|
71
|
+
headersMaxCount: 32,
|
|
72
|
+
headerNameMaxBytes: 1024,
|
|
73
|
+
headerValueMaxBytes: 8192,
|
|
74
|
+
jsonHeaderMaxBytes: 16384
|
|
75
|
+
};
|
|
76
|
+
/** REQ.headers 禁止透传的凭据类 / 归属类头(双向零过桥;spec REQ 条款)。 */
|
|
77
|
+
const FORBIDDEN_REQ_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
78
|
+
"authorization",
|
|
79
|
+
"proxy-authorization",
|
|
80
|
+
"cookie",
|
|
81
|
+
"host",
|
|
82
|
+
"content-type"
|
|
83
|
+
]);
|
|
84
|
+
/** RESP_META.headers 白名单子集(spec 响应下行条款)。 */
|
|
85
|
+
const RESP_META_HEADER_WHITELIST = /* @__PURE__ */ new Set([
|
|
86
|
+
"x-request-id",
|
|
87
|
+
"retry-after",
|
|
88
|
+
"sec-websocket-accept"
|
|
89
|
+
]);
|
|
90
|
+
/** HTTP 方法枚举(REQ.method;越界字符串 → forbidden_method,非字符串 → protocol_error)。 */
|
|
91
|
+
const HTTP_METHODS = [
|
|
92
|
+
"GET",
|
|
93
|
+
"HEAD",
|
|
94
|
+
"POST",
|
|
95
|
+
"PUT",
|
|
96
|
+
"PATCH",
|
|
97
|
+
"DELETE"
|
|
98
|
+
];
|
|
99
|
+
const TEXT_ENCODER = new TextEncoder();
|
|
100
|
+
function byteLen(s) {
|
|
101
|
+
return TEXT_ENCODER.encode(s).length;
|
|
102
|
+
}
|
|
103
|
+
function isRecord(x) {
|
|
104
|
+
return typeof x === "object" && x !== null && !Array.isArray(x);
|
|
105
|
+
}
|
|
106
|
+
/** 标识字段(z32 形态:request-id 26 字符 / serviceId 13 / keyId 同族;上限宽松防滥用)。 */
|
|
107
|
+
const Z_ID = z.string().min(1).max(128);
|
|
108
|
+
/** 分片序号:非负整数(缺断/重复由 mux 的期望序号判定,schema 不设上界)。 */
|
|
109
|
+
const Z_SEQ = z.number().int().min(0);
|
|
110
|
+
/** contentType 形态:任意字符串,字节长受单值上限约束(与头值同规)。 */
|
|
111
|
+
const Z_CONTENT_TYPE = z.string().refine((s) => byteLen(s) <= STRUCT_LIMITS.headerValueMaxBytes, { message: `contentType exceeds ${STRUCT_LIMITS.headerValueMaxBytes} bytes` });
|
|
112
|
+
const REQ_PATH_SCHEMA = z.string().superRefine((p, ctx) => {
|
|
113
|
+
const add = (message) => ctx.addIssue({
|
|
114
|
+
code: "custom",
|
|
115
|
+
message,
|
|
116
|
+
path: ["path"]
|
|
117
|
+
});
|
|
118
|
+
if (byteLen(p) > STRUCT_LIMITS.pathMaxBytes) {
|
|
119
|
+
add(`path exceeds ${STRUCT_LIMITS.pathMaxBytes} bytes`);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (!p.startsWith("/")) {
|
|
123
|
+
add("path must start with a single '/'");
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (p.startsWith("//") || p.startsWith("/\\")) {
|
|
127
|
+
add("path must not start with '//' or '/\\'");
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const pathPart = p.split("?", 1)[0];
|
|
131
|
+
if (pathPart.includes("://")) {
|
|
132
|
+
add("path must not contain a scheme");
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
for (const seg of pathPart.split("/").slice(1)) if (seg === "." || seg === "..") {
|
|
136
|
+
add("path must not contain '.' or '..' segments");
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
const REQ_HEADERS_SCHEMA = z.record(z.string(), z.string()).superRefine((headers, ctx) => {
|
|
141
|
+
const names = Object.keys(headers);
|
|
142
|
+
if (names.length > STRUCT_LIMITS.headersMaxCount) ctx.addIssue({
|
|
143
|
+
code: "custom",
|
|
144
|
+
message: `headers exceed ${STRUCT_LIMITS.headersMaxCount} entries`,
|
|
145
|
+
path: ["headers"]
|
|
146
|
+
});
|
|
147
|
+
for (const name of names) {
|
|
148
|
+
const value = headers[name];
|
|
149
|
+
if (FORBIDDEN_REQ_HEADER_NAMES.has(name)) {
|
|
150
|
+
ctx.addIssue({
|
|
151
|
+
code: "custom",
|
|
152
|
+
message: `forbidden header: ${name}`,
|
|
153
|
+
path: ["headers", name]
|
|
154
|
+
});
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (name !== name.toLowerCase()) ctx.addIssue({
|
|
158
|
+
code: "custom",
|
|
159
|
+
message: `header name not lowercase-normalized: ${name}`,
|
|
160
|
+
path: ["headers", name]
|
|
161
|
+
});
|
|
162
|
+
if (byteLen(name) > STRUCT_LIMITS.headerNameMaxBytes) ctx.addIssue({
|
|
163
|
+
code: "custom",
|
|
164
|
+
message: `header name exceeds ${STRUCT_LIMITS.headerNameMaxBytes} bytes`,
|
|
165
|
+
path: ["headers", name]
|
|
166
|
+
});
|
|
167
|
+
if (byteLen(value) > STRUCT_LIMITS.headerValueMaxBytes) ctx.addIssue({
|
|
168
|
+
code: "custom",
|
|
169
|
+
message: `header value exceeds ${STRUCT_LIMITS.headerValueMaxBytes} bytes`,
|
|
170
|
+
path: ["headers", name]
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
const RESP_META_HEADERS_SCHEMA = z.record(z.string(), z.string()).superRefine((headers, ctx) => {
|
|
175
|
+
const names = Object.keys(headers);
|
|
176
|
+
if (names.length > STRUCT_LIMITS.headersMaxCount) ctx.addIssue({
|
|
177
|
+
code: "custom",
|
|
178
|
+
message: `headers exceed ${STRUCT_LIMITS.headersMaxCount} entries`,
|
|
179
|
+
path: ["headers"]
|
|
180
|
+
});
|
|
181
|
+
for (const name of names) {
|
|
182
|
+
const value = headers[name];
|
|
183
|
+
if (!RESP_META_HEADER_WHITELIST.has(name)) {
|
|
184
|
+
ctx.addIssue({
|
|
185
|
+
code: "custom",
|
|
186
|
+
message: `response header not whitelisted: ${name}`,
|
|
187
|
+
path: ["headers", name]
|
|
188
|
+
});
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (byteLen(value) > STRUCT_LIMITS.headerValueMaxBytes) ctx.addIssue({
|
|
192
|
+
code: "custom",
|
|
193
|
+
message: `header value exceeds ${STRUCT_LIMITS.headerValueMaxBytes} bytes`,
|
|
194
|
+
path: ["headers", name]
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
/** match 展示集条目(exact / suffix / regex;纯展示元数据,运行时路由走 serviceId)。 */
|
|
199
|
+
const SERVICE_MATCH_SCHEMA = z.strictObject({
|
|
200
|
+
type: z.enum([
|
|
201
|
+
"exact",
|
|
202
|
+
"suffix",
|
|
203
|
+
"regex"
|
|
204
|
+
]),
|
|
205
|
+
value: z.string().min(1).max(2048)
|
|
206
|
+
});
|
|
207
|
+
/** 服务完整配置的脱敏披露($env 注入的头值仅显示 ●,变量名不显示)。 */
|
|
208
|
+
const SERVICE_DETAIL_SCHEMA = z.strictObject({
|
|
209
|
+
upstream: z.string().min(1).max(2048),
|
|
210
|
+
match: z.array(SERVICE_MATCH_SCHEMA).max(64),
|
|
211
|
+
rewrite: z.strictObject({
|
|
212
|
+
host: z.string().min(1).max(2048).optional(),
|
|
213
|
+
prefix: z.string().min(1).max(2048).optional(),
|
|
214
|
+
headerSet: z.array(z.strictObject({
|
|
215
|
+
name: z.string().min(1).max(1024),
|
|
216
|
+
value: z.string().max(256)
|
|
217
|
+
})).max(32).optional()
|
|
218
|
+
}),
|
|
219
|
+
/** 路径路由披露(M3-r7:prefix/pattern 双模式;forms 为 AI 层标注,可为空)。 */
|
|
220
|
+
routes: z.array(z.strictObject({
|
|
221
|
+
forms: z.array(z.enum([
|
|
222
|
+
"openai-chat",
|
|
223
|
+
"openai-responses",
|
|
224
|
+
"anthropic"
|
|
225
|
+
])).max(3),
|
|
226
|
+
mode: z.enum(["prefix", "pattern"]).optional(),
|
|
227
|
+
localPrefix: z.string().max(2048).optional(),
|
|
228
|
+
upstreamPrefix: z.string().max(2048).optional(),
|
|
229
|
+
matchPattern: z.string().max(2048).optional(),
|
|
230
|
+
template: z.string().max(2048).optional()
|
|
231
|
+
})).max(4).optional()
|
|
232
|
+
});
|
|
233
|
+
const SERVICE_ENTRY_SCHEMA = z.strictObject({
|
|
234
|
+
serviceId: Z_ID,
|
|
235
|
+
name: z.string().min(1).max(256),
|
|
236
|
+
match: z.array(SERVICE_MATCH_SCHEMA).max(64),
|
|
237
|
+
defaultPort: z.number().int().min(1).max(65535),
|
|
238
|
+
detail: SERVICE_DETAIL_SCHEMA.optional()
|
|
239
|
+
});
|
|
240
|
+
const GROUP_ENTRY_SCHEMA = z.strictObject({
|
|
241
|
+
keyId: Z_ID,
|
|
242
|
+
group: z.string().min(1).max(256),
|
|
243
|
+
limits: z.strictObject({
|
|
244
|
+
maxConcurrency: z.number().int().min(1).optional(),
|
|
245
|
+
dailyRequests: z.number().int().min(1).optional()
|
|
246
|
+
}),
|
|
247
|
+
services: z.array(SERVICE_ENTRY_SCHEMA)
|
|
248
|
+
});
|
|
249
|
+
/** AUTH {v, keys[]}:钥环一次呈交(密钥格式细则归 §3 store,wire 层仅防明显垃圾)。 */
|
|
250
|
+
const AUTH_HEADER_SCHEMA = z.strictObject({
|
|
251
|
+
v: z.literal(1),
|
|
252
|
+
keys: z.array(z.string().min(8).max(256)).min(1)
|
|
253
|
+
});
|
|
254
|
+
/**
|
|
255
|
+
* AUTH_OK:目录全量载荷(初次授权与 refresh 推送同构;groups ≥1——全无效走 AUTH_ERR)。
|
|
256
|
+
*/
|
|
257
|
+
const AUTH_OK_HEADER_SCHEMA = z.strictObject({
|
|
258
|
+
v: z.literal(1),
|
|
259
|
+
alias: z.string().min(1).max(256),
|
|
260
|
+
relayUrls: z.array(z.string().min(1).max(2048)),
|
|
261
|
+
groups: z.array(GROUP_ENTRY_SCHEMA).min(1),
|
|
262
|
+
rejected: z.array(z.strictObject({ code: z.enum([REJECTED_CODE.key_invalid, REJECTED_CODE.key_revoked]) })).max(64).optional(),
|
|
263
|
+
refresh: z.literal(true).optional()
|
|
264
|
+
});
|
|
265
|
+
/** AUTH_ERR:全部密钥无效(单次即断,由 §3 引擎层执行断开)。 */
|
|
266
|
+
const AUTH_ERR_HEADER_SCHEMA = z.strictObject({
|
|
267
|
+
v: z.literal(1),
|
|
268
|
+
code: z.literal(ERROR_CODE.key_all_invalid),
|
|
269
|
+
message: z.string().max(2048).optional()
|
|
270
|
+
});
|
|
271
|
+
/** REQ {v,id,serviceId,method,path,headers?,contentType?,bodyLen}:body 内联或空。 */
|
|
272
|
+
const REQ_HEADER_SCHEMA = z.strictObject({
|
|
273
|
+
v: z.literal(1),
|
|
274
|
+
id: Z_ID,
|
|
275
|
+
serviceId: Z_ID,
|
|
276
|
+
method: z.enum(HTTP_METHODS),
|
|
277
|
+
path: REQ_PATH_SCHEMA,
|
|
278
|
+
headers: REQ_HEADERS_SCHEMA.optional(),
|
|
279
|
+
contentType: Z_CONTENT_TYPE.optional(),
|
|
280
|
+
bodyLen: z.number().int().min(0).max(2 ** 32)
|
|
281
|
+
});
|
|
282
|
+
/** REQ_BODY {id,seq,end}:正文分片续帧(seq 从 0 递增)。 */
|
|
283
|
+
const REQ_BODY_HEADER_SCHEMA = z.strictObject({
|
|
284
|
+
id: Z_ID,
|
|
285
|
+
seq: Z_SEQ,
|
|
286
|
+
end: z.boolean()
|
|
287
|
+
});
|
|
288
|
+
/** RESP_META {id,status,contentType,headers?}:响应元信息(WS 101 时含 accept 头)。 */
|
|
289
|
+
const RESP_META_HEADER_SCHEMA = z.strictObject({
|
|
290
|
+
id: Z_ID,
|
|
291
|
+
status: z.number().int().min(100).max(599),
|
|
292
|
+
contentType: Z_CONTENT_TYPE,
|
|
293
|
+
headers: RESP_META_HEADERS_SCHEMA.optional()
|
|
294
|
+
});
|
|
295
|
+
/** RESP_CHUNK {id,seq}:响应字节分片。 */
|
|
296
|
+
const RESP_CHUNK_HEADER_SCHEMA = z.strictObject({
|
|
297
|
+
id: Z_ID,
|
|
298
|
+
seq: Z_SEQ
|
|
299
|
+
});
|
|
300
|
+
/** RESP_END {id}:成功终结。 */
|
|
301
|
+
const RESP_END_HEADER_SCHEMA = z.strictObject({ id: Z_ID });
|
|
302
|
+
/** ERROR {id?,code,message}:id 可得时携带;message 脱敏(不含密钥与上游凭据)。 */
|
|
303
|
+
const ERROR_HEADER_SCHEMA = z.strictObject({
|
|
304
|
+
id: Z_ID.optional(),
|
|
305
|
+
code: z.enum([
|
|
306
|
+
ERROR_CODE.aborted,
|
|
307
|
+
ERROR_CODE.buffer_overflow,
|
|
308
|
+
ERROR_CODE.idle_timeout,
|
|
309
|
+
ERROR_CODE.unauthorized,
|
|
310
|
+
ERROR_CODE.key_all_invalid,
|
|
311
|
+
ERROR_CODE.unknown_service,
|
|
312
|
+
ERROR_CODE.path_not_offered,
|
|
313
|
+
ERROR_CODE.upstream_unreachable,
|
|
314
|
+
ERROR_CODE.upstream_status,
|
|
315
|
+
ERROR_CODE.secret_missing,
|
|
316
|
+
ERROR_CODE.body_too_large,
|
|
317
|
+
ERROR_CODE.rate_limited,
|
|
318
|
+
ERROR_CODE.quota_exceeded,
|
|
319
|
+
ERROR_CODE.forbidden_method,
|
|
320
|
+
ERROR_CODE.forbidden_header,
|
|
321
|
+
ERROR_CODE.protocol_version,
|
|
322
|
+
ERROR_CODE.protocol_seq,
|
|
323
|
+
ERROR_CODE.protocol_error,
|
|
324
|
+
ERROR_CODE.internal
|
|
325
|
+
]),
|
|
326
|
+
message: z.string()
|
|
327
|
+
});
|
|
328
|
+
/** ABORT {id}:使用方本地中止信号(提供方回 ERROR(aborted) 终结)。 */
|
|
329
|
+
const ABORT_HEADER_SCHEMA = z.strictObject({ id: Z_ID });
|
|
330
|
+
/** PING {id}:首字节等待期心跳(提供方 → 使用方,每 30s)。 */
|
|
331
|
+
const PING_HEADER_SCHEMA = z.strictObject({ id: Z_ID });
|
|
332
|
+
/** DATA_UP / DATA_DOWN {v,id,seq}:WS 双向原始字节分片(spec WS 条款字面含 v)。 */
|
|
333
|
+
const DATA_HEADER_SCHEMA = z.strictObject({
|
|
334
|
+
v: z.literal(1),
|
|
335
|
+
id: Z_ID,
|
|
336
|
+
seq: Z_SEQ
|
|
337
|
+
});
|
|
338
|
+
/** CLOSE {id,code?}:WS 终结帧(语义同 RESP_END)。 */
|
|
339
|
+
const CLOSE_HEADER_SCHEMA = z.strictObject({
|
|
340
|
+
id: Z_ID,
|
|
341
|
+
code: z.number().int().min(1e3).max(65535).optional()
|
|
342
|
+
});
|
|
343
|
+
/** 类型号 → 头 schema 注册表(mux 入站校验入口)。 */
|
|
344
|
+
const FRAME_HEADER_SCHEMAS = {
|
|
345
|
+
[FRAME_TYPE.AUTH]: AUTH_HEADER_SCHEMA,
|
|
346
|
+
[FRAME_TYPE.AUTH_OK]: AUTH_OK_HEADER_SCHEMA,
|
|
347
|
+
[FRAME_TYPE.AUTH_ERR]: AUTH_ERR_HEADER_SCHEMA,
|
|
348
|
+
[FRAME_TYPE.REQ]: REQ_HEADER_SCHEMA,
|
|
349
|
+
[FRAME_TYPE.REQ_BODY]: REQ_BODY_HEADER_SCHEMA,
|
|
350
|
+
[FRAME_TYPE.RESP_META]: RESP_META_HEADER_SCHEMA,
|
|
351
|
+
[FRAME_TYPE.RESP_CHUNK]: RESP_CHUNK_HEADER_SCHEMA,
|
|
352
|
+
[FRAME_TYPE.RESP_END]: RESP_END_HEADER_SCHEMA,
|
|
353
|
+
[FRAME_TYPE.ERROR]: ERROR_HEADER_SCHEMA,
|
|
354
|
+
[FRAME_TYPE.ABORT]: ABORT_HEADER_SCHEMA,
|
|
355
|
+
[FRAME_TYPE.PING]: PING_HEADER_SCHEMA,
|
|
356
|
+
[FRAME_TYPE.DATA_UP]: DATA_HEADER_SCHEMA,
|
|
357
|
+
[FRAME_TYPE.DATA_DOWN]: DATA_HEADER_SCHEMA,
|
|
358
|
+
[FRAME_TYPE.CLOSE]: CLOSE_HEADER_SCHEMA
|
|
359
|
+
};
|
|
360
|
+
/**
|
|
361
|
+
* 依据原始输入对 schema 失败做语义分类:
|
|
362
|
+
* - REQ.method 为字符串但越界 → forbidden_method;非字符串 → protocol_error;
|
|
363
|
+
* - REQ.headers 命中凭据类头(大小写不敏感)→ forbidden_header;
|
|
364
|
+
* - 其余(未知字段、类型非法、path 形态、结构超限、版本字面量)→ protocol_error。
|
|
365
|
+
* 注意:protocol_version 不在此处产生——JSON 头中 v≠1 由 codec 层先行拦截。
|
|
366
|
+
*/
|
|
367
|
+
function classifySchemaFailure(frameType, input) {
|
|
368
|
+
let id;
|
|
369
|
+
if (isRecord(input) && typeof input.id === "string") id = input.id;
|
|
370
|
+
if (isRecord(input) && frameType === FRAME_TYPE.REQ) {
|
|
371
|
+
const method = input.method;
|
|
372
|
+
if (typeof method === "string" && !HTTP_METHODS.includes(method)) return withId({ code: "forbidden_method" }, id);
|
|
373
|
+
const headers = input.headers;
|
|
374
|
+
if (isRecord(headers)) {
|
|
375
|
+
for (const key of Object.keys(headers)) if (FORBIDDEN_REQ_HEADER_NAMES.has(key) || FORBIDDEN_REQ_HEADER_NAMES.has(key.toLowerCase())) return withId({ code: "forbidden_header" }, id);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return withId({ code: "protocol_error" }, id);
|
|
379
|
+
}
|
|
380
|
+
function withId(fail, id) {
|
|
381
|
+
if (id !== void 0) fail.id = id;
|
|
382
|
+
return fail;
|
|
383
|
+
}
|
|
384
|
+
//#endregion
|
|
385
|
+
//#region src/sdk.ts
|
|
386
|
+
async function loadSdk() {
|
|
387
|
+
const candidate = await import("@jixo/opendweb-client-sdk");
|
|
388
|
+
if (typeof candidate.Fabric === "function") return candidate;
|
|
389
|
+
const def = candidate.default;
|
|
390
|
+
if (def !== void 0 && typeof def.Fabric === "function") return def;
|
|
391
|
+
throw new Error("error: @jixo/opendweb-client-sdk export shape unexpected (no Fabric via named or default interop)");
|
|
392
|
+
}
|
|
393
|
+
//#endregion
|
|
394
|
+
export { FRAME_TYPE as a, REJECTED_CODE as c, classifySchemaFailure as d, isKnownFrameType as f, FRAME_HEADER_SCHEMAS as i, RESP_META_HEADER_WHITELIST as l, ERROR_CODE as n, FRAME_TYPE_NAME as o, FORBIDDEN_REQ_HEADER_NAMES as r, HTTP_METHODS as s, loadSdk as t, SERVICE_ENTRY_SCHEMA as u };
|