@h-ai/kit 0.1.0-alpha.67 → 0.1.0-alpha.69
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.en.md +67 -13
- package/README.ja.md +67 -13
- package/README.md +67 -13
- package/dist/adapter/index.d.ts +17 -2
- package/dist/adapter/index.js.map +1 -1
- package/dist/browser.d.ts +12 -6
- package/dist/browser.js +6 -7
- package/dist/browser.js.map +1 -1
- package/dist/{chunk-DVW7CQ3H.js → chunk-2PWGJCWX.js} +3 -3
- package/dist/chunk-2PWGJCWX.js.map +1 -0
- package/dist/chunk-OVRZIDCD.js +175 -0
- package/dist/chunk-OVRZIDCD.js.map +1 -0
- package/dist/{chunk-VOTDAVCB.js → chunk-QNLZLTVC.js} +29 -42
- package/dist/chunk-QNLZLTVC.js.map +1 -0
- package/dist/{chunk-MLSP67JF.js → chunk-UTCS6FR7.js} +67 -110
- package/dist/chunk-UTCS6FR7.js.map +1 -0
- package/dist/{chunk-NVYIUBU7.js → chunk-XC7I2L3G.js} +2 -2
- package/dist/chunk-XC7I2L3G.js.map +1 -0
- package/dist/{chunk-L7BTMAKX.js → chunk-Y2YUA3VR.js} +5 -6
- package/dist/chunk-Y2YUA3VR.js.map +1 -0
- package/dist/client/index.d.ts +47 -1
- package/dist/client/index.js +3 -3
- package/dist/client/index.js.map +1 -1
- package/dist/index.d.ts +251 -347
- package/dist/index.js +347 -171
- package/dist/index.js.map +1 -1
- package/dist/kit-a2a-types-DwcmE668.d.ts +136 -0
- package/dist/{index-B7s-0mZl.d.ts → kit-client-auth-B5y1g5_A.d.ts} +228 -102
- package/dist/kit-crypto-types-Cwj9bprV.d.ts +196 -0
- package/dist/{kit-i18n-D9Qqk-1k.d.ts → kit-i18n-DEzmzE5T.d.ts} +142 -29
- package/dist/{kit-types-ByWdbCeO.d.ts → kit-types-DQYqP1Cn.d.ts} +242 -162
- package/dist/modules/a2a/index.d.ts +13 -111
- package/dist/modules/a2a/index.js +2 -2
- package/dist/modules/crypto/index.d.ts +28 -195
- package/dist/modules/crypto/index.js +3 -3
- package/dist/modules/crypto/index.js.map +1 -1
- package/package.json +16 -11
- package/dist/chunk-DVW7CQ3H.js.map +0 -1
- package/dist/chunk-L7BTMAKX.js.map +0 -1
- package/dist/chunk-MLSP67JF.js.map +0 -1
- package/dist/chunk-NVYIUBU7.js.map +0 -1
- package/dist/chunk-QHYNL24G.js +0 -117
- package/dist/chunk-QHYNL24G.js.map +0 -1
- package/dist/chunk-VOTDAVCB.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import { defineCrud } from './chunk-
|
|
2
|
-
export { KitConfigSchema, defineCrud, resolveKitConfig, resolveKitTransportConfig, resolveOptions, resolveText } from './chunk-
|
|
3
|
-
import {
|
|
4
|
-
import { resolveA2AConfig, handleA2ARequest } from './chunk-
|
|
5
|
-
import { transportEncryptionMiddleware } from './chunk-
|
|
6
|
-
import './chunk-
|
|
7
|
-
import { kitM, setAllModulesLocale } from './chunk-
|
|
8
|
-
import { core } from '@h-ai/core';
|
|
9
|
-
import {
|
|
1
|
+
import { defineCrud } from './chunk-Y2YUA3VR.js';
|
|
2
|
+
export { KitConfigSchema, defineCrud, resolveKitConfig, resolveKitTransportConfig, resolveOptions, resolveText } from './chunk-Y2YUA3VR.js';
|
|
3
|
+
import { clearBrowserToken, setBrowserToken, logout, registerAndLogin, loginWithApiKey, loginWithLdap, loginWithOtp, login, installBrowserTransportFetch, installBrowserTransport, createKitAuthActions, createKitClient, configureAuth, getAccessToken } from './chunk-QNLZLTVC.js';
|
|
4
|
+
import { resolveA2AConfig, handleA2ARequest } from './chunk-2PWGJCWX.js';
|
|
5
|
+
import { transportEncryptionMiddleware } from './chunk-UTCS6FR7.js';
|
|
6
|
+
import './chunk-XC7I2L3G.js';
|
|
7
|
+
import { kitM, setAllModulesLocale } from './chunk-OVRZIDCD.js';
|
|
8
|
+
import { core, HaiCommonError } from '@h-ai/core';
|
|
9
|
+
import { createHash } from 'crypto';
|
|
10
|
+
import { isIP } from 'net';
|
|
11
|
+
import process2 from 'process';
|
|
12
|
+
import { IamConfirmPasswordResetInputSchema, IamRequestPasswordResetInputSchema, IamSendOtpInputSchema, IamRegisterInputSchema, IamOtpLoginInputSchema, IamLdapLoginInputSchema, IamLoginInputSchema, FeedbackSubmitInputSchema, FeedbackListInputSchema, FeedbackIdInputSchema, FeedbackEvidenceQuerySchema, FeedbackAppendEvidenceInputSchema, FeedbackSetStatusInputSchema, FeedbackRetryInputSchema, FeedbackCompleteInputSchema, FeedbackExportInputSchema, FeedbackUploadInputSchema, FeedbackAttachmentQuerySchema, FeedbackExportQuerySchema } from '@h-ai/api-contract';
|
|
10
13
|
import { z } from 'zod';
|
|
11
14
|
|
|
12
15
|
// src/guards/kit-permission.ts
|
|
@@ -116,67 +119,93 @@ function loggingMiddleware(config = {}) {
|
|
|
116
119
|
function redactObject(obj, fields) {
|
|
117
120
|
return core.sanitize.sanitizeSensitiveFields(obj, { matcher: fields });
|
|
118
121
|
}
|
|
122
|
+
function permitsMemory() {
|
|
123
|
+
return !core.module.requiresSharedState(process2.env);
|
|
124
|
+
}
|
|
125
|
+
function createCacheRateLimitStore(backend, options) {
|
|
126
|
+
if (!options.namespace || options.namespace.length > 128)
|
|
127
|
+
throw new TypeError("A rate limit namespace of 1-128 characters is required");
|
|
128
|
+
return {
|
|
129
|
+
scope: "shared",
|
|
130
|
+
async increment(key, windowMs) {
|
|
131
|
+
if (!backend.config || backend.config.type !== "redis" && !(backend.config.type === "memory" && options.allowMemory && permitsMemory()))
|
|
132
|
+
throw new Error("Rate limiting requires a shared Redis cache");
|
|
133
|
+
const digest = createHash("sha256").update(JSON.stringify([options.namespace, windowMs, key])).digest("hex");
|
|
134
|
+
const cacheKey = `hai:kit:ratelimit:{${digest}}`;
|
|
135
|
+
for (let attempt = 0; attempt < 64; attempt++) {
|
|
136
|
+
const read = await backend.kv.get(cacheKey);
|
|
137
|
+
if (!read.success)
|
|
138
|
+
throw new Error("Rate limit cache read failed");
|
|
139
|
+
const old = read.data ?? void 0;
|
|
140
|
+
if (old && (!Number.isSafeInteger(old.count) || old.count <= 0 || old.count >= Number.MAX_SAFE_INTEGER || !Number.isSafeInteger(old.resetAt)))
|
|
141
|
+
throw new Error("Invalid rate limit state");
|
|
142
|
+
const next = old ? { count: old.count + 1, resetAt: old.resetAt } : { count: 1, resetAt: Date.now() + windowMs };
|
|
143
|
+
const written = await backend.kv.compareAndSwap(cacheKey, old, next, old ? { keepTtl: true } : { px: windowMs });
|
|
144
|
+
if (!written.success)
|
|
145
|
+
throw new Error("Rate limit cache commit failed");
|
|
146
|
+
if (written.data)
|
|
147
|
+
return next;
|
|
148
|
+
}
|
|
149
|
+
throw new Error("Rate limit contention limit exceeded");
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function createMemoryRateLimitStore() {
|
|
154
|
+
if (!permitsMemory())
|
|
155
|
+
throw new Error("Memory rate limiting is restricted to development and tests");
|
|
156
|
+
return new MemoryRateLimitStore();
|
|
157
|
+
}
|
|
158
|
+
function clientKey(address) {
|
|
159
|
+
if (isIP(address) !== 6)
|
|
160
|
+
return address;
|
|
161
|
+
const normalized = new URL(`http://[${address}]`).hostname.slice(1, -1);
|
|
162
|
+
const mapped = /^::ffff:([0-9a-f]+):([0-9a-f]+)$/.exec(normalized);
|
|
163
|
+
if (!mapped)
|
|
164
|
+
return normalized;
|
|
165
|
+
const value = Number.parseInt(mapped[1], 16) * 65536 + Number.parseInt(mapped[2], 16);
|
|
166
|
+
return [value >>> 24, value >>> 16 & 255, value >>> 8 & 255, value & 255].join(".");
|
|
167
|
+
}
|
|
119
168
|
var MemoryRateLimitStore = class {
|
|
169
|
+
scope = "memory";
|
|
120
170
|
store = /* @__PURE__ */ new Map();
|
|
121
|
-
|
|
171
|
+
cleanupCursor;
|
|
122
172
|
/**
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
* @param intervalMs - 清理间隔(毫秒)
|
|
173
|
+
* 有界惰性回收;保留迭代位置,避免每次从头扫描使尾部过期项永久滞留。
|
|
174
|
+
* @param now - 当前请求时间,等于 resetAt 时窗口已经失效。
|
|
126
175
|
*/
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
this.store.delete(key);
|
|
135
|
-
}
|
|
176
|
+
prune(now) {
|
|
177
|
+
this.cleanupCursor ??= this.store.entries();
|
|
178
|
+
for (let index = 0; index < 64; index++) {
|
|
179
|
+
const next = this.cleanupCursor.next();
|
|
180
|
+
if (next.done) {
|
|
181
|
+
this.cleanupCursor = void 0;
|
|
182
|
+
break;
|
|
136
183
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
184
|
+
const [key, entry] = next.value;
|
|
185
|
+
if (entry.resetAt <= now)
|
|
186
|
+
this.store.delete(key);
|
|
140
187
|
}
|
|
141
188
|
}
|
|
142
|
-
/**
|
|
143
|
-
* 停止定期清理并释放定时器资源
|
|
144
|
-
*
|
|
145
|
-
* 调用后可再次 startCleanup 重启。进程退出 / 热重载 / 测试拆装场景必须调用以
|
|
146
|
-
* 避免资源泄漏。
|
|
147
|
-
*/
|
|
148
|
-
stopCleanup() {
|
|
149
|
-
if (this.cleanupTimer) {
|
|
150
|
-
clearInterval(this.cleanupTimer);
|
|
151
|
-
this.cleanupTimer = null;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
get(key) {
|
|
155
|
-
return this.store.get(key);
|
|
156
|
-
}
|
|
157
|
-
set(key, entry) {
|
|
158
|
-
this.store.set(key, entry);
|
|
159
|
-
}
|
|
160
|
-
delete(key) {
|
|
161
|
-
this.store.delete(key);
|
|
162
|
-
}
|
|
163
189
|
/**
|
|
164
190
|
* 原子自增(内存实现为同步操作,天然原子)
|
|
165
191
|
*
|
|
166
192
|
* @param key - 限流键
|
|
167
193
|
* @param windowMs - 窗口时长
|
|
168
|
-
* @returns
|
|
194
|
+
* @returns 自增后的快照,修改返回对象不会改变已保存的计数。
|
|
195
|
+
* @throws 新 key 超出容量或期限非法;中间件将其转换为 503。
|
|
196
|
+
* @example store.increment('client', 60_000)
|
|
169
197
|
*/
|
|
170
198
|
increment(key, windowMs) {
|
|
171
199
|
const now = Date.now();
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
200
|
+
if (!Number.isSafeInteger(windowMs) || windowMs <= 0 || windowMs > 2147483647)
|
|
201
|
+
throw new TypeError(kitM("kit_rateLimitWindowInvalid"));
|
|
202
|
+
this.prune(now);
|
|
203
|
+
const current = this.store.get(key);
|
|
204
|
+
if (!current && this.store.size >= 1e4)
|
|
205
|
+
throw new Error(kitM("kit_rateLimitMemoryCapacity"));
|
|
206
|
+
const entry = !current || current.resetAt <= now ? { count: 1, resetAt: now + windowMs } : { count: current.count + 1, resetAt: current.resetAt };
|
|
178
207
|
this.store.set(key, entry);
|
|
179
|
-
return entry;
|
|
208
|
+
return { ...entry };
|
|
180
209
|
}
|
|
181
210
|
};
|
|
182
211
|
function rateLimitMiddleware(config) {
|
|
@@ -185,38 +214,36 @@ function rateLimitMiddleware(config) {
|
|
|
185
214
|
maxRequests,
|
|
186
215
|
keyGenerator = (event) => {
|
|
187
216
|
try {
|
|
188
|
-
return event.getClientAddress?.() ?? "unknown";
|
|
217
|
+
return clientKey(event.getClientAddress?.() ?? "unknown");
|
|
189
218
|
} catch {
|
|
190
219
|
return "unknown";
|
|
191
220
|
}
|
|
192
221
|
},
|
|
193
222
|
onLimitReached
|
|
194
223
|
} = config;
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
224
|
+
if (!Number.isSafeInteger(windowMs) || windowMs <= 0 || windowMs > 2147483647 || !Number.isSafeInteger(maxRequests) || maxRequests <= 0)
|
|
225
|
+
throw new TypeError("Rate limit window/count must be positive integers (window <= 2147483647ms)");
|
|
226
|
+
const store = config.store;
|
|
227
|
+
if (!store || typeof store.increment !== "function")
|
|
228
|
+
throw new TypeError("An atomic rate limit store is required");
|
|
229
|
+
if (store.scope === "memory" && !permitsMemory())
|
|
230
|
+
throw new Error("Memory rate limiting is restricted to development and tests");
|
|
200
231
|
return async (context, next) => {
|
|
201
232
|
const { event, requestId } = context;
|
|
202
|
-
const key = keyGenerator(event);
|
|
203
233
|
const now = Date.now();
|
|
204
234
|
let entry;
|
|
205
|
-
|
|
206
|
-
entry = await store.increment(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
212
|
-
entry = { ...existing, count: existing.count + 1 };
|
|
213
|
-
}
|
|
214
|
-
await store.set(key, entry);
|
|
235
|
+
try {
|
|
236
|
+
entry = await store.increment(keyGenerator(event), windowMs);
|
|
237
|
+
if (!Number.isSafeInteger(entry.count) || entry.count <= 0 || !Number.isSafeInteger(entry.resetAt))
|
|
238
|
+
throw new TypeError("Invalid atomic rate limit result");
|
|
239
|
+
} catch {
|
|
240
|
+
core.logger.error("Rate limit store unavailable", { requestId });
|
|
241
|
+
return new Response(JSON.stringify({ success: false, error: { code: "RATE_LIMIT_UNAVAILABLE", message: kitM("kit_internalError") }, requestId }), { status: 503, headers: { "Content-Type": "application/json" } });
|
|
215
242
|
}
|
|
216
243
|
const remaining = Math.max(0, maxRequests - entry.count);
|
|
217
244
|
const resetTime = Math.ceil(entry.resetAt / 1e3);
|
|
218
245
|
if (entry.count > maxRequests) {
|
|
219
|
-
core.logger.warn("Rate limit exceeded", {
|
|
246
|
+
core.logger.warn("Rate limit exceeded", { requestId });
|
|
220
247
|
if (onLimitReached) {
|
|
221
248
|
return onLimitReached(event);
|
|
222
249
|
}
|
|
@@ -236,7 +263,7 @@ function rateLimitMiddleware(config) {
|
|
|
236
263
|
"X-RateLimit-Limit": String(maxRequests),
|
|
237
264
|
"X-RateLimit-Remaining": "0",
|
|
238
265
|
"X-RateLimit-Reset": String(resetTime),
|
|
239
|
-
"Retry-After": String(Math.ceil((entry.resetAt - now) / 1e3))
|
|
266
|
+
"Retry-After": String(Math.max(1, Math.ceil((entry.resetAt - now) / 1e3)))
|
|
240
267
|
}
|
|
241
268
|
}
|
|
242
269
|
);
|
|
@@ -344,6 +371,7 @@ function createHandle(config = {}) {
|
|
|
344
371
|
}
|
|
345
372
|
if (rateLimitConfig) {
|
|
346
373
|
builtinMiddleware.push(rateLimitMiddleware({
|
|
374
|
+
...rateLimitConfig,
|
|
347
375
|
windowMs: rateLimitConfig.windowMs ?? 6e4,
|
|
348
376
|
maxRequests: rateLimitConfig.maxRequests ?? 100
|
|
349
377
|
}));
|
|
@@ -352,99 +380,104 @@ function createHandle(config = {}) {
|
|
|
352
380
|
const transportMiddleware = buildTransportMiddleware(cryptoConfig);
|
|
353
381
|
const cookieProxyConfig = buildCookieProxyConfig(cryptoConfig);
|
|
354
382
|
return async ({ event, resolve }) => {
|
|
355
|
-
const
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
if (
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
if (authConfig) {
|
|
379
|
-
const token = getAccessToken(event.request, event.cookies);
|
|
380
|
-
if (token) {
|
|
381
|
-
session = await authConfig.verifyToken(token) ?? void 0;
|
|
382
|
-
locals.session = session;
|
|
383
|
-
if (session) {
|
|
384
|
-
locals.accessToken = token;
|
|
385
|
-
}
|
|
383
|
+
const suppliedRequestId = event.request.headers.get("x-request-id");
|
|
384
|
+
const requestId = suppliedRequestId && /^[\w.:-]{1,128}$/.test(suppliedRequestId) ? suppliedRequestId : generateId("req");
|
|
385
|
+
return core.context.run({ requestId, userId: void 0, scopeId: void 0, jobId: void 0 }, async () => {
|
|
386
|
+
const context = {
|
|
387
|
+
event,
|
|
388
|
+
requestId
|
|
389
|
+
};
|
|
390
|
+
const locals = event.locals;
|
|
391
|
+
locals.requestId = requestId;
|
|
392
|
+
if (cookieProxyConfig) {
|
|
393
|
+
const proxiedCookies = createEncryptedCookieProxy(event.cookies, cookieProxyConfig);
|
|
394
|
+
Object.defineProperty(event, "cookies", {
|
|
395
|
+
value: proxiedCookies,
|
|
396
|
+
writable: true,
|
|
397
|
+
configurable: true
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
const runRequest = async () => {
|
|
401
|
+
try {
|
|
402
|
+
if (a2aResolved) {
|
|
403
|
+
const a2aResponse = await handleA2ARequest(event, requestId, a2aResolved);
|
|
404
|
+
if (a2aResponse)
|
|
405
|
+
return a2aResponse;
|
|
386
406
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
});
|
|
407
|
+
let session;
|
|
408
|
+
if (authConfig) {
|
|
409
|
+
const token = getAccessToken(event.request, event.cookies);
|
|
410
|
+
if (token) {
|
|
411
|
+
session = await authConfig.verifyToken(token) ?? void 0;
|
|
412
|
+
locals.session = session;
|
|
413
|
+
if (session) {
|
|
414
|
+
locals.accessToken = token;
|
|
415
|
+
}
|
|
397
416
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
417
|
+
}
|
|
418
|
+
context.session = session;
|
|
419
|
+
return await core.context.run({ userId: session?.userId }, async () => {
|
|
420
|
+
for (const guardConfig of guards) {
|
|
421
|
+
const guardResult = await executeGuard(guardConfig, event, session);
|
|
422
|
+
if (!guardResult.allowed) {
|
|
423
|
+
if (guardResult.redirect) {
|
|
424
|
+
return new Response(null, {
|
|
425
|
+
status: 302,
|
|
426
|
+
headers: { Location: guardResult.redirect }
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
return new Response(
|
|
430
|
+
JSON.stringify({
|
|
431
|
+
success: false,
|
|
432
|
+
error: {
|
|
433
|
+
code: "FORBIDDEN",
|
|
434
|
+
message: guardResult.message ?? kitM("kit_accessDenied")
|
|
435
|
+
},
|
|
436
|
+
requestId
|
|
437
|
+
}),
|
|
438
|
+
{
|
|
439
|
+
status: guardResult.status ?? 403,
|
|
440
|
+
headers: { "Content-Type": "application/json" }
|
|
441
|
+
}
|
|
442
|
+
);
|
|
410
443
|
}
|
|
444
|
+
}
|
|
445
|
+
const response2 = await executeMiddlewareChain(
|
|
446
|
+
allMiddleware,
|
|
447
|
+
context,
|
|
448
|
+
() => resolve(event)
|
|
411
449
|
);
|
|
450
|
+
return response2;
|
|
451
|
+
});
|
|
452
|
+
} catch (error2) {
|
|
453
|
+
if (isSvelteKitControlFlow(error2)) {
|
|
454
|
+
throw error2;
|
|
412
455
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
context,
|
|
417
|
-
() => resolve(event)
|
|
418
|
-
);
|
|
419
|
-
return response2;
|
|
420
|
-
} catch (error2) {
|
|
421
|
-
if (isSvelteKitControlFlow(error2)) {
|
|
422
|
-
throw error2;
|
|
423
|
-
}
|
|
424
|
-
core.logger.error("Request failed", { requestId, error: error2 instanceof Error ? error2.message : error2 });
|
|
425
|
-
if (onError) {
|
|
426
|
-
return onError(error2, event);
|
|
427
|
-
}
|
|
428
|
-
return new Response(
|
|
429
|
-
JSON.stringify({
|
|
430
|
-
success: false,
|
|
431
|
-
error: {
|
|
432
|
-
code: "INTERNAL_ERROR",
|
|
433
|
-
message: kitM("kit_internalError")
|
|
434
|
-
},
|
|
435
|
-
requestId
|
|
436
|
-
}),
|
|
437
|
-
{
|
|
438
|
-
status: 500,
|
|
439
|
-
headers: { "Content-Type": "application/json" }
|
|
456
|
+
core.logger.error("Request failed", { requestId, error: error2 instanceof Error ? error2.message : error2 });
|
|
457
|
+
if (onError) {
|
|
458
|
+
return onError(error2, event);
|
|
440
459
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
460
|
+
return new Response(
|
|
461
|
+
JSON.stringify({
|
|
462
|
+
success: false,
|
|
463
|
+
error: {
|
|
464
|
+
code: "INTERNAL_ERROR",
|
|
465
|
+
message: kitM("kit_internalError")
|
|
466
|
+
},
|
|
467
|
+
requestId
|
|
468
|
+
}),
|
|
469
|
+
{
|
|
470
|
+
status: 500,
|
|
471
|
+
headers: { "Content-Type": "application/json" }
|
|
472
|
+
}
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
const response = transportMiddleware ? await transportMiddleware(context, runRequest) : await runRequest();
|
|
477
|
+
applyDefaultSecurityHeaders(response);
|
|
478
|
+
response.headers.set("X-Request-Id", requestId);
|
|
479
|
+
return response;
|
|
480
|
+
});
|
|
448
481
|
};
|
|
449
482
|
}
|
|
450
483
|
function buildAuthGuard(verifyToken, loginUrl) {
|
|
@@ -506,16 +539,15 @@ function matchPath(pathname, pattern) {
|
|
|
506
539
|
function buildTransportMiddleware(cryptoConfig) {
|
|
507
540
|
if (!cryptoConfig?.transport)
|
|
508
541
|
return null;
|
|
509
|
-
const transportOpts =
|
|
542
|
+
const transportOpts = cryptoConfig.transport;
|
|
510
543
|
return transportEncryptionMiddleware({
|
|
511
544
|
enabled: true,
|
|
512
545
|
crypto: cryptoConfig.crypto,
|
|
546
|
+
serverKeyRing: transportOpts.serverKeyRing,
|
|
513
547
|
keyExchangePath: transportOpts.keyExchangePath,
|
|
514
548
|
excludePaths: transportOpts.excludePaths,
|
|
515
|
-
encryptResponse: transportOpts.encryptResponse,
|
|
516
549
|
keyStore: transportOpts.keyStore,
|
|
517
|
-
|
|
518
|
-
requireEncryption: transportOpts.requireEncryption
|
|
550
|
+
maxPlaintextBytes: transportOpts.maxPlaintextBytes
|
|
519
551
|
});
|
|
520
552
|
}
|
|
521
553
|
function buildCookieProxyConfig(cryptoConfig) {
|
|
@@ -540,10 +572,32 @@ function sequence(...handles) {
|
|
|
540
572
|
return filtered[0];
|
|
541
573
|
}
|
|
542
574
|
return async ({ event, resolve }) => {
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
575
|
+
function applyHandle(index, currentEvent, options) {
|
|
576
|
+
if (index === filtered.length)
|
|
577
|
+
return resolve(currentEvent, options);
|
|
578
|
+
return filtered[index]({
|
|
579
|
+
event: currentEvent,
|
|
580
|
+
resolve: async (nextEvent, nextOptions) => {
|
|
581
|
+
const merged = {
|
|
582
|
+
...options,
|
|
583
|
+
...nextOptions,
|
|
584
|
+
preload: options?.preload ?? nextOptions?.preload,
|
|
585
|
+
filterSerializedResponseHeaders: options?.filterSerializedResponseHeaders ?? nextOptions?.filterSerializedResponseHeaders
|
|
586
|
+
};
|
|
587
|
+
if (options?.transformPageChunk || nextOptions?.transformPageChunk) {
|
|
588
|
+
merged.transformPageChunk = async ({ html, done }) => {
|
|
589
|
+
if (nextOptions?.transformPageChunk)
|
|
590
|
+
html = await nextOptions.transformPageChunk({ html, done }) ?? "";
|
|
591
|
+
if (options?.transformPageChunk)
|
|
592
|
+
html = await options.transformPageChunk({ html, done }) ?? "";
|
|
593
|
+
return html;
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
return applyHandle(index + 1, nextEvent, merged);
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
return applyHandle(0, event);
|
|
547
601
|
};
|
|
548
602
|
}
|
|
549
603
|
|
|
@@ -767,9 +821,130 @@ function createAuthApi(iam) {
|
|
|
767
821
|
})
|
|
768
822
|
};
|
|
769
823
|
}
|
|
824
|
+
function createFeedbackHandler(options) {
|
|
825
|
+
const basePath = (options.basePath ?? "/api/feedback").replace(/\/+$/, "");
|
|
826
|
+
return async (event) => {
|
|
827
|
+
let actor;
|
|
828
|
+
try {
|
|
829
|
+
actor = await options.resolveContext(event);
|
|
830
|
+
} catch {
|
|
831
|
+
return fromError({ ...HaiCommonError.INTERNAL_ERROR, message: kitM("kit_internalError") });
|
|
832
|
+
}
|
|
833
|
+
if (!actor?.scopeId || !actor.userId)
|
|
834
|
+
return fromError({ ...HaiCommonError.UNAUTHORIZED, message: kitM("kit_unauthorized") });
|
|
835
|
+
const suffix = event.url.pathname.startsWith(`${basePath}/`) ? event.url.pathname.slice(basePath.length + 1) : "";
|
|
836
|
+
let parts;
|
|
837
|
+
try {
|
|
838
|
+
parts = suffix.split("/").map(decodeURIComponent);
|
|
839
|
+
} catch {
|
|
840
|
+
return invalid(400);
|
|
841
|
+
}
|
|
842
|
+
if (parts[0] !== "reports" || parts.length > 4)
|
|
843
|
+
return fromError({ ...HaiCommonError.NOT_FOUND, message: core.i18n.coreM("core_errorNotFound") });
|
|
844
|
+
let body = {};
|
|
845
|
+
if (!["GET", "HEAD"].includes(event.request.method)) {
|
|
846
|
+
if (event.request.headers.get("content-type")?.split(";")[0]?.trim().toLowerCase() !== "application/json")
|
|
847
|
+
return invalid(415);
|
|
848
|
+
try {
|
|
849
|
+
body = await readBoundedJson(event.request);
|
|
850
|
+
} catch (error2) {
|
|
851
|
+
return invalid(error2 instanceof RangeError ? 413 : 400);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
if (!body || typeof body !== "object" || Array.isArray(body))
|
|
855
|
+
return invalid(400);
|
|
856
|
+
const bodyRecord = body;
|
|
857
|
+
if (parts[1] && Object.hasOwn(bodyRecord, "id") && bodyRecord.id !== parts[1])
|
|
858
|
+
return invalid(400);
|
|
859
|
+
const input = { ...Object.fromEntries(event.url.searchParams), ...bodyRecord, ...parts[1] ? { id: parts[1] } : {} };
|
|
860
|
+
const method = event.request.method;
|
|
861
|
+
const service = options.feedback;
|
|
862
|
+
async function execute(schema, operation) {
|
|
863
|
+
const parsed = schema.safeParse(input);
|
|
864
|
+
if (!parsed.success)
|
|
865
|
+
return invalid(400);
|
|
866
|
+
try {
|
|
867
|
+
const result = await core.context.run(actor, () => operation(parsed.data));
|
|
868
|
+
return result.success ? ok(result.data) : fromError(result.error);
|
|
869
|
+
} catch {
|
|
870
|
+
return fromError({ ...HaiCommonError.INTERNAL_ERROR, message: kitM("kit_internalError") });
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
if (parts.length === 1) {
|
|
874
|
+
if (method === "POST")
|
|
875
|
+
return execute(FeedbackSubmitInputSchema, (value) => service.submit(value, actor));
|
|
876
|
+
if (method === "GET")
|
|
877
|
+
return execute(FeedbackListInputSchema, (value) => service.list(value, actor));
|
|
878
|
+
}
|
|
879
|
+
if (parts.length === 2 && method === "GET")
|
|
880
|
+
return execute(FeedbackIdInputSchema, (value) => service.get(value.id, actor));
|
|
881
|
+
if (parts.length === 3) {
|
|
882
|
+
if (parts[2] === "evidence" && method === "GET")
|
|
883
|
+
return execute(FeedbackEvidenceQuerySchema, (value) => service.getEvidence(value, actor));
|
|
884
|
+
if (parts[2] === "evidence" && method === "POST")
|
|
885
|
+
return execute(FeedbackAppendEvidenceInputSchema, (value) => service.appendEvidence(value, actor));
|
|
886
|
+
if (parts[2] === "status" && method === "PATCH")
|
|
887
|
+
return execute(FeedbackSetStatusInputSchema, (value) => service.setStatus(value, actor));
|
|
888
|
+
if (parts[2] === "retry" && method === "POST")
|
|
889
|
+
return execute(FeedbackRetryInputSchema, (value) => service.retry(value, actor));
|
|
890
|
+
if (parts[2] === "complete" && method === "POST")
|
|
891
|
+
return execute(FeedbackCompleteInputSchema, (value) => service.completeCollection(value, actor));
|
|
892
|
+
if (parts[2] === "exports" && method === "POST")
|
|
893
|
+
return execute(FeedbackExportInputSchema, (value) => service.export(value, actor));
|
|
894
|
+
}
|
|
895
|
+
if (parts.length === 4 && parts[2] === "attachments") {
|
|
896
|
+
Object.assign(input, { attachmentId: parts[3] });
|
|
897
|
+
if (method === "POST")
|
|
898
|
+
return execute(FeedbackUploadInputSchema, (value) => service.uploadAttachment(value, actor));
|
|
899
|
+
if (method === "GET")
|
|
900
|
+
return execute(FeedbackAttachmentQuerySchema, (value) => service.getAttachment(value, actor));
|
|
901
|
+
}
|
|
902
|
+
if (parts.length === 4 && parts[2] === "exports" && method === "GET") {
|
|
903
|
+
Object.assign(input, { jobId: parts[3] });
|
|
904
|
+
return execute(FeedbackExportQuerySchema, (value) => service.getExport(value, actor));
|
|
905
|
+
}
|
|
906
|
+
return fromError({ ...HaiCommonError.NOT_FOUND, message: core.i18n.coreM("core_errorNotFound") });
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
function invalid(status) {
|
|
910
|
+
return fromError({ ...HaiCommonError.INVALID_REQUEST, httpStatus: status, message: kitM("kit_validationFailed") });
|
|
911
|
+
}
|
|
912
|
+
async function readBoundedJson(request) {
|
|
913
|
+
const reader = request.body?.getReader();
|
|
914
|
+
if (!reader)
|
|
915
|
+
return {};
|
|
916
|
+
const decoder = new TextDecoder();
|
|
917
|
+
let length = 0;
|
|
918
|
+
let text = "";
|
|
919
|
+
try {
|
|
920
|
+
while (true) {
|
|
921
|
+
const chunk = await reader.read();
|
|
922
|
+
if (chunk.done)
|
|
923
|
+
break;
|
|
924
|
+
length += chunk.value.byteLength;
|
|
925
|
+
if (length > 8 * 1024 * 1024) {
|
|
926
|
+
await reader.cancel();
|
|
927
|
+
throw new RangeError("Feedback request is too large");
|
|
928
|
+
}
|
|
929
|
+
text += decoder.decode(chunk.value, { stream: true });
|
|
930
|
+
}
|
|
931
|
+
text += decoder.decode();
|
|
932
|
+
return text ? JSON.parse(text) : {};
|
|
933
|
+
} finally {
|
|
934
|
+
reader.releaseLock();
|
|
935
|
+
}
|
|
936
|
+
}
|
|
770
937
|
|
|
771
938
|
// src/kit-main.ts
|
|
772
939
|
var kit = {
|
|
940
|
+
/** 创建反馈的受控 catch-all HTTP handler,身份范围由宿主解析。 */
|
|
941
|
+
createFeedbackHandler,
|
|
942
|
+
/** 原子限流工厂;共享store生命周期由应用管理。 */
|
|
943
|
+
middleware: {
|
|
944
|
+
rateLimit: rateLimitMiddleware,
|
|
945
|
+
createCacheRateLimitStore,
|
|
946
|
+
createMemoryRateLimitStore
|
|
947
|
+
},
|
|
773
948
|
// ─── Handle Hook ───
|
|
774
949
|
/** 创建 SvelteKit Handle Hook(含 auth / logging / rateLimit 内置配置) */
|
|
775
950
|
createHandle,
|
|
@@ -863,9 +1038,8 @@ var kit = {
|
|
|
863
1038
|
/** 写入浏览器端 Access Token(客户端 login/register 用) */
|
|
864
1039
|
setBrowserToken,
|
|
865
1040
|
/** 清除浏览器端 Access Token(客户端 logout 用) */
|
|
866
|
-
clearBrowserToken
|
|
1041
|
+
clearBrowserToken
|
|
867
1042
|
/** 创建浏览器端 Token 存储器(自定义 key 时使用) */
|
|
868
|
-
createTokenStore
|
|
869
1043
|
},
|
|
870
1044
|
// ─── CRUD ───
|
|
871
1045
|
crud: {
|
|
@@ -875,7 +1049,9 @@ var kit = {
|
|
|
875
1049
|
// ─── i18n ───
|
|
876
1050
|
i18n: {
|
|
877
1051
|
/** 统一设置所有 hai 模块的默认语言 */
|
|
878
|
-
setLocale: setAllModulesLocale
|
|
1052
|
+
setLocale: setAllModulesLocale,
|
|
1053
|
+
/** 请求/SSR 语言隔离;异步处理使用同一请求上下文。 */
|
|
1054
|
+
runWithLocale: core.i18n.runWithLocale
|
|
879
1055
|
}
|
|
880
1056
|
};
|
|
881
1057
|
|