feat: productionize learning engine and classroom
This commit is contained in:
100
OpenMAIC/lib/ops/api-fetch.ts
Normal file
100
OpenMAIC/lib/ops/api-fetch.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
'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;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user