import { randomBytes } from
"node:crypto" ;
export
const CANVAS_CAPABILITY_PATH_PREFIX =
"/__openclaw__/cap" ;
export
const CANVAS_CAPABILITY_QUERY_PARAM =
"oc_cap" ;
export
const CANVAS_CAPABILITY_TTL_MS =
10 *
60 _
000 ;
export type NormalizedCanvasScopedUrl = {
pathname: string;
capability?: string;
rewrittenUrl?: string;
scopedPath:
boolean ;
malformedScopedPath:
boolean ;
};
function normalizeCapability(raw: string |
null | undefined): string | undefined {
const trimmed = raw?.trim();
return trimmed ? trimmed : undefined;
}
export
function mintCanvasCapabilityToken(): string {
return randomBytes(
18 ).toString(
"base64url" );
}
export
function buildCanvasScopedHostUrl(baseUrl: string, capability: string): string |
undefined {
const normalizedCapability = normalizeCapability(capability);
if (!normalizedCapability) {
return undefined;
}
try {
const url = new URL(baseUrl);
const trimmedPath = url.pathname.replace(/\/+$/, "" );
const prefix = `${CANVAS_CAPABILITY_PATH_PREFIX}/${encodeURIComponent(normalizedCapability)}`;
url.pathname = `${trimmedPath}${prefix}`;
url.search = "" ;
url.hash = "" ;
return url.toString().replace(/\/$/, "" );
} catch {
return undefined;
}
}
export function normalizeCanvasScopedUrl(rawUrl: string): NormalizedCanvasScopedUrl {
const url = new URL(rawUrl, "http://localhost ");
const prefix = `${CANVAS_CAPABILITY_PATH_PREFIX}/`;
let scopedPath = false ;
let malformedScopedPath = false ;
let capabilityFromPath: string | undefined;
let rewrittenUrl: string | undefined;
if (url.pathname.startsWith(prefix)) {
scopedPath = true ;
const remainder = url.pathname.slice(prefix.length);
const slashIndex = remainder.indexOf("/" );
if (slashIndex <= 0 ) {
malformedScopedPath = true ;
} else {
const encodedCapability = remainder.slice(0 , slashIndex);
const canonicalPath = remainder.slice(slashIndex) || "/" ;
let decoded: string | undefined;
try {
decoded = decodeURIComponent(encodedCapability);
} catch {
malformedScopedPath = true ;
}
capabilityFromPath = normalizeCapability(decoded);
if (!capabilityFromPath || !canonicalPath.startsWith("/" )) {
malformedScopedPath = true ;
} else {
url.pathname = canonicalPath;
if (!url.searchParams.has(CANVAS_CAPABILITY_QUERY_PARAM)) {
url.searchParams.set(CANVAS_CAPABILITY_QUERY_PARAM, capabilityFromPath);
}
rewrittenUrl = `${url.pathname}${url.search}`;
}
}
}
const capability =
capabilityFromPath ?? normalizeCapability(url.searchParams.get(CANVAS_CAPABILITY_QUERY_PARAM));
return {
pathname: url.pathname,
capability,
rewrittenUrl,
scopedPath,
malformedScopedPath,
};
}
Messung V0.5 in Prozent C=98 H=97 G=97
¤ Dauer der Verarbeitung: 0.12 Sekunden
(vorverarbeitet am 2026-06-10)
¤
*© Formatika GbR, Deutschland