@kevisual/router 0.0.41 → 0.0.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,70 @@
1
+ import * as http from 'http';
2
+
3
+ export type Listener = {
4
+ id?: string;
5
+ io?: boolean;
6
+ path?: string;
7
+ fun: (...args: any[]) => Promise<void> | void;
8
+ }
9
+ export type ListenerFun = (...args: any[]) => Promise<void> | void;
10
+ export type OnListener = Listener | Listener[] | ListenerFun | ListenerFun[];
11
+ export type Cors = {
12
+ /**
13
+ * @default '*''
14
+ */
15
+ origin?: string | undefined;
16
+ };
17
+
18
+ export type ServerOpts<T = {}> = {
19
+ /**path default `/api/router` */
20
+ path?: string;
21
+ /**handle Fn */
22
+ handle?: (msg?: { path: string; key?: string;[key: string]: any }, ctx?: { req: http.IncomingMessage; res: http.ServerResponse }) => any;
23
+ cors?: Cors;
24
+ io?: boolean;
25
+ } & T;
26
+
27
+ export interface ServerType {
28
+ path?: string;
29
+ server?: any;
30
+ handle: ServerOpts['handle'];
31
+ setHandle(handle?: any): void;
32
+ listeners: Listener[];
33
+ listen(port: number, hostname?: string, backlog?: number, listeningListener?: () => void): void;
34
+ listen(port: number, hostname?: string, listeningListener?: () => void): void;
35
+ listen(port: number, backlog?: number, listeningListener?: () => void): void;
36
+ listen(port: number, listeningListener?: () => void): void;
37
+ listen(path: string, backlog?: number, listeningListener?: () => void): void;
38
+ listen(path: string, listeningListener?: () => void): void;
39
+ listen(handle: any, backlog?: number, listeningListener?: () => void): void;
40
+ listen(handle: any, listeningListener?: () => void): void;
41
+ /**
42
+ * 兜底监听,当除开 `/api/router` 之外的请求,框架只监听一个api,所以有其他的请求都执行其他的监听
43
+ * @description 主要是为了兼容其他的监听
44
+ * @param listener
45
+ */
46
+ on(listener: OnListener): void;
47
+ onWebSocket({ ws, message, pathname, token, id }: { ws: WS; message: string | Buffer; pathname: string, token?: string, id?: string }): void;
48
+ }
49
+
50
+ type WS = {
51
+ send: (data: any) => void;
52
+ close: () => void;
53
+ }
54
+
55
+ export type CommonReq = {
56
+ url: string;
57
+ method: string;
58
+ headers: Record<string, string>;
59
+ [key: string]: any;
60
+ }
61
+
62
+ export type CommonRes = {
63
+ statusCode: number;
64
+ writableEnded: boolean;
65
+ writeHead: (statusCode: number, headers?: Record<string, string>) => void;
66
+ setHeader: (name: string, value: string | string[]) => void;
67
+ cookie: (name: string, value: string, options?: any) => void;
68
+ end: (data?: any) => void;
69
+ [key: string]: any;
70
+ }
@@ -1,64 +1,22 @@
1
- import type { IncomingMessage, ServerResponse } from 'node:http';
2
1
  import http from 'node:http';
3
2
  import https from 'node:https';
4
3
  import http2 from 'node:http2';
5
- import { handleServer } from './handle-server.ts';
6
- import * as cookie from './cookie.ts';
7
- export type Listener = (...args: any[]) => void;
4
+ import { isBun } from '../utils/is-engine.ts';
5
+ import { ServerType, Listener, ServerOpts } from './server-type.ts';
6
+ import { ServerBase } from './server-base.ts';
7
+ import { WsServer } from './ws-server.ts';
8
8
 
9
- type CookieFn = (name: string, value: string, options?: cookie.SerializeOptions, end?: boolean) => void;
10
-
11
- export type HandleCtx = {
12
- req: IncomingMessage & { cookies: Record<string, string> };
13
- res: ServerResponse & {
14
- /**
15
- * cookie 函数, end 参数用于设置是否立即设置到响应头,设置了后面的cookie再设置会覆盖前面的
16
- */
17
- cookie: CookieFn; //
18
- };
19
- };
20
- // 实现函数
21
- export function createHandleCtx(req: IncomingMessage, res: ServerResponse): HandleCtx {
22
- // 用于存储所有的 Set-Cookie 字符串
23
- const cookies: string[] = [];
24
- let handReq = req as HandleCtx['req'];
25
- let handRes = res as HandleCtx['res'];
26
- // 扩展 res.cookie 方法
27
- const cookieFn: CookieFn = (name, value, options = {}, end = true) => {
28
- // 序列化新的 Cookie
29
- const serializedCookie = cookie.serialize(name, value, options);
30
- cookies.push(serializedCookie); // 将新的 Cookie 添加到数组
31
- if (end) {
32
- // 如果设置了 end 参数,则立即设置到响应头
33
- res.setHeader('Set-Cookie', cookies);
34
- }
35
- };
36
- // 解析请求中的现有 Cookie
37
- const parsedCookies = cookie.parse(req.headers.cookie || '');
38
- handReq.cookies = parsedCookies;
39
- handRes.cookie = cookieFn;
40
- // 返回扩展的上下文
41
- return {
42
- req: handReq,
43
- res: handRes,
44
- };
45
- }
46
9
  export type Cors = {
47
10
  /**
48
11
  * @default '*''
49
12
  */
50
13
  origin?: string | undefined;
51
14
  };
52
- export type ServerOpts = {
53
- /**path default `/api/router` */
54
- path?: string;
55
- /**handle Fn */
56
- handle?: (msg?: { path: string; key?: string;[key: string]: any }, ctx?: { req: http.IncomingMessage; res: http.ServerResponse }) => any;
57
- cors?: Cors;
15
+ export type ServerNodeOpts = ServerOpts<{
58
16
  httpType?: 'http' | 'https' | 'http2';
59
17
  httpsKey?: string;
60
18
  httpsCert?: string;
61
- };
19
+ }>;
62
20
  export const resultError = (error: string, code = 500) => {
63
21
  const r = {
64
22
  code: code,
@@ -67,41 +25,39 @@ export const resultError = (error: string, code = 500) => {
67
25
  return JSON.stringify(r);
68
26
  };
69
27
 
70
- export class Server {
71
- path = '/api/router';
72
- private _server: http.Server | https.Server | http2.Http2SecureServer;
73
- public handle: ServerOpts['handle'];
74
- private _callback: any;
75
- private cors: Cors;
76
- private hasOn = false;
28
+ export class ServerNode extends ServerBase implements ServerType {
29
+ declare _server: http.Server | https.Server | http2.Http2SecureServer;
30
+ declare _callback: any;
31
+ declare cors: Cors;
77
32
  private httpType = 'http';
33
+ declare listeners: Listener[];
78
34
  private options = {
79
35
  key: '',
80
36
  cert: '',
81
37
  };
82
- constructor(opts?: ServerOpts) {
83
- this.path = opts?.path || '/api/router';
84
- this.handle = opts?.handle;
85
- this.cors = opts?.cors;
38
+ io: WsServer | undefined;
39
+ constructor(opts?: ServerNodeOpts) {
40
+ super(opts);
86
41
  this.httpType = opts?.httpType || 'http';
87
42
  this.options = {
88
43
  key: opts?.httpsKey || '',
89
44
  cert: opts?.httpsCert || '',
90
45
  };
46
+ const io = opts?.io ?? false;
47
+ if (io) {
48
+ this.io = new WsServer(this);
49
+ }
91
50
  }
92
- listen(port: number, hostname?: string, backlog?: number, listeningListener?: () => void): void;
93
- listen(port: number, hostname?: string, listeningListener?: () => void): void;
94
- listen(port: number, backlog?: number, listeningListener?: () => void): void;
95
- listen(port: number, listeningListener?: () => void): void;
96
- listen(path: string, backlog?: number, listeningListener?: () => void): void;
97
- listen(path: string, listeningListener?: () => void): void;
98
- listen(handle: any, backlog?: number, listeningListener?: () => void): void;
99
- listen(handle: any, listeningListener?: () => void): void;
100
- listen(...args: any[]) {
51
+ customListen(...args: any[]): void {
52
+ if (isBun) {
53
+ throw new Error('Use BunServer from server-bun module for Bun runtime');
54
+ }
101
55
  this._server = this.createServer();
102
56
  const callback = this.createCallback();
103
57
  this._server.on('request', callback);
104
58
  this._server.listen(...args);
59
+
60
+ this.io?.listen();
105
61
  }
106
62
  createServer() {
107
63
  let server: http.Server | https.Server | http2.Http2SecureServer;
@@ -132,112 +88,4 @@ export class Server {
132
88
  server = http.createServer();
133
89
  return server;
134
90
  }
135
- setHandle(handle?: any) {
136
- this.handle = handle;
137
- }
138
- /**
139
- * get callback
140
- * @returns
141
- */
142
- createCallback() {
143
- const path = this.path;
144
- const handle = this.handle;
145
- const cors = this.cors;
146
- const _callback = async (req: IncomingMessage, res: ServerResponse) => {
147
- // only handle /api/router
148
- if (req.url === '/favicon.ico') {
149
- return;
150
- }
151
- if (res.headersSent) {
152
- // 程序已经在其他地方响应了
153
- return;
154
- }
155
- if (this.hasOn && !req.url.startsWith(path)) {
156
- // 其他监听存在,不判断不是当前路径的请求,
157
- // 也就是不处理!url.startsWith(path)这个请求了
158
- // 交给其他监听处理
159
- return;
160
- }
161
- if (cors) {
162
- res.setHeader('Access-Control-Allow-Origin', cors?.origin || '*'); // 允许所有域名的请求访问,可以根据需要设置具体的域名
163
- res.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
164
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST');
165
- if (req.method === 'OPTIONS') {
166
- res.end();
167
- return;
168
- }
169
- }
170
- const url = req.url;
171
- if (!url.startsWith(path)) {
172
- res.end(resultError(`not path:[${path}]`));
173
- return;
174
- }
175
- const messages = await handleServer(req, res);
176
- if (!handle) {
177
- res.end(resultError('no handle'));
178
- return;
179
- }
180
- try {
181
- const end = await handle(messages as any, { req, res });
182
- if (res.writableEnded) {
183
- // 如果响应已经结束,则不进行任何操作
184
- return;
185
- }
186
- res.setHeader('Content-Type', 'application/json; charset=utf-8');
187
- if (typeof end === 'string') {
188
- res.end(end);
189
- } else {
190
- res.end(JSON.stringify(end));
191
- }
192
- } catch (e) {
193
- console.error(e);
194
- res.setHeader('Content-Type', 'application/json; charset=utf-8');
195
- if (e.code && typeof e.code === 'number') {
196
- res.end(resultError(e.message || `Router Server error`, e.code));
197
- } else {
198
- res.end(resultError('Router Server error'));
199
- }
200
- }
201
- };
202
- this._callback = _callback;
203
- return _callback;
204
- }
205
- get handleServer() {
206
- return this._callback;
207
- }
208
- set handleServer(fn: any) {
209
- this._callback = fn;
210
- }
211
- /**
212
- * 兜底监听,当除开 `/api/router` 之外的请求,框架只监听一个api,所以有其他的请求都执行其他的监听
213
- * @description 主要是为了兼容其他的监听
214
- * @param listener
215
- */
216
- on(listener: Listener | Listener[]) {
217
- this._server = this._server || this.createServer();
218
- this._server.removeAllListeners('request');
219
- this.hasOn = true;
220
- if (Array.isArray(listener)) {
221
- listener.forEach((l) => this._server.on('request', l));
222
- } else {
223
- this._server.on('request', listener);
224
- }
225
- const callbackListener = this._callback || this.createCallback();
226
- this._server.on('request', callbackListener);
227
- return () => {
228
- if (Array.isArray(listener)) {
229
- listener.forEach((l) => this._server.removeListener('request', l as Listener));
230
- } else {
231
- this._server.removeListener('request', listener as Listener);
232
- }
233
- this.hasOn = false;
234
- this._server.removeListener('request', callbackListener);
235
- }
236
- }
237
- get callback() {
238
- return this._callback || this.createCallback();
239
- }
240
- get server() {
241
- return this._server;
242
- }
243
- }
91
+ }
@@ -1,39 +1,38 @@
1
1
  // @ts-type=ws
2
2
  import { WebSocketServer } from 'ws';
3
3
  import type { WebSocket } from 'ws';
4
- import { Server } from './server.ts';
4
+ import { ServerType } from './server-type.ts'
5
5
  import { parseIfJson } from '../utils/parse.ts';
6
+ import { isBun } from '../utils/is-engine.ts';
6
7
 
7
8
 
8
- export const createWsServer = (server: Server) => {
9
+ export const createWsServer = (server: ServerType) => {
9
10
  // 将 WebSocket 服务器附加到 HTTP 服务器
10
11
  const wss = new WebSocketServer({ server: server.server as any });
11
12
  return wss;
12
13
  };
13
14
  type WsServerBaseOpts = {
14
- wss?: WebSocketServer;
15
+ wss?: WebSocketServer | null;
15
16
  path?: string;
16
17
  };
17
18
  export type ListenerFn = (message: { data: Record<string, any>; ws: WebSocket; end: (data: any) => any }) => Promise<any>;
18
19
  export type Listener<T = 'router' | 'chat' | 'ai'> = {
19
20
  type: T;
21
+ path?: string;
20
22
  listener: ListenerFn;
21
23
  };
22
24
 
23
25
  export class WsServerBase {
24
- wss: WebSocketServer;
25
- path: string;
26
- listeners: { type: string; listener: ListenerFn }[] = [];
26
+ wss: WebSocketServer | null;
27
+ listeners: Listener[] = [];
27
28
  listening: boolean = false;
29
+ server: ServerType;
30
+
28
31
  constructor(opts: WsServerBaseOpts) {
29
32
  this.wss = opts.wss;
30
- if (!this.wss) {
33
+ if (!this.wss && !isBun) {
31
34
  throw new Error('wss is required');
32
35
  }
33
- this.path = opts.path || '';
34
- }
35
- setPath(path: string) {
36
- this.path = path;
37
36
  }
38
37
  listen() {
39
38
  if (this.listening) {
@@ -42,116 +41,49 @@ export class WsServerBase {
42
41
  }
43
42
  this.listening = true;
44
43
 
45
- this.wss.on('connection', (ws) => {
46
- ws.on('message', async (message: string | Buffer) => {
47
- const data = parseIfJson(message);
48
- if (typeof data === 'string') {
49
- const cleanMessage = data.trim().replace(/^["']|["']$/g, '');
50
- ws.emit('string', cleanMessage);
51
- return;
52
- }
53
- const { type, data: typeData, ...rest } = data;
54
- if (!type) {
55
- ws.send(JSON.stringify({ code: 500, message: 'type is required' }));
56
- }
57
- const listeners = this.listeners.find((item) => item.type === type);
58
- const res = {
59
- type,
60
- data: {} as any,
61
- ...rest,
62
- };
63
- const end = (data: any, all?: Record<string, any>) => {
64
- const result = {
65
- ...res,
66
- data,
67
- ...all,
68
- };
69
- ws.send(JSON.stringify(result));
70
- };
44
+ if (!this.wss) {
45
+ // Bun 环境下,wss 可能为 null
46
+ return;
47
+ }
71
48
 
72
- if (!listeners) {
73
- const data = { code: 500, message: `${type} server is error` };
74
- end(data);
75
- return;
76
- }
77
- listeners.listener({
78
- data: typeData,
79
- ws,
80
- end: end,
81
- });
82
- });
83
- ws.on('string', (message: string) => {
84
- if (message === 'close') {
85
- ws.close();
86
- }
87
- if (message == 'ping') {
88
- ws.send('pong');
89
- }
49
+ this.wss.on('connection', (ws, req) => {
50
+ const url = new URL(req.url, 'http://localhost');
51
+ const pathname = url.pathname;
52
+ const token = url.searchParams.get('token') || '';
53
+ const id = url.searchParams.get('id') || '';
54
+ ws.on('message', async (message: string | Buffer) => {
55
+ await this.server.onWebSocket({ ws, message, pathname, token, id });
90
56
  });
91
57
  ws.send('connected');
92
58
  });
93
59
  }
94
- addListener(type: string, listener: ListenerFn) {
95
- if (!type || !listener) {
96
- throw new Error('type and listener is required');
97
- }
98
- const find = this.listeners.find((item) => item.type === type);
99
- if (find) {
100
- this.listeners = this.listeners.filter((item) => item.type !== type);
101
- }
102
- this.listeners.push({ type, listener });
103
- }
104
- removeListener(type: string) {
105
- this.listeners = this.listeners.filter((item) => item.type !== type);
106
- }
107
60
  }
108
61
  // TODO: ws handle and path and routerContext
109
62
  export class WsServer extends WsServerBase {
110
- server: Server;
111
- constructor(server: Server, opts?: any) {
112
- const wss = new WebSocketServer({ noServer: true });
113
- const path = server.path;
63
+ constructor(server: ServerType) {
64
+ const wss = isBun ? null : new WebSocketServer({ noServer: true });
114
65
  super({ wss });
115
66
  this.server = server;
116
- this.setPath(opts?.path || path);
117
- this.initListener();
118
- }
119
- initListener() {
120
- const server = this.server;
121
- const listener: Listener = {
122
- type: 'router',
123
- listener: async ({ data, ws, end }) => {
124
- if (!server) {
125
- end({ code: 500, message: 'server handle is error' });
126
- return;
127
- }
128
- const handle = this.server.handle;
129
- try {
130
- const result = await handle(data as any);
131
- end(result);
132
- } catch (e) {
133
- if (e.code && typeof e.code === 'number') {
134
- end({
135
- code: e.code,
136
- message: e.message,
137
- });
138
- } else {
139
- end({ code: 500, message: 'Router Server error' });
140
- }
141
- }
142
- },
143
- };
144
- this.addListener(listener.type, listener.listener);
145
67
  }
146
68
  listen() {
69
+ if (isBun) {
70
+ // Bun 的 WebSocket 在 Bun.serve 中处理,这里不需要额外操作
71
+ // WebSocket 升级会在 listenWithBun 中处理
72
+ this.listening = true;
73
+ return;
74
+ }
147
75
  super.listen();
148
76
  const server = this.server;
149
77
  const wss = this.wss;
78
+
150
79
  // HTTP 服务器的 upgrade 事件
80
+ // @ts-ignore
151
81
  server.server.on('upgrade', (req, socket, head) => {
152
- if (req.url === this.path) {
82
+ const url = new URL(req.url, 'http://localhost');
83
+ const listenPath = this.server.listeners.map((item) => item.path).filter((item) => item);
84
+ if (listenPath.includes(url.pathname) || url.pathname === this.server.path) {
153
85
  wss.handleUpgrade(req, socket, head, (ws) => {
154
- // 这里手动触发 connection 事件
86
+ // 这里手动触发 connection事件
155
87
  // @ts-ignore
156
88
  wss.emit('connection', ws, req);
157
89
  });
@@ -1,7 +1,7 @@
1
1
  // import { Server } from 'node:http';
2
- import { Server } from '../server/server.ts'
2
+ import { ServerNode } from '../server/server.ts'
3
3
 
4
- const server = new Server({
4
+ const server = new ServerNode({
5
5
  path: '/',
6
6
  handle: async (data, ctx) => {
7
7
  console.log('ctx', ctx.req.url)
@@ -2,9 +2,13 @@ export const isNode = typeof process !== 'undefined' && process.versions != null
2
2
  export const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined' && typeof document.createElement === 'function';
3
3
  // @ts-ignore
4
4
  export const isDeno = typeof Deno !== 'undefined' && typeof Deno.version === 'object' && typeof Deno.version.deno === 'string';
5
+ // @ts-ignore
6
+ export const isBun = typeof Bun !== 'undefined' && typeof Bun.version === 'string';
5
7
 
6
8
  export const getEngine = () => {
7
- if (isNode) {
9
+ if (isBun) {
10
+ return 'bun';
11
+ } else if (isNode) {
8
12
  return 'node';
9
13
  } else if (isBrowser) {
10
14
  return 'browser';