Files
query-awesome/query/query-proxy/index.ts

346 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<any>({ 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<Result> => {
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;
}
/**
* 运行路由
* @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<string, any>;
}