Files
openclaw/src/channels/allowlists/resolve-utils.ts
2026-02-15 17:45:16 +00:00

137 lines
4.0 KiB
TypeScript

import type { RuntimeEnv } from "../../runtime.js";
export type AllowlistUserResolutionLike = {
input: string;
resolved: boolean;
id?: string;
};
export function mergeAllowlist(params: {
existing?: Array<string | number>;
additions: string[];
}): string[] {
const seen = new Set<string>();
const merged: string[] = [];
const push = (value: string) => {
const normalized = value.trim();
if (!normalized) {
return;
}
const key = normalized.toLowerCase();
if (seen.has(key)) {
return;
}
seen.add(key);
merged.push(normalized);
};
for (const entry of params.existing ?? []) {
push(String(entry));
}
for (const entry of params.additions) {
push(entry);
}
return merged;
}
export function buildAllowlistResolutionSummary<T extends AllowlistUserResolutionLike>(
resolvedUsers: T[],
opts?: { formatResolved?: (entry: T) => string },
): {
resolvedMap: Map<string, T>;
mapping: string[];
unresolved: string[];
additions: string[];
} {
const resolvedMap = new Map(resolvedUsers.map((entry) => [entry.input, entry]));
const resolvedOk = (entry: T) => Boolean(entry.resolved && entry.id);
const formatResolved = opts?.formatResolved ?? ((entry: T) => `${entry.input}${entry.id}`);
const mapping = resolvedUsers.filter(resolvedOk).map(formatResolved);
const additions = resolvedUsers
.filter(resolvedOk)
.map((entry) => entry.id)
.filter((entry): entry is string => Boolean(entry));
const unresolved = resolvedUsers
.filter((entry) => !resolvedOk(entry))
.map((entry) => entry.input);
return { resolvedMap, mapping, unresolved, additions };
}
export function resolveAllowlistIdAdditions<T extends AllowlistUserResolutionLike>(params: {
existing: Array<string | number>;
resolvedMap: Map<string, T>;
}): string[] {
const additions: string[] = [];
for (const entry of params.existing) {
const trimmed = String(entry).trim();
const resolved = params.resolvedMap.get(trimmed);
if (resolved?.resolved && resolved.id) {
additions.push(resolved.id);
}
}
return additions;
}
export function patchAllowlistUsersInConfigEntries<
T extends AllowlistUserResolutionLike,
TEntries extends Record<string, unknown>,
>(params: { entries: TEntries; resolvedMap: Map<string, T> }): TEntries {
const nextEntries: Record<string, unknown> = { ...params.entries };
for (const [entryKey, entryConfig] of Object.entries(params.entries)) {
if (!entryConfig || typeof entryConfig !== "object") {
continue;
}
const users = (entryConfig as { users?: Array<string | number> }).users;
if (!Array.isArray(users) || users.length === 0) {
continue;
}
const additions = resolveAllowlistIdAdditions({
existing: users,
resolvedMap: params.resolvedMap,
});
nextEntries[entryKey] = {
...entryConfig,
users: mergeAllowlist({ existing: users, additions }),
};
}
return nextEntries as TEntries;
}
export function addAllowlistUserEntriesFromConfigEntry(target: Set<string>, entry: unknown): void {
if (!entry || typeof entry !== "object") {
return;
}
const users = (entry as { users?: Array<string | number> }).users;
if (!Array.isArray(users)) {
return;
}
for (const value of users) {
const trimmed = String(value).trim();
if (trimmed && trimmed !== "*") {
target.add(trimmed);
}
}
}
export function summarizeMapping(
label: string,
mapping: string[],
unresolved: string[],
runtime: RuntimeEnv,
): void {
const lines: string[] = [];
if (mapping.length > 0) {
const sample = mapping.slice(0, 6);
const suffix = mapping.length > sample.length ? ` (+${mapping.length - sample.length})` : "";
lines.push(`${label} resolved: ${sample.join(", ")}${suffix}`);
}
if (unresolved.length > 0) {
const sample = unresolved.slice(0, 6);
const suffix =
unresolved.length > sample.length ? ` (+${unresolved.length - sample.length})` : "";
lines.push(`${label} unresolved: ${sample.join(", ")}${suffix}`);
}
if (lines.length > 0) {
runtime.log?.(lines.join("\n"));
}
}