// Local dev ModuleContext factory (standalone vite, no Host) import type { AppConfig, HttpClient, HttpOptions, ModuleContext, ModuleGlobalState, ModuleStorage, ModuleUser, } from '../module-context.js'; import { executeHttpRequest } from '../http-client.js'; import { joinModuleApiPath, resolveModuleApiBaseUrl } from '../module-api-path.js'; import { encodeModuleRequestUser, MODULE_USER_HEADER, type ModuleRequestUser, } from '../platform/module-user.js'; export interface CreateDevModuleContextOptions { /** Module name for storage namespace */ moduleName?: string; /** Override dev user */ user?: Partial; /** Current path within module (sync with react-router) */ currentPath?: string; /** Navigate callback (e.g. react-router navigate) */ navigate?: (path: string) => void; /** Platform config overrides */ config?: Partial; } function toModuleRequestUser(user: ModuleUser): ModuleRequestUser { return { id: user.id, phone: user.phone, name: user.name, roles: [user.role], }; } const devGlobalStore = new Map< string, { value: unknown; subscribers: Set<(val: unknown) => void> } >(); const devGlobalState: ModuleGlobalState = { get: (key: string): T | undefined => { const entry = devGlobalStore.get(key); return entry?.value as T | undefined; }, set: (key: string, value: T): void => { const entry = devGlobalStore.get(key) || { value: undefined, subscribers: new Set() }; entry.value = value; devGlobalStore.set(key, entry); entry.subscribers.forEach(cb => cb(value)); }, subscribe: (key: string, callback: (value: unknown) => void): (() => void) => { const entry = devGlobalStore.get(key) || { value: undefined, subscribers: new Set() }; entry.subscribers.add(callback); devGlobalStore.set(key, entry); return () => { entry.subscribers.delete(callback); }; }, clear: (): void => { devGlobalStore.clear(); }, }; function createDevHttpClient(apiBaseUrl: string, user: ModuleUser): HttpClient { const resolveUrl = (url: string) => url.startsWith('http://') || url.startsWith('https://') ? url : joinModuleApiPath(apiBaseUrl, url); const encodedUser = encodeModuleRequestUser(toModuleRequestUser(user)); const request = async ( method: string, url: string, body?: unknown, options?: HttpOptions, ): Promise => executeHttpRequest(resolveUrl(url), { method, body, options, // 模拟云台网关:本地无 JWT 校验,把 Context 用户编进内部协议头 headers: options?.skipAuth ? undefined : { [MODULE_USER_HEADER]: encodedUser }, }); return { get: (url: string, options?: HttpOptions) => request('GET', url, undefined, options), post: (url: string, body?: unknown, options?: HttpOptions) => request('POST', url, body, options), put: (url: string, body?: unknown, options?: HttpOptions) => request('PUT', url, body, options), patch: (url: string, body?: unknown, options?: HttpOptions) => request('PATCH', url, body, options), delete: (url: string, options?: HttpOptions) => request('DELETE', url, undefined, options), }; } function createDevModuleStorage(moduleName: string): ModuleStorage { const prefix = `stago:dev:${moduleName}:`; return { get: (key: string): T | null => { if (typeof localStorage === 'undefined') return null; const stored = localStorage.getItem(prefix + key); if (!stored) return null; try { return JSON.parse(stored) as T; } catch { return stored as unknown as T; } }, set: (key: string, value: T): void => { if (typeof localStorage === 'undefined') return; localStorage.setItem(prefix + key, JSON.stringify(value)); }, remove: (key: string): void => { if (typeof localStorage === 'undefined') return; localStorage.removeItem(prefix + key); }, clear: (): void => { if (typeof localStorage === 'undefined') return; const keysToRemove: string[] = []; for (let i = 0; i < localStorage.length; i++) { const k = localStorage.key(i); if (k?.startsWith(prefix)) keysToRemove.push(k); } keysToRemove.forEach(k => localStorage.removeItem(k)); }, }; } let devThemeMode: 'light' | 'dark' = 'light'; /** * Build a ModuleContext for local `vite` development (no Stago Host). */ export function createDevModuleContext(options: CreateDevModuleContextOptions = {}): ModuleContext { const moduleName = options.moduleName ?? 'dev-module'; const currentPath = options.currentPath ?? '/'; const navigate = options.navigate ?? ((path: string) => { if (typeof window !== 'undefined') { window.history.pushState(null, '', path); } }); const user: ModuleUser = { id: 1, phone: 'dev-user', name: '开发用户', role: 'admin', permissions: ['*'], ...options.user, }; const queryIndex = currentPath.indexOf('?'); const pathname = queryIndex > -1 ? currentPath.slice(0, queryIndex) : currentPath; const query = queryIndex > -1 ? new URLSearchParams(currentPath.slice(queryIndex + 1)) : new URLSearchParams(); const apiBaseUrl = resolveModuleApiBaseUrl(options.config?.apiBaseUrl); return { user, theme: { mode: devThemeMode, primaryColor: '#1677ff', toggle: () => { devThemeMode = devThemeMode === 'light' ? 'dark' : 'light'; }, }, router: { currentPath: pathname, params: {}, query, navigate, }, notifications: { success: (message: string, description?: string) => { console.log(`[dev][success] ${message}`, description ?? ''); }, error: (message: string, description?: string) => { console.error(`[dev][error] ${message}`, description ?? ''); }, warning: (message: string, description?: string) => { console.warn(`[dev][warning] ${message}`, description ?? ''); }, info: (message: string, description?: string) => { console.info(`[dev][info] ${message}`, description ?? ''); }, }, globalState: devGlobalState, platform: { http: createDevHttpClient(apiBaseUrl, user), storage: createDevModuleStorage(moduleName), config: { moduleBaseUrl: '', version: '1.0.0', environment: 'development', ...options.config, apiBaseUrl, }, }, }; }