@zhin.js/adapter-onebot11 1.1.4 → 1.1.6
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/CHANGELOG.md +41 -0
- package/README.md +4 -0
- package/lib/protocol.js +15 -1
- package/lib/ws-endpoint.d.ts +2 -1
- package/lib/ws-endpoint.js +28 -4
- package/lib/ws-transport.js +12 -4
- package/lib/ws-types.d.ts +1 -1
- package/lib/wss-endpoint.d.ts +2 -1
- package/lib/wss-endpoint.js +41 -3
- package/package.json +16 -16
- package/src/protocol.ts +16 -1
- package/src/ws-endpoint.ts +26 -4
- package/src/ws-transport.ts +11 -4
- package/src/ws-types.ts +1 -1
- package/src/wss-endpoint.ts +36 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# @zhin.js/adapter-onebot11
|
|
2
2
|
|
|
3
|
+
## 1.1.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [badcc1f]
|
|
8
|
+
- @zhin.js/core@1.1.41
|
|
9
|
+
- @zhin.js/feature-kit@1.1.3
|
|
10
|
+
- @zhin.js/adapter@1.1.17
|
|
11
|
+
- @zhin.js/agent-feature@1.1.4
|
|
12
|
+
- @zhin.js/skill@1.1.4
|
|
13
|
+
- @zhin.js/tool@1.1.4
|
|
14
|
+
- @zhin.js/host-http@1.1.4
|
|
15
|
+
- @zhin.js/permission@1.1.4
|
|
16
|
+
|
|
17
|
+
## 1.1.5
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- b9b3953: Fix adapter connection races, transport health reporting, candidate rollback and delivery acknowledgement boundaries. Separate generation admission from locally observed connectivity, reject unconfirmed message receipts, and mark uncertain delivery results without recommending automatic replay.
|
|
22
|
+
|
|
23
|
+
Class and compact endpoints should expose a `transportState` getter to report physical connection or local listener health. Unobserved endpoints now report an unknown transport and remain offline in diagnostics. Platform stability tiers remain unchanged until real account acceptance evidence is available.
|
|
24
|
+
|
|
25
|
+
- Updated dependencies [b9b3953]
|
|
26
|
+
- Updated dependencies [b9b3953]
|
|
27
|
+
- Updated dependencies [b9b3953]
|
|
28
|
+
- Updated dependencies [b9b3953]
|
|
29
|
+
- Updated dependencies [b9b3953]
|
|
30
|
+
- Updated dependencies [b9b3953]
|
|
31
|
+
- Updated dependencies [b9b3953]
|
|
32
|
+
- Updated dependencies [b9b3953]
|
|
33
|
+
- Updated dependencies [b9b3953]
|
|
34
|
+
- Updated dependencies [b9b3953]
|
|
35
|
+
- Updated dependencies [b9b3953]
|
|
36
|
+
- @zhin.js/adapter@1.1.16
|
|
37
|
+
- @zhin.js/im-contract@1.1.3
|
|
38
|
+
- @zhin.js/core@1.1.40
|
|
39
|
+
- @zhin.js/host-http@1.1.3
|
|
40
|
+
- @zhin.js/tool@1.1.3
|
|
41
|
+
- @zhin.js/skill@1.1.3
|
|
42
|
+
- @zhin.js/agent-feature@1.1.3
|
|
43
|
+
|
|
3
44
|
## 1.1.4
|
|
4
45
|
|
|
5
46
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -103,3 +103,7 @@ plugins:
|
|
|
103
103
|
## 许可证
|
|
104
104
|
|
|
105
105
|
MIT License
|
|
106
|
+
|
|
107
|
+
### 高阶消息支持边界
|
|
108
|
+
|
|
109
|
+
OneBot11 标准支持链接分享:canonical share 的 url/title/description/image 分别映射标准 url/title/content/image,保留标题与描述。具体桥是否实现需实机确认;NapCat 的 share 发送限制不能以标准支持推断通过。标准没有 Markdown 与 keyboard 段,直接出站明确 `unsupported_operation / not_sent`;文字交互降级不能算原生按钮支持。参考:[OneBot11 消息段规范](https://github.com/botuniverse/onebot-11/blob/master/message/segment.md)。
|
package/lib/protocol.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Canonicalization is owned by gateway/core before endpoint.send.
|
|
4
4
|
* Spec: https://github.com/botuniverse/onebot-11
|
|
5
5
|
*/
|
|
6
|
-
import { isMediaRef } from '@zhin.js/im-contract';
|
|
6
|
+
import { EndpointDeliveryError, isMediaRef } from '@zhin.js/im-contract';
|
|
7
7
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
8
8
|
const logger = getLogger('onebot11');
|
|
9
9
|
function normalizeConnection(connection) {
|
|
@@ -224,6 +224,20 @@ function oneBotMediaSegment(type, data) {
|
|
|
224
224
|
function canonicalToOneBotSegment(segment) {
|
|
225
225
|
const data = segment.data ?? {};
|
|
226
226
|
switch (segment.type) {
|
|
227
|
+
case 'markdown':
|
|
228
|
+
case 'keyboard':
|
|
229
|
+
throw new EndpointDeliveryError('unsupported_operation', `OneBot11 standard has no ${segment.type} message segment`, 'not_sent');
|
|
230
|
+
case 'share': {
|
|
231
|
+
if (['audio', 'artist', 'duration', 'config'].some(key => data[key] !== undefined)) {
|
|
232
|
+
throw new EndpointDeliveryError('unsupported_operation', 'onebot11 rich share metadata is not implemented', 'not_sent');
|
|
233
|
+
}
|
|
234
|
+
if (typeof data.url !== 'string' || !/^https?:\/\//.test(data.url) || typeof data.title !== 'string' || !data.title) {
|
|
235
|
+
throw new EndpointDeliveryError('unsupported_operation', 'Invalid OneBot11 share URL or title', 'not_sent');
|
|
236
|
+
}
|
|
237
|
+
return { type: 'share', data: { url: data.url, title: data.title,
|
|
238
|
+
...(typeof (data.description ?? data.content) === 'string' ? { content: data.description ?? data.content } : {}),
|
|
239
|
+
...(typeof data.image === 'string' ? { image: data.image } : {}) } };
|
|
240
|
+
}
|
|
227
241
|
case 'mention': {
|
|
228
242
|
const target = data.target ?? data.qq ?? data.id;
|
|
229
243
|
if (target == null)
|
package/lib/ws-endpoint.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ClientEndpoint, type EndpointControl, type EndpointManagement, type EndpointSendRequest } from 'zhin.js/adapter';
|
|
1
|
+
import { ClientEndpoint, type EndpointControl, type EndpointManagement, type EndpointSendRequest, type EndpointTransportState } from 'zhin.js/adapter';
|
|
2
2
|
import type { CapabilityId } from 'zhin.js';
|
|
3
3
|
import { type OneBot11WsConfig } from './protocol.js';
|
|
4
4
|
import { type OneBot11WsCreateOptions, type OneBot11WsSocket } from './ws-types.js';
|
|
@@ -15,6 +15,7 @@ export declare class OneBot11WsEndpoint extends ClientEndpoint<Onebot11Client> {
|
|
|
15
15
|
readonly control: EndpointControl;
|
|
16
16
|
readonly content: import("@zhin.js/adapter").EndpointContentPort;
|
|
17
17
|
constructor(options: OneBot11WsEndpointOptions);
|
|
18
|
+
get transportState(): EndpointTransportState;
|
|
18
19
|
start(): Promise<void>;
|
|
19
20
|
stop(): Promise<void>;
|
|
20
21
|
send({ conversation, payload }: EndpointSendRequest): Promise<string>;
|
package/lib/ws-endpoint.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { EndpointDeliveryError } from '@zhin.js/im-contract';
|
|
1
2
|
/**
|
|
2
3
|
* OneBot11 WS client endpoint — outbound connect to OneBot implementation.
|
|
3
4
|
*/
|
|
@@ -43,9 +44,12 @@ export class OneBot11WsEndpoint extends ClientEndpoint {
|
|
|
43
44
|
maxIntervalMs: config.reconnect_interval,
|
|
44
45
|
jitterMs: 0,
|
|
45
46
|
},
|
|
46
|
-
heartbeat: { intervalMs: config.heartbeat_interval },
|
|
47
|
+
heartbeat: { intervalMs: config.heartbeat_interval, watchdogMisses: 2 },
|
|
47
48
|
});
|
|
48
49
|
}
|
|
50
|
+
get transportState() {
|
|
51
|
+
return this.#lifecycle.state;
|
|
52
|
+
}
|
|
49
53
|
async start() {
|
|
50
54
|
if (this.#lifecycle.started)
|
|
51
55
|
return;
|
|
@@ -77,7 +81,11 @@ export class OneBot11WsEndpoint extends ClientEndpoint {
|
|
|
77
81
|
const message = formatOutboundSegments(payload);
|
|
78
82
|
const { action, params } = buildSendAction(conversation, message);
|
|
79
83
|
const data = await callOnebot11Client(this.client, action, params);
|
|
80
|
-
const
|
|
84
|
+
const rawId = data?.message_id;
|
|
85
|
+
if ((typeof rawId !== 'number' && typeof rawId !== 'string') || String(rawId).trim() === '' || (typeof rawId === 'number' && !Number.isFinite(rawId))) {
|
|
86
|
+
throw new EndpointDeliveryError('delivery_unconfirmed', 'Message delivery is unconfirmed: platform returned no message_id', 'unknown');
|
|
87
|
+
}
|
|
88
|
+
const messageId = String(rawId);
|
|
81
89
|
this.#logger.debug(formatCompact({
|
|
82
90
|
op: 'onebot11_send',
|
|
83
91
|
endpoint: this.#options.config.id,
|
|
@@ -92,7 +100,7 @@ export class OneBot11WsEndpoint extends ClientEndpoint {
|
|
|
92
100
|
await callOnebot11Client(this.client, 'delete_msg', { message_id: Number(messageId) });
|
|
93
101
|
}
|
|
94
102
|
#callApi(action, params = {}) {
|
|
95
|
-
return callOneBot11WsAction(this.#ws, this.#pending, this.#requestId, action, params);
|
|
103
|
+
return callOneBot11WsAction(this.#lifecycle.state === 'open' ? this.#ws : undefined, this.#pending, this.#requestId, action, params);
|
|
96
104
|
}
|
|
97
105
|
#admitRaw(ev) {
|
|
98
106
|
if (!isMessageEvent(ev)) {
|
|
@@ -138,6 +146,9 @@ export class OneBot11WsEndpoint extends ClientEndpoint {
|
|
|
138
146
|
const ws = create(url, { headers });
|
|
139
147
|
this.#ws = ws;
|
|
140
148
|
handle.onForceClose(() => {
|
|
149
|
+
if (this.#ws === ws) {
|
|
150
|
+
rejectAllPending(this.#pending);
|
|
151
|
+
}
|
|
141
152
|
try {
|
|
142
153
|
ws.close();
|
|
143
154
|
}
|
|
@@ -146,7 +157,7 @@ export class OneBot11WsEndpoint extends ClientEndpoint {
|
|
|
146
157
|
}
|
|
147
158
|
});
|
|
148
159
|
ws.on('open', () => {
|
|
149
|
-
if (settled)
|
|
160
|
+
if (settled || this.#ws !== ws || !this.#lifecycle.started)
|
|
150
161
|
return;
|
|
151
162
|
settled = true;
|
|
152
163
|
if (!this.#options.config.access_token) {
|
|
@@ -164,7 +175,14 @@ export class OneBot11WsEndpoint extends ClientEndpoint {
|
|
|
164
175
|
this.#lifecycle.startHeartbeat(() => this.#ws?.ping?.());
|
|
165
176
|
resolve();
|
|
166
177
|
});
|
|
178
|
+
ws.on('pong', () => {
|
|
179
|
+
if (this.#ws === ws)
|
|
180
|
+
this.#lifecycle.notifyHeartbeatAck();
|
|
181
|
+
});
|
|
167
182
|
ws.on('message', (data) => {
|
|
183
|
+
if (this.#ws !== ws || ws.readyState !== 1 || !this.#lifecycle.started)
|
|
184
|
+
return;
|
|
185
|
+
this.#lifecycle.notifyHeartbeatAck();
|
|
168
186
|
handleOneBot11WsMessage(data, {
|
|
169
187
|
endpointId: this.#options.config.id,
|
|
170
188
|
pending: this.#pending,
|
|
@@ -172,6 +190,10 @@ export class OneBot11WsEndpoint extends ClientEndpoint {
|
|
|
172
190
|
});
|
|
173
191
|
});
|
|
174
192
|
ws.on('close', (code, reason) => {
|
|
193
|
+
if (this.#ws !== ws)
|
|
194
|
+
return;
|
|
195
|
+
this.#ws = undefined;
|
|
196
|
+
rejectAllPending(this.#pending);
|
|
175
197
|
const reasonStr = typeof reason === 'string'
|
|
176
198
|
? reason
|
|
177
199
|
: Buffer.isBuffer(reason)
|
|
@@ -192,6 +214,8 @@ export class OneBot11WsEndpoint extends ClientEndpoint {
|
|
|
192
214
|
handle.notifyClosed(`OneBot11 WS 关闭: ${codeNum} ${reasonStr || 'closed'}${codeHint}`);
|
|
193
215
|
});
|
|
194
216
|
ws.on('error', (err) => {
|
|
217
|
+
if (this.#ws !== ws)
|
|
218
|
+
return;
|
|
195
219
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
196
220
|
this.#logger.warn(formatCompact({
|
|
197
221
|
op: 'ws_error',
|
package/lib/ws-transport.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { EndpointDeliveryError } from '@zhin.js/im-contract';
|
|
1
2
|
import { clearTimeout, setTimeout } from 'node:timers';
|
|
2
3
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
3
4
|
import { WS_OPEN, } from './ws-types.js';
|
|
@@ -37,23 +38,30 @@ export function handleOneBot11WsMessage(data, options) {
|
|
|
37
38
|
}
|
|
38
39
|
export function callOneBot11WsAction(ws, pending, requestId, action, params) {
|
|
39
40
|
if (!ws || ws.readyState !== WS_OPEN) {
|
|
40
|
-
return Promise.reject(new
|
|
41
|
+
return Promise.reject(new EndpointDeliveryError('endpoint_disconnected', 'WebSocket 未连接', 'not_sent'));
|
|
41
42
|
}
|
|
42
43
|
const echo = `ob11_${++requestId.value}`;
|
|
43
44
|
const req = { action, params, echo };
|
|
44
45
|
return new Promise((resolve, reject) => {
|
|
45
46
|
const timeout = setTimeout(() => {
|
|
46
47
|
pending.delete(echo);
|
|
47
|
-
reject(new
|
|
48
|
+
reject(new EndpointDeliveryError('endpoint_timeout', `OneBot11 动作超时: ${action}`, 'unknown'));
|
|
48
49
|
}, 30_000);
|
|
49
50
|
pending.set(echo, { resolve, reject, timeout });
|
|
50
|
-
|
|
51
|
+
try {
|
|
52
|
+
ws.send(JSON.stringify(req));
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
pending.delete(echo);
|
|
56
|
+
clearTimeout(timeout);
|
|
57
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
58
|
+
}
|
|
51
59
|
});
|
|
52
60
|
}
|
|
53
61
|
export function rejectAllPending(pending, message = '连接已关闭') {
|
|
54
62
|
for (const [, entry] of pending) {
|
|
55
63
|
clearTimeout(entry.timeout);
|
|
56
|
-
entry.reject(new
|
|
64
|
+
entry.reject(new EndpointDeliveryError('endpoint_disconnected', message, 'unknown'));
|
|
57
65
|
}
|
|
58
66
|
pending.clear();
|
|
59
67
|
}
|
package/lib/ws-types.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export interface OneBot11WsSocket {
|
|
|
4
4
|
send(data: string): void;
|
|
5
5
|
close(code?: number, reason?: string): void;
|
|
6
6
|
ping?(): void;
|
|
7
|
-
on(event: 'open' | 'message' | 'close' | 'error', listener: (...args: unknown[]) => void): void;
|
|
7
|
+
on(event: 'open' | 'message' | 'close' | 'error' | 'pong', listener: (...args: unknown[]) => void): void;
|
|
8
8
|
}
|
|
9
9
|
export interface OneBot11WsCreateOptions {
|
|
10
10
|
readonly headers?: Record<string, string>;
|
package/lib/wss-endpoint.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OneBot11 reverse WSS endpoint — accepts inbound WebSocket from OneBot implementation.
|
|
3
3
|
*/
|
|
4
|
-
import { ClientEndpoint, type EndpointControl, type EndpointManagement, type EndpointSendRequest } from 'zhin.js/adapter';
|
|
4
|
+
import { ClientEndpoint, type EndpointControl, type EndpointManagement, type EndpointSendRequest, type EndpointTransportState } from 'zhin.js/adapter';
|
|
5
5
|
import type { HttpHost } from '@zhin.js/host-http';
|
|
6
6
|
import type { CapabilityId } from 'zhin.js';
|
|
7
7
|
import { type OneBot11WssConfig } from './protocol.js';
|
|
@@ -18,6 +18,7 @@ export declare class OneBot11WssEndpoint extends ClientEndpoint<Onebot11Client>
|
|
|
18
18
|
readonly control: EndpointControl;
|
|
19
19
|
readonly content: import("@zhin.js/adapter").EndpointContentPort;
|
|
20
20
|
constructor(options: OneBot11WssEndpointOptions);
|
|
21
|
+
get transportState(): EndpointTransportState;
|
|
21
22
|
start(): Promise<void>;
|
|
22
23
|
stop(): Promise<void>;
|
|
23
24
|
send({ conversation, payload }: EndpointSendRequest): Promise<string>;
|
package/lib/wss-endpoint.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { EndpointDeliveryError } from '@zhin.js/im-contract';
|
|
1
2
|
/**
|
|
2
3
|
* OneBot11 reverse WSS endpoint — accepts inbound WebSocket from OneBot implementation.
|
|
3
4
|
*/
|
|
@@ -24,6 +25,7 @@ export class OneBot11WssEndpoint extends ClientEndpoint {
|
|
|
24
25
|
#requestId = { value: 0 };
|
|
25
26
|
#pending = new Map();
|
|
26
27
|
#started = false;
|
|
28
|
+
#stopped = false;
|
|
27
29
|
constructor(options) {
|
|
28
30
|
super();
|
|
29
31
|
this.#logger = getAdapterLogger('onebot11', options.config.id);
|
|
@@ -31,6 +33,7 @@ export class OneBot11WssEndpoint extends ClientEndpoint {
|
|
|
31
33
|
this.#connectionLifecycle = createEndpointLifecycle({
|
|
32
34
|
name: `${options.config.id}:inbound`,
|
|
33
35
|
reconnect: false,
|
|
36
|
+
heartbeat: { intervalMs: options.config.heartbeat_interval, watchdogMisses: 2 },
|
|
34
37
|
});
|
|
35
38
|
this.client = createOnebot11EndpointClient(options.config, (action, params) => this.#callApi(action, params));
|
|
36
39
|
const callApi = (action, params) => callOnebot11Client(this.client, action, params);
|
|
@@ -41,9 +44,21 @@ export class OneBot11WssEndpoint extends ClientEndpoint {
|
|
|
41
44
|
this.#admitRaw(payload);
|
|
42
45
|
}, (_name, error) => this.#warnPlatformEvent(error));
|
|
43
46
|
}
|
|
47
|
+
get transportState() {
|
|
48
|
+
if (this.#stopped)
|
|
49
|
+
return 'stopped';
|
|
50
|
+
if (!this.#started)
|
|
51
|
+
return 'idle';
|
|
52
|
+
if (this.#connectionLifecycle.state === 'closed')
|
|
53
|
+
return 'closed';
|
|
54
|
+
if (this.#ws?.readyState === 1)
|
|
55
|
+
return 'open';
|
|
56
|
+
return this.#connectionLifecycle.state === 'connecting' ? 'connecting' : 'idle';
|
|
57
|
+
}
|
|
44
58
|
async start() {
|
|
45
59
|
if (this.#started)
|
|
46
60
|
return;
|
|
61
|
+
this.#stopped = false;
|
|
47
62
|
if (!this.#options.config.access_token) {
|
|
48
63
|
// wss 模式未配 access_token 时任何连接都会被放行(verifyOneBotAccessToken 直接 return true)
|
|
49
64
|
this.#logger.warn(formatCompact({
|
|
@@ -74,6 +89,7 @@ export class OneBot11WssEndpoint extends ClientEndpoint {
|
|
|
74
89
|
}));
|
|
75
90
|
}
|
|
76
91
|
async stop() {
|
|
92
|
+
this.#stopped = true;
|
|
77
93
|
this.close();
|
|
78
94
|
this.#wsRelease?.();
|
|
79
95
|
this.#wsRelease = undefined;
|
|
@@ -87,7 +103,11 @@ export class OneBot11WssEndpoint extends ClientEndpoint {
|
|
|
87
103
|
const message = formatOutboundSegments(payload);
|
|
88
104
|
const { action, params } = buildSendAction(conversation, message);
|
|
89
105
|
const data = await callOnebot11Client(this.client, action, params);
|
|
90
|
-
|
|
106
|
+
const rawId = data?.message_id;
|
|
107
|
+
if ((typeof rawId !== 'number' && typeof rawId !== 'string') || String(rawId).trim() === '' || (typeof rawId === 'number' && !Number.isFinite(rawId))) {
|
|
108
|
+
throw new EndpointDeliveryError('delivery_unconfirmed', 'Message delivery is unconfirmed: platform returned no message_id', 'unknown');
|
|
109
|
+
}
|
|
110
|
+
return String(rawId);
|
|
91
111
|
}
|
|
92
112
|
async recallMessage(messageId) {
|
|
93
113
|
if (!messageId)
|
|
@@ -95,7 +115,7 @@ export class OneBot11WssEndpoint extends ClientEndpoint {
|
|
|
95
115
|
await callOnebot11Client(this.client, 'delete_msg', { message_id: Number(messageId) });
|
|
96
116
|
}
|
|
97
117
|
#callApi(action, params = {}) {
|
|
98
|
-
return callOneBot11WsAction(this.#ws, this.#pending, this.#requestId, action, params);
|
|
118
|
+
return callOneBot11WsAction(this.#connectionLifecycle.state === 'closed' ? undefined : this.#ws, this.#pending, this.#requestId, action, params);
|
|
99
119
|
}
|
|
100
120
|
#admitRaw(ev) {
|
|
101
121
|
if (!isMessageEvent(ev)) {
|
|
@@ -132,6 +152,10 @@ export class OneBot11WssEndpoint extends ClientEndpoint {
|
|
|
132
152
|
}));
|
|
133
153
|
}
|
|
134
154
|
async #acceptConnection(connection) {
|
|
155
|
+
if (!this.#started || this.#stopped) {
|
|
156
|
+
connection.socket.close();
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
135
159
|
if (!verifyOneBotAccessToken(this.#options.config.access_token, connection.request)) {
|
|
136
160
|
connection.socket.close(4003, 'Unauthorized');
|
|
137
161
|
return;
|
|
@@ -142,6 +166,9 @@ export class OneBot11WssEndpoint extends ClientEndpoint {
|
|
|
142
166
|
await this.#connectionLifecycle.start(async (lifecycleHandle) => {
|
|
143
167
|
this.#ws = socket;
|
|
144
168
|
lifecycleHandle.onForceClose(() => {
|
|
169
|
+
if (this.#ws === socket) {
|
|
170
|
+
rejectAllPending(this.#pending);
|
|
171
|
+
}
|
|
145
172
|
try {
|
|
146
173
|
socket.close();
|
|
147
174
|
}
|
|
@@ -159,8 +186,12 @@ export class OneBot11WssEndpoint extends ClientEndpoint {
|
|
|
159
186
|
/* ignore */
|
|
160
187
|
}
|
|
161
188
|
}, this.#options.config.heartbeat_interval);
|
|
189
|
+
socket.on('pong', () => {
|
|
190
|
+
if (this.#ws === socket)
|
|
191
|
+
this.#connectionLifecycle.notifyHeartbeatAck();
|
|
192
|
+
});
|
|
162
193
|
socket.on('message', (data) => {
|
|
163
|
-
if (this.#ws !== socket)
|
|
194
|
+
if (this.#ws !== socket || this.#connectionLifecycle.state === 'closed')
|
|
164
195
|
return;
|
|
165
196
|
this.#connectionLifecycle.notifyHeartbeatAck();
|
|
166
197
|
handleOneBot11WsMessage(data, {
|
|
@@ -169,7 +200,14 @@ export class OneBot11WssEndpoint extends ClientEndpoint {
|
|
|
169
200
|
ingest: (ev) => this.client.ingest(ev),
|
|
170
201
|
});
|
|
171
202
|
});
|
|
203
|
+
socket.on('error', (error) => {
|
|
204
|
+
if (this.#ws !== socket)
|
|
205
|
+
return;
|
|
206
|
+
this.#logger.warn(formatCompact({ op: 'ws_error', error: String(error) }));
|
|
207
|
+
});
|
|
172
208
|
socket.on('close', () => {
|
|
209
|
+
if (this.#ws !== socket)
|
|
210
|
+
return;
|
|
173
211
|
if (this.#ws === socket) {
|
|
174
212
|
this.#ws = undefined;
|
|
175
213
|
rejectAllPending(this.#pending);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-onebot11",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6",
|
|
4
4
|
"description": "Zhin.js OneBot 11 adapter for Plugin Runtime (WebSocket client)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -45,16 +45,16 @@
|
|
|
45
45
|
"@imhelper/onebot-v11": "1.0.7",
|
|
46
46
|
"imhelper": "1.0.7",
|
|
47
47
|
"ws": "^8.21.1",
|
|
48
|
-
"@zhin.js/adapter": "1.1.
|
|
49
|
-
"@zhin.js/agent-feature": "1.1.
|
|
50
|
-
"@zhin.js/core": "1.1.
|
|
51
|
-
"@zhin.js/feature-kit": "1.1.
|
|
52
|
-
"@zhin.js/host-http": "1.1.
|
|
53
|
-
"@zhin.js/im-contract": "1.1.
|
|
48
|
+
"@zhin.js/adapter": "1.1.17",
|
|
49
|
+
"@zhin.js/agent-feature": "1.1.4",
|
|
50
|
+
"@zhin.js/core": "1.1.41",
|
|
51
|
+
"@zhin.js/feature-kit": "1.1.3",
|
|
52
|
+
"@zhin.js/host-http": "1.1.4",
|
|
53
|
+
"@zhin.js/im-contract": "1.1.3",
|
|
54
54
|
"@zhin.js/logger": "1.1.1",
|
|
55
|
-
"@zhin.js/permission": "1.1.
|
|
56
|
-
"@zhin.js/skill": "1.1.
|
|
57
|
-
"@zhin.js/tool": "1.1.
|
|
55
|
+
"@zhin.js/permission": "1.1.4",
|
|
56
|
+
"@zhin.js/skill": "1.1.4",
|
|
57
|
+
"@zhin.js/tool": "1.1.4"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@types/node": "^26.1.2",
|
|
@@ -62,15 +62,15 @@
|
|
|
62
62
|
"typescript": "^6.0.3",
|
|
63
63
|
"vitest": "^4.1.10",
|
|
64
64
|
"zod": "^4.4.3",
|
|
65
|
-
"@zhin.js/host-http": "1.1.
|
|
66
|
-
"zhin.js": "1.1.
|
|
65
|
+
"@zhin.js/host-http": "1.1.4",
|
|
66
|
+
"zhin.js": "1.1.6"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
69
|
"zod": "^4.0.0",
|
|
70
|
-
"@zhin.js/adapter": "^1.1.
|
|
71
|
-
"@zhin.js/command": "^1.1.
|
|
72
|
-
"@zhin.js/core": "^1.1.
|
|
73
|
-
"zhin.js": "^1.1.
|
|
70
|
+
"@zhin.js/adapter": "^1.1.17",
|
|
71
|
+
"@zhin.js/command": "^1.1.5",
|
|
72
|
+
"@zhin.js/core": "^1.1.41",
|
|
73
|
+
"zhin.js": "^1.1.6"
|
|
74
74
|
},
|
|
75
75
|
"peerDependenciesMeta": {
|
|
76
76
|
"@zhin.js/command": {
|
package/src/protocol.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Canonicalization is owned by gateway/core before endpoint.send.
|
|
4
4
|
* Spec: https://github.com/botuniverse/onebot-11
|
|
5
5
|
*/
|
|
6
|
-
import { isMediaRef, type MediaRef, type ConversationRef } from '@zhin.js/im-contract';
|
|
6
|
+
import { EndpointDeliveryError, isMediaRef, type MediaRef, type ConversationRef } from '@zhin.js/im-contract';
|
|
7
7
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
8
8
|
|
|
9
9
|
const logger = getLogger('onebot11');
|
|
@@ -340,6 +340,21 @@ function oneBotMediaSegment(
|
|
|
340
340
|
function canonicalToOneBotSegment(segment: OneBot11WireSegment): OneBot11Segment | null {
|
|
341
341
|
const data = segment.data ?? {};
|
|
342
342
|
switch (segment.type) {
|
|
343
|
+
case 'markdown':
|
|
344
|
+
case 'keyboard':
|
|
345
|
+
throw new EndpointDeliveryError('unsupported_operation', `OneBot11 standard has no ${segment.type} message segment`, 'not_sent');
|
|
346
|
+
case 'share': {
|
|
347
|
+
if (['audio', 'artist', 'duration', 'config'].some(key => data[key] !== undefined)) {
|
|
348
|
+
throw new EndpointDeliveryError('unsupported_operation', 'onebot11 rich share metadata is not implemented', 'not_sent');
|
|
349
|
+
}
|
|
350
|
+
if (typeof data.url !== 'string' || !/^https?:\/\//.test(data.url) || typeof data.title !== 'string' || !data.title) {
|
|
351
|
+
throw new EndpointDeliveryError('unsupported_operation', 'Invalid OneBot11 share URL or title', 'not_sent');
|
|
352
|
+
}
|
|
353
|
+
return { type: 'share', data: { url: data.url, title: data.title,
|
|
354
|
+
...(typeof (data.description ?? data.content) === 'string' ? { content: data.description ?? data.content } : {}),
|
|
355
|
+
...(typeof data.image === 'string' ? { image: data.image } : {}) } };
|
|
356
|
+
}
|
|
357
|
+
|
|
343
358
|
case 'mention': {
|
|
344
359
|
const target = data.target ?? data.qq ?? data.id;
|
|
345
360
|
if (target == null) return { type: segment.type, data };
|
package/src/ws-endpoint.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { EndpointDeliveryError } from '@zhin.js/im-contract';
|
|
1
2
|
/**
|
|
2
3
|
* OneBot11 WS client endpoint — outbound connect to OneBot implementation.
|
|
3
4
|
*/
|
|
@@ -11,6 +12,7 @@ import {
|
|
|
11
12
|
type EndpointLifecycle,
|
|
12
13
|
type EndpointManagement,
|
|
13
14
|
type EndpointSendRequest,
|
|
15
|
+
type EndpointTransportState,
|
|
14
16
|
} from 'zhin.js/adapter';
|
|
15
17
|
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
16
18
|
import type { CapabilityId } from 'zhin.js';
|
|
@@ -89,10 +91,14 @@ export class OneBot11WsEndpoint extends ClientEndpoint<Onebot11Client> {
|
|
|
89
91
|
maxIntervalMs: config.reconnect_interval,
|
|
90
92
|
jitterMs: 0,
|
|
91
93
|
},
|
|
92
|
-
heartbeat: { intervalMs: config.heartbeat_interval },
|
|
94
|
+
heartbeat: { intervalMs: config.heartbeat_interval, watchdogMisses: 2 },
|
|
93
95
|
});
|
|
94
96
|
}
|
|
95
97
|
|
|
98
|
+
get transportState(): EndpointTransportState {
|
|
99
|
+
return this.#lifecycle.state;
|
|
100
|
+
}
|
|
101
|
+
|
|
96
102
|
async start(): Promise<void> {
|
|
97
103
|
if (this.#lifecycle.started) return;
|
|
98
104
|
try {
|
|
@@ -123,7 +129,11 @@ export class OneBot11WsEndpoint extends ClientEndpoint<Onebot11Client> {
|
|
|
123
129
|
const message = formatOutboundSegments(payload);
|
|
124
130
|
const { action, params } = buildSendAction(conversation, message);
|
|
125
131
|
const data = await callOnebot11Client<{ message_id?: number | string }>(this.client, action, params);
|
|
126
|
-
const
|
|
132
|
+
const rawId = data?.message_id;
|
|
133
|
+
if ((typeof rawId !== 'number' && typeof rawId !== 'string') || String(rawId).trim() === '' || (typeof rawId === 'number' && !Number.isFinite(rawId))) {
|
|
134
|
+
throw new EndpointDeliveryError('delivery_unconfirmed', 'Message delivery is unconfirmed: platform returned no message_id', 'unknown');
|
|
135
|
+
}
|
|
136
|
+
const messageId = String(rawId);
|
|
127
137
|
this.#logger.debug(formatCompact({
|
|
128
138
|
op: 'onebot11_send',
|
|
129
139
|
endpoint: this.#options.config.id,
|
|
@@ -142,7 +152,7 @@ export class OneBot11WsEndpoint extends ClientEndpoint<Onebot11Client> {
|
|
|
142
152
|
action: string,
|
|
143
153
|
params: Record<string, unknown> = {},
|
|
144
154
|
): ReturnType<typeof callOneBot11WsAction> {
|
|
145
|
-
return callOneBot11WsAction(this.#ws, this.#pending, this.#requestId, action, params);
|
|
155
|
+
return callOneBot11WsAction(this.#lifecycle.state === 'open' ? this.#ws : undefined, this.#pending, this.#requestId, action, params);
|
|
146
156
|
}
|
|
147
157
|
|
|
148
158
|
#admitRaw(ev: OneBot11Event): void {
|
|
@@ -199,6 +209,9 @@ export class OneBot11WsEndpoint extends ClientEndpoint<Onebot11Client> {
|
|
|
199
209
|
const ws = create(url, { headers });
|
|
200
210
|
this.#ws = ws;
|
|
201
211
|
handle.onForceClose(() => {
|
|
212
|
+
if (this.#ws === ws) {
|
|
213
|
+
rejectAllPending(this.#pending);
|
|
214
|
+
}
|
|
202
215
|
try {
|
|
203
216
|
ws.close();
|
|
204
217
|
} catch {
|
|
@@ -207,7 +220,7 @@ export class OneBot11WsEndpoint extends ClientEndpoint<Onebot11Client> {
|
|
|
207
220
|
});
|
|
208
221
|
|
|
209
222
|
ws.on('open', () => {
|
|
210
|
-
if (settled) return;
|
|
223
|
+
if (settled || this.#ws !== ws || !this.#lifecycle.started) return;
|
|
211
224
|
settled = true;
|
|
212
225
|
if (!this.#options.config.access_token) {
|
|
213
226
|
this.#logger.warn(formatCompact({
|
|
@@ -225,7 +238,12 @@ export class OneBot11WsEndpoint extends ClientEndpoint<Onebot11Client> {
|
|
|
225
238
|
resolve();
|
|
226
239
|
});
|
|
227
240
|
|
|
241
|
+
ws.on('pong', () => {
|
|
242
|
+
if (this.#ws === ws) this.#lifecycle.notifyHeartbeatAck();
|
|
243
|
+
});
|
|
228
244
|
ws.on('message', (data) => {
|
|
245
|
+
if (this.#ws !== ws || ws.readyState !== 1 || !this.#lifecycle.started) return;
|
|
246
|
+
this.#lifecycle.notifyHeartbeatAck();
|
|
229
247
|
handleOneBot11WsMessage(data, {
|
|
230
248
|
endpointId: this.#options.config.id,
|
|
231
249
|
pending: this.#pending,
|
|
@@ -234,6 +252,9 @@ export class OneBot11WsEndpoint extends ClientEndpoint<Onebot11Client> {
|
|
|
234
252
|
});
|
|
235
253
|
|
|
236
254
|
ws.on('close', (code, reason) => {
|
|
255
|
+
if (this.#ws !== ws) return;
|
|
256
|
+
this.#ws = undefined;
|
|
257
|
+
rejectAllPending(this.#pending);
|
|
237
258
|
const reasonStr = typeof reason === 'string'
|
|
238
259
|
? reason
|
|
239
260
|
: Buffer.isBuffer(reason)
|
|
@@ -255,6 +276,7 @@ export class OneBot11WsEndpoint extends ClientEndpoint<Onebot11Client> {
|
|
|
255
276
|
});
|
|
256
277
|
|
|
257
278
|
ws.on('error', (err) => {
|
|
279
|
+
if (this.#ws !== ws) return;
|
|
258
280
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
259
281
|
this.#logger.warn(formatCompact({
|
|
260
282
|
op: 'ws_error',
|
package/src/ws-transport.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { EndpointDeliveryError } from '@zhin.js/im-contract';
|
|
1
2
|
import { clearTimeout, setTimeout } from 'node:timers';
|
|
2
3
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
3
4
|
import {
|
|
@@ -59,17 +60,23 @@ export function callOneBot11WsAction(
|
|
|
59
60
|
params: Record<string, unknown>,
|
|
60
61
|
): Promise<OneBot11ActionResponse> {
|
|
61
62
|
if (!ws || ws.readyState !== WS_OPEN) {
|
|
62
|
-
return Promise.reject(new
|
|
63
|
+
return Promise.reject(new EndpointDeliveryError('endpoint_disconnected', 'WebSocket 未连接', 'not_sent'));
|
|
63
64
|
}
|
|
64
65
|
const echo = `ob11_${++requestId.value}`;
|
|
65
66
|
const req: OneBot11ActionRequest = { action, params, echo };
|
|
66
67
|
return new Promise((resolve, reject) => {
|
|
67
68
|
const timeout = setTimeout(() => {
|
|
68
69
|
pending.delete(echo);
|
|
69
|
-
reject(new
|
|
70
|
+
reject(new EndpointDeliveryError('endpoint_timeout', `OneBot11 动作超时: ${action}`, 'unknown'));
|
|
70
71
|
}, 30_000);
|
|
71
72
|
pending.set(echo, { resolve, reject, timeout });
|
|
72
|
-
|
|
73
|
+
try {
|
|
74
|
+
ws.send(JSON.stringify(req));
|
|
75
|
+
} catch (error) {
|
|
76
|
+
pending.delete(echo);
|
|
77
|
+
clearTimeout(timeout);
|
|
78
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
79
|
+
}
|
|
73
80
|
});
|
|
74
81
|
}
|
|
75
82
|
|
|
@@ -79,7 +86,7 @@ export function rejectAllPending(
|
|
|
79
86
|
): void {
|
|
80
87
|
for (const [, entry] of pending) {
|
|
81
88
|
clearTimeout(entry.timeout);
|
|
82
|
-
entry.reject(new
|
|
89
|
+
entry.reject(new EndpointDeliveryError('endpoint_disconnected', message, 'unknown'));
|
|
83
90
|
}
|
|
84
91
|
pending.clear();
|
|
85
92
|
}
|
package/src/ws-types.ts
CHANGED
|
@@ -4,7 +4,7 @@ export interface OneBot11WsSocket {
|
|
|
4
4
|
send(data: string): void;
|
|
5
5
|
close(code?: number, reason?: string): void;
|
|
6
6
|
ping?(): void;
|
|
7
|
-
on(event: 'open' | 'message' | 'close' | 'error', listener: (...args: unknown[]) => void): void;
|
|
7
|
+
on(event: 'open' | 'message' | 'close' | 'error' | 'pong', listener: (...args: unknown[]) => void): void;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export interface OneBot11WsCreateOptions {
|
package/src/wss-endpoint.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { EndpointDeliveryError } from '@zhin.js/im-contract';
|
|
1
2
|
/**
|
|
2
3
|
* OneBot11 reverse WSS endpoint — accepts inbound WebSocket from OneBot implementation.
|
|
3
4
|
*/
|
|
@@ -9,6 +10,7 @@ import {
|
|
|
9
10
|
type EndpointControl,
|
|
10
11
|
type EndpointManagement,
|
|
11
12
|
type EndpointSendRequest,
|
|
13
|
+
type EndpointTransportState,
|
|
12
14
|
} from 'zhin.js/adapter';
|
|
13
15
|
import type { HttpHost, WsConnection } from '@zhin.js/host-http';
|
|
14
16
|
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
@@ -62,6 +64,7 @@ export class OneBot11WssEndpoint extends ClientEndpoint<Onebot11Client> {
|
|
|
62
64
|
#requestId = { value: 0 };
|
|
63
65
|
#pending = new Map<string, OneBot11PendingAction>();
|
|
64
66
|
#started = false;
|
|
67
|
+
#stopped = false;
|
|
65
68
|
|
|
66
69
|
constructor(options: OneBot11WssEndpointOptions) {
|
|
67
70
|
super();
|
|
@@ -70,6 +73,7 @@ export class OneBot11WssEndpoint extends ClientEndpoint<Onebot11Client> {
|
|
|
70
73
|
this.#connectionLifecycle = createEndpointLifecycle({
|
|
71
74
|
name: `${options.config.id}:inbound`,
|
|
72
75
|
reconnect: false,
|
|
76
|
+
heartbeat: { intervalMs: options.config.heartbeat_interval, watchdogMisses: 2 },
|
|
73
77
|
});
|
|
74
78
|
this.client = createOnebot11EndpointClient(options.config, (action, params) => this.#callApi(action, params));
|
|
75
79
|
const callApi = (action: string, params?: Record<string, unknown>) => callOnebot11Client(this.client, action, params);
|
|
@@ -84,8 +88,17 @@ export class OneBot11WssEndpoint extends ClientEndpoint<Onebot11Client> {
|
|
|
84
88
|
);
|
|
85
89
|
}
|
|
86
90
|
|
|
91
|
+
get transportState(): EndpointTransportState {
|
|
92
|
+
if (this.#stopped) return 'stopped';
|
|
93
|
+
if (!this.#started) return 'idle';
|
|
94
|
+
if (this.#connectionLifecycle.state === 'closed') return 'closed';
|
|
95
|
+
if (this.#ws?.readyState === 1) return 'open';
|
|
96
|
+
return this.#connectionLifecycle.state === 'connecting' ? 'connecting' : 'idle';
|
|
97
|
+
}
|
|
98
|
+
|
|
87
99
|
async start(): Promise<void> {
|
|
88
100
|
if (this.#started) return;
|
|
101
|
+
this.#stopped = false;
|
|
89
102
|
if (!this.#options.config.access_token) {
|
|
90
103
|
// wss 模式未配 access_token 时任何连接都会被放行(verifyOneBotAccessToken 直接 return true)
|
|
91
104
|
this.#logger.warn(formatCompact({
|
|
@@ -117,6 +130,7 @@ export class OneBot11WssEndpoint extends ClientEndpoint<Onebot11Client> {
|
|
|
117
130
|
}
|
|
118
131
|
|
|
119
132
|
async stop(): Promise<void> {
|
|
133
|
+
this.#stopped = true;
|
|
120
134
|
this.close();
|
|
121
135
|
this.#wsRelease?.();
|
|
122
136
|
this.#wsRelease = undefined;
|
|
@@ -131,7 +145,11 @@ export class OneBot11WssEndpoint extends ClientEndpoint<Onebot11Client> {
|
|
|
131
145
|
const message = formatOutboundSegments(payload);
|
|
132
146
|
const { action, params } = buildSendAction(conversation, message);
|
|
133
147
|
const data = await callOnebot11Client<{ message_id?: number | string }>(this.client, action, params);
|
|
134
|
-
|
|
148
|
+
const rawId = data?.message_id;
|
|
149
|
+
if ((typeof rawId !== 'number' && typeof rawId !== 'string') || String(rawId).trim() === '' || (typeof rawId === 'number' && !Number.isFinite(rawId))) {
|
|
150
|
+
throw new EndpointDeliveryError('delivery_unconfirmed', 'Message delivery is unconfirmed: platform returned no message_id', 'unknown');
|
|
151
|
+
}
|
|
152
|
+
return String(rawId);
|
|
135
153
|
}
|
|
136
154
|
|
|
137
155
|
async recallMessage(messageId: string): Promise<void> {
|
|
@@ -143,7 +161,7 @@ export class OneBot11WssEndpoint extends ClientEndpoint<Onebot11Client> {
|
|
|
143
161
|
action: string,
|
|
144
162
|
params: Record<string, unknown> = {},
|
|
145
163
|
): ReturnType<typeof callOneBot11WsAction> {
|
|
146
|
-
return callOneBot11WsAction(this.#ws, this.#pending, this.#requestId, action, params);
|
|
164
|
+
return callOneBot11WsAction(this.#connectionLifecycle.state === 'closed' ? undefined : this.#ws, this.#pending, this.#requestId, action, params);
|
|
147
165
|
}
|
|
148
166
|
|
|
149
167
|
#admitRaw(ev: OneBot11Event): void {
|
|
@@ -189,6 +207,10 @@ export class OneBot11WssEndpoint extends ClientEndpoint<Onebot11Client> {
|
|
|
189
207
|
}
|
|
190
208
|
|
|
191
209
|
async #acceptConnection(connection: WsConnection): Promise<void> {
|
|
210
|
+
if (!this.#started || this.#stopped) {
|
|
211
|
+
connection.socket.close();
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
192
214
|
if (!verifyOneBotAccessToken(this.#options.config.access_token, connection.request)) {
|
|
193
215
|
connection.socket.close(4003, 'Unauthorized');
|
|
194
216
|
return;
|
|
@@ -199,6 +221,9 @@ export class OneBot11WssEndpoint extends ClientEndpoint<Onebot11Client> {
|
|
|
199
221
|
await this.#connectionLifecycle.start(async (lifecycleHandle) => {
|
|
200
222
|
this.#ws = socket;
|
|
201
223
|
lifecycleHandle.onForceClose(() => {
|
|
224
|
+
if (this.#ws === socket) {
|
|
225
|
+
rejectAllPending(this.#pending);
|
|
226
|
+
}
|
|
202
227
|
try {
|
|
203
228
|
socket.close();
|
|
204
229
|
} catch {
|
|
@@ -213,8 +238,11 @@ export class OneBot11WssEndpoint extends ClientEndpoint<Onebot11Client> {
|
|
|
213
238
|
/* ignore */
|
|
214
239
|
}
|
|
215
240
|
}, this.#options.config.heartbeat_interval);
|
|
241
|
+
socket.on('pong', () => {
|
|
242
|
+
if (this.#ws === socket) this.#connectionLifecycle.notifyHeartbeatAck();
|
|
243
|
+
});
|
|
216
244
|
socket.on('message', (data) => {
|
|
217
|
-
if (this.#ws !== socket) return;
|
|
245
|
+
if (this.#ws !== socket || this.#connectionLifecycle.state === 'closed') return;
|
|
218
246
|
this.#connectionLifecycle.notifyHeartbeatAck();
|
|
219
247
|
handleOneBot11WsMessage(data, {
|
|
220
248
|
endpointId: this.#options.config.id,
|
|
@@ -222,7 +250,12 @@ export class OneBot11WssEndpoint extends ClientEndpoint<Onebot11Client> {
|
|
|
222
250
|
ingest: (ev) => this.client.ingest(ev as Parameters<Onebot11Client['ingest']>[0]),
|
|
223
251
|
});
|
|
224
252
|
});
|
|
253
|
+
socket.on('error', (error) => {
|
|
254
|
+
if (this.#ws !== socket) return;
|
|
255
|
+
this.#logger.warn(formatCompact({ op: 'ws_error', error: String(error) }));
|
|
256
|
+
});
|
|
225
257
|
socket.on('close', () => {
|
|
258
|
+
if (this.#ws !== socket) return;
|
|
226
259
|
if (this.#ws === socket) {
|
|
227
260
|
this.#ws = undefined;
|
|
228
261
|
rejectAllPending(this.#pending);
|