oox 0.3.4 → 0.3.5

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/README.md CHANGED
@@ -27,3 +27,16 @@ npm install oox
27
27
  ```bash
28
28
  npx oox --help
29
29
  ```
30
+
31
+ ### Examples
32
+ - [basic-example](./examples/basic-example)
33
+ - [routing-example](./examples/routing-example)
34
+ - [rpc-example](./examples/rpc-example)
35
+ - [distributed-example](./examples/distributed-example)
36
+ - [load-balancing-example](./examples/load-balancing-example)
37
+ - [tracing-example](./examples/tracing-example)
38
+ - [service-discovery-example](./examples/service-discovery-example)
39
+
40
+
41
+ ### Docs
42
+ [config](./docs/config.md)
package/app.js CHANGED
@@ -2,7 +2,7 @@ import { EventEmitter } from 'node:events';
2
2
  import { AsyncLocalStorage } from 'node:async_hooks';
3
3
  import { genKVMethods } from './utils.js';
4
4
  export * as logger from './logger.js';
5
- export class Context {
5
+ export class AppContext {
6
6
  // 请求溯源ID
7
7
  traceId = '';
8
8
  }
package/bin/cli.js CHANGED
@@ -16,7 +16,7 @@ if (execFilename.endsWith('oox') || fileURLToPath(import.meta.url) === execFilen
16
16
  if (!command || ['help', '-h', '-help', '--help', 'version', '-v', '-version', '--version'].includes(command)) {
17
17
  isStartup = false;
18
18
  console.log();
19
- console.log('OOX Service Engine');
19
+ console.log(pkg.description);
20
20
  console.log(chalk.bold('version'), chalk.bold.green(`${pkg.version}`));
21
21
  console.log(chalk.underline(`${pkg.homepage}`));
22
22
  console.log();
@@ -28,24 +28,54 @@ if (execFilename.endsWith('oox') || fileURLToPath(import.meta.url) === execFilen
28
28
  console.log(' oox entry.js port=8080');
29
29
  console.log();
30
30
  console.log(' oox entry.js group=app/ registry=:6000');
31
- console.log(' oox app/entry/index.js group=app/ env=envs/prod.js ignore=core');
31
+ console.log(' oox app/entry/index.js group=app/ env=.env/prod.js ignore=core');
32
32
  console.log();
33
33
  console.log(chalk.bold('Params:'));
34
- const params = [
34
+ const params_base = [
35
35
  ['default-env', 'file', '.js or .json file, merge to oox.config'],
36
- ['env', 'file', `${chalk.bold.red('default-env')}.js or .json file, merge to oox.config, ${chalk.bold('(after default-env)')}`],
36
+ ['env', 'file', `.js or .json file, merge to oox.config, ${chalk.bold('(after default-env)')}`],
37
37
  ['port', 'int', `default is ${chalk.bold('0')}, for random port, or any integer > 0`],
38
38
  ['group', 'dir', 'service group directory, all LocalCall transform to RPC'],
39
39
  ['ignore', 'name', 'set a name for LocalCall do not transform to RPC, support string | array<string>'],
40
- ['http', 'json', 'HTTP server options, support flat name, ex: http.path=/api'],
41
- ['socketio', 'json', 'SocketIO server options, support flat name'],
42
- ['isRegistry', 'bool', `default is ${chalk.bold.red('true')}, set no to disable service registry`],
40
+ ['errorStack', 'bool', 'set no to hidden error stack return'],
41
+ ];
42
+ const params_registry = [
43
+ ['isRegistry', 'bool', `set yes to enable service registry`],
43
44
  ['registryAdapter', 'name', 'set service registry adapter name'],
45
+ ['registryToken', 'token', 'set token for service registry'],
44
46
  ['registry', 'urls', 'registry service url, support string | array<string>'],
47
+ ['registry.url', 'url', 'set service registry url'],
48
+ ['registry.token', 'token', 'set service registry token'],
49
+ ];
50
+ const params_auth = [
51
+ ['token', 'token', 'set token for connection auth'],
52
+ ['allow.untrusted', 'bool', `set yes to allow untrusted caller`],
53
+ ['allow.ip', 'ip', 'set allow IP, support string | array<string>'],
54
+ ['allow.caller', 'name', 'set allow caller, support string | array<string>'],
45
55
  ['origin', 'urls', `set ${chalk.bold('*')}, allow any connections <Access-Control-Allow-Origin>`],
46
- ['errorStack', 'bool', 'set no to hidden error stack return'],
47
56
  ];
48
- console.log(mergeCommandParams(params));
57
+ const params_modules = [
58
+ ['http', 'json/bool', 'HTTP server options, support flat name, ex: http.path=/api, or set no to disable HTTP server'],
59
+ ['http.port', 'int server port'],
60
+ ['http.path', 'string', 'set HTTP server path'],
61
+ ['http.origin', 'urls', 'set HTTP server origin, support string | array<string>'],
62
+ ['http.ssl', 'json', 'set yes to enable HTTPS'],
63
+ ['http.ssl.cert', 'file', 'set HTTPS certificate path'],
64
+ ['http.ssl.key', 'file', 'set HTTPS private key path'],
65
+ ['http.ssl.ca', 'file', 'set HTTPS CA path'],
66
+ ['socketio', 'json/bool', 'SocketIO server options, support flat name, or set no to disable SocketIO server'],
67
+ ['socketio.port', 'int server port'],
68
+ ['socketio.path', 'string', 'set SocketIO server path'],
69
+ ['socketio.origin', 'urls', 'set SocketIO server origin, support string | array<string>'],
70
+ ['socketio.ssl', 'json', 'like http.ssl'],
71
+ ];
72
+ console.log(mergeCommandParams(params_base));
73
+ console.log(chalk.bold('Registry:'));
74
+ console.log(mergeCommandParams(params_registry));
75
+ console.log(chalk.bold('Auth:'));
76
+ console.log(mergeCommandParams(params_auth));
77
+ console.log(chalk.bold('Modules:'));
78
+ console.log(mergeCommandParams(params_modules));
49
79
  console.log(' ...', 'set params as', chalk.bold('foo=bar') + ',', 'usage as', chalk.bold('oox.config.foo'));
50
80
  console.log();
51
81
  }
@@ -73,10 +103,13 @@ function mergeCommandParams(params) {
73
103
  result += ' '.repeat(nameMaxLength);
74
104
  }
75
105
  else {
76
- result += ' '.repeat(nameMaxLength - name.length);
106
+ result += ' '.repeat(Math.max(0, nameMaxLength - name.length));
77
107
  }
78
- result += '[' + type + ']';
79
- result += ' '.repeat(typeMaxLength - type.length);
108
+ const typeLeftSpaceLength = Math.max(0, Math.floor((typeMaxLength - type.length) / 2));
109
+ const typeLeftSpaces = ' '.repeat(typeLeftSpaceLength);
110
+ const typeRightSpaces = ' '.repeat(Math.max(0, typeMaxLength - type.length - typeLeftSpaceLength));
111
+ result += '[' + typeLeftSpaces + type + typeRightSpaces + ']';
112
+ result += ' ';
80
113
  result += desc;
81
114
  result += '\n';
82
115
  }
package/bin/configurer.js CHANGED
@@ -12,7 +12,11 @@ function mergeFlatEnv(env) {
12
12
  const valueKey = subKeys.pop() || '';
13
13
  let tmpEnv = env;
14
14
  for (const subKey of subKeys) {
15
- if (subKey in tmpEnv) { }
15
+ if (subKey in tmpEnv) {
16
+ if ('object' !== typeof tmpEnv[subKey] || null === tmpEnv[subKey]) {
17
+ tmpEnv[subKey] = {};
18
+ }
19
+ }
16
20
  else {
17
21
  tmpEnv[subKey] = {};
18
22
  }
@@ -42,20 +46,13 @@ async function readEnvFile(filePath) {
42
46
  return env;
43
47
  }
44
48
  export async function buildConfig(mergeEnv) {
45
- const env = Object.create(null);
49
+ const env = {};
46
50
  const defaultEnvPath = argv.getEnvArg('default-env');
47
51
  const targetEnvPath = argv.getEnvArg('env');
48
52
  const defaultEnv = defaultEnvPath ? await readEnvFile(defaultEnvPath) : {};
49
53
  const targetEnv = targetEnvPath ? await readEnvFile(targetEnvPath) : {};
50
54
  Object.assign(env, defaultEnv, targetEnv, argv.getAllEnvArgs());
51
55
  mergeFlatEnv(env);
52
- if ('string' === typeof env.ignore)
53
- env.ignore = env.ignore.split(',');
54
- if ('string' === typeof env.registry)
55
- env.registry = env.registry.split(',');
56
- if ('string' === typeof env.origin && env.origin.includes(',')) {
57
- env.origin = env.origin.split(',');
58
- }
59
56
  if (mergeEnv)
60
57
  Object.assign(env, mergeEnv);
61
58
  return env;
package/bin/register.js CHANGED
@@ -1,32 +1,65 @@
1
1
  import chalk from 'chalk';
2
2
  import * as oox from '../index.js';
3
+ import { randomUUID } from 'node:crypto';
3
4
  const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
4
- async function connect(url, prevError = null) {
5
- const adapter = oox.keepAliveConnectionAdapters.get(oox.config.registryAdapter);
5
+ async function connect(identify, prevError = null) {
6
+ let url = '';
7
+ let connectionData;
8
+ if (typeof identify === 'string') {
9
+ url = identify;
10
+ connectionData = {
11
+ adapter: oox.config.registryAdapter,
12
+ url,
13
+ ip: '',
14
+ name: 'anonymous',
15
+ id: randomUUID(),
16
+ token: oox.config.registryToken,
17
+ };
18
+ }
19
+ else {
20
+ url = String(identify.url || '');
21
+ connectionData = identify;
22
+ }
23
+ if (!connectionData.name)
24
+ connectionData.name = 'anonymous';
25
+ if (!connectionData.id)
26
+ connectionData.id = randomUUID();
27
+ if (!connectionData.token)
28
+ connectionData.token = oox.config.registryToken;
29
+ if (!connectionData.adapter)
30
+ connectionData.adapter = oox.config.registryAdapter;
31
+ const adapter = oox.keepAliveConnectionAdapters.get(connectionData.adapter);
32
+ if (!adapter) {
33
+ console.error(chalk.red('[Registry]'), `Registry Adapter ${chalk.bold(connectionData.adapter)} is not found`);
34
+ return;
35
+ }
36
+ if (!url) {
37
+ console.error(chalk.red('[Registry]'), `Registry URL ${chalk.bold(url)} is empty`);
38
+ return;
39
+ }
6
40
  try {
7
- const connection = await adapter.open(url);
8
- onConnection(connection, url);
41
+ const connection = await adapter.open(connectionData);
42
+ connection.isRegistry = true;
43
+ onConnection(connection);
9
44
  }
10
45
  catch (error) {
11
46
  if (!prevError)
12
- console.log(chalk.red('[Registry]'), chalk.underline.red(`${url}`), 'error.');
47
+ console.error(chalk.red('[Registry]'), chalk.underline.red(`${url}`), 'error.');
13
48
  await delay(2000);
14
- connect(url, error);
49
+ connect(identify, error);
15
50
  }
16
51
  }
17
- async function onConnection(connection, url) {
52
+ async function onConnection(connection) {
18
53
  const { name } = connection.data;
19
54
  connection.once('disconnect', async () => {
20
- console.log(chalk.red('[Registry]'), `Service<${name}>`, chalk.underline.red(`${connection.data.url || url}`), 'disconnected.');
55
+ console.error(chalk.red('[Registry]'), `Service<${name}>`, chalk.underline.red(`${connection.data.url || name}`), 'disconnected.');
21
56
  await delay(1000);
22
- connect(url);
57
+ connect(connection.data);
23
58
  });
24
- console.log(chalk.green('[Registry]'), `Service<${name}>`, chalk.underline.green(`${connection.data.url || url}`), 'connected.');
59
+ console.info(chalk.green('[Registry]'), `Service<${name}>`, chalk.underline.green(`${connection.data.url || name}`), 'connected.');
25
60
  }
26
- export async function registry(urls) {
27
- if ('string' === typeof urls)
28
- urls = [urls];
29
- for (const url of urls) {
30
- connect(url);
61
+ export async function registry(identifies) {
62
+ for (const identify of identifies) {
63
+ connect(identify);
31
64
  }
32
65
  }
package/bin/starter.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as path from 'node:path';
2
2
  import chalk from 'chalk';
3
+ import { formatters as configFormatters } from '../config.js';
3
4
  import * as oox from '../index.js';
4
5
  import { buildConfig } from './configurer.js';
5
6
  import { registry } from './register.js';
@@ -17,6 +18,8 @@ function getEntryInfo(env, entryFilename) {
17
18
  const directory = fullDirectory.split(path.sep).pop();
18
19
  const groupFullDirectory = env.group ? path.resolve(env.group) : '';
19
20
  const entryMatch = filename.match(entryMatchRegExp);
21
+ if (!entryMatch)
22
+ throw new Error('Invalid entry file name: ' + entryFilename);
20
23
  const entryFilenameWithoutExtension = entryMatch[1];
21
24
  const name = entryFilenameWithoutExtension === 'index' && groupFullDirectory !== fullDirectory ? directory : entryFilenameWithoutExtension;
22
25
  return { name, path: fullPath, group: groupFullDirectory };
@@ -37,6 +40,11 @@ export async function configure(env, entryFilename) {
37
40
  path: entryInfo.path.replace(/\\/g, '/'),
38
41
  group: entryInfo.group.replace(/\\/g, '/'),
39
42
  };
43
+ for (const key in oox.config) {
44
+ if (key in configFormatters) {
45
+ oox.config[key] = configFormatters[key](oox.config[key]);
46
+ }
47
+ }
40
48
  // 模块配置
41
49
  oox.modules.setConfig(oox.config);
42
50
  oox.emit('app:configured');
package/config.js ADDED
@@ -0,0 +1,116 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { getIPAddress } from "./utils.js";
3
+ export class Config {
4
+ // 服务ID
5
+ id = randomUUID();
6
+ // 服务名称
7
+ name = 'local';
8
+ // 服务列表路径
9
+ group = '';
10
+ // 非服务模块
11
+ ignore = [];
12
+ // 启动文件
13
+ entryInfo = {
14
+ // 启动文件路径
15
+ path: '',
16
+ // 服务列表路径
17
+ group: '',
18
+ };
19
+ // 主机地址
20
+ host = getIPAddress(4)[0];
21
+ // 默认监听端口
22
+ port = 0;
23
+ // 默认跨域设置
24
+ origin = '';
25
+ // 默认返回错误调用栈信息信息
26
+ errorStack = true;
27
+ // 是否开启服务注册功能
28
+ isRegistry = false;
29
+ // 服务注册列表
30
+ registry = [];
31
+ // 注册服务令牌
32
+ registryToken = '';
33
+ // 服务注册适配器
34
+ registryAdapter = 'socketio';
35
+ // 当前服务的令牌,验证成功则忽略allow配置
36
+ token = randomUUID();
37
+ allow = {
38
+ // 是否允许未验证令牌连接
39
+ untrusted: false,
40
+ // 是否允许指定IP连接
41
+ ip: [],
42
+ // 是否允许指定名称连接
43
+ caller: [],
44
+ };
45
+ }
46
+ export const formatters = {
47
+ id: function (value) {
48
+ return String(value || randomUUID());
49
+ },
50
+ name: function (value) {
51
+ return String(value || 'local');
52
+ },
53
+ group: function (value) {
54
+ return String(value || '');
55
+ },
56
+ ignore: function (value) {
57
+ if (Array.isArray(value))
58
+ return value;
59
+ if (typeof value === 'string')
60
+ return value.split(',');
61
+ return [];
62
+ },
63
+ entryInfo: function (value) {
64
+ return value;
65
+ },
66
+ host: function (value) {
67
+ return String(value || getIPAddress(4)[0]);
68
+ },
69
+ port: function (value) {
70
+ return Number(value) || 0;
71
+ },
72
+ origin: function (value) {
73
+ if (value === '*')
74
+ return value;
75
+ if (Array.isArray(value))
76
+ return value;
77
+ if (typeof value === 'string')
78
+ return value.split(',');
79
+ return '';
80
+ },
81
+ errorStack: function (value) {
82
+ return Boolean(value);
83
+ },
84
+ isRegistry: function (value) {
85
+ return Boolean(value);
86
+ },
87
+ registry: function (value) {
88
+ if ('string' === typeof value)
89
+ value = value.split(',');
90
+ if (value && !Array.isArray(value))
91
+ value = [value];
92
+ return value;
93
+ },
94
+ registryToken: function (value) {
95
+ return String(value || '');
96
+ },
97
+ registryAdapter: function (value) {
98
+ return String(value || '');
99
+ },
100
+ token: function (value) {
101
+ return String(value || '');
102
+ },
103
+ allow: function (value) {
104
+ value = value || {
105
+ untrusted: false,
106
+ ip: [],
107
+ caller: [],
108
+ };
109
+ const { ip, caller } = value;
110
+ if ('string' === typeof ip)
111
+ value.ip = ip.split(',');
112
+ if ('string' === typeof caller)
113
+ value.caller = caller.split(',');
114
+ return value;
115
+ }
116
+ };
package/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { randomUUID } from 'node:crypto';
2
- import * as app from './app.js';
3
- import { getIPAddress } from './utils.js';
2
+ import { asyncStore, AppContext, } from './app.js';
4
3
  import * as registry from './registry.js';
5
- import Module, { ModuleConfig } from './modules/module.js';
6
- import { KeepAliveConnection, keepAliveConnectionAdapters, keepAliveConnections, enabledKeepAliveConnections } from './keepalive-connection.js';
7
- export class Context extends app.Context {
4
+ import { Config } from './config.js';
5
+ import Module from './modules/module.js';
6
+ import { KeepAliveConnection, keepAliveConnections, enabledKeepAliveConnections, KeepAliveConnectionError, } from './keepalive-connection.js';
7
+ export class Context extends AppContext {
8
8
  // 请求溯源IP
9
9
  sourceIP = '';
10
10
  // 请求者IP
@@ -15,43 +15,21 @@ export class Context extends app.Context {
15
15
  callerId = '';
16
16
  // 请求者连接把柄
17
17
  connection;
18
+ constructor(context) {
19
+ super();
20
+ if (context) {
21
+ if ('toJSON' in context && typeof context.toJSON !== 'function') {
22
+ throw new Error('Context.toJSON must be a function');
23
+ }
24
+ Object.assign(this, context);
25
+ }
26
+ }
18
27
  toJSON() {
19
28
  const context = Object.assign({}, this);
20
29
  delete context.connection;
21
30
  return context;
22
31
  }
23
32
  }
24
- export class Config {
25
- // 服务ID
26
- id = randomUUID();
27
- // 服务名称
28
- name = 'local';
29
- // 服务列表路径
30
- group = '';
31
- // 非服务模块
32
- ignore = [];
33
- // 启动文件
34
- entryInfo = {
35
- // 启动文件路径
36
- path: '',
37
- // 服务列表路径
38
- group: '',
39
- };
40
- // 主机地址
41
- host = getIPAddress(4)[0];
42
- // 默认监听端口
43
- port = 0;
44
- // 默认跨域设置
45
- origin = '';
46
- // 默认返回错误调用栈信息信息
47
- errorStack = true;
48
- // 是否开启服务注册功能
49
- isRegistry = true;
50
- // 服务注册列表
51
- registry = [];
52
- // 服务注册适配器
53
- registryAdapter = 'socketio';
54
- }
55
33
  export const config = new Config();
56
34
  let genTraceIdFunction = () => randomUUID();
57
35
  export function setGenTraceIdFunction(fn) {
@@ -67,7 +45,7 @@ export function genTraceId() {
67
45
  * 获取链路跟踪上下文
68
46
  */
69
47
  export function genContext(context) {
70
- const newContext = Object.assign(new Context(), context);
48
+ const newContext = new Context(context);
71
49
  if (!newContext.traceId)
72
50
  newContext.traceId = genTraceId();
73
51
  if (!newContext.sourceIP)
@@ -80,19 +58,62 @@ export function getContext() {
80
58
  }
81
59
  export async function serve() {
82
60
  await modules.serve();
61
+ registry.startBroadcastTimer();
83
62
  }
84
63
  export async function stop() {
85
64
  await modules.stop();
65
+ registry.stopBroadcastTimer();
86
66
  }
67
+ /**
68
+ * 检查连接是否被允许
69
+ * @param ip 连接主机 IP
70
+ * @param caller 连接服务名称
71
+ * @param token 令牌
72
+ * @returns 是否被允许
73
+ */
74
+ export function checkAllow(ip, caller, token) {
75
+ // check trusted token
76
+ const trusted = !config.token || token === config.token;
77
+ if (!trusted) {
78
+ const { allow } = config;
79
+ if (!allow.untrusted) {
80
+ return { allow: false, reason: 'untrusted' };
81
+ }
82
+ if (Array.isArray(allow.ip) && allow.ip.length) {
83
+ let allowed = allow.ip.some(item => item === '*' || new RegExp(item).test(ip));
84
+ if (!allowed)
85
+ return { allow: false, reason: 'not allow ip=' + ip };
86
+ }
87
+ if (Array.isArray(allow.caller) && allow.caller.length) {
88
+ let allowed = allow.caller.some(item => item === '*' || new RegExp(item).test(caller));
89
+ if (!allowed)
90
+ return { allow: false, reason: 'not allow caller=' + caller };
91
+ }
92
+ }
93
+ return { allow: true };
94
+ }
95
+ /**
96
+ * 添加长连接
97
+ * @throws {KeepAliveConnectionError} 连接已存在
98
+ * @param connection
99
+ */
87
100
  export function addKeepAliveConnection(connection) {
101
+ if (keepAliveConnections.has(connection.data.name, connection.data.id)) {
102
+ connection.disconnect();
103
+ throw new KeepAliveConnectionError('Connection already exists', connection);
104
+ }
88
105
  keepAliveConnections.add(connection);
89
106
  }
90
107
  export function removeKeepAliveConnection(arg1, arg2) {
91
108
  if (typeof arg1 === 'string') {
109
+ const connection = keepAliveConnections.get(arg1, arg2);
110
+ if (connection)
111
+ connection.disconnect();
92
112
  keepAliveConnections.remove(arg1, arg2);
93
113
  enabledKeepAliveConnections.remove(arg1, arg2);
94
114
  }
95
115
  else {
116
+ arg1.disconnect();
96
117
  keepAliveConnections.remove(arg1);
97
118
  enabledKeepAliveConnections.remove(arg1);
98
119
  }
@@ -117,23 +138,23 @@ export async function rpc(arg1, action, params, context) {
117
138
  if (!context || !context.traceId) {
118
139
  context = getContext();
119
140
  }
120
- let connection;
141
+ let connection = null;
121
142
  if (arg1 instanceof KeepAliveConnection) {
122
143
  connection = arg1;
123
144
  }
124
145
  else if ('string' === typeof arg1) {
125
146
  connection = loadBalancePolicy(arg1);
126
- if (!connection)
127
- throw new Error(`Connection<${arg1}> not found`);
128
147
  }
129
148
  else
130
149
  throw new Error(`Unknown rpc arg1<${arg1}>`);
150
+ if (!connection)
151
+ throw new Error(`Connection<${arg1}> not found`);
131
152
  return connection.adapter.rpc(connection, action, params, context);
132
153
  }
133
- export { KeepAliveConnection, keepAliveConnectionAdapters, keepAliveConnections, enabledKeepAliveConnections };
134
- export { Module, ModuleConfig };
154
+ export * from './app.js';
155
+ export * from './keepalive-connection.js';
156
+ export { Module };
135
157
  export { registry };
136
- export const { asyncStore, setMethods, getMethods, kvMethods, sourceKVMethods, call, execute, logger, on, once, off, emit, } = app;
137
- // 放在最后导入,解决循环依赖问题
138
- import Modules from './modules/index.js';
139
- export const modules = new Modules;
158
+ // 放在最后导入,避免循环依赖问题
159
+ import * as modules from './modules/index.js';
160
+ export { modules };
@@ -1,5 +1,14 @@
1
1
  import EventEmitter from 'node:events';
2
+ export class KeepAliveConnectionError extends Error {
3
+ connection;
4
+ constructor(message, connection) {
5
+ super(message);
6
+ this.connection = connection;
7
+ }
8
+ }
2
9
  export class KeepAliveConnection extends EventEmitter {
10
+ // 是否为注册服务连接
11
+ isRegistry = false;
3
12
  // 连接是否可用
4
13
  #enabled = false;
5
14
  data;
@@ -49,6 +58,10 @@ export class KeepAliveConnection extends EventEmitter {
49
58
  keepAliveConnections.add(this);
50
59
  this.enabled = true;
51
60
  }
61
+ disconnect() {
62
+ this.enabled = false;
63
+ this.nativeConnection.disconnect();
64
+ }
52
65
  }
53
66
  export class KeepAliveConnectionStore {
54
67
  #map = new Map();