- 新增 NANO_OUTLOOK_MCP_URL 和 NANO_OUTLOOK_MCP_SERVER_ID 环境变量配置 - 实现 Outlook 邮件和日历的分页查询功能,添加安全参数验证 - 为 app-instance 创建脚本添加 Outlook MCP 服务器 ID 参数 - 更新前端 Outlook 页面实现邮件列表和日历事件的分页浏览 - 添加 Git 忽略文件配置和 Docker 挂载路径修复 BREAKING CHANGE: Outlook 集成现在需要配置 MCP URL 和服务器 ID 环境变量
129 lines
4.0 KiB
TypeScript
129 lines
4.0 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): 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),
|
|
});
|
|
}
|
|
|
|
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;
|
|
}
|
|
): TokenResponse {
|
|
const frontendBaseUrl = asString(routing.frontend_base_url);
|
|
const apiBaseUrl = asString(routing.api_base_url) || asString(routing.public_url);
|
|
const publicUrl = asString(routing.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,
|
|
};
|
|
}
|