Files
openmaic/OpenMAIC/lib/ops/api-fetch.ts

101 lines
3.6 KiB
TypeScript

'use client';
const WORKS_OPERATIONS_AUTH_STORAGE_KEY = 'works-square.operations.auth.v1';
export function worksAccessToken(): string | null {
try {
const raw = window.localStorage.getItem(WORKS_OPERATIONS_AUTH_STORAGE_KEY);
if (!raw) return null;
const session = JSON.parse(raw) as { accessToken?: unknown; expiresAt?: unknown };
if (typeof session.accessToken !== 'string' || !session.accessToken.trim()) return null;
if (typeof session.expiresAt === 'number' && session.expiresAt <= Date.now()) return null;
return session.accessToken.trim();
} catch {
return null;
}
}
export function opsApiPath(pathname: string): string {
const path = pathname.startsWith('/') ? pathname : `/${pathname}`;
return `${process.env.NEXT_PUBLIC_OPENMAIC_BASE_PATH ?? ''}${path}`;
}
export function opsApiFetch(pathname: string, init: RequestInit = {}): Promise<Response> {
const headers = new Headers(init.headers);
const token = worksAccessToken();
if (token) headers.set('Authorization', `Bearer ${token}`);
headers.set('Accept', headers.get('Accept') || 'application/json');
return fetch(opsApiPath(pathname), { ...init, headers });
}
const OPS_PUBLIC_ASSET_ROOTS = [
'/avatars/',
'/logos/',
'/vendor/',
'/mailuo-logo.png',
'/openmaic-mark.png',
'/logo-horizontal.png',
] as const;
function opsRootRelativePath(pathname: string): string | null {
if (pathname === '/api' || pathname.startsWith('/api/')) return opsApiPath(pathname);
if (OPS_PUBLIC_ASSET_ROOTS.some((root) => pathname === root || pathname.startsWith(root))) {
return opsApiPath(pathname);
}
return null;
}
/** Install before child effects so legacy root-relative API calls remain basePath-aware. */
export function installOpsBrowserBoundary(): () => void {
if (process.env.NEXT_PUBLIC_OPENMAIC_DEPLOYMENT_ROLE !== 'ops') return () => undefined;
const nativeFetch = window.fetch.bind(window);
const wrappedFetch: typeof window.fetch = (input, init = {}) => {
const mapped = typeof input === 'string' ? opsRootRelativePath(input) : null;
if (!mapped) return nativeFetch(input, init);
if (typeof input !== 'string' || !input.startsWith('/api')) return nativeFetch(mapped, init);
const headers = new Headers(init.headers);
const token = worksAccessToken();
if (token && !headers.has('Authorization')) headers.set('Authorization', `Bearer ${token}`);
return nativeFetch(mapped, { ...init, headers });
};
window.fetch = wrappedFetch;
const rewriteElement = (element: Element) => {
for (const attribute of ['src', 'poster']) {
const value = element.getAttribute(attribute);
if (!value) continue;
const mapped = opsRootRelativePath(value);
if (mapped && mapped !== value && !value.startsWith('/api')) {
element.setAttribute(attribute, mapped);
}
}
};
const rewriteTree = (root: ParentNode) => {
if (root instanceof Element) rewriteElement(root);
root.querySelectorAll('[src], [poster]').forEach(rewriteElement);
};
rewriteTree(document);
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type === 'attributes') rewriteElement(mutation.target as Element);
for (const node of mutation.addedNodes) {
if (node instanceof Element) rewriteTree(node);
}
}
});
observer.observe(document.documentElement, {
subtree: true,
childList: true,
attributes: true,
attributeFilter: ['src', 'poster'],
});
let closed = false;
return () => {
if (closed) return;
closed = true;
observer.disconnect();
if (window.fetch === wrappedFetch) window.fetch = nativeFetch;
};
}