Files
clawdbot/src/browser/profiles.ts
2026-01-05 01:27:13 +01:00

108 lines
3.0 KiB
TypeScript

/**
* CDP port allocation for browser profiles.
*
* Default port range: 18800-18899 (100 profiles max)
* Ports are allocated once at profile creation and persisted in config.
* Multi-instance: callers may pass an explicit range to avoid collisions.
*
* Reserved ports (do not use for CDP):
* 18789 - Gateway WebSocket
* 18790 - Bridge
* 18791 - Browser control server
* 18792-18799 - Reserved for future one-off services (canvas at 18793)
*/
export const CDP_PORT_RANGE_START = 18800;
export const CDP_PORT_RANGE_END = 18899;
export const PROFILE_NAME_REGEX = /^[a-z0-9][a-z0-9-]*$/;
export function isValidProfileName(name: string): boolean {
if (!name || name.length > 64) return false;
return PROFILE_NAME_REGEX.test(name);
}
export function allocateCdpPort(
usedPorts: Set<number>,
range?: { start: number; end: number },
): number | null {
const start = range?.start ?? CDP_PORT_RANGE_START;
const end = range?.end ?? CDP_PORT_RANGE_END;
if (
!Number.isFinite(start) ||
!Number.isFinite(end) ||
start <= 0 ||
end <= 0
) {
return null;
}
if (start > end) return null;
for (let port = start; port <= end; port++) {
if (!usedPorts.has(port)) return port;
}
return null;
}
export function getUsedPorts(
profiles: Record<string, { cdpPort?: number; cdpUrl?: string }> | undefined,
): Set<number> {
if (!profiles) return new Set();
const used = new Set<number>();
for (const profile of Object.values(profiles)) {
if (typeof profile.cdpPort === "number") {
used.add(profile.cdpPort);
continue;
}
const rawUrl = profile.cdpUrl?.trim();
if (!rawUrl) continue;
try {
const parsed = new URL(rawUrl);
const port =
parsed.port && Number.parseInt(parsed.port, 10) > 0
? Number.parseInt(parsed.port, 10)
: parsed.protocol === "https:"
? 443
: 80;
if (!Number.isNaN(port) && port > 0 && port <= 65535) {
used.add(port);
}
} catch {
// ignore invalid URLs
}
}
return used;
}
export const PROFILE_COLORS = [
"#FF4500", // Orange-red (clawd default)
"#0066CC", // Blue
"#00AA00", // Green
"#9933FF", // Purple
"#FF6699", // Pink
"#00CCCC", // Cyan
"#FF9900", // Orange
"#6666FF", // Indigo
"#CC3366", // Magenta
"#339966", // Teal
];
export function allocateColor(usedColors: Set<string>): string {
// Find first unused color from palette
for (const color of PROFILE_COLORS) {
if (!usedColors.has(color.toUpperCase())) {
return color;
}
}
// All colors used, cycle based on count
const index = usedColors.size % PROFILE_COLORS.length;
// biome-ignore lint/style/noNonNullAssertion: Array is non-empty constant
return PROFILE_COLORS[index] ?? PROFILE_COLORS[0]!;
}
export function getUsedColors(
profiles: Record<string, { color: string }> | undefined,
): Set<string> {
if (!profiles) return new Set();
return new Set(Object.values(profiles).map((p) => p.color.toUpperCase()));
}