feat: update package version and dependencies; add ReconnectingWebSocket for automatic reconnection
This commit is contained in:
20
package.json
20
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package",
|
"$schema": "https://json.schemastore.org/package",
|
||||||
"name": "@kevisual/router",
|
"name": "@kevisual/router",
|
||||||
"version": "0.0.66",
|
"version": "0.0.67",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/router.js",
|
"main": "./dist/router.js",
|
||||||
@@ -24,17 +24,18 @@
|
|||||||
"packageManager": "pnpm@10.28.2",
|
"packageManager": "pnpm@10.28.2",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@kevisual/context": "^0.0.4",
|
"@kevisual/context": "^0.0.4",
|
||||||
|
"@kevisual/dts": "^0.0.3",
|
||||||
"@kevisual/js-filter": "^0.0.5",
|
"@kevisual/js-filter": "^0.0.5",
|
||||||
"@kevisual/local-proxy": "^0.0.8",
|
"@kevisual/local-proxy": "^0.0.8",
|
||||||
"@kevisual/query": "^0.0.38",
|
"@kevisual/query": "^0.0.39",
|
||||||
"@kevisual/use-config": "^1.0.28",
|
"@kevisual/use-config": "^1.0.30",
|
||||||
"@opencode-ai/plugin": "^1.1.47",
|
"@opencode-ai/plugin": "^1.1.48",
|
||||||
"@rollup/plugin-alias": "^6.0.0",
|
"@rollup/plugin-alias": "^6.0.0",
|
||||||
"@rollup/plugin-commonjs": "29.0.0",
|
"@rollup/plugin-commonjs": "29.0.0",
|
||||||
"@rollup/plugin-node-resolve": "^16.0.3",
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
||||||
"@rollup/plugin-typescript": "^12.3.0",
|
"@rollup/plugin-typescript": "^12.3.0",
|
||||||
"@types/bun": "^1.3.8",
|
"@types/bun": "^1.3.8",
|
||||||
"@types/node": "^25.1.0",
|
"@types/node": "^25.2.0",
|
||||||
"@types/send": "^1.2.1",
|
"@types/send": "^1.2.1",
|
||||||
"@types/ws": "^8.18.1",
|
"@types/ws": "^8.18.1",
|
||||||
"@types/xml2js": "^0.4.14",
|
"@types/xml2js": "^0.4.14",
|
||||||
@@ -52,16 +53,14 @@
|
|||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"ws": "npm:@kevisual/ws",
|
"ws": "npm:@kevisual/ws",
|
||||||
"xml2js": "^0.6.2",
|
"xml2js": "^0.6.2",
|
||||||
"zod": "^4.3.6"
|
"zod": "^4.3.6",
|
||||||
|
"hono": "^4.11.7"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/abearxiong/kevisual-router.git"
|
"url": "git+https://github.com/abearxiong/kevisual-router.git"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {},
|
||||||
"@kevisual/dts": "^0.0.3",
|
|
||||||
"hono": "^4.11.7"
|
|
||||||
},
|
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
@@ -88,6 +87,7 @@
|
|||||||
"require": "./dist/router-define.js",
|
"require": "./dist/router-define.js",
|
||||||
"types": "./dist/router-define.d.ts"
|
"types": "./dist/router-define.d.ts"
|
||||||
},
|
},
|
||||||
|
"./ws": "./dist/ws.js",
|
||||||
"./mod.ts": {
|
"./mod.ts": {
|
||||||
"import": "./mod.ts",
|
"import": "./mod.ts",
|
||||||
"require": "./mod.ts",
|
"require": "./mod.ts",
|
||||||
|
|||||||
79
pnpm-lock.yaml
generated
79
pnpm-lock.yaml
generated
@@ -7,17 +7,13 @@ settings:
|
|||||||
importers:
|
importers:
|
||||||
|
|
||||||
.:
|
.:
|
||||||
dependencies:
|
|
||||||
'@kevisual/dts':
|
|
||||||
specifier: ^0.0.3
|
|
||||||
version: 0.0.3(typescript@5.9.3)
|
|
||||||
hono:
|
|
||||||
specifier: ^4.11.7
|
|
||||||
version: 4.11.7
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@kevisual/context':
|
'@kevisual/context':
|
||||||
specifier: ^0.0.4
|
specifier: ^0.0.4
|
||||||
version: 0.0.4
|
version: 0.0.4
|
||||||
|
'@kevisual/dts':
|
||||||
|
specifier: ^0.0.3
|
||||||
|
version: 0.0.3(typescript@5.9.3)
|
||||||
'@kevisual/js-filter':
|
'@kevisual/js-filter':
|
||||||
specifier: ^0.0.5
|
specifier: ^0.0.5
|
||||||
version: 0.0.5
|
version: 0.0.5
|
||||||
@@ -25,14 +21,14 @@ importers:
|
|||||||
specifier: ^0.0.8
|
specifier: ^0.0.8
|
||||||
version: 0.0.8
|
version: 0.0.8
|
||||||
'@kevisual/query':
|
'@kevisual/query':
|
||||||
specifier: ^0.0.38
|
specifier: ^0.0.39
|
||||||
version: 0.0.38
|
version: 0.0.39
|
||||||
'@kevisual/use-config':
|
'@kevisual/use-config':
|
||||||
specifier: ^1.0.28
|
specifier: ^1.0.30
|
||||||
version: 1.0.28(dotenv@17.2.3)
|
version: 1.0.30(dotenv@17.2.3)
|
||||||
'@opencode-ai/plugin':
|
'@opencode-ai/plugin':
|
||||||
specifier: ^1.1.47
|
specifier: ^1.1.48
|
||||||
version: 1.1.47
|
version: 1.1.48
|
||||||
'@rollup/plugin-alias':
|
'@rollup/plugin-alias':
|
||||||
specifier: ^6.0.0
|
specifier: ^6.0.0
|
||||||
version: 6.0.0(rollup@4.57.1)
|
version: 6.0.0(rollup@4.57.1)
|
||||||
@@ -49,8 +45,8 @@ importers:
|
|||||||
specifier: ^1.3.8
|
specifier: ^1.3.8
|
||||||
version: 1.3.8
|
version: 1.3.8
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^25.1.0
|
specifier: ^25.2.0
|
||||||
version: 25.1.0
|
version: 25.2.0
|
||||||
'@types/send':
|
'@types/send':
|
||||||
specifier: ^1.2.1
|
specifier: ^1.2.1
|
||||||
version: 1.2.1
|
version: 1.2.1
|
||||||
@@ -66,6 +62,9 @@ importers:
|
|||||||
fast-glob:
|
fast-glob:
|
||||||
specifier: ^3.3.3
|
specifier: ^3.3.3
|
||||||
version: 3.3.3
|
version: 3.3.3
|
||||||
|
hono:
|
||||||
|
specifier: ^4.11.7
|
||||||
|
version: 4.11.7
|
||||||
nanoid:
|
nanoid:
|
||||||
specifier: ^5.1.6
|
specifier: ^5.1.6
|
||||||
version: 5.1.6
|
version: 5.1.6
|
||||||
@@ -86,7 +85,7 @@ importers:
|
|||||||
version: 9.5.4(typescript@5.9.3)(webpack@5.104.1)
|
version: 9.5.4(typescript@5.9.3)(webpack@5.104.1)
|
||||||
ts-node:
|
ts-node:
|
||||||
specifier: ^10.9.2
|
specifier: ^10.9.2
|
||||||
version: 10.9.2(@types/node@25.1.0)(typescript@5.9.3)
|
version: 10.9.2(@types/node@25.2.0)(typescript@5.9.3)
|
||||||
tslib:
|
tslib:
|
||||||
specifier: ^2.8.1
|
specifier: ^2.8.1
|
||||||
version: 2.8.1
|
version: 2.8.1
|
||||||
@@ -117,7 +116,7 @@ importers:
|
|||||||
version: 1.1.1
|
version: 1.1.1
|
||||||
ts-node:
|
ts-node:
|
||||||
specifier: ^10.9.2
|
specifier: ^10.9.2
|
||||||
version: 10.9.2(@types/node@25.1.0)(typescript@5.9.3)
|
version: 10.9.2(@types/node@25.2.0)(typescript@5.9.3)
|
||||||
typescript:
|
typescript:
|
||||||
specifier: ^5.5.4
|
specifier: ^5.5.4
|
||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
@@ -327,11 +326,11 @@ packages:
|
|||||||
'@kevisual/local-proxy@0.0.8':
|
'@kevisual/local-proxy@0.0.8':
|
||||||
resolution: {integrity: sha512-VX/P+6/Cc8ruqp34ag6gVX073BchUmf5VNZcTV/6MJtjrNE76G8V6TLpBE8bywLnrqyRtFLIspk4QlH8up9B5Q==}
|
resolution: {integrity: sha512-VX/P+6/Cc8ruqp34ag6gVX073BchUmf5VNZcTV/6MJtjrNE76G8V6TLpBE8bywLnrqyRtFLIspk4QlH8up9B5Q==}
|
||||||
|
|
||||||
'@kevisual/query@0.0.38':
|
'@kevisual/query@0.0.39':
|
||||||
resolution: {integrity: sha512-bfvbSodsZyMfwY+1T2SvDeOCKsT/AaIxlVe0+B1R/fNhlg2MDq2CP0L9HKiFkEm+OXrvXcYDMKPUituVUM5J6Q==}
|
resolution: {integrity: sha512-3UEPBIvtdykNkrby3hvrgrHdgd17Uq+Pnr4zs+JBzATkU2eKaOqtTUJqdyIEwuySCwzGTxrnlUzWP4tziDQDLQ==}
|
||||||
|
|
||||||
'@kevisual/use-config@1.0.28':
|
'@kevisual/use-config@1.0.30':
|
||||||
resolution: {integrity: sha512-ngF+LDbjxpXWrZNmnShIKF/jPpAa+ezV+DcgoZIIzHlRnIjE+rr9sLkN/B7WJbiH9C/j1tQXOILY8ujBqILrow==}
|
resolution: {integrity: sha512-kPdna0FW/X7D600aMdiZ5UTjbCo6d8d4jjauSc8RMmBwUU6WliFDSPUNKVpzm2BsDX5Nth1IXFPYMqH+wxqAmw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
dotenv: ^17
|
dotenv: ^17
|
||||||
|
|
||||||
@@ -351,11 +350,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
|
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
|
|
||||||
'@opencode-ai/plugin@1.1.47':
|
'@opencode-ai/plugin@1.1.48':
|
||||||
resolution: {integrity: sha512-gNMPz72altieDfLhUw3VAT1xbduKi3w3wZ57GLeS7qU9W474HdvdIiLBnt2Xq3U7Ko0/0tvK3nzCker6IIDqmQ==}
|
resolution: {integrity: sha512-KkaSMevXmz7tOwYDMJeWiXE5N8LmRP18qWI5Xhv3+c+FdGPL+l1hQrjSgyv3k7Co7qpCyW3kAUESBB7BzIOl2w==}
|
||||||
|
|
||||||
'@opencode-ai/sdk@1.1.47':
|
'@opencode-ai/sdk@1.1.48':
|
||||||
resolution: {integrity: sha512-s3PBHwk1sP6Zt/lJxIWSBWZ1TnrI1nFxSP97LCODUytouAQgbygZ1oDH7O2sGMBEuGdA8B1nNSPla0aRSN3IpA==}
|
resolution: {integrity: sha512-j5/79X45fUPWVD2Ffm/qvwLclDCdPeV+TYMDrm9to0p4pmzhmeKevCsyiRdLg0o0HE3AFRUnOo2rdO9NetN79A==}
|
||||||
|
|
||||||
'@rollup/plugin-alias@6.0.0':
|
'@rollup/plugin-alias@6.0.0':
|
||||||
resolution: {integrity: sha512-tPCzJOtS7uuVZd+xPhoy5W4vThe6KWXNmsFCNktaAh5RTqcLiSfT4huPQIXkgJ6YCOjJHvecOAzQxLFhPxKr+g==}
|
resolution: {integrity: sha512-tPCzJOtS7uuVZd+xPhoy5W4vThe6KWXNmsFCNktaAh5RTqcLiSfT4huPQIXkgJ6YCOjJHvecOAzQxLFhPxKr+g==}
|
||||||
@@ -580,8 +579,8 @@ packages:
|
|||||||
'@types/json-schema@7.0.15':
|
'@types/json-schema@7.0.15':
|
||||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||||
|
|
||||||
'@types/node@25.1.0':
|
'@types/node@25.2.0':
|
||||||
resolution: {integrity: sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA==}
|
resolution: {integrity: sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w==}
|
||||||
|
|
||||||
'@types/resolve@1.20.2':
|
'@types/resolve@1.20.2':
|
||||||
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
|
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
|
||||||
@@ -1342,11 +1341,11 @@ snapshots:
|
|||||||
|
|
||||||
'@kevisual/local-proxy@0.0.8': {}
|
'@kevisual/local-proxy@0.0.8': {}
|
||||||
|
|
||||||
'@kevisual/query@0.0.38':
|
'@kevisual/query@0.0.39':
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@kevisual/use-config@1.0.28(dotenv@17.2.3)':
|
'@kevisual/use-config@1.0.30(dotenv@17.2.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@kevisual/load': 0.0.6
|
'@kevisual/load': 0.0.6
|
||||||
dotenv: 17.2.3
|
dotenv: 17.2.3
|
||||||
@@ -1365,12 +1364,12 @@ snapshots:
|
|||||||
'@nodelib/fs.scandir': 2.1.5
|
'@nodelib/fs.scandir': 2.1.5
|
||||||
fastq: 1.20.1
|
fastq: 1.20.1
|
||||||
|
|
||||||
'@opencode-ai/plugin@1.1.47':
|
'@opencode-ai/plugin@1.1.48':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@opencode-ai/sdk': 1.1.47
|
'@opencode-ai/sdk': 1.1.48
|
||||||
zod: 4.1.8
|
zod: 4.1.8
|
||||||
|
|
||||||
'@opencode-ai/sdk@1.1.47': {}
|
'@opencode-ai/sdk@1.1.48': {}
|
||||||
|
|
||||||
'@rollup/plugin-alias@6.0.0(rollup@4.57.1)':
|
'@rollup/plugin-alias@6.0.0(rollup@4.57.1)':
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
@@ -1528,7 +1527,7 @@ snapshots:
|
|||||||
|
|
||||||
'@types/json-schema@7.0.15': {}
|
'@types/json-schema@7.0.15': {}
|
||||||
|
|
||||||
'@types/node@25.1.0':
|
'@types/node@25.2.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 7.16.0
|
undici-types: 7.16.0
|
||||||
|
|
||||||
@@ -1536,15 +1535,15 @@ snapshots:
|
|||||||
|
|
||||||
'@types/send@1.2.1':
|
'@types/send@1.2.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 25.1.0
|
'@types/node': 25.2.0
|
||||||
|
|
||||||
'@types/ws@8.18.1':
|
'@types/ws@8.18.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 25.1.0
|
'@types/node': 25.2.0
|
||||||
|
|
||||||
'@types/xml2js@0.4.14':
|
'@types/xml2js@0.4.14':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 25.1.0
|
'@types/node': 25.2.0
|
||||||
|
|
||||||
'@webassemblyjs/ast@1.14.1':
|
'@webassemblyjs/ast@1.14.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -1676,7 +1675,7 @@ snapshots:
|
|||||||
|
|
||||||
bun-types@1.3.8:
|
bun-types@1.3.8:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 25.1.0
|
'@types/node': 25.2.0
|
||||||
|
|
||||||
caniuse-lite@1.0.30001761: {}
|
caniuse-lite@1.0.30001761: {}
|
||||||
|
|
||||||
@@ -1861,7 +1860,7 @@ snapshots:
|
|||||||
|
|
||||||
jest-worker@27.5.1:
|
jest-worker@27.5.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 25.1.0
|
'@types/node': 25.2.0
|
||||||
merge-stream: 2.0.0
|
merge-stream: 2.0.0
|
||||||
supports-color: 8.1.1
|
supports-color: 8.1.1
|
||||||
|
|
||||||
@@ -2076,14 +2075,14 @@ snapshots:
|
|||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
webpack: 5.104.1
|
webpack: 5.104.1
|
||||||
|
|
||||||
ts-node@10.9.2(@types/node@25.1.0)(typescript@5.9.3):
|
ts-node@10.9.2(@types/node@25.2.0)(typescript@5.9.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@cspotcode/source-map-support': 0.8.1
|
'@cspotcode/source-map-support': 0.8.1
|
||||||
'@tsconfig/node10': 1.0.12
|
'@tsconfig/node10': 1.0.12
|
||||||
'@tsconfig/node12': 1.0.11
|
'@tsconfig/node12': 1.0.11
|
||||||
'@tsconfig/node14': 1.0.3
|
'@tsconfig/node14': 1.0.3
|
||||||
'@tsconfig/node16': 1.0.4
|
'@tsconfig/node16': 1.0.4
|
||||||
'@types/node': 25.1.0
|
'@types/node': 25.2.0
|
||||||
acorn: 8.15.0
|
acorn: 8.15.0
|
||||||
acorn-walk: 8.3.4
|
acorn-walk: 8.3.4
|
||||||
arg: 4.1.3
|
arg: 4.1.3
|
||||||
|
|||||||
@@ -148,4 +148,27 @@ export default [
|
|||||||
},
|
},
|
||||||
plugins: [dts()],
|
plugins: [dts()],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
input: 'src/ws.ts',
|
||||||
|
output: {
|
||||||
|
file: 'dist/ws.js',
|
||||||
|
format: 'es',
|
||||||
|
},
|
||||||
|
external: ['ws'],
|
||||||
|
plugins: [
|
||||||
|
resolve({
|
||||||
|
// browser: true,
|
||||||
|
}),
|
||||||
|
commonjs(),
|
||||||
|
typescript(),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: 'src/ws.ts',
|
||||||
|
output: {
|
||||||
|
file: 'dist/ws.d.ts',
|
||||||
|
format: 'es',
|
||||||
|
},
|
||||||
|
plugins: [dts()],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
170
src/server/reconnect-ws.ts
Normal file
170
src/server/reconnect-ws.ts
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
import WebSocket from 'ws';
|
||||||
|
|
||||||
|
export type ReconnectConfig = {
|
||||||
|
/**
|
||||||
|
* 重连配置选项, 最大重试次数,默认无限
|
||||||
|
*/
|
||||||
|
maxRetries?: number;
|
||||||
|
/**
|
||||||
|
* 重连配置选项, 重试延迟(ms),默认1000
|
||||||
|
*/
|
||||||
|
retryDelay?: number;
|
||||||
|
/**
|
||||||
|
* 重连配置选项, 最大延迟(ms),默认30000
|
||||||
|
*/
|
||||||
|
maxDelay?: number;
|
||||||
|
/**
|
||||||
|
* 重连配置选项, 退避倍数,默认2
|
||||||
|
*/
|
||||||
|
backoffMultiplier?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一个支持自动重连的 WebSocket 客户端。
|
||||||
|
* 在连接断开时会根据配置进行重连尝试,支持指数退避。
|
||||||
|
*/
|
||||||
|
export class ReconnectingWebSocket {
|
||||||
|
private ws: WebSocket | null = null;
|
||||||
|
private url: string;
|
||||||
|
private config: Required<ReconnectConfig>;
|
||||||
|
private retryCount: number = 0;
|
||||||
|
private reconnectTimer: NodeJS.Timeout | null = null;
|
||||||
|
private isManualClose: boolean = false;
|
||||||
|
private messageHandlers: Array<(data: any) => void> = [];
|
||||||
|
private openHandlers: Array<() => void> = [];
|
||||||
|
private closeHandlers: Array<(code: number, reason: Buffer) => void> = [];
|
||||||
|
private errorHandlers: Array<(error: Error) => void> = [];
|
||||||
|
|
||||||
|
constructor(url: string, config: ReconnectConfig = {}) {
|
||||||
|
this.url = url;
|
||||||
|
this.config = {
|
||||||
|
maxRetries: config.maxRetries ?? Infinity,
|
||||||
|
retryDelay: config.retryDelay ?? 1000,
|
||||||
|
maxDelay: config.maxDelay ?? 30000,
|
||||||
|
backoffMultiplier: config.backoffMultiplier ?? 2,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
log(...args: any[]): void {
|
||||||
|
console.log('[ReconnectingWebSocket]', ...args);
|
||||||
|
}
|
||||||
|
error(...args: any[]): void {
|
||||||
|
console.error('[ReconnectingWebSocket]', ...args);
|
||||||
|
}
|
||||||
|
connect(): void {
|
||||||
|
if (this.ws?.readyState === WebSocket.OPEN) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log(`正在连接到 ${this.url}...`);
|
||||||
|
this.ws = new WebSocket(this.url);
|
||||||
|
|
||||||
|
this.ws.on('open', () => {
|
||||||
|
this.log('WebSocket 连接已打开');
|
||||||
|
this.retryCount = 0;
|
||||||
|
this.openHandlers.forEach(handler => handler());
|
||||||
|
this.send({ type: 'heartbeat', timestamp: new Date().toISOString() });
|
||||||
|
});
|
||||||
|
|
||||||
|
this.ws.on('message', (data: any) => {
|
||||||
|
this.messageHandlers.forEach(handler => {
|
||||||
|
try {
|
||||||
|
const message = JSON.parse(data.toString());
|
||||||
|
handler(message);
|
||||||
|
} catch {
|
||||||
|
handler(data.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.ws.on('close', (code: number, reason: Buffer) => {
|
||||||
|
this.log(`WebSocket 连接已关闭: code=${code}, reason=${reason.toString()}`);
|
||||||
|
this.closeHandlers.forEach(handler => handler(code, reason));
|
||||||
|
|
||||||
|
if (!this.isManualClose) {
|
||||||
|
this.scheduleReconnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.ws.on('error', (error: Error) => {
|
||||||
|
this.error('WebSocket 错误:', error.message);
|
||||||
|
this.errorHandlers.forEach(handler => handler(error));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private scheduleReconnect(): void {
|
||||||
|
if (this.reconnectTimer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.retryCount >= this.config.maxRetries) {
|
||||||
|
this.error(`已达到最大重试次数 (${this.config.maxRetries}),停止重连`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算延迟(指数退避)
|
||||||
|
const delay = Math.min(
|
||||||
|
this.config.retryDelay * Math.pow(this.config.backoffMultiplier, this.retryCount),
|
||||||
|
this.config.maxDelay
|
||||||
|
);
|
||||||
|
|
||||||
|
this.retryCount++;
|
||||||
|
this.log(`将在 ${delay}ms 后进行第 ${this.retryCount} 次重连尝试...`);
|
||||||
|
|
||||||
|
this.reconnectTimer = setTimeout(() => {
|
||||||
|
this.reconnectTimer = null;
|
||||||
|
this.connect();
|
||||||
|
}, delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
send(data: any): boolean {
|
||||||
|
if (this.ws?.readyState === WebSocket.OPEN) {
|
||||||
|
this.ws.send(JSON.stringify(data));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
this.log('WebSocket 未连接,无法发送消息');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMessage(handler: (data: any) => void): void {
|
||||||
|
this.messageHandlers.push(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
onOpen(handler: () => void): void {
|
||||||
|
this.openHandlers.push(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose(handler: (code: number, reason: Buffer) => void): void {
|
||||||
|
this.closeHandlers.push(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
onError(handler: (error: Error) => void): void {
|
||||||
|
this.errorHandlers.push(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
close(): void {
|
||||||
|
this.isManualClose = true;
|
||||||
|
if (this.reconnectTimer) {
|
||||||
|
clearTimeout(this.reconnectTimer);
|
||||||
|
this.reconnectTimer = null;
|
||||||
|
}
|
||||||
|
if (this.ws) {
|
||||||
|
this.ws.close();
|
||||||
|
this.ws = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getReadyState(): number {
|
||||||
|
return this.ws?.readyState ?? WebSocket.CLOSED;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRetryCount(): number {
|
||||||
|
return this.retryCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// const ws = new ReconnectingWebSocket('ws://localhost:51516/livecode/ws?id=test-live-app', {
|
||||||
|
// maxRetries: Infinity, // 无限重试
|
||||||
|
// retryDelay: 1000, // 初始重试延迟 1 秒
|
||||||
|
// maxDelay: 30000, // 最大延迟 30 秒
|
||||||
|
// backoffMultiplier: 2, // 指数退避倍数
|
||||||
|
// });
|
||||||
65
src/ws.ts
Normal file
65
src/ws.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { ReconnectingWebSocket, ReconnectConfig } from "./server/reconnect-ws.ts";
|
||||||
|
|
||||||
|
export * from "./server/reconnect-ws.ts";
|
||||||
|
import type { App } from "./app.ts";
|
||||||
|
|
||||||
|
export const handleCallWsApp = async (ws: ReconnectingWebSocket, app: App, message: any) => {
|
||||||
|
return handleCallApp((data: any) => {
|
||||||
|
ws.send(data);
|
||||||
|
}, app, message);
|
||||||
|
}
|
||||||
|
export const handleCallApp = async (send: (data: any) => void, app: App, message: any) => {
|
||||||
|
if (message.type === 'router' && message.id) {
|
||||||
|
const data = message?.data;
|
||||||
|
if (!message.id) {
|
||||||
|
console.error('Message id is required for router type');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!data) {
|
||||||
|
send({
|
||||||
|
type: 'router',
|
||||||
|
id: message.id,
|
||||||
|
data: { code: 500, message: 'No data received' }
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { tokenUser, ...rest } = data || {};
|
||||||
|
const res = await app.run(rest, {
|
||||||
|
state: { tokenUser },
|
||||||
|
appId: app.appId,
|
||||||
|
});
|
||||||
|
send({
|
||||||
|
type: 'router',
|
||||||
|
id: message.id,
|
||||||
|
data: res
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class Ws {
|
||||||
|
wsClient: ReconnectingWebSocket;
|
||||||
|
app: App;
|
||||||
|
showLog: boolean = true;
|
||||||
|
constructor(opts?: ReconnectConfig & {
|
||||||
|
url: string;
|
||||||
|
app: App;
|
||||||
|
showLog?: boolean;
|
||||||
|
handleMessage?: (ws: ReconnectingWebSocket, app: App, message: any) => void;
|
||||||
|
}) {
|
||||||
|
const { url, app, showLog = true, handleMessage = handleCallWsApp, ...rest } = opts;
|
||||||
|
this.wsClient = new ReconnectingWebSocket(url, rest);
|
||||||
|
this.app = app;
|
||||||
|
this.showLog = showLog;
|
||||||
|
this.wsClient.connect();
|
||||||
|
const onMessage = async (data: any) => {
|
||||||
|
return handleMessage(this.wsClient, this.app, data);
|
||||||
|
}
|
||||||
|
this.wsClient.onMessage(onMessage);
|
||||||
|
}
|
||||||
|
send(data: any): boolean {
|
||||||
|
return this.wsClient.send(data);
|
||||||
|
}
|
||||||
|
log(...args: any[]): void {
|
||||||
|
if (this.showLog)
|
||||||
|
console.log('[Ws]', ...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user