Files
beaver_project/auth-portal/src/lib/runtime-control.ts
steven_li b736fc9c81 feat(auth-portal): 添加部署控制服务调用支持
- 导入callDeployControl和normalizeTokenResponse函数用于处理部署配置
- 新增hasTargetFrontendUrl函数检查响应中是否存在目标前端URL
- 在注册流程中添加部署路由解析逻辑,当缺少前端URL时调用部署控制服务获取配置
- 更新normalizeTokenResponse函数以支持从实例对象中提取URL配置

refactor(runtime-control): 增强令牌响应标准化功能

- 扩展normalizeTokenResponse函数支持从instance对象中获取URL配置
- 添加对instance字段的支持,优先级为routing > instance配置
- 支持从instance中提取frontend_base_url、api_base_url和public_url

build(tsconfig): 排除测试文件构建

- 在tsconfig.json中添加排除规则,排除**/*.test.ts和**/*.test.tsx文件
- 避免测试文件参与生产构建

refactor(authz-service): 优化Python后端令牌响应处理

- 更新_normalize_portal_token_response函数支持从实例对象中提取URL配置
- 重构URL优先级逻辑,支持routing和instance双重数据源
- 改进代码可读性,将复杂的URL赋值逻辑拆分为多行
2026-06-16 10:17:30 +08:00

138 lines
4.3 KiB
TypeScript

import type { TokenResponse } from '@/types/auth';
const AUTHZ_API_BASE_URL = (process.env.AUTHZ_API_BASE_URL || 'http://127.0.0.1:19090').trim().replace(/\/+$/, '');
const DEPLOY_API_BASE_URL = (process.env.DEPLOY_API_BASE_URL || 'http://127.0.0.1:8090').trim().replace(/\/+$/, '');
const DEPLOY_API_TOKEN = (process.env.DEPLOY_API_TOKEN || '').trim();
const REQUEST_TIMEOUT_MS = 15000;
const REGISTER_REQUEST_TIMEOUT_MS = 90000;
type JsonObject = Record<string, unknown>;
export class HttpError extends Error {
status: number;
constructor(status: number, message: string) {
super(message);
this.status = status;
}
}
function asObject(value: unknown): JsonObject {
return value && typeof value === 'object' && !Array.isArray(value) ? (value as JsonObject) : {};
}
function asString(value: unknown): string {
return typeof value === 'string' ? value.trim() : '';
}
async function fetchJson<T>(url: string, init?: RequestInit, timeoutMs = REQUEST_TIMEOUT_MS): Promise<T> {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(url, {
...init,
headers: {
'Content-Type': 'application/json',
...(init?.headers || {}),
},
cache: 'no-store',
signal: controller.signal,
});
const raw = await response.text();
let payload: unknown = {};
if (raw) {
try {
payload = JSON.parse(raw);
} catch {
payload = { detail: raw };
}
}
if (!response.ok) {
const detail = asString(asObject(payload).detail) || `request failed with status ${response.status}`;
throw new HttpError(response.status, detail);
}
return payload as T;
} catch (error) {
if (error instanceof HttpError) {
throw error;
}
if (error instanceof DOMException && error.name === 'AbortError') {
throw new HttpError(504, 'request timed out');
}
throw new HttpError(502, error instanceof Error ? error.message : 'request failed');
} finally {
clearTimeout(timeoutId);
}
}
export async function callDeployControl<T>(path: string, payload: JsonObject, timeoutMs = REQUEST_TIMEOUT_MS): Promise<T> {
const headers: Record<string, string> = {};
if (DEPLOY_API_TOKEN) {
headers.Authorization = `Bearer ${DEPLOY_API_TOKEN}`;
}
return fetchJson<T>(`${DEPLOY_API_BASE_URL}${path}`, {
method: 'POST',
headers,
body: JSON.stringify(payload),
}, timeoutMs);
}
export async function callAuthzService<T>(path: string, payload: JsonObject, timeoutMs = REQUEST_TIMEOUT_MS): Promise<T> {
return fetchJson<T>(`${AUTHZ_API_BASE_URL}${path}`, {
method: 'POST',
body: JSON.stringify(payload),
}, timeoutMs);
}
export { REGISTER_REQUEST_TIMEOUT_MS };
export async function callInstanceApi<T>(apiBaseUrl: string, path: string, payload: JsonObject): Promise<T> {
const baseUrl = apiBaseUrl.trim().replace(/\/+$/, '');
if (!baseUrl) {
throw new HttpError(500, 'instance api base url is missing');
}
return fetchJson<T>(`${baseUrl}${path}`, {
method: 'POST',
body: JSON.stringify(payload),
});
}
export function normalizeTokenResponse(
response: TokenResponse,
routing: {
frontend_base_url?: unknown;
api_base_url?: unknown;
public_url?: unknown;
instance?: unknown;
}
): TokenResponse {
const instance = asObject(routing.instance);
const frontendBaseUrl =
asString(routing.frontend_base_url) ||
asString(instance.frontend_base_url) ||
asString(instance.public_url);
const apiBaseUrl =
asString(routing.api_base_url) ||
asString(instance.api_base_url) ||
asString(routing.public_url) ||
asString(instance.public_url);
const publicUrl = asString(routing.public_url) || asString(instance.public_url) || apiBaseUrl;
const backendConnection = asObject(response.backend_connection);
const mergedBackendConnection = {
...backendConnection,
frontend_base_url: asString(backendConnection.frontend_base_url) || frontendBaseUrl || publicUrl || null,
api_base_url: asString(backendConnection.api_base_url) || apiBaseUrl || publicUrl || null,
public_base_url: asString(backendConnection.public_base_url) || publicUrl || apiBaseUrl || null,
};
return {
...response,
backend_connection: mergedBackendConnection,
};
}