alemonjs 2.1.0 → 2.1.1
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_CONFIG.md +228 -45
- package/lib/cbp/connects/client.js +13 -32
- package/lib/cbp/connects/connect.js +3 -3
- package/lib/cbp/connects/platform.js +2 -1
- package/lib/cbp/core/connection-manager.d.ts +18 -0
- package/lib/cbp/core/connection-manager.js +67 -0
- package/lib/cbp/core/constants.d.ts +19 -0
- package/lib/cbp/core/constants.js +23 -0
- package/lib/cbp/core/load-balancer.d.ts +37 -0
- package/lib/cbp/core/load-balancer.js +119 -0
- package/lib/cbp/processor/actions.d.ts +3 -0
- package/lib/cbp/processor/actions.js +13 -24
- package/lib/cbp/processor/api.d.ts +3 -0
- package/lib/cbp/processor/api.js +13 -24
- package/lib/cbp/processor/config.d.ts +44 -16
- package/lib/cbp/processor/config.js +271 -20
- package/lib/cbp/processor/request-handler.d.ts +17 -0
- package/lib/cbp/processor/request-handler.js +65 -0
- package/lib/cbp/routers/router.js +9 -0
- package/lib/cbp/server/main.d.ts +18 -1
- package/lib/cbp/server/main.js +141 -166
- package/lib/cbp/server/testone.js +1 -5
- package/lib/main.d.ts +18 -1
- package/lib/main.js +13 -1
- package/package.json +1 -1
- package/lib/app.d.ts +0 -18
- package/lib/app.js +0 -82
- package/lib/cbp/processor/hello.html.d.ts +0 -1
- package/lib/cbp/processor/hello.html.js +0 -47
- package/lib/cbp/routers/middleware.d.ts +0 -2
- package/lib/cbp/routers/middleware.js +0 -40
- package/lib/cbp/routers/utils.d.ts +0 -2
- package/lib/cbp/routers/utils.js +0 -39
package/README_CONFIG.md
CHANGED
|
@@ -1,62 +1,245 @@
|
|
|
1
|
-
|
|
1
|
+
## 配置相关
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
### 基础配置
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
`PKG_PATH`:`string` package.json 地址
|
|
5
|
+
```yaml
|
|
6
|
+
# === 服务器配置 ===
|
|
7
|
+
port: 17117 # CBP服务器端口,快捷参数 --port
|
|
8
|
+
serverPort: 18110 # 应用服务器端口(可选,仅在需要Web服务时设置)
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
# === 应用配置 ===
|
|
11
|
+
input: 'lib/index.js' # 应用入口文件路径,快捷参数 --input
|
|
12
|
+
login: 'discord' # 登录平台标识,快捷参数 --login
|
|
13
|
+
url: 'ws://127.0.0.1:17117' # CBP服务器连接地址,快捷参数 --url
|
|
14
|
+
is_full_receive: false # 是否全量接收消息(用于分流处理)
|
|
12
15
|
|
|
13
|
-
|
|
16
|
+
# === CBP(Chat Bot Protocol)配置 ===
|
|
17
|
+
loadBalanceStrategy: 'least-connections' # 负载均衡策略,快捷参数 --loadBalanceStrategy
|
|
18
|
+
```
|
|
14
19
|
|
|
15
|
-
|
|
20
|
+
### 权限管理
|
|
16
21
|
|
|
17
22
|
```yaml
|
|
18
|
-
#
|
|
19
|
-
port: 17117 # 端口,快捷参数 --port
|
|
20
|
-
input: 'lib/index.js' # 入口地址,快捷参数 --input
|
|
21
|
-
login: 'discord' # 选择登录的平台,快捷参数 --login
|
|
22
|
-
url: 'ws://127.0.0.1:17117' # 连接阿柠檬服务URL,快捷参数 --url
|
|
23
|
-
is_full_receive: false # 不全量接收消息(用于分流处理)
|
|
24
|
-
# 禁用设置
|
|
25
|
-
disabled_text_regular: '/闭关' # 设置正则,若匹配则禁用
|
|
26
|
-
disabled_selects: # 禁用事件。若匹配则禁用
|
|
27
|
-
'private.message.create': true # 禁用私聊
|
|
28
|
-
disabled_user_id:
|
|
29
|
-
'1715713638': true # 若匹配则禁用
|
|
30
|
-
disabled_user_key:
|
|
31
|
-
'123456': true # 多匹配则禁用
|
|
32
|
-
# 重定向:把指定的文本,转为指定的内容 (禁用规则比重定向优先)
|
|
33
|
-
redirect_text_regular: '^#' # 识别前缀 #
|
|
34
|
-
redirect_text_target: '/' # 替换为 /
|
|
35
|
-
# 映射规则
|
|
36
|
-
mapping_text:
|
|
37
|
-
- regular: '/开始游戏'
|
|
38
|
-
target: '/踏入仙途'
|
|
39
|
-
# ismaster 设置
|
|
23
|
+
# === 主人权限设置 ===
|
|
40
24
|
master_id:
|
|
41
|
-
'1715713638': true
|
|
25
|
+
'1715713638': true # 通过用户ID设置主人权限
|
|
42
26
|
master_key:
|
|
43
|
-
'123456': true
|
|
44
|
-
|
|
27
|
+
'123456': true # 通过用户Key设置主人权限
|
|
28
|
+
|
|
29
|
+
# === 机器人标识设置 ===
|
|
45
30
|
bot_id:
|
|
46
|
-
'1715713638': true #
|
|
31
|
+
'1715713638': true # 将指定用户ID标识为机器人
|
|
47
32
|
bot_key:
|
|
48
|
-
'123456': true #
|
|
49
|
-
|
|
33
|
+
'123456': true # 将指定用户Key标识为机器人
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 消息过滤与控制
|
|
37
|
+
|
|
38
|
+
```yaml
|
|
39
|
+
# === 禁用控制 ===
|
|
40
|
+
disabled_text_regular: '/闭关' # 文本正则匹配,若匹配则禁用所有功能
|
|
41
|
+
disabled_selects: # 禁用特定事件类型
|
|
42
|
+
'private.message.create': true # 禁用私聊消息
|
|
43
|
+
'message.create': true # 禁用群组消息
|
|
44
|
+
'interaction.create': true # 禁用交互事件
|
|
45
|
+
disabled_user_id: # 禁用特定用户ID
|
|
46
|
+
'1715713638': true
|
|
47
|
+
disabled_user_key: # 禁用特定用户Key
|
|
48
|
+
'123456': true
|
|
49
|
+
|
|
50
|
+
# === 文本重定向 ===
|
|
51
|
+
redirect_text_regular: '^#' # 文本前缀匹配正则
|
|
52
|
+
redirect_text_target: '/' # 将匹配的前缀替换为指定内容
|
|
53
|
+
|
|
54
|
+
# === 文本映射规则 ===
|
|
55
|
+
mapping_text:
|
|
56
|
+
- regular: '/开始游戏' # 匹配的文本
|
|
57
|
+
target: '/踏入仙途' # 替换成的文本
|
|
58
|
+
- regular: '/帮助'
|
|
59
|
+
target: '/help'
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 消息处理器配置
|
|
63
|
+
|
|
64
|
+
```yaml
|
|
50
65
|
processor:
|
|
51
|
-
repeated_event_time: 60000 #
|
|
52
|
-
repeated_user_time: 1000 #
|
|
53
|
-
|
|
66
|
+
repeated_event_time: 60000 # 过滤重复MessageId的时间窗口(毫秒)
|
|
67
|
+
repeated_user_time: 1000 # 过滤同一用户连续消息的时间窗口(毫秒)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 模块管理
|
|
71
|
+
|
|
72
|
+
```yaml
|
|
73
|
+
# === 子模块配置 ===
|
|
54
74
|
apps:
|
|
55
|
-
'alemonjs-openai': true
|
|
56
|
-
#
|
|
57
|
-
#
|
|
75
|
+
'alemonjs-openai': true # 启用OpenAI模块
|
|
76
|
+
'alemonjs-xianyu': true # 启用咸鱼模块
|
|
77
|
+
# 也支持数组格式
|
|
78
|
+
# - 'alemonjs-openai'
|
|
79
|
+
# - 'alemonjs-xianyu'
|
|
80
|
+
|
|
81
|
+
# === 模块特定配置 ===
|
|
82
|
+
# 模块配置的键名应与模块名一致
|
|
58
83
|
alemonjs-openai:
|
|
59
84
|
baseURL: 'https://api.deepseek.com'
|
|
60
|
-
apiKey: ''
|
|
85
|
+
apiKey: 'your-api-key-here'
|
|
61
86
|
model: 'deepseek-chat'
|
|
62
87
|
```
|
|
88
|
+
|
|
89
|
+
## 快捷参数
|
|
90
|
+
|
|
91
|
+
启动时可使用的快捷参数:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# === 基础参数 ===
|
|
95
|
+
# 指定CBP服务器端口
|
|
96
|
+
node index.js --port 8080
|
|
97
|
+
|
|
98
|
+
# 指定应用服务器端口
|
|
99
|
+
node index.js --serverPort 3000
|
|
100
|
+
|
|
101
|
+
# 指定入口文件
|
|
102
|
+
node index.js --input ./lib/index.js
|
|
103
|
+
|
|
104
|
+
# 指定登录平台
|
|
105
|
+
node index.js --login discord
|
|
106
|
+
|
|
107
|
+
# 指定CBP服务器连接地址
|
|
108
|
+
node index.js --url ws://localhost:8080
|
|
109
|
+
|
|
110
|
+
# === CBP相关参数 ===
|
|
111
|
+
# 指定负载均衡策略
|
|
112
|
+
node index.js --loadBalanceStrategy least-connections
|
|
113
|
+
|
|
114
|
+
# 启用全量接收模式
|
|
115
|
+
node index.js --is_full_receive true
|
|
116
|
+
|
|
117
|
+
# === 组合使用示例 ===
|
|
118
|
+
# 启动Discord平台,使用轮询策略,端口8080
|
|
119
|
+
node index.js --login discord --loadBalanceStrategy round-robin --port 8080
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## 配置管理工具
|
|
123
|
+
|
|
124
|
+
使用 `alemonc` 命令行工具来管理配置:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
# 查看帮助
|
|
128
|
+
npx alemonc -h
|
|
129
|
+
|
|
130
|
+
# 添加模块
|
|
131
|
+
alemonc add apps alemonjs-openai alemonjs-xianyu
|
|
132
|
+
|
|
133
|
+
# 移除模块
|
|
134
|
+
alemonc remove apps alemonjs-openai
|
|
135
|
+
|
|
136
|
+
# 设置配置项
|
|
137
|
+
alemonc set login qq
|
|
138
|
+
alemonc set discord.token your-token
|
|
139
|
+
alemonc set loadBalanceStrategy least-connections
|
|
140
|
+
|
|
141
|
+
# 删除配置项
|
|
142
|
+
alemonc del discord
|
|
143
|
+
|
|
144
|
+
# 获取配置项
|
|
145
|
+
alemonc get login
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## 基础环境变量
|
|
149
|
+
|
|
150
|
+
`platform`:`string` - 平台标识,支持的平台类型包括:
|
|
151
|
+
- `discord` - Discord平台
|
|
152
|
+
- `qq` - QQ平台
|
|
153
|
+
- `telegram` - Telegram平台
|
|
154
|
+
- 或其他自定义平台包名(如:`@alemonjs/xxx`)
|
|
155
|
+
|
|
156
|
+
`login`:`string` - 登录标识,通常与平台名称相同或为平台的特定实例名
|
|
157
|
+
|
|
158
|
+
`url`:`string` - CBP服务器连接地址,默认为 `ws://127.0.0.1:17117`
|
|
159
|
+
|
|
160
|
+
`port`:`string | number` - CBP服务器端口,默认为 `17117`
|
|
161
|
+
|
|
162
|
+
`serverPort`:`string | number` - 应用服务器端口(可选,仅在需要启动Web服务时设置)
|
|
163
|
+
|
|
164
|
+
`is_full_receive`:`string` - 是否全量接收消息,可选值:`'true'` | `'1'` | `'false'` | `'0'`
|
|
165
|
+
|
|
166
|
+
## 文件路径相关
|
|
167
|
+
|
|
168
|
+
`LOG_PATH`:`string` - 日志文件存储路径
|
|
169
|
+
|
|
170
|
+
`PKG_PATH`:`string` - package.json 文件路径,默认为当前工作目录下的 `package.json`
|
|
171
|
+
|
|
172
|
+
`CFG_PATH`:`string` - 配置文件路径,默认为当前工作目录下的 `alemon.config.yaml`
|
|
173
|
+
|
|
174
|
+
## 运行环境
|
|
175
|
+
|
|
176
|
+
`NODE_ENV`:`development | production` - 环境判断,用于区分开发环境和生产环境
|
|
177
|
+
|
|
178
|
+
## CBP
|
|
179
|
+
|
|
180
|
+
ChatBot Protocol
|
|
181
|
+
|
|
182
|
+
ALemonJS的核心通信协议,负责客户端与平台之间的消息传输和负载均衡。
|
|
183
|
+
|
|
184
|
+
### 负载均衡策略
|
|
185
|
+
|
|
186
|
+
```yaml
|
|
187
|
+
# 负载均衡策略配置
|
|
188
|
+
loadBalanceStrategy: 'least-connections' # 默认策略
|
|
189
|
+
|
|
190
|
+
# 支持的策略类型:
|
|
191
|
+
# - 'round-robin': 轮询算法
|
|
192
|
+
# - 'least-connections': 最少连接算法(推荐)
|
|
193
|
+
# - 'random': 随机算法
|
|
194
|
+
# - 'first-available': 首个可用算法
|
|
195
|
+
|
|
196
|
+
# 支持的策略类型:
|
|
197
|
+
# - 'round-robin': 轮询算法
|
|
198
|
+
# - 'least-connections': 最少连接算法(推荐)
|
|
199
|
+
# - 'random': 随机算法
|
|
200
|
+
# - 'first-available': 首个可用算法
|
|
201
|
+
|
|
202
|
+
#1. **轮询算法 (round-robin)**
|
|
203
|
+
# - 按固定顺序依次分配请求
|
|
204
|
+
# - 适用于客户端处理能力相近的场景
|
|
205
|
+
# - 请求分布均匀
|
|
206
|
+
|
|
207
|
+
#2. **最少连接算法 (least-connections)**
|
|
208
|
+
# - 优选当前连接数最少的客户端
|
|
209
|
+
# - 适用于客户端处理能力不同的场景
|
|
210
|
+
# - 提供最优的负载分配
|
|
211
|
+
|
|
212
|
+
#3. **随机算法 (random)**
|
|
213
|
+
# - 随机选择可用客户端
|
|
214
|
+
# - 简单的概率性分布
|
|
215
|
+
# - 适用于临时测试场景
|
|
216
|
+
|
|
217
|
+
#4. **首个可用算法 (first-available)**
|
|
218
|
+
# - 总是选择第一个健康的客户端
|
|
219
|
+
# - 类似主备模式
|
|
220
|
+
# - 适用于有优先级需求的场景
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### CBP连接配置
|
|
224
|
+
|
|
225
|
+
```yaml
|
|
226
|
+
# CBP服务器配置
|
|
227
|
+
cbp:
|
|
228
|
+
# 连接超时时间(毫秒)
|
|
229
|
+
timeout: 180000 # 3分钟
|
|
230
|
+
|
|
231
|
+
# 重连间隔(毫秒)
|
|
232
|
+
reconnectInterval: 6000 # 6秒
|
|
233
|
+
|
|
234
|
+
# 心跳间隔(毫秒)
|
|
235
|
+
heartbeatInterval: 18000 # 18秒
|
|
236
|
+
|
|
237
|
+
# 健康检查间隔(毫秒)
|
|
238
|
+
healthCheckInterval: 30000 # 30秒
|
|
239
|
+
|
|
240
|
+
# 连接认证头配置
|
|
241
|
+
headers:
|
|
242
|
+
user-agent: 'platform' # 平台标识
|
|
243
|
+
x-device-id: 'auto-generated' # 设备ID(自动生成)
|
|
244
|
+
x-full-receive: '0' # 是否全量接收:'1' 开启,'0' 关闭
|
|
245
|
+
```
|
|
@@ -18,8 +18,11 @@ import { createResult } from '../../core/utils.js';
|
|
|
18
18
|
import '../../app/hook-use-api.js';
|
|
19
19
|
import '../../app/message-api.js';
|
|
20
20
|
import '../../app/message-format.js';
|
|
21
|
-
import { deviceId,
|
|
21
|
+
import { deviceId, reconnectInterval } from '../processor/config.js';
|
|
22
|
+
import { handleApiResponse } from '../processor/api.js';
|
|
23
|
+
import { handleActionResponse } from '../processor/actions.js';
|
|
22
24
|
import { useHeartbeat } from './connect.js';
|
|
25
|
+
import { FULL_RECEIVE_HEADER, DEVICE_ID_HEADER, USER_AGENT_HEADER } from '../core/constants.js';
|
|
23
26
|
|
|
24
27
|
const cbpClient = (url, options = {}) => {
|
|
25
28
|
if (global.chatbotClient) {
|
|
@@ -72,45 +75,23 @@ const cbpClient = (url, options = {}) => {
|
|
|
72
75
|
if (parsedMessage?.activeId) {
|
|
73
76
|
if (parsedMessage.active === 'sync') {
|
|
74
77
|
const configs = parsedMessage.payload;
|
|
75
|
-
const env = configs
|
|
78
|
+
const env = configs?.env ?? {};
|
|
76
79
|
for (const key in env) {
|
|
77
80
|
process.env[key] = env[key];
|
|
78
81
|
}
|
|
79
82
|
}
|
|
80
83
|
}
|
|
81
84
|
else if (parsedMessage?.apiId) {
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (timeout) {
|
|
87
|
-
apiTimeouts.delete(parsedMessage.apiId);
|
|
88
|
-
clearTimeout(timeout);
|
|
89
|
-
}
|
|
90
|
-
if (Array.isArray(parsedMessage.payload)) {
|
|
91
|
-
resolve(parsedMessage.payload);
|
|
92
|
-
}
|
|
93
|
-
else {
|
|
94
|
-
resolve([createResult(ResultCode.Fail, '接口处理错误', null)]);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
85
|
+
const results = Array.isArray(parsedMessage.payload)
|
|
86
|
+
? parsedMessage.payload
|
|
87
|
+
: [createResult(ResultCode.Fail, '接口处理错误', null)];
|
|
88
|
+
handleApiResponse(parsedMessage.apiId, results);
|
|
97
89
|
}
|
|
98
90
|
else if (parsedMessage?.actionId) {
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (timeout) {
|
|
104
|
-
actionTimeouts.delete(parsedMessage.actionId);
|
|
105
|
-
clearTimeout(timeout);
|
|
106
|
-
}
|
|
107
|
-
if (Array.isArray(parsedMessage.payload)) {
|
|
108
|
-
resolve(parsedMessage.payload);
|
|
109
|
-
}
|
|
110
|
-
else {
|
|
111
|
-
resolve([createResult(ResultCode.Fail, '消费处理错误', null)]);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
91
|
+
const results = Array.isArray(parsedMessage.payload)
|
|
92
|
+
? parsedMessage.payload
|
|
93
|
+
: [createResult(ResultCode.Fail, '消费处理错误', null)];
|
|
94
|
+
handleActionResponse(parsedMessage.actionId, results);
|
|
114
95
|
}
|
|
115
96
|
else if (parsedMessage.name) {
|
|
116
97
|
onProcessor(parsedMessage.name, parsedMessage, parsedMessage.value);
|
|
@@ -3,7 +3,7 @@ import 'fs';
|
|
|
3
3
|
import 'path';
|
|
4
4
|
import 'yaml';
|
|
5
5
|
import '../../core/utils.js';
|
|
6
|
-
import {
|
|
6
|
+
import { TIME_CONFIG } from '../core/constants.js';
|
|
7
7
|
|
|
8
8
|
const useHeartbeat = ({ ping, isConnected, terminate }) => {
|
|
9
9
|
let heartbeatTimer = null;
|
|
@@ -17,7 +17,7 @@ const useHeartbeat = ({ ping, isConnected, terminate }) => {
|
|
|
17
17
|
const callback = () => {
|
|
18
18
|
if (isConnected()) {
|
|
19
19
|
const diff = Date.now() - lastPong;
|
|
20
|
-
const max = HEARTBEAT_INTERVAL * 2;
|
|
20
|
+
const max = TIME_CONFIG.HEARTBEAT_INTERVAL * 2;
|
|
21
21
|
if (diff > max) {
|
|
22
22
|
logger.debug({
|
|
23
23
|
code: ResultCode.Fail,
|
|
@@ -33,7 +33,7 @@ const useHeartbeat = ({ ping, isConnected, terminate }) => {
|
|
|
33
33
|
message: '发送 ping',
|
|
34
34
|
data: null
|
|
35
35
|
});
|
|
36
|
-
heartbeatTimer = setTimeout(callback, HEARTBEAT_INTERVAL);
|
|
36
|
+
heartbeatTimer = setTimeout(callback, TIME_CONFIG.HEARTBEAT_INTERVAL);
|
|
37
37
|
}
|
|
38
38
|
else {
|
|
39
39
|
stopHeartbeat();
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import * as flattedJSON from 'flatted';
|
|
2
2
|
import { WebSocket } from 'ws';
|
|
3
|
-
import { deviceId,
|
|
3
|
+
import { deviceId, reconnectInterval } from '../processor/config.js';
|
|
4
4
|
import { ResultCode } from '../../core/variable.js';
|
|
5
5
|
import 'fs';
|
|
6
6
|
import 'path';
|
|
7
7
|
import 'yaml';
|
|
8
8
|
import '../../core/utils.js';
|
|
9
9
|
import { useHeartbeat } from './connect.js';
|
|
10
|
+
import { DEVICE_ID_HEADER, USER_AGENT_HEADER } from '../core/constants.js';
|
|
10
11
|
|
|
11
12
|
const cbpPlatform = (url, options = {
|
|
12
13
|
open: () => { }
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { WebSocket } from 'ws';
|
|
2
|
+
export interface ConnectionState {
|
|
3
|
+
connections: Map<string, WebSocket>;
|
|
4
|
+
lastActivity: Map<string, number>;
|
|
5
|
+
metadata: Map<string, any>;
|
|
6
|
+
}
|
|
7
|
+
export declare const createConnectionState: () => ConnectionState;
|
|
8
|
+
export declare const addConnection: (state: ConnectionState, id: string, ws: WebSocket, metadata?: any) => ConnectionState;
|
|
9
|
+
export declare const removeConnection: (state: ConnectionState, id: string) => ConnectionState;
|
|
10
|
+
export declare const getConnection: (state: ConnectionState, id: string) => WebSocket | undefined;
|
|
11
|
+
export declare const getHealthyConnectionIds: (state: ConnectionState) => string[];
|
|
12
|
+
export declare const getConnectionStats: (state: ConnectionState) => {
|
|
13
|
+
total: number;
|
|
14
|
+
healthy: number;
|
|
15
|
+
unhealthy: number;
|
|
16
|
+
lastUpdate: number;
|
|
17
|
+
};
|
|
18
|
+
export declare const clearAllConnections: (state: ConnectionState) => ConnectionState;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { WebSocket } from 'ws';
|
|
2
|
+
|
|
3
|
+
const createConnectionState = () => ({
|
|
4
|
+
connections: new Map(),
|
|
5
|
+
lastActivity: new Map(),
|
|
6
|
+
metadata: new Map()
|
|
7
|
+
});
|
|
8
|
+
const addConnection = (state, id, ws, metadata) => {
|
|
9
|
+
const newState = {
|
|
10
|
+
connections: new Map(state.connections),
|
|
11
|
+
lastActivity: new Map(state.lastActivity),
|
|
12
|
+
metadata: new Map(state.metadata)
|
|
13
|
+
};
|
|
14
|
+
newState.connections.set(id, ws);
|
|
15
|
+
newState.lastActivity.set(id, Date.now());
|
|
16
|
+
if (metadata) {
|
|
17
|
+
newState.metadata.set(id, metadata);
|
|
18
|
+
}
|
|
19
|
+
return newState;
|
|
20
|
+
};
|
|
21
|
+
const removeConnection = (state, id) => {
|
|
22
|
+
const newState = {
|
|
23
|
+
connections: new Map(state.connections),
|
|
24
|
+
lastActivity: new Map(state.lastActivity),
|
|
25
|
+
metadata: new Map(state.metadata)
|
|
26
|
+
};
|
|
27
|
+
const ws = newState.connections.get(id);
|
|
28
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
29
|
+
ws.close();
|
|
30
|
+
}
|
|
31
|
+
newState.connections.delete(id);
|
|
32
|
+
newState.lastActivity.delete(id);
|
|
33
|
+
newState.metadata.delete(id);
|
|
34
|
+
return newState;
|
|
35
|
+
};
|
|
36
|
+
const getConnection = (state, id) => {
|
|
37
|
+
return state.connections.get(id);
|
|
38
|
+
};
|
|
39
|
+
const getHealthyConnectionIds = (state) => {
|
|
40
|
+
const healthyIds = [];
|
|
41
|
+
for (const [id, ws] of state.connections.entries()) {
|
|
42
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
43
|
+
healthyIds.push(id);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return healthyIds;
|
|
47
|
+
};
|
|
48
|
+
const getConnectionStats = (state) => {
|
|
49
|
+
const total = state.connections.size;
|
|
50
|
+
const healthy = getHealthyConnectionIds(state).length;
|
|
51
|
+
return {
|
|
52
|
+
total,
|
|
53
|
+
healthy,
|
|
54
|
+
unhealthy: total - healthy,
|
|
55
|
+
lastUpdate: Date.now()
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
const clearAllConnections = (state) => {
|
|
59
|
+
for (const ws of state.connections.values()) {
|
|
60
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
61
|
+
ws.close();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return createConnectionState();
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export { addConnection, clearAllConnections, createConnectionState, getConnection, getConnectionStats, getHealthyConnectionIds, removeConnection };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare const USER_AGENT_HEADER = "user-agent";
|
|
2
|
+
export declare const DEVICE_ID_HEADER = "x-device-id";
|
|
3
|
+
export declare const FULL_RECEIVE_HEADER = "x-full-receive";
|
|
4
|
+
export declare const USER_AGENT_HEADER_VALUE_MAP: {
|
|
5
|
+
readonly platform: "platform";
|
|
6
|
+
readonly client: "client";
|
|
7
|
+
readonly testone: "testone";
|
|
8
|
+
};
|
|
9
|
+
export declare const TIME_CONFIG: {
|
|
10
|
+
readonly TIMEOUT: number;
|
|
11
|
+
readonly RECONNECT_INTERVAL: number;
|
|
12
|
+
readonly HEARTBEAT_INTERVAL: number;
|
|
13
|
+
};
|
|
14
|
+
export declare const HEALTH_CHECK_CONFIG: {
|
|
15
|
+
readonly INTERVAL: 30000;
|
|
16
|
+
readonly MAX_CONSECUTIVE_FAILURES: 3;
|
|
17
|
+
readonly PING_TIMEOUT: 5000;
|
|
18
|
+
};
|
|
19
|
+
export declare const generateUniqueId: () => string;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const USER_AGENT_HEADER = 'user-agent';
|
|
2
|
+
const DEVICE_ID_HEADER = 'x-device-id';
|
|
3
|
+
const FULL_RECEIVE_HEADER = 'x-full-receive';
|
|
4
|
+
const USER_AGENT_HEADER_VALUE_MAP = {
|
|
5
|
+
platform: 'platform',
|
|
6
|
+
client: 'client',
|
|
7
|
+
testone: 'testone'
|
|
8
|
+
};
|
|
9
|
+
const TIME_CONFIG = {
|
|
10
|
+
TIMEOUT: 1000 * 60 * 3,
|
|
11
|
+
RECONNECT_INTERVAL: 1000 * 6,
|
|
12
|
+
HEARTBEAT_INTERVAL: 1000 * 18
|
|
13
|
+
};
|
|
14
|
+
const HEALTH_CHECK_CONFIG = {
|
|
15
|
+
INTERVAL: 30000,
|
|
16
|
+
MAX_CONSECUTIVE_FAILURES: 3,
|
|
17
|
+
PING_TIMEOUT: 5000
|
|
18
|
+
};
|
|
19
|
+
const generateUniqueId = () => {
|
|
20
|
+
return Date.now().toString(36) + Math.random().toString(36).substring(2);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export { DEVICE_ID_HEADER, FULL_RECEIVE_HEADER, HEALTH_CHECK_CONFIG, TIME_CONFIG, USER_AGENT_HEADER, USER_AGENT_HEADER_VALUE_MAP, generateUniqueId };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ConnectionState } from './connection-manager';
|
|
2
|
+
export type LoadBalanceStrategy = 'round-robin' | 'least-connections' | 'random' | 'first-available';
|
|
3
|
+
export declare const LoadBalanceStrategyEnum: {
|
|
4
|
+
readonly ROUND_ROBIN: "round-robin";
|
|
5
|
+
readonly LEAST_CONNECTIONS: "least-connections";
|
|
6
|
+
readonly RANDOM: "random";
|
|
7
|
+
readonly FIRST_AVAILABLE: "first-available";
|
|
8
|
+
};
|
|
9
|
+
export interface LoadBalancerState {
|
|
10
|
+
strategy: LoadBalanceStrategy;
|
|
11
|
+
roundRobinIndex: number;
|
|
12
|
+
bindingCounts: Map<string, number>;
|
|
13
|
+
}
|
|
14
|
+
export declare const createLoadBalancerState: (strategy?: LoadBalanceStrategy) => LoadBalancerState;
|
|
15
|
+
export declare const selectByRoundRobin: (availableIds: string[], currentIndex: number) => {
|
|
16
|
+
selectedId: string | null;
|
|
17
|
+
nextIndex: number;
|
|
18
|
+
};
|
|
19
|
+
export declare const selectByLeastConnections: (availableIds: string[], bindingCounts: Map<string, number>) => string | null;
|
|
20
|
+
export declare const selectByRandom: (availableIds: string[]) => string | null;
|
|
21
|
+
export declare const selectFirstAvailable: (availableIds: string[]) => string | null;
|
|
22
|
+
export declare const selectConnection: (connectionState: ConnectionState, balancerState: LoadBalancerState) => {
|
|
23
|
+
selectedId: string | null;
|
|
24
|
+
newBalancerState: LoadBalancerState;
|
|
25
|
+
};
|
|
26
|
+
export declare const incrementBindingCount: (balancerState: LoadBalancerState, connectionId: string) => LoadBalancerState;
|
|
27
|
+
export declare const decrementBindingCount: (balancerState: LoadBalancerState, connectionId: string) => LoadBalancerState;
|
|
28
|
+
export declare const getLoadBalancerStats: (balancerState: LoadBalancerState) => {
|
|
29
|
+
strategy: LoadBalanceStrategy;
|
|
30
|
+
roundRobinIndex: number;
|
|
31
|
+
totalConnections: number;
|
|
32
|
+
totalBindings: number;
|
|
33
|
+
bindingCounts: {
|
|
34
|
+
[k: string]: number;
|
|
35
|
+
};
|
|
36
|
+
lastUpdate: number;
|
|
37
|
+
};
|