chore: migrate frontend assets to OSS and same-origin APIs

This commit is contained in:
2026-06-04 16:03:49 +08:00
parent 7c6129555b
commit c7c52c1467
55 changed files with 728 additions and 292 deletions
-1
View File
@@ -67,7 +67,6 @@ let modelCapabilitiesRouteMissing = false;
export const modelCapabilitiesClient = {
async get(name = "web-model-capabilities"): Promise<WebModelCapabilities> {
if (import.meta.env.DEV && name === "web-model-capabilities") return createFallbackCapabilities();
if (modelCapabilitiesRouteMissing) return createFallbackCapabilities();
let payload: unknown;
+51
View File
@@ -0,0 +1,51 @@
import { isOptionalApiRouteMissing } from "./apiErrorUtils";
import { isRecord, serverRequest } from "./serverConnection";
export interface WebPublicConfig {
contactEmail?: string;
contactPhone?: string;
companyAddress?: string;
icpRecord?: string;
}
function readString(config: Record<string, unknown>, keys: string[]): string | undefined {
for (const key of keys) {
const value = config[key];
if (typeof value === "string" && value.trim()) return value.trim();
}
return undefined;
}
function normalizePublicConfig(raw: unknown): WebPublicConfig {
const config = isRecord(raw) && isRecord(raw.config) ? raw.config : raw;
if (!isRecord(config)) return {};
return {
contactEmail: readString(config, ["contactEmail", "contact_email", "supportEmail", "support_email"]),
contactPhone: readString(config, ["contactPhone", "contact_phone", "supportPhone", "support_phone"]),
companyAddress: readString(config, ["companyAddress", "company_address", "address"]),
icpRecord: readString(config, ["icpRecord", "icp_record", "filingInfo", "filing_info"]),
};
}
let cachedPublicConfig: WebPublicConfig | null = null;
let publicConfigRouteMissing = false;
export const publicConfigClient = {
async get(): Promise<WebPublicConfig> {
if (cachedPublicConfig) return cachedPublicConfig;
if (publicConfigRouteMissing) return {};
try {
const payload = await serverRequest<unknown>("public/config/profile?name=web-public-config");
cachedPublicConfig = normalizePublicConfig(payload);
return cachedPublicConfig;
} catch (error) {
if (isOptionalApiRouteMissing(error)) {
publicConfigRouteMissing = true;
return {};
}
throw error;
}
},
};
+48 -25
View File
@@ -1,6 +1,5 @@
import type { WebUserSession } from "../types";
export const DEFAULT_SERVER_BASE_URL = import.meta.env.VITE_API_BASE_URL || "";
export const SERVER_SESSION_STORAGE_KEY = "omniai-web-session";
export const SERVER_SESSION_REPLACED_EVENT = "omniai:session-replaced";
export const SERVER_SESSION_EXPIRED_EVENT = "omniai:session-expired";
@@ -59,34 +58,12 @@ export function compactMessage(value: string): string {
}
export function getServerBaseUrl(): string {
const envBaseUrl = String(
import.meta.env.VITE_KEY_SERVER_URL ||
import.meta.env.VITE_SERVER_BASE_URL ||
import.meta.env.VITE_API_BASE_URL ||
"",
).trim();
const shouldUseSameOriginApi =
typeof window !== "undefined" &&
(window.location.protocol === "https:" ||
window.location.hostname === "omniai.net.cn" ||
window.location.hostname === "www.omniai.net.cn");
const rawBaseUrl = envBaseUrl || (shouldUseSameOriginApi ? "" : DEFAULT_SERVER_BASE_URL);
if (!rawBaseUrl || rawBaseUrl.replace(/\/+$/, "").toLowerCase() === "/api") {
return "";
}
return rawBaseUrl.replace(/\/+$/, "").replace(/\/api$/i, "");
return "";
}
export function buildApiUrl(path: string): string {
const cleanPath = path.replace(/^\/+/, "");
const baseUrl = getServerBaseUrl();
if (!baseUrl) return `/api/${cleanPath}`;
try {
return new URL(`api/${cleanPath}`, baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`).toString();
} catch {
return `${baseUrl}/api/${cleanPath}`;
}
return `/api/${cleanPath}`;
}
export function canUseSessionStorage(): boolean {
@@ -167,6 +144,39 @@ export function writeStoredSession(session: WebUserSession | null): void {
}
}
export function clearAllUserStorage(): void {
writeStoredSession(null);
try {
if (typeof window === "undefined") return;
const legacyKeys = ["omniai:token", "omniai:session"];
for (const key of legacyKeys) {
window.localStorage.removeItem(key);
window.sessionStorage.removeItem(key);
}
const prefixKeys = [
"omniai-web-profile-ui",
"omniai:more-recent-tools",
"omniai:generation-queue",
"omniai-canvas-saved-assets",
];
for (let i = window.localStorage.length - 1; i >= 0; i--) {
const key = window.localStorage.key(i);
if (key && prefixKeys.some((p) => key.startsWith(p))) {
window.localStorage.removeItem(key);
}
}
for (let i = window.sessionStorage.length - 1; i >= 0; i--) {
const key = window.sessionStorage.key(i);
if (key && prefixKeys.some((p) => key.startsWith(p))) {
window.sessionStorage.removeItem(key);
}
}
} catch {
// best-effort cleanup
}
}
export function getStoredToken(): string | null {
return readStoredSession()?.token ?? null;
}
@@ -226,6 +236,15 @@ let lastSessionReplacedEventAt = 0;
let lastSessionExpiredEventAt = 0;
function isNonAuthErrorCode(code: string | undefined): boolean {
if (!code) return false;
return [
"ENTERPRISE_VIDEO_MODEL_NOT_ALLOWED",
"INSUFFICIENT_BALANCE",
"INSUFFICIENT_ENTERPRISE_BALANCE",
].includes(code);
}
function notifySessionExpired(status: number, response: Response, payload: unknown): void {
if (status !== 401 && status !== 403) return;
if (typeof window === "undefined") return;
@@ -238,6 +257,9 @@ function notifySessionExpired(status: number, response: Response, payload: unkno
if (!readStoredSession()) return;
// Deliberate early-exit for unauthenticated users — not a real auth failure.
if (getPayloadCode(payload) === "NOT_LOGGED_IN") return;
// Non-auth 403 errors (enterprise model access, insufficient balance) must
// not trigger session expiry.
if (status === 403 && isNonAuthErrorCode(getPayloadCode(payload))) return;
const now = Date.now();
if (now - lastSessionExpiredEventAt < 1500) return;
@@ -341,6 +363,7 @@ export async function serverRequest<T>(path: string, options?: ServerRequestOpti
headers,
body: options?.body === undefined ? undefined : JSON.stringify(options.body),
signal: controller ? controller.signal : options?.signal,
credentials: "include",
});
const payload = await readJsonResponse<unknown>(response, "Request failed");