dashboard-nanobot/frontend/src/utils/botAccess.ts

101 lines
3.0 KiB
TypeScript

import axios from 'axios';
const BOT_PASSWORD_HEADER = 'X-Bot-Password';
let initialized = false;
const memoryMap = new Map<string, string>();
function normalizeBotId(raw: string): string {
return String(raw || '').trim();
}
function resolveAbsoluteUrl(input: string): string {
const url = String(input || '').trim();
if (!url) return '';
try {
if (url.startsWith('http://') || url.startsWith('https://')) {
return new URL(url).pathname;
}
if (typeof window !== 'undefined') {
return new URL(url, window.location.origin).pathname;
}
return url;
} catch {
return url;
}
}
export function extractBotIdFromApiPath(rawPath: string): string | null {
const path = resolveAbsoluteUrl(rawPath);
if (!path) return null;
const match = path.match(/\/api\/bots\/([^/?#]+)/i);
if (!match?.[1]) return null;
try {
return decodeURIComponent(match[1]).trim() || null;
} catch {
return String(match[1]).trim() || null;
}
}
export function getBotAccessPassword(botId: string): string {
const key = normalizeBotId(botId);
if (!key) return '';
return memoryMap.get(key) || '';
}
export function setBotAccessPassword(botId: string, password: string): void {
const key = normalizeBotId(botId);
const value = String(password || '').trim();
if (!key || !value) return;
memoryMap.set(key, value);
}
export function clearBotAccessPassword(botId: string): void {
const key = normalizeBotId(botId);
if (!key) return;
memoryMap.delete(key);
}
export function clearAllBotAccessPasswords(): void {
if (memoryMap.size === 0) return;
memoryMap.clear();
}
export function isBotUnauthorizedError(error: any, botId?: string): boolean {
if (!axios.isAxiosError(error)) return false;
if (Number(error.response?.status) !== 401) return false;
if (!botId) return true;
const fromConfig = extractBotIdFromApiPath(String(error.config?.url || ''));
const fromRequest = extractBotIdFromApiPath(String(error.request?.responseURL || ''));
const expected = normalizeBotId(botId);
return expected === fromConfig || expected === fromRequest;
}
export function buildMonitorWsUrl(base: string, botId: string): string {
const target = `${String(base || '').replace(/\/$/, '')}/${encodeURIComponent(botId)}`;
const password = getBotAccessPassword(botId);
if (!password) return target;
const joiner = target.includes('?') ? '&' : '?';
return `${target}${joiner}access_password=${encodeURIComponent(password)}`;
}
export function setupBotAccessAuth(): void {
if (initialized) return;
initialized = true;
axios.interceptors.request.use((config) => {
const botId = extractBotIdFromApiPath(String(config.url || ''));
if (!botId) return config;
const password = getBotAccessPassword(botId);
if (!password) return config;
const headers = config.headers || {};
if (!(BOT_PASSWORD_HEADER in (headers as Record<string, unknown>))) {
(headers as Record<string, string>)[BOT_PASSWORD_HEADER] = password;
config.headers = headers;
}
return config;
});
}