veryfront 0.1.746 → 0.1.749
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/esm/cli/templates/manifest.js +2 -2
- package/esm/deno.js +1 -1
- package/esm/src/build/production-build/static-generation.d.ts.map +1 -1
- package/esm/src/build/production-build/static-generation.js +2 -0
- package/esm/src/cache/backends/api.d.ts.map +1 -1
- package/esm/src/cache/backends/api.js +9 -5
- package/esm/src/cache/backends/disk.d.ts +1 -1
- package/esm/src/cache/backends/disk.d.ts.map +1 -1
- package/esm/src/cache/backends/disk.js +41 -13
- package/esm/src/cache/backends/glob.d.ts +6 -0
- package/esm/src/cache/backends/glob.d.ts.map +1 -0
- package/esm/src/cache/backends/glob.js +41 -0
- package/esm/src/cache/backends/memory.d.ts +1 -1
- package/esm/src/cache/backends/memory.d.ts.map +1 -1
- package/esm/src/cache/backends/memory.js +14 -12
- package/esm/src/cache/backends/redis.d.ts.map +1 -1
- package/esm/src/cache/backends/redis.js +33 -7
- package/esm/src/cache/dependency-graph.d.ts +1 -0
- package/esm/src/cache/dependency-graph.d.ts.map +1 -1
- package/esm/src/cache/dependency-graph.js +15 -1
- package/esm/src/cache/paths.d.ts.map +1 -1
- package/esm/src/cache/paths.js +5 -0
- package/esm/src/embedding/index.d.ts +4 -1
- package/esm/src/embedding/index.d.ts.map +1 -1
- package/esm/src/embedding/index.js +3 -1
- package/esm/src/embedding/rag-store.d.ts.map +1 -1
- package/esm/src/embedding/rag-store.js +157 -8
- package/esm/src/embedding/react/use-uploads.d.ts +1 -0
- package/esm/src/embedding/react/use-uploads.d.ts.map +1 -1
- package/esm/src/embedding/react/use-uploads.js +4 -1
- package/esm/src/embedding/upload-handler.d.ts +29 -8
- package/esm/src/embedding/upload-handler.d.ts.map +1 -1
- package/esm/src/embedding/upload-handler.js +66 -6
- package/esm/src/html/html-shell-generator.js +5 -5
- package/esm/src/html/nonce-injection.d.ts.map +1 -1
- package/esm/src/html/nonce-injection.js +11 -4
- package/esm/src/html/utils.d.ts +5 -0
- package/esm/src/html/utils.d.ts.map +1 -1
- package/esm/src/html/utils.js +19 -9
- package/esm/src/middleware/builtin/security/redis-rate-limit.d.ts +2 -0
- package/esm/src/middleware/builtin/security/redis-rate-limit.d.ts.map +1 -1
- package/esm/src/middleware/builtin/security/redis-rate-limit.js +46 -13
- package/esm/src/modules/react-loader/ssr-module-loader/import-rewriter.d.ts.map +1 -1
- package/esm/src/modules/react-loader/ssr-module-loader/import-rewriter.js +12 -5
- package/esm/src/modules/server/module-batch-handler.d.ts +1 -0
- package/esm/src/modules/server/module-batch-handler.d.ts.map +1 -1
- package/esm/src/modules/server/module-batch-handler.js +52 -26
- package/esm/src/modules/server/module-server.d.ts.map +1 -1
- package/esm/src/modules/server/module-server.js +87 -69
- package/esm/src/modules/server/module-source-resolution-cache.d.ts +16 -0
- package/esm/src/modules/server/module-source-resolution-cache.d.ts.map +1 -0
- package/esm/src/modules/server/module-source-resolution-cache.js +43 -0
- package/esm/src/oauth/providers/base.d.ts +0 -2
- package/esm/src/oauth/providers/base.d.ts.map +1 -1
- package/esm/src/oauth/providers/base.js +35 -10
- package/esm/src/oauth/token-store/memory.d.ts +7 -0
- package/esm/src/oauth/token-store/memory.d.ts.map +1 -1
- package/esm/src/oauth/token-store/memory.js +14 -1
- package/esm/src/observability/auto-instrument/http-instrumentation.d.ts.map +1 -1
- package/esm/src/observability/auto-instrument/http-instrumentation.js +10 -6
- package/esm/src/observability/file-log-subscriber.d.ts +1 -0
- package/esm/src/observability/file-log-subscriber.d.ts.map +1 -1
- package/esm/src/observability/file-log-subscriber.js +2 -1
- package/esm/src/observability/tracing/api-shim.d.ts +1 -0
- package/esm/src/observability/tracing/api-shim.d.ts.map +1 -1
- package/esm/src/observability/tracing/api-shim.js +7 -0
- package/esm/src/observability/tracing/otlp-setup.d.ts.map +1 -1
- package/esm/src/observability/tracing/otlp-setup.js +17 -7
- package/esm/src/proxy/handler.d.ts +5 -2
- package/esm/src/proxy/handler.d.ts.map +1 -1
- package/esm/src/proxy/handler.js +26 -25
- package/esm/src/proxy/main.js +8 -11
- package/esm/src/rendering/renderer.d.ts.map +1 -1
- package/esm/src/rendering/renderer.js +32 -12
- package/esm/src/server/context/cache-invalidation.d.ts.map +1 -1
- package/esm/src/server/context/cache-invalidation.js +2 -0
- package/esm/src/server/handlers/dev/scripts/error-overlay.d.ts.map +1 -1
- package/esm/src/server/handlers/dev/scripts/error-overlay.js +32 -18
- package/esm/src/server/handlers/monitoring/client-log.handler.d.ts +1 -0
- package/esm/src/server/handlers/monitoring/client-log.handler.d.ts.map +1 -1
- package/esm/src/server/handlers/monitoring/client-log.handler.js +39 -5
- package/esm/src/server/handlers/request/module/batch-module-handler.d.ts.map +1 -1
- package/esm/src/server/handlers/request/module/batch-module-handler.js +2 -1
- package/esm/src/utils/logger/redact.d.ts +10 -0
- package/esm/src/utils/logger/redact.d.ts.map +1 -1
- package/esm/src/utils/logger/redact.js +60 -1
- package/esm/src/utils/redis-client.d.ts +1 -0
- package/esm/src/utils/redis-client.d.ts.map +1 -1
- package/esm/src/utils/version-constant.d.ts +1 -1
- package/esm/src/utils/version-constant.js +1 -1
- package/package.json +1 -1
package/esm/src/proxy/main.js
CHANGED
|
@@ -111,8 +111,7 @@ if (Object.keys(proxyHandler.localProjects).length > 0) {
|
|
|
111
111
|
* Handle WebSocket upgrade requests.
|
|
112
112
|
* Bridges browser WebSocket to server HMR WebSocket endpoint.
|
|
113
113
|
*/
|
|
114
|
-
function handleWebSocketUpgrade(req) {
|
|
115
|
-
const url = new URL(req.url);
|
|
114
|
+
function handleWebSocketUpgrade(req, url) {
|
|
116
115
|
const host = req.headers.get("host") || "";
|
|
117
116
|
const parsed = parseProjectDomain(host);
|
|
118
117
|
const scope = parsed.environment === "preview" ? "preview" : "production";
|
|
@@ -227,16 +226,15 @@ function handleWebSocketUpgrade(req) {
|
|
|
227
226
|
};
|
|
228
227
|
return response;
|
|
229
228
|
}
|
|
230
|
-
function forwardToServer(req) {
|
|
229
|
+
function forwardToServer(req, url) {
|
|
231
230
|
const startTime = performance.now();
|
|
232
|
-
const url = new URL(req.url);
|
|
233
231
|
const requestId = dntShim.crypto.randomUUID();
|
|
234
232
|
const host = req.headers.get("host") || "";
|
|
235
233
|
const parentContext = extractContext(req.headers);
|
|
236
234
|
const spanInfo = startServerSpan(req.method, url.pathname, parentContext);
|
|
237
235
|
const execute = async () => {
|
|
238
236
|
try {
|
|
239
|
-
const ctx = await proxyHandler.processRequest(req);
|
|
237
|
+
const ctx = await proxyHandler.processRequest(req, { url });
|
|
240
238
|
return runWithProxyRequestContext({
|
|
241
239
|
requestId,
|
|
242
240
|
projectSlug: ctx.projectSlug,
|
|
@@ -414,9 +412,8 @@ async function handleStats() {
|
|
|
414
412
|
* Proxy API requests directly to Veryfront API (BFF pattern).
|
|
415
413
|
* Routes: /_vf/api/* -> api.veryfront.com/*
|
|
416
414
|
*/
|
|
417
|
-
async function handleApiProxy(req) {
|
|
418
|
-
const
|
|
419
|
-
const token = await proxyHandler.getTokenForApi(req);
|
|
415
|
+
async function handleApiProxy(req, url) {
|
|
416
|
+
const token = await proxyHandler.getTokenForApi(req, { url });
|
|
420
417
|
if (!token)
|
|
421
418
|
return jsonErrorResponse(401, { error: "No authentication token" });
|
|
422
419
|
const apiPath = url.pathname.replace(/^\/_vf\/api/, "");
|
|
@@ -460,7 +457,7 @@ async function handleApiProxy(req) {
|
|
|
460
457
|
function router(req) {
|
|
461
458
|
const url = new URL(req.url);
|
|
462
459
|
if (req.headers.get("upgrade")?.toLowerCase() === "websocket") {
|
|
463
|
-
return Promise.resolve(handleWebSocketUpgrade(req));
|
|
460
|
+
return Promise.resolve(handleWebSocketUpgrade(req, url));
|
|
464
461
|
}
|
|
465
462
|
switch (url.pathname) {
|
|
466
463
|
case "/_proxy/stats":
|
|
@@ -472,8 +469,8 @@ function router(req) {
|
|
|
472
469
|
return Promise.resolve(Response.json({ service: "veryfront-proxy", status: "ok" }));
|
|
473
470
|
}
|
|
474
471
|
if (url.pathname.startsWith("/_vf/api/"))
|
|
475
|
-
return handleApiProxy(req);
|
|
476
|
-
return forwardToServer(req);
|
|
472
|
+
return handleApiProxy(req, url);
|
|
473
|
+
return forwardToServer(req, url);
|
|
477
474
|
}
|
|
478
475
|
// Create server before signal registration so early SIGTERM/SIGINT can close it safely.
|
|
479
476
|
const server = createHttpServer();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../../src/src/rendering/renderer.ts"],"names":[],"mappings":"AAwCA,OAAO,EACL,mBAAmB,EACnB,+BAA+B,EAC/B,KAAK,0BAA0B,EAC/B,KAAK,aAAa,EACnB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAKL,KAAK,qBAAqB,EAC3B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAEL,KAAK,wBAAwB,EAC9B,MAAM,iCAAiC,CAAC;AAgBzC,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC7F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAuBxD;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,8BAA8B;IAC9B,MAAM,CAAC,EAAE,qBAAqB,CAAC;IAC/B,oBAAoB;IACpB,KAAK,CAAC,EAAE,wBAAwB,CAAC;CAClC;AAeD;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,QAAQ;IACnB,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,oBAAoB,CAAgC;IAC5D,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,qBAAqB,CAA8B;IAE3D;;;;OAIG;IACH,OAAO,CAAC,YAAY,CAAwC;gBAEhD,OAAO,GAAE,eAAoB;IAInC,UAAU,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../../src/src/rendering/renderer.ts"],"names":[],"mappings":"AAwCA,OAAO,EACL,mBAAmB,EACnB,+BAA+B,EAC/B,KAAK,0BAA0B,EAC/B,KAAK,aAAa,EACnB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAKL,KAAK,qBAAqB,EAC3B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAEL,KAAK,wBAAwB,EAC9B,MAAM,iCAAiC,CAAC;AAgBzC,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC7F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAuBxD;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,8BAA8B;IAC9B,MAAM,CAAC,EAAE,qBAAqB,CAAC;IAC/B,oBAAoB;IACpB,KAAK,CAAC,EAAE,wBAAwB,CAAC;CAClC;AAeD;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,QAAQ;IACnB,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,oBAAoB,CAAgC;IAC5D,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,qBAAqB,CAA8B;IAE3D;;;;OAIG;IACH,OAAO,CAAC,YAAY,CAAwC;gBAEhD,OAAO,GAAE,eAAoB;IAInC,UAAU,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBhE,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,aAAa,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;IAkF5F;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IAU1B;;;;;;;;;OASG;IACH,OAAO,CAAC,aAAa;YAwBP,YAAY;IAoF1B,eAAe,CACb,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,aAAa,EAClB,OAAO,CAAC,EAAE,aAAa,GACtB,OAAO,CAAC,gBAAgB,CAAC;IAkB5B,WAAW,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAU5C,UAAU,CAAC,GAAG,EAAE,aAAa,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ5D,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKtD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAO9B,OAAO,CAAC,wBAAwB;CA0FjC;AAED,OAAO,EACL,mBAAmB,EACnB,+BAA+B,EAC/B,KAAK,0BAA0B,EAC/B,KAAK,aAAa,GACnB,CAAC;AAMF,wBAAgB,WAAW,IAAI,QAAQ,CAOtC;AAED,wBAAsB,kBAAkB,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,QAAQ,CAAC,CAsBrF;AAED,wBAAgB,qBAAqB,IAAI,OAAO,CAE/C;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAQrD;AAED,wBAAsB,4BAA4B,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAYnF;AAED,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,cAAc,EAC1B,OAAO,CAAC,EAAE,aAAa,EACvB,cAAc,CAAC,EAAE,0BAA0B,GAC1C,OAAO,CAAC,YAAY,CAAC,CAIvB"}
|
|
@@ -102,12 +102,8 @@ export class Renderer {
|
|
|
102
102
|
duration: `${(performance.now() - startTime).toFixed(2)}ms`,
|
|
103
103
|
});
|
|
104
104
|
})();
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
finally {
|
|
109
|
-
this.initializationPromise = null;
|
|
110
|
-
}
|
|
105
|
+
await this.initializationPromise;
|
|
106
|
+
this.initializationPromise = null;
|
|
111
107
|
}
|
|
112
108
|
renderPage(slug, ctx, options) {
|
|
113
109
|
return withSpan("renderer.renderPage", async () => {
|
|
@@ -318,6 +314,7 @@ export class Renderer {
|
|
|
318
314
|
async destroy() {
|
|
319
315
|
await this.cache.destroy();
|
|
320
316
|
this.initialized = false;
|
|
317
|
+
this.initializationPromise = null;
|
|
321
318
|
logger.debug("Destroyed");
|
|
322
319
|
}
|
|
323
320
|
createServicesForContext(ctx, colorScheme) {
|
|
@@ -400,6 +397,8 @@ export class Renderer {
|
|
|
400
397
|
}
|
|
401
398
|
export { createRenderContext, createRenderContextFromEnriched, };
|
|
402
399
|
let renderer = null;
|
|
400
|
+
let rendererInitializationPromise = null;
|
|
401
|
+
let rendererGeneration = 0;
|
|
403
402
|
export function getRenderer() {
|
|
404
403
|
if (!renderer) {
|
|
405
404
|
throw INITIALIZATION_ERROR.create({
|
|
@@ -411,18 +410,39 @@ export function getRenderer() {
|
|
|
411
410
|
export async function initializeRenderer(options) {
|
|
412
411
|
if (renderer)
|
|
413
412
|
return renderer;
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
413
|
+
if (rendererInitializationPromise)
|
|
414
|
+
return rendererInitializationPromise;
|
|
415
|
+
const nextRenderer = new Renderer(options);
|
|
416
|
+
const generation = rendererGeneration;
|
|
417
|
+
rendererInitializationPromise = (async () => {
|
|
418
|
+
try {
|
|
419
|
+
await nextRenderer.initialize(options?.shared);
|
|
420
|
+
if (generation !== rendererGeneration) {
|
|
421
|
+
await nextRenderer.destroy();
|
|
422
|
+
throw INITIALIZATION_ERROR.create({
|
|
423
|
+
detail: "Renderer initialization was cancelled before it completed.",
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
renderer = nextRenderer;
|
|
427
|
+
return nextRenderer;
|
|
428
|
+
}
|
|
429
|
+
finally {
|
|
430
|
+
rendererInitializationPromise = null;
|
|
431
|
+
}
|
|
432
|
+
})();
|
|
433
|
+
return rendererInitializationPromise;
|
|
417
434
|
}
|
|
418
435
|
export function isRendererInitialized() {
|
|
419
436
|
return renderer !== null && areSharedServicesInitialized();
|
|
420
437
|
}
|
|
421
438
|
export async function destroyRenderer() {
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
439
|
+
rendererGeneration++;
|
|
440
|
+
rendererInitializationPromise = null;
|
|
441
|
+
const currentRenderer = renderer;
|
|
425
442
|
renderer = null;
|
|
443
|
+
if (!currentRenderer)
|
|
444
|
+
return;
|
|
445
|
+
await currentRenderer.destroy();
|
|
426
446
|
}
|
|
427
447
|
export async function clearRendererCacheForProject(projectId) {
|
|
428
448
|
logger.debug("clearRendererCacheForProject called", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache-invalidation.d.ts","sourceRoot":"","sources":["../../../../src/src/server/context/cache-invalidation.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cache-invalidation.d.ts","sourceRoot":"","sources":["../../../../src/src/server/context/cache-invalidation.ts"],"names":[],"mappings":"AAsBA,UAAU,mBAAmB;IAC3B,qEAAqE;IACrE,WAAW,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;IACvC,yCAAyC;IACzC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,WAAW,EAAE,MAAM,EACnB,YAAY,CAAC,EAAE,MAAM,EAAE,EACvB,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAmHf"}
|
|
@@ -7,6 +7,7 @@ import { clearRendererCacheForProject } from "../../rendering/renderer.js";
|
|
|
7
7
|
import { clearRouterDetectionCache, clearRouterDetectionCacheForProject, } from "../../rendering/router-detection.js";
|
|
8
8
|
import { clearSnippetCacheForProject } from "../../rendering/snippet-renderer.js";
|
|
9
9
|
import { resetApiHandlerForProject } from "../handlers/request/api/pages-api-handler.js";
|
|
10
|
+
import { clearSourceMissCache } from "../../modules/server/module-source-resolution-cache.js";
|
|
10
11
|
const logger = serverLogger.component("cache-invalidation");
|
|
11
12
|
/**
|
|
12
13
|
* Invalidate project caches with optional environment scoping.
|
|
@@ -34,6 +35,7 @@ export async function invalidateProjectCaches(projectSlug, changedPaths, options
|
|
|
34
35
|
logger.debug("Clearing module path cache (full)", { projectSlug });
|
|
35
36
|
clearModulePathCache();
|
|
36
37
|
}
|
|
38
|
+
clearSourceMissCache();
|
|
37
39
|
logger.debug("Clearing SSR module cache", {
|
|
38
40
|
projectSlug,
|
|
39
41
|
projectId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-overlay.d.ts","sourceRoot":"","sources":["../../../../../../src/src/server/handlers/dev/scripts/error-overlay.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;GAGG;AACH,wBAAgB,eAAe,IAAI,MAAM,
|
|
1
|
+
{"version":3,"file":"error-overlay.d.ts","sourceRoot":"","sources":["../../../../../../src/src/server/handlers/dev/scripts/error-overlay.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;GAGG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAwGxC"}
|
|
@@ -11,6 +11,7 @@ export function getErrorOverlay() {
|
|
|
11
11
|
// Veryfront Error Overlay
|
|
12
12
|
(function() {
|
|
13
13
|
let overlayElement = null;
|
|
14
|
+
let entriesElement = null;
|
|
14
15
|
|
|
15
16
|
window.addEventListener('error', (event) => {
|
|
16
17
|
showError({
|
|
@@ -34,20 +35,37 @@ export function getErrorOverlay() {
|
|
|
34
35
|
createOverlay();
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
<div style="color: #ff5555; font-size: 18px; font-weight: bold; margin-bottom: 10px;">
|
|
40
|
-
\${escapeHtml(error.message)}
|
|
41
|
-
</div>
|
|
42
|
-
\${error.filename ? \`<div style="color: #8b8b8b; margin-bottom: 5px;">\${escapeHtml(error.filename)}:\${error.lineno}:\${error.colno}</div>\` : ''}
|
|
43
|
-
\${error.stack ? \`<pre style="color: #cccccc; font-size: 12px; overflow-x: auto;">\${escapeHtml(error.stack)}</pre>\` : ''}
|
|
44
|
-
</div>
|
|
45
|
-
\`;
|
|
46
|
-
|
|
47
|
-
overlayElement.innerHTML = errorHtml + overlayElement.innerHTML;
|
|
38
|
+
const errorEntry = createErrorEntry(error);
|
|
39
|
+
entriesElement.insertBefore(errorEntry, entriesElement.firstChild);
|
|
48
40
|
overlayElement.style.display = 'block';
|
|
49
41
|
}
|
|
50
42
|
|
|
43
|
+
function createErrorEntry(error) {
|
|
44
|
+
const entry = document.createElement('div');
|
|
45
|
+
entry.style.cssText = 'margin-bottom: 20px;';
|
|
46
|
+
|
|
47
|
+
const message = document.createElement('div');
|
|
48
|
+
message.style.cssText = 'color: #ff5555; font-size: 18px; font-weight: bold; margin-bottom: 10px;';
|
|
49
|
+
message.textContent = String(error.message || 'Unknown error');
|
|
50
|
+
entry.appendChild(message);
|
|
51
|
+
|
|
52
|
+
if (error.filename) {
|
|
53
|
+
const location = document.createElement('div');
|
|
54
|
+
location.style.cssText = 'color: #8b8b8b; margin-bottom: 5px;';
|
|
55
|
+
location.textContent = String(error.filename) + ':' + error.lineno + ':' + error.colno;
|
|
56
|
+
entry.appendChild(location);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (error.stack) {
|
|
60
|
+
const stack = document.createElement('pre');
|
|
61
|
+
stack.style.cssText = 'color: #cccccc; font-size: 12px; overflow-x: auto;';
|
|
62
|
+
stack.textContent = String(error.stack);
|
|
63
|
+
entry.appendChild(stack);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return entry;
|
|
67
|
+
}
|
|
68
|
+
|
|
51
69
|
function createOverlay() {
|
|
52
70
|
overlayElement = document.createElement('div');
|
|
53
71
|
overlayElement.style.cssText = \`
|
|
@@ -82,18 +100,14 @@ export function getErrorOverlay() {
|
|
|
82
100
|
\`;
|
|
83
101
|
closeButton.onclick = () => {
|
|
84
102
|
overlayElement.style.display = 'none';
|
|
85
|
-
|
|
103
|
+
entriesElement.replaceChildren();
|
|
86
104
|
};
|
|
87
105
|
|
|
106
|
+
entriesElement = document.createElement('div');
|
|
88
107
|
overlayElement.appendChild(closeButton);
|
|
108
|
+
overlayElement.appendChild(entriesElement);
|
|
89
109
|
document.body.appendChild(overlayElement);
|
|
90
110
|
}
|
|
91
|
-
|
|
92
|
-
function escapeHtml(text) {
|
|
93
|
-
const div = document.createElement('div');
|
|
94
|
-
div.textContent = text;
|
|
95
|
-
return div.innerHTML;
|
|
96
|
-
}
|
|
97
111
|
})();
|
|
98
112
|
`.trim();
|
|
99
113
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BaseHandler } from "../response/base.js";
|
|
2
2
|
import type { HandlerContext, HandlerMetadata, HandlerResult } from "../types.js";
|
|
3
|
+
export declare function sanitizeClientLogPreview(value: string, maxLength?: number): string;
|
|
3
4
|
export declare class ClientLogHandler extends BaseHandler {
|
|
4
5
|
metadata: HandlerMetadata;
|
|
5
6
|
handle(req: Request, ctx: HandlerContext): Promise<HandlerResult>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-log.handler.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/monitoring/client-log.handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAmB,aAAa,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"client-log.handler.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/monitoring/client-log.handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAmB,aAAa,EAAE,MAAM,aAAa,CAAC;AA+BnG,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,MAAM,EACb,SAAS,SAAiC,GACzC,MAAM,CASR;AAED,qBAAa,gBAAiB,SAAQ,WAAW;IAC/C,QAAQ,EAAE,eAAe,CAKvB;IAEI,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IAwDvE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAIzB;IAEX,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,gBAAgB;CAkCzB"}
|
|
@@ -11,6 +11,28 @@ const CLIENT_LOG_MAX_BODY_BYTES = 64 * 1024;
|
|
|
11
11
|
const CLIENT_LOG_MESSAGE_MAX_LENGTH = 5_000;
|
|
12
12
|
/** Max chars of raw body shown in parse-error diagnostics */
|
|
13
13
|
const CLIENT_LOG_BODY_PREVIEW_LENGTH = 500;
|
|
14
|
+
function escapeControlCharacter(char) {
|
|
15
|
+
switch (char) {
|
|
16
|
+
case "\n":
|
|
17
|
+
return "\\n";
|
|
18
|
+
case "\r":
|
|
19
|
+
return "\\r";
|
|
20
|
+
case "\t":
|
|
21
|
+
return "\\t";
|
|
22
|
+
default:
|
|
23
|
+
return `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function sanitizeClientLogPreview(value, maxLength = CLIENT_LOG_BODY_PREVIEW_LENGTH) {
|
|
27
|
+
let sanitized = "";
|
|
28
|
+
for (const char of value.slice(0, maxLength)) {
|
|
29
|
+
const code = char.charCodeAt(0);
|
|
30
|
+
sanitized += code <= 0x1f || (code >= 0x7f && code <= 0x9f)
|
|
31
|
+
? escapeControlCharacter(char)
|
|
32
|
+
: char;
|
|
33
|
+
}
|
|
34
|
+
return sanitized;
|
|
35
|
+
}
|
|
14
36
|
export class ClientLogHandler extends BaseHandler {
|
|
15
37
|
metadata = {
|
|
16
38
|
name: "ClientLogHandler",
|
|
@@ -71,9 +93,14 @@ export class ClientLogHandler extends BaseHandler {
|
|
|
71
93
|
return ClientLogHandler.LOG_PREFIXES[level] ?? ClientLogHandler.LOG_PREFIXES.info ?? "[CLIENT]";
|
|
72
94
|
}
|
|
73
95
|
handleParseError(e, body) {
|
|
74
|
-
serverLogger.error("[ClientLogHandler] Failed to parse client log
|
|
75
|
-
|
|
76
|
-
|
|
96
|
+
serverLogger.error("[ClientLogHandler] Failed to parse client log", {
|
|
97
|
+
parse_error_message: getErrorMessage(e),
|
|
98
|
+
});
|
|
99
|
+
serverLogger.error("[ClientLogHandler] Sanitized body preview", {
|
|
100
|
+
body_length: body.length,
|
|
101
|
+
body_preview: sanitizeClientLogPreview(body),
|
|
102
|
+
preview_length: CLIENT_LOG_BODY_PREVIEW_LENGTH,
|
|
103
|
+
});
|
|
77
104
|
// Try to identify the problematic character for SyntaxError
|
|
78
105
|
if (!(e instanceof SyntaxError) || !e.message.includes("position")) {
|
|
79
106
|
return;
|
|
@@ -85,7 +112,14 @@ export class ClientLogHandler extends BaseHandler {
|
|
|
85
112
|
const pos = parseInt(posStr, 10);
|
|
86
113
|
const start = Math.max(0, pos - 20);
|
|
87
114
|
const end = Math.min(body.length, pos + 20);
|
|
88
|
-
logger.error("Context around error position
|
|
89
|
-
|
|
115
|
+
logger.error("Context around error position", {
|
|
116
|
+
body_context: sanitizeClientLogPreview(body.slice(start, end)),
|
|
117
|
+
error_position: pos,
|
|
118
|
+
});
|
|
119
|
+
serverLogger.error("[ClientLogHandler] Character at error position", {
|
|
120
|
+
char_code: body.charCodeAt(pos),
|
|
121
|
+
char_preview: sanitizeClientLogPreview(body.charAt(pos), 1),
|
|
122
|
+
error_position: pos,
|
|
123
|
+
});
|
|
90
124
|
}
|
|
91
125
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"batch-module-handler.d.ts","sourceRoot":"","sources":["../../../../../../src/src/server/handlers/request/module/batch-module-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAOrE,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,cAAc,EACnB,sBAAsB,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,eAAe,EAChE,OAAO,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,aAAa,GAC7C,OAAO,CAAC,aAAa,CAAC,
|
|
1
|
+
{"version":3,"file":"batch-module-handler.d.ts","sourceRoot":"","sources":["../../../../../../src/src/server/handlers/request/module/batch-module-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAOrE,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,cAAc,EACnB,sBAAsB,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,eAAe,EAChE,OAAO,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,aAAa,GAC7C,OAAO,CAAC,aAAa,CAAC,CAwBxB"}
|
|
@@ -13,7 +13,8 @@ export function handleBatchModuleEndpoint(req, ctx, _createResponseBuilder, resp
|
|
|
13
13
|
adapter: ctx.adapter,
|
|
14
14
|
projectSlug: ctx.projectSlug,
|
|
15
15
|
projectId: ctx.projectId,
|
|
16
|
-
branch: ctx.parsedDomain?.branch ?? null,
|
|
16
|
+
branch: ctx.requestContext?.branch ?? ctx.parsedDomain?.branch ?? null,
|
|
17
|
+
releaseId: ctx.releaseId ?? null,
|
|
17
18
|
dev: !!ctx.isLocalProject,
|
|
18
19
|
allowedImportDirs: ctx.config?.security?.allowedImportDirs,
|
|
19
20
|
});
|
|
@@ -50,6 +50,16 @@ export declare function redactSensitive<T>(context: T): T;
|
|
|
50
50
|
* Non-URL strings pass through unchanged.
|
|
51
51
|
*/
|
|
52
52
|
export declare function sanitizeUrlCredentials(input: string): string;
|
|
53
|
+
/**
|
|
54
|
+
* Return the URL form safe to attach to observability span attributes.
|
|
55
|
+
*
|
|
56
|
+
* Span attributes bypass the logger's structured redaction pass, so `http.url`
|
|
57
|
+
* must not include query strings, fragments, or URL userinfo. This intentionally
|
|
58
|
+
* strips every query parameter instead of selectively redacting credential-like
|
|
59
|
+
* names because cache keys and callback state can be sensitive even when the
|
|
60
|
+
* parameter name is not obviously a credential.
|
|
61
|
+
*/
|
|
62
|
+
export declare function sanitizeUrlForSpan(input: string): string;
|
|
53
63
|
/**
|
|
54
64
|
* Apply {@link sanitizeUrlCredentials} to the `message` and `stack` of a
|
|
55
65
|
* serialized-error-shaped object, returning a new object. Used by the logger's
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../../../../src/src/utils/logger/redact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,6DAA6D;AAC7D,eAAO,MAAM,QAAQ,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../../../../src/src/utils/logger/redact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,6DAA6D;AAC7D,eAAO,MAAM,QAAQ,eAAe,CAAC;AA8CrC;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAcnD;AA6ED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,CAEhD;AA2BD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CA4B5D;AAqBD;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAgBxD;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,CAAC,SAAS;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,EAC5D,KAAK,EAAE,CAAC,GAAG,CAAC,CAMb"}
|
|
@@ -56,6 +56,8 @@ const SENSITIVE_KEY_PATTERNS = [
|
|
|
56
56
|
"xsrf",
|
|
57
57
|
"csrf",
|
|
58
58
|
];
|
|
59
|
+
const SENSITIVE_KEY_CACHE_MAX_SIZE = 512;
|
|
60
|
+
const sensitiveKeyCache = new Map();
|
|
59
61
|
/** Stop traversing past this depth to keep the pass cheap and stack-safe. */
|
|
60
62
|
const MAX_DEPTH = 16;
|
|
61
63
|
/**
|
|
@@ -67,8 +69,18 @@ const MAX_DEPTH = 16;
|
|
|
67
69
|
* normalizes to `author`, which contains none of the patterns.
|
|
68
70
|
*/
|
|
69
71
|
export function isSensitiveKey(key) {
|
|
72
|
+
const cached = sensitiveKeyCache.get(key);
|
|
73
|
+
if (cached !== undefined)
|
|
74
|
+
return cached;
|
|
70
75
|
const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
71
|
-
|
|
76
|
+
const sensitive = SENSITIVE_KEY_PATTERNS.some((pattern) => normalized.includes(pattern));
|
|
77
|
+
if (sensitiveKeyCache.size >= SENSITIVE_KEY_CACHE_MAX_SIZE) {
|
|
78
|
+
const oldestKey = sensitiveKeyCache.keys().next().value;
|
|
79
|
+
if (oldestKey !== undefined)
|
|
80
|
+
sensitiveKeyCache.delete(oldestKey);
|
|
81
|
+
}
|
|
82
|
+
sensitiveKeyCache.set(key, sensitive);
|
|
83
|
+
return sensitive;
|
|
72
84
|
}
|
|
73
85
|
/**
|
|
74
86
|
* A non-null, non-array object. {@link isRecord} covers class instances too,
|
|
@@ -216,6 +228,53 @@ export function sanitizeUrlCredentials(input) {
|
|
|
216
228
|
});
|
|
217
229
|
return out;
|
|
218
230
|
}
|
|
231
|
+
function firstUrlDelimiterIndex(input) {
|
|
232
|
+
const queryIndex = input.indexOf("?");
|
|
233
|
+
const hashIndex = input.indexOf("#");
|
|
234
|
+
if (queryIndex === -1)
|
|
235
|
+
return hashIndex;
|
|
236
|
+
if (hashIndex === -1)
|
|
237
|
+
return queryIndex;
|
|
238
|
+
return Math.min(queryIndex, hashIndex);
|
|
239
|
+
}
|
|
240
|
+
function sanitizeProtocolRelativeUrlForSpan(input) {
|
|
241
|
+
if (!input.startsWith("//"))
|
|
242
|
+
return null;
|
|
243
|
+
try {
|
|
244
|
+
const url = new URL(`https:${input}`);
|
|
245
|
+
return `//${url.host}${url.pathname}`;
|
|
246
|
+
}
|
|
247
|
+
catch (_) {
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Return the URL form safe to attach to observability span attributes.
|
|
253
|
+
*
|
|
254
|
+
* Span attributes bypass the logger's structured redaction pass, so `http.url`
|
|
255
|
+
* must not include query strings, fragments, or URL userinfo. This intentionally
|
|
256
|
+
* strips every query parameter instead of selectively redacting credential-like
|
|
257
|
+
* names because cache keys and callback state can be sensitive even when the
|
|
258
|
+
* parameter name is not obviously a credential.
|
|
259
|
+
*/
|
|
260
|
+
export function sanitizeUrlForSpan(input) {
|
|
261
|
+
if (typeof input !== "string" || input.length === 0)
|
|
262
|
+
return input;
|
|
263
|
+
try {
|
|
264
|
+
const url = new URL(input);
|
|
265
|
+
if (url.origin !== "null")
|
|
266
|
+
return `${url.origin}${url.pathname}`;
|
|
267
|
+
}
|
|
268
|
+
catch (_) {
|
|
269
|
+
// Relative or malformed URL-shaped strings are handled by the fallback.
|
|
270
|
+
}
|
|
271
|
+
const delimiterIndex = firstUrlDelimiterIndex(input);
|
|
272
|
+
const withoutQueryOrFragment = delimiterIndex === -1 ? input : input.slice(0, delimiterIndex);
|
|
273
|
+
const protocolRelativeUrl = sanitizeProtocolRelativeUrlForSpan(withoutQueryOrFragment);
|
|
274
|
+
if (protocolRelativeUrl)
|
|
275
|
+
return protocolRelativeUrl;
|
|
276
|
+
return sanitizeUrlCredentials(withoutQueryOrFragment);
|
|
277
|
+
}
|
|
219
278
|
/**
|
|
220
279
|
* Apply {@link sanitizeUrlCredentials} to the `message` and `stack` of a
|
|
221
280
|
* serialized-error-shaped object, returning a new object. Used by the logger's
|
|
@@ -2,6 +2,7 @@ export interface RedisClient {
|
|
|
2
2
|
connect(): Promise<void>;
|
|
3
3
|
disconnect(): Promise<void>;
|
|
4
4
|
get(key: string): Promise<string | null>;
|
|
5
|
+
mGet(keys: string[]): Promise<Array<string | null>>;
|
|
5
6
|
set(key: string, value: string, options?: {
|
|
6
7
|
EX?: number;
|
|
7
8
|
}): Promise<string | null>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redis-client.d.ts","sourceRoot":"","sources":["../../../src/src/utils/redis-client.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,WAAW;IAC1B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACzC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACnF,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7C,IAAI,CACF,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAC3C,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IAC/C,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACtD,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,IAAI,CAAC;IACjE,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAsBD,wBAAsB,cAAc,CAAC,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,WAAW,CAAC,CA4B3F;AA6DD,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C"}
|
|
1
|
+
{"version":3,"file":"redis-client.d.ts","sourceRoot":"","sources":["../../../src/src/utils/redis-client.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,WAAW;IAC1B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACzC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC;IACpD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACnF,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7C,IAAI,CACF,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAC3C,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IAC/C,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACtD,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,IAAI,CAAC;IACjE,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAsBD,wBAAsB,cAAc,CAAC,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,WAAW,CAAC,CA4B3F;AA6DD,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C"}
|