@kevisual/router 0.0.26-alpha.5 → 0.0.27
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.
- package/dist/router-browser.d.ts +6 -7
- package/dist/router-browser.js +1381 -113
- package/dist/router-sign.d.ts +16 -0
- package/dist/router-sign.js +28698 -0
- package/dist/router-simple-lib.d.ts +3 -0
- package/dist/router-simple-lib.js +35 -0
- package/dist/router-simple.js +153 -147
- package/dist/router.d.ts +38 -7
- package/dist/router.js +1556 -222
- package/package.json +27 -24
- package/src/app.ts +1 -1
- package/src/connect.ts +67 -0
- package/src/index.ts +3 -3
- package/src/io.ts +6 -0
- package/src/route.ts +10 -5
- package/src/server/ws-server.ts +4 -3
- package/src/sign.ts +1 -1
- package/src/test/ws.ts +25 -0
- package/src/utils/parse.ts +4 -3
- package/src/validator/index.ts +1 -5
- package/src/validator/rule.ts +3 -2
- package/auto.ts +0 -20
- package/src/auto/call-sock.ts +0 -164
- package/src/auto/listen/cleanup.ts +0 -102
- package/src/auto/listen/run-check.ts +0 -50
- package/src/auto/listen/server-time.ts +0 -33
- package/src/auto/listen-sock.ts +0 -264
- package/src/auto/load-ts.ts +0 -38
- package/src/auto/runtime.ts +0 -19
- package/src/auto/utils/glob.ts +0 -83
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package",
|
|
3
3
|
"name": "@kevisual/router",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.27",
|
|
5
5
|
"description": "",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/router.js",
|
|
@@ -10,15 +10,12 @@
|
|
|
10
10
|
"build": "npm run clean && rollup -c",
|
|
11
11
|
"build:app": "npm run build && rsync dist/*browser* ../deploy/dist",
|
|
12
12
|
"watch": "rollup -c -w",
|
|
13
|
-
"clean": "rm -rf dist"
|
|
14
|
-
"auto:bun": "bun test/auto/app.ts",
|
|
15
|
-
"auto:deno": "bun test/auto/app.ts"
|
|
13
|
+
"clean": "rm -rf dist"
|
|
16
14
|
},
|
|
17
15
|
"files": [
|
|
18
16
|
"dist",
|
|
19
17
|
"src",
|
|
20
|
-
"mod.ts"
|
|
21
|
-
"auto.ts"
|
|
18
|
+
"mod.ts"
|
|
22
19
|
],
|
|
23
20
|
"keywords": [],
|
|
24
21
|
"author": "abearxiong",
|
|
@@ -26,33 +23,34 @@
|
|
|
26
23
|
"devDependencies": {
|
|
27
24
|
"@kevisual/local-proxy": "^0.0.6",
|
|
28
25
|
"@kevisual/query": "^0.0.29",
|
|
29
|
-
"@kevisual/use-config": "^1.0.19",
|
|
30
26
|
"@rollup/plugin-alias": "^5.1.1",
|
|
31
27
|
"@rollup/plugin-commonjs": "^28.0.6",
|
|
32
|
-
"@rollup/plugin-node-resolve": "^16.0.
|
|
28
|
+
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
33
29
|
"@rollup/plugin-typescript": "^12.1.4",
|
|
34
|
-
"@types/
|
|
35
|
-
"@types/node": "^24.2
|
|
36
|
-
"@types/send": "^
|
|
30
|
+
"@types/lodash-es": "^4.17.12",
|
|
31
|
+
"@types/node": "^24.7.2",
|
|
32
|
+
"@types/send": "^1.2.0",
|
|
37
33
|
"@types/ws": "^8.18.1",
|
|
38
34
|
"@types/xml2js": "^0.4.14",
|
|
39
35
|
"cookie": "^1.0.2",
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"rollup": "^
|
|
44
|
-
"
|
|
45
|
-
"
|
|
36
|
+
"lodash-es": "^4.17.21",
|
|
37
|
+
"nanoid": "^5.1.6",
|
|
38
|
+
"rollup": "^4.52.4",
|
|
39
|
+
"rollup-plugin-dts": "^6.2.3",
|
|
40
|
+
"ts-loader": "^9.5.4",
|
|
41
|
+
"ts-node": "^10.9.2",
|
|
42
|
+
"tslib": "^2.8.1",
|
|
43
|
+
"typescript": "^5.9.3",
|
|
46
44
|
"ws": "npm:@kevisual/ws",
|
|
47
45
|
"xml2js": "^0.6.2",
|
|
48
|
-
"zod": "^4.
|
|
46
|
+
"zod": "^4.1.12"
|
|
49
47
|
},
|
|
50
48
|
"repository": {
|
|
51
49
|
"type": "git",
|
|
52
50
|
"url": "git+https://github.com/abearxiong/kevisual-router.git"
|
|
53
51
|
},
|
|
54
52
|
"dependencies": {
|
|
55
|
-
"path-to-regexp": "^8.
|
|
53
|
+
"path-to-regexp": "^8.3.0",
|
|
56
54
|
"selfsigned": "^3.0.1",
|
|
57
55
|
"send": "^1.2.0"
|
|
58
56
|
},
|
|
@@ -70,6 +68,11 @@
|
|
|
70
68
|
"require": "./dist/router-browser.js",
|
|
71
69
|
"types": "./dist/router-browser.d.ts"
|
|
72
70
|
},
|
|
71
|
+
"./sign": {
|
|
72
|
+
"import": "./dist/router-sign.js",
|
|
73
|
+
"require": "./dist/router-sign.js",
|
|
74
|
+
"types": "./dist/router-sign.d.ts"
|
|
75
|
+
},
|
|
73
76
|
"./simple": {
|
|
74
77
|
"import": "./dist/router-simple.js",
|
|
75
78
|
"require": "./dist/router-simple.js",
|
|
@@ -80,16 +83,16 @@
|
|
|
80
83
|
"require": "./dist/router-define.js",
|
|
81
84
|
"types": "./dist/router-define.d.ts"
|
|
82
85
|
},
|
|
86
|
+
"./simple-lib": {
|
|
87
|
+
"import": "./dist/router-simple-lib.js",
|
|
88
|
+
"require": "./dist/router-simple-lib.js",
|
|
89
|
+
"types": "./dist/router-simple-lib.d.ts"
|
|
90
|
+
},
|
|
83
91
|
"./mod.ts": {
|
|
84
92
|
"import": "./mod.ts",
|
|
85
93
|
"require": "./mod.ts",
|
|
86
94
|
"types": "./mod.d.ts"
|
|
87
95
|
},
|
|
88
|
-
"./auto.ts": {
|
|
89
|
-
"import": "./auto.ts",
|
|
90
|
-
"require": "./auto.ts",
|
|
91
|
-
"types": "./auto.ts"
|
|
92
|
-
},
|
|
93
96
|
"./src/*": {
|
|
94
97
|
"import": "./src/*",
|
|
95
98
|
"require": "./src/*"
|
package/src/app.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { Server, ServerOpts, HandleCtx } from './server/server.ts';
|
|
|
3
3
|
import { WsServer } from './server/ws-server.ts';
|
|
4
4
|
import { CustomError } from './result/error.ts';
|
|
5
5
|
import { handleServer } from './server/handle-server.ts';
|
|
6
|
-
import { IncomingMessage, ServerResponse } from '
|
|
6
|
+
import { IncomingMessage, ServerResponse } from 'http';
|
|
7
7
|
|
|
8
8
|
type RouterHandle = (msg: { path: string; [key: string]: any }) => { code: string; data?: any; message?: string; [key: string]: any };
|
|
9
9
|
type AppOptions<T = {}> = {
|
package/src/connect.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { nanoid } from 'nanoid';
|
|
2
|
+
import { RouteContext } from './route.ts';
|
|
3
|
+
|
|
4
|
+
export class Connect {
|
|
5
|
+
path: string;
|
|
6
|
+
key?: string;
|
|
7
|
+
_fn?: (ctx?: RouteContext) => Promise<RouteContext>;
|
|
8
|
+
description?: string;
|
|
9
|
+
connects: { path: string; key?: string }[];
|
|
10
|
+
share = false;
|
|
11
|
+
|
|
12
|
+
constructor(path: string) {
|
|
13
|
+
this.path = path;
|
|
14
|
+
this.key = nanoid();
|
|
15
|
+
}
|
|
16
|
+
use(path: string) {
|
|
17
|
+
this.connects.push({ path });
|
|
18
|
+
}
|
|
19
|
+
useList(paths: string[]) {
|
|
20
|
+
paths.forEach((path) => {
|
|
21
|
+
this.connects.push({ path });
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
useConnect(connect: Connect) {
|
|
25
|
+
this.connects.push({ path: connect.path, key: connect.key });
|
|
26
|
+
}
|
|
27
|
+
useConnectList(connects: Connect[]) {
|
|
28
|
+
connects.forEach((connect) => {
|
|
29
|
+
this.connects.push({ path: connect.path, key: connect.key });
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
getPathList() {
|
|
33
|
+
return this.connects.map((c) => c.path).filter(Boolean);
|
|
34
|
+
}
|
|
35
|
+
set fn(fn: (ctx?: RouteContext) => Promise<RouteContext>) {
|
|
36
|
+
this._fn = fn;
|
|
37
|
+
}
|
|
38
|
+
get fn() {
|
|
39
|
+
return this._fn;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export class QueryConnect {
|
|
43
|
+
connects: Connect[];
|
|
44
|
+
constructor() {
|
|
45
|
+
this.connects = [];
|
|
46
|
+
}
|
|
47
|
+
add(connect: Connect) {
|
|
48
|
+
const has = this.connects.find((c) => c.path === connect.path && c.key === connect.key);
|
|
49
|
+
if (has) {
|
|
50
|
+
// remove the old connect
|
|
51
|
+
console.log('[replace connect]:', connect.path, connect.key);
|
|
52
|
+
this.connects = this.connects.filter((c) => c.path !== connect.path && c.key !== connect.key);
|
|
53
|
+
}
|
|
54
|
+
this.connects.push(connect);
|
|
55
|
+
}
|
|
56
|
+
remove(connect: Connect) {
|
|
57
|
+
this.connects = this.connects.filter((c) => c.path !== connect.path && c.key !== connect.key);
|
|
58
|
+
}
|
|
59
|
+
getList() {
|
|
60
|
+
return this.connects.map((c) => {
|
|
61
|
+
return {
|
|
62
|
+
path: c.path,
|
|
63
|
+
key: c.key,
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { Route, QueryRouter, QueryRouterServer } from './route.ts';
|
|
2
|
+
export { Connect, QueryConnect } from './connect.ts';
|
|
2
3
|
|
|
3
4
|
export type { RouteContext, RouteOpts, RouteMiddleware } from './route.ts';
|
|
4
5
|
|
|
@@ -10,9 +11,8 @@ export { Server, handleServer } from './server/index.ts';
|
|
|
10
11
|
*/
|
|
11
12
|
export { CustomError } from './result/error.ts';
|
|
12
13
|
|
|
13
|
-
export { createSchema } from './validator/index.ts';
|
|
14
|
-
|
|
15
|
-
export type { Schema } from 'zod';
|
|
14
|
+
export { Rule, Schema, createSchema } from './validator/index.ts';
|
|
15
|
+
|
|
16
16
|
export { App } from './app.ts';
|
|
17
17
|
|
|
18
18
|
export * from './router-define.ts';
|
package/src/io.ts
ADDED
package/src/route.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { nanoid } from 'nanoid';
|
|
|
2
2
|
import { CustomError } from './result/error.ts';
|
|
3
3
|
import { Schema, Rule, createSchema } from './validator/index.ts';
|
|
4
4
|
import { pick } from './utils/pick.ts';
|
|
5
|
+
import { get } from 'lodash-es';
|
|
5
6
|
|
|
6
7
|
export type RouterContextT = { code?: number; [key: string]: any };
|
|
7
8
|
export type RouteContext<T = { code?: number }, S = any> = {
|
|
@@ -86,7 +87,7 @@ export type RouteOpts = {
|
|
|
86
87
|
* }
|
|
87
88
|
*/
|
|
88
89
|
validator?: { [key: string]: Rule };
|
|
89
|
-
schema?: { [key: string]: any };
|
|
90
|
+
schema?: { [key: string]: Schema<any> };
|
|
90
91
|
isVerify?: boolean;
|
|
91
92
|
verify?: (ctx?: RouteContext, dev?: boolean) => boolean;
|
|
92
93
|
verifyKey?: (key: string, ctx?: RouteContext, dev?: boolean) => boolean;
|
|
@@ -121,7 +122,7 @@ export class Route<U = { [key: string]: any }> {
|
|
|
121
122
|
middleware?: RouteMiddleware[]; // middleware
|
|
122
123
|
type? = 'route';
|
|
123
124
|
private _validator?: { [key: string]: Rule };
|
|
124
|
-
schema?: { [key: string]: any };
|
|
125
|
+
schema?: { [key: string]: Schema<any> };
|
|
125
126
|
data?: any;
|
|
126
127
|
/**
|
|
127
128
|
* 是否需要验证
|
|
@@ -205,8 +206,12 @@ export class Route<U = { [key: string]: any }> {
|
|
|
205
206
|
if (schema[key]) {
|
|
206
207
|
const result = schema[key].safeParse(value);
|
|
207
208
|
if (!result.success) {
|
|
208
|
-
|
|
209
|
-
|
|
209
|
+
const path = result.error.errors[0]?.path?.join?.('.properties.');
|
|
210
|
+
let message = 'Invalid params';
|
|
211
|
+
if (path) {
|
|
212
|
+
const keyS = `${key}.properties.${path}.message`;
|
|
213
|
+
message = get(validator, keyS, 'Invalid params') as any;
|
|
214
|
+
}
|
|
210
215
|
throw new CustomError(500, message);
|
|
211
216
|
}
|
|
212
217
|
}
|
|
@@ -603,7 +608,7 @@ export class QueryRouter {
|
|
|
603
608
|
* @description 这里的上下文是为了在handle函数中使用
|
|
604
609
|
* @param ctx
|
|
605
610
|
*/
|
|
606
|
-
|
|
611
|
+
setContext(ctx: RouteContext) {
|
|
607
612
|
this.context = ctx;
|
|
608
613
|
}
|
|
609
614
|
getList(): RouteInfo[] {
|
package/src/server/ws-server.ts
CHANGED
|
@@ -43,10 +43,11 @@ export class WsServerBase {
|
|
|
43
43
|
this.listening = true;
|
|
44
44
|
|
|
45
45
|
this.wss.on('connection', (ws) => {
|
|
46
|
-
ws.on('message', async (message: string) => {
|
|
46
|
+
ws.on('message', async (message: string | Buffer) => {
|
|
47
47
|
const data = parseIfJson(message);
|
|
48
48
|
if (typeof data === 'string') {
|
|
49
|
-
|
|
49
|
+
const cleanMessage = data.trim().replace(/^["']|["']$/g, '');
|
|
50
|
+
ws.emit('string', cleanMessage);
|
|
50
51
|
return;
|
|
51
52
|
}
|
|
52
53
|
const { type, data: typeData, ...rest } = data;
|
|
@@ -83,7 +84,7 @@ export class WsServerBase {
|
|
|
83
84
|
if (message === 'close') {
|
|
84
85
|
ws.close();
|
|
85
86
|
}
|
|
86
|
-
if (message
|
|
87
|
+
if (message == 'ping') {
|
|
87
88
|
ws.send('pong');
|
|
88
89
|
}
|
|
89
90
|
});
|
package/src/sign.ts
CHANGED
package/src/test/ws.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { App } from "../app.ts";
|
|
2
|
+
|
|
3
|
+
const app = new App({
|
|
4
|
+
io: true
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
app
|
|
8
|
+
.route('demo', '03')
|
|
9
|
+
.define(async (ctx) => {
|
|
10
|
+
ctx.body = '03';
|
|
11
|
+
return ctx;
|
|
12
|
+
})
|
|
13
|
+
.addTo(app);
|
|
14
|
+
app
|
|
15
|
+
.route('test', 'test')
|
|
16
|
+
.define(async (ctx) => {
|
|
17
|
+
ctx.body = 'test';
|
|
18
|
+
return ctx;
|
|
19
|
+
})
|
|
20
|
+
.addTo(app);
|
|
21
|
+
console.log(`http://localhost:4002/api/router?path=demo&key=03`);
|
|
22
|
+
|
|
23
|
+
app.listen(4002, () => {
|
|
24
|
+
console.log("Server started on http://localhost:4002");
|
|
25
|
+
});
|
package/src/utils/parse.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
export const parseIfJson = (input: string): { [key: string]: any } | string => {
|
|
1
|
+
export const parseIfJson = (input: string|Buffer): { [key: string]: any } | string => {
|
|
2
|
+
const str = typeof input === 'string' ? input : input.toString();
|
|
2
3
|
try {
|
|
3
4
|
// 尝试解析 JSON
|
|
4
|
-
const parsed = JSON.parse(
|
|
5
|
+
const parsed = JSON.parse(str);
|
|
5
6
|
// 检查解析结果是否为对象(数组或普通对象)
|
|
6
7
|
if (typeof parsed === 'object' && parsed !== null) {
|
|
7
8
|
return parsed;
|
|
@@ -9,5 +10,5 @@ export const parseIfJson = (input: string): { [key: string]: any } | string => {
|
|
|
9
10
|
} catch (e) {
|
|
10
11
|
// 如果解析失败,直接返回原始字符串
|
|
11
12
|
}
|
|
12
|
-
return
|
|
13
|
+
return str;
|
|
13
14
|
};
|
package/src/validator/index.ts
CHANGED
package/src/validator/rule.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { z, ZodError } from 'zod';
|
|
1
|
+
import { z, ZodError, Schema } from 'zod';
|
|
2
|
+
export { Schema };
|
|
2
3
|
type BaseRule = {
|
|
3
4
|
value?: any;
|
|
4
5
|
required?: boolean;
|
|
@@ -63,7 +64,7 @@ export const schemaFormRule = (rule: Rule): z.ZodType<any, any, any> => {
|
|
|
63
64
|
throw new Error(`Unknown rule type: ${(rule as any)?.type}`);
|
|
64
65
|
}
|
|
65
66
|
};
|
|
66
|
-
export const createSchema = (rule: Rule):
|
|
67
|
+
export const createSchema = (rule: Rule): Schema => {
|
|
67
68
|
try {
|
|
68
69
|
rule.required = rule.required ?? false;
|
|
69
70
|
if (!rule.required) {
|
package/auto.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { loadTS, getMatchFiles } from './src/auto/load-ts.ts';
|
|
2
|
-
import { listenSocket } from './src/auto/listen-sock.ts';
|
|
3
|
-
import { Route, QueryRouter, QueryRouterServer } from './src/route.ts';
|
|
4
|
-
|
|
5
|
-
export { Route, QueryRouter, QueryRouterServer };
|
|
6
|
-
|
|
7
|
-
export const App = QueryRouterServer;
|
|
8
|
-
|
|
9
|
-
export { createSchema } from './src/validator/index.ts';
|
|
10
|
-
export type { Rule } from './src/validator/rule.ts';
|
|
11
|
-
export type { Schema } from 'zod';
|
|
12
|
-
export type { RouteContext, RouteOpts } from './src/route.ts';
|
|
13
|
-
|
|
14
|
-
export type { Run } from './src/route.ts';
|
|
15
|
-
|
|
16
|
-
export { CustomError } from './src/result/error.ts';
|
|
17
|
-
|
|
18
|
-
export { listenSocket, loadTS, getMatchFiles };
|
|
19
|
-
|
|
20
|
-
export { autoCall } from './src/auto/call-sock.ts';
|
package/src/auto/call-sock.ts
DELETED
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
import { createConnection } from 'node:net';
|
|
2
|
-
|
|
3
|
-
type QueryData = {
|
|
4
|
-
path?: string;
|
|
5
|
-
key?: string;
|
|
6
|
-
payload?: any;
|
|
7
|
-
[key: string]: any;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
type CallSockOptions = {
|
|
11
|
-
socketPath?: string;
|
|
12
|
-
timeout?: number;
|
|
13
|
-
method?: 'GET' | 'POST';
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export const callSock = async (data: QueryData, options: CallSockOptions = {}): Promise<any> => {
|
|
17
|
-
const { socketPath = './app.sock', timeout = 10000, method = 'POST' } = options;
|
|
18
|
-
|
|
19
|
-
return new Promise((resolve, reject) => {
|
|
20
|
-
const client = createConnection(socketPath);
|
|
21
|
-
let responseData = '';
|
|
22
|
-
let timer: NodeJS.Timeout;
|
|
23
|
-
|
|
24
|
-
// 设置超时
|
|
25
|
-
if (timeout > 0) {
|
|
26
|
-
timer = setTimeout(() => {
|
|
27
|
-
client.destroy();
|
|
28
|
-
reject(new Error(`Socket call timeout after ${timeout}ms`));
|
|
29
|
-
}, timeout);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
client.on('connect', () => {
|
|
33
|
-
try {
|
|
34
|
-
let request: string;
|
|
35
|
-
|
|
36
|
-
if (method === 'GET') {
|
|
37
|
-
// GET 请求:参数放在 URL 中
|
|
38
|
-
const searchParams = new URLSearchParams();
|
|
39
|
-
Object.entries(data).forEach(([key, value]) => {
|
|
40
|
-
if (key === 'payload' && typeof value === 'object') {
|
|
41
|
-
searchParams.append(key, JSON.stringify(value));
|
|
42
|
-
} else {
|
|
43
|
-
searchParams.append(key, String(value));
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
const queryString = searchParams.toString();
|
|
48
|
-
const url = queryString ? `/?${queryString}` : '/';
|
|
49
|
-
|
|
50
|
-
request = [`GET ${url} HTTP/1.1`, 'Host: localhost', 'Connection: close', '', ''].join('\r\n');
|
|
51
|
-
} else {
|
|
52
|
-
// POST 请求:数据放在 body 中
|
|
53
|
-
const body = JSON.stringify(data);
|
|
54
|
-
const contentLength = Buffer.byteLength(body, 'utf8');
|
|
55
|
-
|
|
56
|
-
request = [
|
|
57
|
-
'POST / HTTP/1.1',
|
|
58
|
-
'Host: localhost',
|
|
59
|
-
'Content-Type: application/json',
|
|
60
|
-
`Content-Length: ${contentLength}`,
|
|
61
|
-
'Connection: close',
|
|
62
|
-
'',
|
|
63
|
-
body,
|
|
64
|
-
].join('\r\n');
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
client.write(request);
|
|
68
|
-
} catch (error) {
|
|
69
|
-
if (timer) clearTimeout(timer);
|
|
70
|
-
client.destroy();
|
|
71
|
-
reject(error);
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
client.on('data', (chunk) => {
|
|
76
|
-
responseData += chunk.toString();
|
|
77
|
-
|
|
78
|
-
// 检查是否收到完整的HTTP响应
|
|
79
|
-
if (responseData.includes('\r\n\r\n')) {
|
|
80
|
-
const [headerSection] = responseData.split('\r\n\r\n');
|
|
81
|
-
const contentLengthMatch = headerSection.match(/content-length:\s*(\d+)/i);
|
|
82
|
-
|
|
83
|
-
if (contentLengthMatch) {
|
|
84
|
-
const expectedLength = parseInt(contentLengthMatch[1]);
|
|
85
|
-
const bodyStart = responseData.indexOf('\r\n\r\n') + 4;
|
|
86
|
-
const currentBodyLength = Buffer.byteLength(responseData.slice(bodyStart), 'utf8');
|
|
87
|
-
|
|
88
|
-
// 如果收到了完整的响应,主动关闭连接
|
|
89
|
-
if (currentBodyLength >= expectedLength) {
|
|
90
|
-
client.end();
|
|
91
|
-
}
|
|
92
|
-
} else if (responseData.includes('\r\n0\r\n\r\n')) {
|
|
93
|
-
// 检查 chunked 编码结束标记
|
|
94
|
-
client.end();
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
client.on('end', () => {
|
|
100
|
-
if (timer) clearTimeout(timer);
|
|
101
|
-
|
|
102
|
-
try {
|
|
103
|
-
// 解析 HTTP 响应
|
|
104
|
-
const response = parseHttpResponse(responseData);
|
|
105
|
-
|
|
106
|
-
if (response.statusCode >= 400) {
|
|
107
|
-
reject(new Error(`HTTP ${response.statusCode}: ${response.body}`));
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// 尝试解析 JSON 响应
|
|
112
|
-
try {
|
|
113
|
-
const result = JSON.parse(response.body);
|
|
114
|
-
resolve(result);
|
|
115
|
-
} catch {
|
|
116
|
-
// 如果不是 JSON,直接返回文本
|
|
117
|
-
resolve(response.body);
|
|
118
|
-
}
|
|
119
|
-
} catch (error) {
|
|
120
|
-
reject(error);
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
client.on('error', (error) => {
|
|
125
|
-
if (timer) clearTimeout(timer);
|
|
126
|
-
reject(error);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
client.on('timeout', () => {
|
|
130
|
-
if (timer) clearTimeout(timer);
|
|
131
|
-
client.destroy();
|
|
132
|
-
reject(new Error('Socket connection timeout'));
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
// 解析 HTTP 响应的辅助函数
|
|
138
|
-
function parseHttpResponse(responseData: string) {
|
|
139
|
-
const [headerSection, ...bodyParts] = responseData.split('\r\n\r\n');
|
|
140
|
-
const body = bodyParts.join('\r\n\r\n');
|
|
141
|
-
|
|
142
|
-
const lines = headerSection.split('\r\n');
|
|
143
|
-
const statusLine = lines[0];
|
|
144
|
-
const statusMatch = statusLine.match(/HTTP\/\d\.\d (\d+)/);
|
|
145
|
-
const statusCode = statusMatch ? parseInt(statusMatch[1]) : 200;
|
|
146
|
-
|
|
147
|
-
const headers: Record<string, string> = {};
|
|
148
|
-
for (let i = 1; i < lines.length; i++) {
|
|
149
|
-
const [key, ...valueParts] = lines[i].split(':');
|
|
150
|
-
if (key && valueParts.length > 0) {
|
|
151
|
-
headers[key.trim().toLowerCase()] = valueParts.join(':').trim();
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return {
|
|
156
|
-
statusCode,
|
|
157
|
-
headers,
|
|
158
|
-
body: body || '',
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export const autoCall = (data: QueryData, options?: Omit<CallSockOptions, 'method'>) => {
|
|
163
|
-
return callSock(data, { ...options, method: 'POST' });
|
|
164
|
-
};
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { getRuntime } from '../runtime.ts';
|
|
2
|
-
|
|
3
|
-
let isClean = false;
|
|
4
|
-
export const deleteFileDetached = async (path: string, pidPath: string = './app.pid') => {
|
|
5
|
-
const runtime = getRuntime();
|
|
6
|
-
if (runtime.isDeno) {
|
|
7
|
-
// Deno 实现 - 启动后不等待结果
|
|
8
|
-
const process = new Deno.Command('sh', {
|
|
9
|
-
args: ['-c', `rm -f "${path}" & rm -f "${pidPath}"`],
|
|
10
|
-
stdout: 'null',
|
|
11
|
-
stderr: 'null',
|
|
12
|
-
});
|
|
13
|
-
process.spawn(); // 不等待结果
|
|
14
|
-
console.log(`[DEBUG] Fire-and-forget delete initiated for ${path}`);
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
const { spawn } = await import('node:child_process');
|
|
18
|
-
const child = spawn('sh', ['-c', `rm -f "${path}" & rm -f "${pidPath}"`], {
|
|
19
|
-
detached: true,
|
|
20
|
-
stdio: 'ignore',
|
|
21
|
-
});
|
|
22
|
-
child.unref(); // 完全分离
|
|
23
|
-
console.log(`[DEBUG] Fire-and-forget delete initiated for ${path}`);
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
type CleanupOptions = {
|
|
27
|
-
path: string;
|
|
28
|
-
close?: () => Promise<void>;
|
|
29
|
-
pidPath?: string;
|
|
30
|
-
};
|
|
31
|
-
export const cleanup = async ({ path, close = async () => {}, pidPath = './app.pid' }: CleanupOptions) => {
|
|
32
|
-
const runtime = getRuntime();
|
|
33
|
-
|
|
34
|
-
// 检查文件是否存在并删除
|
|
35
|
-
const cleanupFile = async () => {
|
|
36
|
-
if (isClean) return;
|
|
37
|
-
isClean = true;
|
|
38
|
-
if (runtime.isDeno) {
|
|
39
|
-
await deleteFileDetached(path, pidPath);
|
|
40
|
-
}
|
|
41
|
-
await close();
|
|
42
|
-
if (!runtime.isDeno) {
|
|
43
|
-
await deleteFileDetached(path, pidPath);
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
// 根据运行时环境注册不同的退出监听器
|
|
48
|
-
if (runtime.isDeno) {
|
|
49
|
-
// Deno 环境
|
|
50
|
-
const handleSignal = () => {
|
|
51
|
-
cleanupFile();
|
|
52
|
-
Deno.exit(0);
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
try {
|
|
56
|
-
Deno.addSignalListener('SIGINT', handleSignal);
|
|
57
|
-
Deno.addSignalListener('SIGTERM', handleSignal);
|
|
58
|
-
} catch (error) {
|
|
59
|
-
console.warn('[DEBUG] Failed to add signal listeners:', error);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// 对于 beforeunload 和 unload,使用异步清理
|
|
63
|
-
const handleUnload = () => {
|
|
64
|
-
cleanupFile();
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
globalThis.addEventListener('beforeunload', handleUnload);
|
|
68
|
-
globalThis.addEventListener('unload', handleUnload);
|
|
69
|
-
} else if (runtime.isNode || runtime.isBun) {
|
|
70
|
-
// Node.js 和 Bun 环境
|
|
71
|
-
import('process').then(({ default: process }) => {
|
|
72
|
-
// 信号处理使用同步清理,然后退出
|
|
73
|
-
const signalHandler = async (signal: string) => {
|
|
74
|
-
await cleanupFile();
|
|
75
|
-
process.exit(0);
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
process.on('SIGINT', () => signalHandler('SIGINT'));
|
|
79
|
-
process.on('SIGTERM', () => signalHandler('SIGTERM'));
|
|
80
|
-
process.on('SIGUSR1', () => signalHandler('SIGUSR1'));
|
|
81
|
-
process.on('SIGUSR2', () => signalHandler('SIGUSR2'));
|
|
82
|
-
|
|
83
|
-
process.on('exit', async () => {
|
|
84
|
-
await cleanupFile();
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
process.on('uncaughtException', async (error) => {
|
|
88
|
-
console.error('Uncaught Exception:', error);
|
|
89
|
-
await cleanupFile();
|
|
90
|
-
process.exit(1);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
process.on('unhandledRejection', async (reason, promise) => {
|
|
94
|
-
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
95
|
-
await cleanupFile();
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// 返回手动清理函数,以便需要时主动调用
|
|
101
|
-
return cleanupFile;
|
|
102
|
-
};
|