import { Query, Result } from '@kevisual/query/query'; import { QueryRouterServer, Route } from '@kevisual/router/src/route.ts'; import { filter } from '@kevisual/js-filter' import { EventEmitter } from 'eventemitter3'; export type RouterViewItem = RouterViewApi | RouterViewContext | RouterViewWorker; type RouteViewBase = { id: string; title: string; description: string; enabled?: boolean; } export type RouterViewApi = { type: 'api', api: { url: string, // 已初始化的query实例,不需要编辑配置 query?: Query } } & RouteViewBase; export type RouterViewContext = { type: 'context', context: { key: string, // 从context中获取router,不需要编辑配置 router?: QueryRouterServer } } & RouteViewBase; export type RouterViewWorker = { type: 'worker', worker: { type: 'Worker' | 'SharedWorker' | 'serviceWorker', url: string, // 已初始化的worker实例,不需要编辑配置 worker?: Worker | SharedWorker | ServiceWorker, /** * worker选项 * default: { type: 'module' } */ workerOptions?: { type: 'module' | 'classic' } } } & RouteViewBase; export type RouterViewQuery = { id: string, query: string, title: string } export type RouterViewData = { data: { items: RouterViewItem[]; } views: RouterViewQuery[]; viewId?: string; [key: string]: any; } export class QueryProxy { router: QueryRouterServer; token?: string; routerViewItems: RouterViewItem[]; views: RouterViewQuery[]; emitter: EventEmitter; constructor(opts?: { router?: QueryRouterServer, token?: string, routerViewData?: RouterViewData }) { this.router = opts?.router || new QueryRouterServer(); this.token = opts?.token || this.getDefulatToken(); this.routerViewItems = opts?.routerViewData?.data?.items || []; this.views = opts?.routerViewData?.views || []; this.initRouterViewQuery(); this.emitter = new EventEmitter(); } getDefulatToken() { try { if (typeof window !== 'undefined' && typeof window.localStorage !== 'undefined') { return localStorage.getItem('token') || undefined; } } catch (e) { return undefined; } } async initRouterViewQuery() { this.routerViewItems = this.routerViewItems.map(item => { if (item.type === 'api' && item.api?.url) { const url = item.api.url; if (item?.api?.query) return item; item['api'] = { url: url, query: new Query({ url: url }) }; } if (item.type === 'worker' && item.worker?.url) { let viewItem = item as RouterViewWorker; if (!item.worker?.workerOptions?.type) { item.worker.workerOptions = { ...item.worker.workerOptions, type: 'module' }; } if (item.worker.worker) { return item; } let worker: Worker | SharedWorker | ServiceWorker | undefined = undefined; if (item.worker.type === 'SharedWorker') { worker = new SharedWorker(item.worker.url, item.worker.workerOptions); worker.port.start(); } else if (viewItem.worker.type === 'serviceWorker') { if ('serviceWorker' in navigator) { navigator.serviceWorker.register(viewItem.worker.url, item.worker.workerOptions).then(function (registration) { console.debug('注册serviceWorker成功 ', registration.scope); }, function (err) { console.debug('注册 serviceWorker 失败: ', err); }); } else { console.warn('当前浏览器不支持serviceWorker'); } } else { worker = new Worker(viewItem.worker.url, item.worker.workerOptions); } viewItem['worker']['worker'] = worker; } if (item.type === 'context' && item.context?.key) { if (item.context?.router) { return item; } // @ts-ignore const context = globalThis['context'] || {} const router = context[item.context.key] as QueryRouterServer; if (router) { item['context']['router'] = router; } } return item; }).filter(item => { const enabled = item.enabled ?? true; return enabled; }); } /** * 初始化路由 * @returns */ async init() { const routerViewItems = this.routerViewItems || []; if (routerViewItems.length === 0) { await this.initApi(); return; } for (const item of routerViewItems) { switch (item.type) { case 'api': this.initApi(item); break; case 'context': break; case 'worker': this.initWorker(item); break; } } } async initApi(item?: RouterViewApi) { const that = this; const query = item?.api?.query || new Query({ url: item?.api?.url || '/api/router' }) const res = await query.post<{ list: RouterItem[] }>({ path: "router", key: 'list', token: this.token }); if (res.code !== 200) { console.error('Failed to init query proxy router:', res.message); return } const _list = res.data?.list || [] for (const r of _list) { if (r.path || r.id) { console.debug(`注册路由: [${r.path}] ${r?.key}`, 'API'); let metadata = r.metadata || {}; metadata.viewItem = item; this.router.route({ path: r.path, key: r.key || '', id: r.id, description: r.description, metadata: metadata, }).define(async (ctx) => { const msg = { ...ctx.query }; if (msg.token === undefined && that.token !== undefined) { msg.token = that.token; } const res = await query.post({ path: r.path, key: r.key, ...msg }); ctx.forward(res) }).addTo(that.router); } } } async initContext(item?: RouterViewContext) { // @ts-ignore const context = globalThis['context'] || {} const router = item?.context?.router || context[item?.context?.key] as QueryRouterServer; if (!router) { console.warn(`未发现Context router ${item?.context?.key}`); return } const routes = router.getList(); for (const r of routes) { console.debug(`注册路由: [${r.path}] ${r?.key}`, 'Context'); let metadata = r.metadata || {}; metadata.viewItem = item; this.router.route({ path: r.path, key: r.key || '', id: r.id, description: r.description, metadata: metadata, }).define(async (ctx) => { const res = await router.run({ path: r.path, key: r.key, ...ctx.query }); ctx.forward(res) }).addTo(this.router); } } generateId() { return 'route_' + Math.random().toString(36).substring(2, 9); } async initWorker(item?: RouterViewWorker) { const that = this; if (!item?.worker?.url) { console.warn('Worker URL not provided'); return; } const viewItem = item.worker; const worker = viewItem?.worker; if (!worker) { console.warn('Worker not initialized'); return; } const callResponse = (e: MessageEvent) => { const msg = e.data; if (msg.requestId) { const requestId = msg.requestId; that.emitter.emit(requestId, msg); } else { that.router.run(msg); } } if (item.worker.type === 'SharedWorker') { const port = (worker as SharedWorker).port; port.onmessage = callResponse; port.start(); } else if (item.worker.type === 'serviceWorker') { navigator.serviceWorker.addEventListener('message', callResponse); } else { (worker as Worker).onmessage = callResponse; } const callWorker = async (msg: any, viewItem: RouterViewWorker['worker']): Promise => { const requestId = this.generateId(); const worker = viewItem?.worker; if (!worker) { return { code: 500, message: 'Worker未初始化' }; } let port: MessagePort | Worker | ServiceWorker; if (viewItem.type === 'SharedWorker') { port = (worker as SharedWorker).port; } else { port = worker as Worker | ServiceWorker; } port.postMessage({ ...msg, requestId: requestId, }) return new Promise((resolve) => { const timer = setTimeout(() => { that.emitter.removeAllListeners(requestId); resolve({ code: 500, message: '请求超时' }); }, 3 * 60 * 1000); // 3分钟超时 that.emitter.once(requestId, (res: any) => { clearTimeout(timer); resolve(res); }); }); } const res = await callWorker({ path: "router", key: 'list', token: this.token, }, viewItem); if (res.code !== 200) { console.error('Failed to init query proxy router:', res.message); return; } const _list = res.data?.list || [] for (const r of _list) { if (r.path || r.id) { console.debug(`注册路由: [${r.path}] ${r?.key}`, 'API'); let metadata = r.metadata || {}; metadata.viewItem = item; this.router.route({ path: r.path, key: r.key || '', id: r.id, description: r.description, metadata: metadata, }).define(async (ctx) => { const msg = { ...ctx.query }; if (msg.token === undefined && that.token !== undefined) { msg.token = that.token; } const res = await callWorker({ path: r.path, key: r.key, ...msg }, viewItem); ctx.forward(res) }).addTo(that.router); } } } /** * 列出路由 * @param filter * @param query WHERE metadata.tags CONTAINS 'premium' * @returns */ async listRoutes(filterFn?: (item: Route) => boolean, opts?: { viewId?: string, query?: string }) { let query = opts?.query; if (opts?.viewId) { const view = this.views.find(v => v.id === opts.viewId); if (view) { query = view.query; } } if (opts?.query) { query = opts.query; } const routes = this.router.routes.filter(filterFn || (() => true)); if (query) { return filter(routes, query); } return routes; } async getViewQuery(viewId: string) { const view = this.views.find(v => v.id === viewId); if (view) { return view.query; } return undefined; } /** * 运行路由 * @param msg * @returns */ async run(msg: { id?: string, path?: string, key?: string }) { return await this.router.run(msg); } } type RouterItem = { id?: string; path?: string; key?: string; description?: string; middleware?: string[]; metadata?: Record; }