@zhin.js/adapter-onebot11 1.1.18 → 1.1.20

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.
Files changed (48) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +13 -6
  3. package/adapters/onebot11/index.js +3 -26
  4. package/adapters/onebot11/index.ts +2 -32
  5. package/commands/onebot11/endpoint/definition.js +0 -2
  6. package/commands/onebot11/endpoint/definition.ts +0 -2
  7. package/lib/adapter.d.ts +20 -0
  8. package/lib/adapter.js +26 -0
  9. package/lib/client.d.ts +13 -11
  10. package/lib/client.js +35 -12
  11. package/lib/console.d.ts +1 -1
  12. package/lib/console.js +2 -5
  13. package/lib/content-port.d.ts +2 -2
  14. package/lib/content-port.js +2 -2
  15. package/lib/endpoint-management.d.ts +2 -2
  16. package/lib/endpoint-management.js +8 -8
  17. package/lib/identity.d.ts +3 -0
  18. package/lib/identity.js +32 -0
  19. package/lib/index.d.ts +3 -2
  20. package/lib/index.js +3 -2
  21. package/lib/self-id.js +1 -1
  22. package/lib/side-event-dispatch.d.ts +8 -1
  23. package/lib/side-event-dispatch.js +2 -1
  24. package/lib/ws-endpoint.d.ts +5 -12
  25. package/lib/ws-endpoint.js +75 -50
  26. package/lib/ws-transport.d.ts +18 -11
  27. package/lib/ws-transport.js +28 -56
  28. package/lib/ws-types.d.ts +0 -6
  29. package/lib/wss-endpoint.d.ts +5 -12
  30. package/lib/wss-endpoint.js +76 -53
  31. package/package.json +18 -18
  32. package/plugin.js +0 -3
  33. package/src/adapter.ts +32 -0
  34. package/src/client.ts +38 -28
  35. package/src/console.ts +2 -5
  36. package/src/content-port.ts +4 -4
  37. package/src/endpoint-management.ts +15 -12
  38. package/src/identity.ts +30 -0
  39. package/src/index.ts +4 -4
  40. package/src/self-id.ts +1 -1
  41. package/src/side-event-dispatch.ts +9 -7
  42. package/src/ws-endpoint.ts +71 -64
  43. package/src/ws-transport.ts +41 -82
  44. package/src/ws-types.ts +0 -7
  45. package/src/wss-endpoint.ts +73 -68
  46. package/lib/onebot11-runtime-state.d.ts +0 -1
  47. package/lib/onebot11-runtime-state.js +0 -6
  48. package/src/onebot11-runtime-state.ts +0 -7
@@ -3,39 +3,59 @@ import { EndpointDeliveryError } from '@zhin.js/im-contract';
3
3
  * OneBot11 WS client endpoint — outbound connect to OneBot implementation.
4
4
  */
5
5
  import WebSocket from 'ws';
6
- import { ClientEndpoint, createRecallEndpointControl, createEndpointLifecycle, } from 'zhin.js/adapter';
6
+ import { createEndpointSpecification, endpointSpec, createRecallEndpointControl, createEndpointLifecycle, } from 'zhin.js/adapter';
7
7
  import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
8
8
  import { createOneBot11EndpointManagement } from './endpoint-management.js';
9
9
  import { buildSendAction, buildWsConnectOptions, formatInboundContent, formatInboundMetadata, formatOutboundSegments, isMessageEvent, onebot11InboundConversation, senderDisplayName, senderUserId, } from './protocol.js';
10
10
  import { receiveOneBot11SideEvent } from './side-event-dispatch.js';
11
11
  import { createOneBot11ContentPort } from './content-port.js';
12
- import { callOneBot11WsAction, handleOneBot11WsMessage, rejectAllPending, } from './ws-transport.js';
12
+ import { createOneBot11Rpc } from './ws-transport.js';
13
13
  import { probeOnebot11SelfId, resolveOnebot11SelfId } from './self-id.js';
14
- import { callOnebot11Client, createOnebot11EndpointClient, forwardOnebot11ClientEvents } from './client.js';
15
- export class OneBot11WsEndpoint extends ClientEndpoint {
16
- client;
14
+ import { callOnebot11Client, readOnebot11Response, onebot11ClientOptions, forwardOnebot11ClientEvents, Onebot11Client } from './client.js';
15
+ export class OneBot11WsEndpoint extends Onebot11Client {
16
+ [endpointSpec];
17
+ #open = false;
17
18
  #logger;
18
19
  #options;
19
- management;
20
- control = createRecallEndpointControl((id) => this.recallMessage(id));
21
- content;
20
+ #management;
21
+ #control = createRecallEndpointControl((id, signal) => this.#recallMessage(id, signal));
22
+ #content;
23
+ #releaseClientEvents;
22
24
  #lifecycle;
23
25
  #ws;
24
26
  #stopped = false;
25
- #requestId = { value: 0 };
26
- #pending = new Map();
27
+ #rpc;
27
28
  constructor(options) {
28
- super();
29
+ const rpc = createOneBot11Rpc({ endpointId: options.config.id, signal: options.signal,
30
+ socket: () => this.#lifecycle.state === 'open' ? this.#ws : undefined,
31
+ });
32
+ super(onebot11ClientOptions(options.config, (action, params) => rpc.call({ action, params })));
33
+ this.#rpc = rpc;
29
34
  this.#logger = getAdapterLogger('onebot11', options.config.id);
30
35
  this.#options = options;
31
- this.client = createOnebot11EndpointClient(options.config, (action, params) => this.#callApi(action, params));
32
- const callApi = (action, params) => callOnebot11Client(this.client, action, params);
33
- this.management = createOneBot11EndpointManagement({ callApi });
34
- this.content = createOneBot11ContentPort(callApi);
35
- this.bindClientEvents((receive) => forwardOnebot11ClientEvents(this.client, receive), (name, payload) => {
36
- if (name === 'event')
37
- this.#admitRaw(payload);
38
- }, (_name, error) => this.#warnPlatformEvent(error));
36
+ const callApi = (request) => this.#rpc.call(request).then(readOnebot11Response);
37
+ this.#management = createOneBot11EndpointManagement({ callApi });
38
+ this.#content = createOneBot11ContentPort(callApi);
39
+ const readTransportState = () => this.#lifecycle.state;
40
+ this[endpointSpec] = createEndpointSpecification(this, {
41
+ name: options.config.id,
42
+ start: () => this.#start(), open: () => { this.#open = true; },
43
+ close: () => { this.#open = false; }, stop: () => this.#stop(),
44
+ send: request => this.#send(request),
45
+ get transportState() { return readTransportState(); },
46
+ management: this.#management, control: this.#control, content: this.#content,
47
+ });
48
+ this.#releaseClientEvents = forwardOnebot11ClientEvents(this, (name, payload) => {
49
+ void this[endpointSpec].events.run(() => {
50
+ // A native frame can arrive before start resolves. The spec owns candidate
51
+ // buffering and Root/child admission; transport start is not publication.
52
+ if (this.#stopped || (!this.#open && !this.#lifecycle.started))
53
+ return;
54
+ void this[endpointSpec].events.platform(name, payload).catch(error => this.#warnPlatformEvent(error));
55
+ if (name === 'event')
56
+ this.#admitRaw(payload);
57
+ }).catch(error => this.#warnPlatformEvent(error));
58
+ });
39
59
  const { config } = options;
40
60
  this.#lifecycle = createEndpointLifecycle({
41
61
  name: config.id,
@@ -49,10 +69,10 @@ export class OneBot11WsEndpoint extends ClientEndpoint {
49
69
  heartbeat: { intervalMs: config.heartbeat_interval, watchdogMisses: 2 },
50
70
  });
51
71
  }
52
- get transportState() {
72
+ get #transportState() {
53
73
  return this.#lifecycle.state;
54
74
  }
55
- async start() {
75
+ async #start() {
56
76
  if (this.#lifecycle.started)
57
77
  return;
58
78
  this.#stopped = false;
@@ -75,19 +95,24 @@ export class OneBot11WsEndpoint extends ClientEndpoint {
75
95
  throw err;
76
96
  }
77
97
  }
78
- async stop() {
98
+ async #stop() {
79
99
  this.#stopped = true;
80
- this.close();
100
+ this.#open = false;
81
101
  // 基座负责:清重连/心跳定时器、强关 ws、唤醒 stop-during-connect 竞态
82
- await this.#lifecycle.stop();
83
- rejectAllPending(this.#pending);
84
- this.#ws = undefined;
85
- this.reportSelfUserId(undefined);
102
+ try {
103
+ await this.#lifecycle.stop();
104
+ }
105
+ finally {
106
+ this.#rpc.dispose();
107
+ this.#releaseClientEvents();
108
+ this.#ws = undefined;
109
+ this[endpointSpec].reportSelfUserId(undefined);
110
+ }
86
111
  }
87
- async send({ conversation, payload }) {
112
+ async #send({ conversation, payload, signal }) {
88
113
  const message = formatOutboundSegments(payload);
89
114
  const { action, params } = buildSendAction(conversation, message);
90
- const data = await callOnebot11Client(this.client, action, params);
115
+ const data = readOnebot11Response(await this.#rpc.call({ action, params, signal }));
91
116
  const rawId = data?.message_id;
92
117
  if ((typeof rawId !== 'number' && typeof rawId !== 'string') || String(rawId).trim() === '' || (typeof rawId === 'number' && !Number.isFinite(rawId))) {
93
118
  throw new EndpointDeliveryError('delivery_unconfirmed', 'Message delivery is unconfirmed: platform returned no message_id', 'unknown');
@@ -101,26 +126,30 @@ export class OneBot11WsEndpoint extends ClientEndpoint {
101
126
  }));
102
127
  return messageId;
103
128
  }
104
- async recallMessage(messageId) {
129
+ async #recallMessage(messageId, signal) {
105
130
  if (!messageId)
106
131
  return;
107
- await callOnebot11Client(this.client, 'delete_msg', { message_id: Number(messageId) });
108
- }
109
- #callApi(action, params = {}) {
110
- return callOneBot11WsAction(this.#lifecycle.state === 'open' ? this.#ws : undefined, this.#pending, this.#requestId, action, params);
132
+ await this.#rpc.call({ action: 'delete_msg', params: { message_id: Number(messageId) }, signal }).then(readOnebot11Response);
111
133
  }
112
134
  #admitRaw(ev) {
113
135
  const selfId = resolveOnebot11SelfId(ev.self_id);
114
- if (!this.#stopped && selfId && selfId !== this.selfUserId)
115
- this.reportSelfUserId(selfId);
136
+ if (!this.#stopped && selfId)
137
+ this[endpointSpec].reportSelfUserId(selfId);
116
138
  if (!isMessageEvent(ev)) {
117
- receiveOneBot11SideEvent((name, payload) => this.emit(name, payload), this.#options.config.id, { callApi: (action, params) => callOnebot11Client(this.client, action, params) }, ev, this.#logger);
139
+ receiveOneBot11SideEvent(ev, {
140
+ emit: (name, payload) => this[endpointSpec].events.emit(name, payload),
141
+ endpointKey: this.#options.config.id,
142
+ caller: { callApi: (action, params) => callOnebot11Client({ client: this, action, params }) },
143
+ logger: this.#logger,
144
+ });
118
145
  return;
119
146
  }
120
- const conversation = onebot11InboundConversation(String(this.#options.id), ev);
147
+ const conversation = { ...onebot11InboundConversation(String(this.#options.id), ev), endpoint: this[endpointSpec].identity };
121
148
  const content = formatInboundContent(ev);
122
149
  const inbound = formatInboundMetadata(ev, this.#options.config.id);
123
- void this.emit('message.receive', {
150
+ void this[endpointSpec].events.message({
151
+ event: ev,
152
+ ...(typeof ev.time === 'number' ? { platformSentAt: ev.time * 1000 } : {}),
124
153
  conversation,
125
154
  message: { conversation, id: String(ev.message_id) },
126
155
  content,
@@ -143,16 +172,16 @@ export class OneBot11WsEndpoint extends ClientEndpoint {
143
172
  }
144
173
  /** Best-effort 登录账号探测;失败仅 debug,不影响连接。 */
145
174
  async #probeSelfUserId() {
146
- const selfId = await probeOnebot11SelfId(this.client, (error) => {
175
+ const selfId = await probeOnebot11SelfId(this, (error) => {
147
176
  this.#logger.debug(formatCompact({
148
177
  op: 'onebot11_get_login_info_failed',
149
178
  error: error instanceof Error ? error.message : String(error),
150
179
  }));
151
180
  });
152
- if (this.#stopped || this.transportState !== 'open')
181
+ if (this.#stopped || this.#transportState !== 'open')
153
182
  return;
154
- if (selfId && selfId !== this.selfUserId)
155
- this.reportSelfUserId(selfId);
183
+ if (selfId)
184
+ this[endpointSpec].reportSelfUserId(selfId);
156
185
  }
157
186
  #warnPlatformEvent(error) {
158
187
  this.#logger.warn(formatCompact({
@@ -170,7 +199,7 @@ export class OneBot11WsEndpoint extends ClientEndpoint {
170
199
  this.#ws = ws;
171
200
  handle.onForceClose(() => {
172
201
  if (this.#ws === ws) {
173
- rejectAllPending(this.#pending);
202
+ this.#rpc.disconnect();
174
203
  }
175
204
  try {
176
205
  ws.close();
@@ -206,17 +235,13 @@ export class OneBot11WsEndpoint extends ClientEndpoint {
206
235
  if (this.#ws !== ws || ws.readyState !== 1 || !this.#lifecycle.started)
207
236
  return;
208
237
  this.#lifecycle.notifyHeartbeatAck();
209
- handleOneBot11WsMessage(data, {
210
- endpointId: this.#options.config.id,
211
- pending: this.#pending,
212
- ingest: (ev) => this.client.ingest(ev),
213
- });
238
+ this.#rpc.receive(data, (ev) => this.ingest(ev));
214
239
  });
215
240
  ws.on('close', (code, reason) => {
216
241
  if (this.#ws !== ws)
217
242
  return;
218
243
  this.#ws = undefined;
219
- rejectAllPending(this.#pending);
244
+ this.#rpc.disconnect();
220
245
  const reasonStr = typeof reason === 'string'
221
246
  ? reason
222
247
  : Buffer.isBuffer(reason)
@@ -1,12 +1,19 @@
1
- import { type OneBot11ActionResponse, type OneBot11Event } from './protocol.js';
2
- import { type OneBot11PendingAction, type OneBot11WsSocket } from './ws-types.js';
3
- export declare function decodeWsPayload(data: unknown): string;
4
- export declare function handleOneBot11WsMessage(data: unknown, options: {
1
+ import { type PendingRpc, type PendingRpcRequest } from 'zhin.js/adapter';
2
+ import type { OneBot11ActionResponse, OneBot11Event } from './protocol.js';
3
+ import { type OneBot11WsSocket } from './ws-types.js';
4
+ export interface OneBot11RpcOptions {
5
5
  readonly endpointId: string;
6
- readonly pending: Map<string, OneBot11PendingAction>;
7
- readonly ingest: (ev: OneBot11Event) => void;
8
- }): void;
9
- export declare function callOneBot11WsAction(ws: OneBot11WsSocket | undefined, pending: Map<string, OneBot11PendingAction>, requestId: {
10
- value: number;
11
- }, action: string, params: Record<string, unknown>): Promise<OneBot11ActionResponse>;
12
- export declare function rejectAllPending(pending: Map<string, OneBot11PendingAction>, message?: string): void;
6
+ readonly signal?: AbortSignal;
7
+ /** Reads the lifecycle's selected socket; RPC never opens or reconnects it. */
8
+ readonly socket: () => OneBot11WsSocket | undefined;
9
+ }
10
+ export interface OneBot11Rpc {
11
+ call(request: PendingRpcRequest): Promise<OneBot11ActionResponse>;
12
+ receive(data: unknown, ingest: (event: OneBot11Event) => void): void;
13
+ disconnect: PendingRpc<OneBot11ActionResponse>['disconnect'];
14
+ dispose(): void;
15
+ readonly pendingCount: number;
16
+ }
17
+ /** A Client's one request owner serves native SDK actions and framework operations. */
18
+ export declare function createOneBot11Rpc(options: OneBot11RpcOptions): OneBot11Rpc;
19
+ export declare function decodeWsPayload(data: unknown): string;
@@ -1,8 +1,33 @@
1
- import { EndpointDeliveryError } from '@zhin.js/im-contract';
2
- import { clearTimeout, setTimeout } from 'node:timers';
1
+ import { createPendingRpc } from 'zhin.js/adapter';
3
2
  import { formatCompact, getLogger } from '@zhin.js/logger';
4
- import { WS_OPEN, } from './ws-types.js';
3
+ import { WS_OPEN } from './ws-types.js';
5
4
  const logger = getLogger('onebot11');
5
+ /** A Client's one request owner serves native SDK actions and framework operations. */
6
+ export function createOneBot11Rpc(options) {
7
+ const pending = createPendingRpc({
8
+ signal: options.signal, requestPrefix: 'ob11_', timeoutMs: 30_000,
9
+ transport: () => { const socket = options.socket(); return socket?.readyState === WS_OPEN ? socket : undefined; },
10
+ serialize: request => JSON.stringify({ action: request.action, params: request.params ?? {}, echo: request.id }),
11
+ });
12
+ return Object.freeze({
13
+ call: pending.call, disconnect: pending.disconnect, dispose: pending.dispose,
14
+ get pendingCount() { return pending.pendingCount; },
15
+ receive(data, ingest) {
16
+ try {
17
+ const message = JSON.parse(decodeWsPayload(data));
18
+ if ('echo' in message && typeof message.echo === 'string') {
19
+ pending.accept({ id: message.echo, value: message });
20
+ return;
21
+ }
22
+ ingest(message);
23
+ }
24
+ catch (error) {
25
+ logger.warn(formatCompact({ op: 'onebot11_parse_failed', endpoint: options.endpointId,
26
+ error: error instanceof Error ? error.message : String(error) }));
27
+ }
28
+ },
29
+ });
30
+ }
6
31
  export function decodeWsPayload(data) {
7
32
  if (typeof data === 'string')
8
33
  return data;
@@ -12,56 +37,3 @@ export function decodeWsPayload(data) {
12
37
  return new TextDecoder().decode(data);
13
38
  return String(data ?? '');
14
39
  }
15
- export function handleOneBot11WsMessage(data, options) {
16
- try {
17
- const raw = decodeWsPayload(data);
18
- const msg = JSON.parse(raw);
19
- if ('echo' in msg && typeof msg.echo === 'string') {
20
- const resp = msg;
21
- const pending = options.pending.get(resp.echo);
22
- if (pending) {
23
- options.pending.delete(resp.echo);
24
- clearTimeout(pending.timeout);
25
- pending.resolve(resp);
26
- }
27
- return;
28
- }
29
- options.ingest(msg);
30
- }
31
- catch (error) {
32
- logger.warn(formatCompact({
33
- op: 'onebot11_parse_failed',
34
- endpoint: options.endpointId,
35
- error: error instanceof Error ? error.message : String(error),
36
- }));
37
- }
38
- }
39
- export function callOneBot11WsAction(ws, pending, requestId, action, params) {
40
- if (!ws || ws.readyState !== WS_OPEN) {
41
- return Promise.reject(new EndpointDeliveryError('endpoint_disconnected', 'WebSocket 未连接', 'not_sent'));
42
- }
43
- const echo = `ob11_${++requestId.value}`;
44
- const req = { action, params, echo };
45
- return new Promise((resolve, reject) => {
46
- const timeout = setTimeout(() => {
47
- pending.delete(echo);
48
- reject(new EndpointDeliveryError('endpoint_timeout', `OneBot11 动作超时: ${action}`, 'unknown'));
49
- }, 30_000);
50
- pending.set(echo, { resolve, reject, timeout });
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
- }
59
- });
60
- }
61
- export function rejectAllPending(pending, message = '连接已关闭') {
62
- for (const [, entry] of pending) {
63
- clearTimeout(entry.timeout);
64
- entry.reject(new EndpointDeliveryError('endpoint_disconnected', message, 'unknown'));
65
- }
66
- pending.clear();
67
- }
package/lib/ws-types.d.ts CHANGED
@@ -10,9 +10,3 @@ export interface OneBot11WsCreateOptions {
10
10
  readonly headers?: Record<string, string>;
11
11
  }
12
12
  export declare const WS_OPEN = 1;
13
- export interface OneBot11PendingAction {
14
- resolve: (value: OneBot11ActionResponse) => void;
15
- reject: (err: Error) => void;
16
- timeout: NodeJS.Timeout;
17
- }
18
- import type { OneBot11ActionResponse } from './protocol.js';
@@ -1,28 +1,21 @@
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, type EndpointTransportState } from 'zhin.js/adapter';
4
+ import { endpointSpec, type Endpoint, type EndpointSpecification } 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';
8
- import { type Onebot11Client } from './client.js';
8
+ import { Onebot11Client } from './client.js';
9
9
  export interface OneBot11WssEndpointOptions {
10
+ readonly signal?: AbortSignal;
10
11
  readonly id: CapabilityId;
11
12
  /** 连接后 best-effort 调用 get_login_info 上报账号(Adapter 工厂开启)。 */
12
13
  readonly probeLoginInfo?: boolean;
13
14
  readonly http: HttpHost;
14
15
  readonly config: OneBot11WssConfig;
15
16
  }
16
- export declare class OneBot11WssEndpoint extends ClientEndpoint<Onebot11Client> {
17
+ export declare class OneBot11WssEndpoint extends Onebot11Client implements Endpoint {
17
18
  #private;
18
- readonly client: Onebot11Client;
19
- readonly management: EndpointManagement;
20
- readonly control: EndpointControl;
21
- readonly content: import("@zhin.js/adapter").EndpointContentPort;
19
+ readonly [endpointSpec]: EndpointSpecification;
22
20
  constructor(options: OneBot11WssEndpointOptions);
23
- get transportState(): EndpointTransportState;
24
- start(): Promise<void>;
25
- stop(): Promise<void>;
26
- send({ conversation, payload }: EndpointSendRequest): Promise<string>;
27
- recallMessage(messageId: string): Promise<void>;
28
21
  }
@@ -2,33 +2,38 @@ import { EndpointDeliveryError } from '@zhin.js/im-contract';
2
2
  /**
3
3
  * OneBot11 reverse WSS endpoint — accepts inbound WebSocket from OneBot implementation.
4
4
  */
5
- import { ClientEndpoint, createEndpointLifecycle, createRecallEndpointControl, } from 'zhin.js/adapter';
5
+ import { createEndpointSpecification, endpointSpec, createEndpointLifecycle, createRecallEndpointControl, } from 'zhin.js/adapter';
6
6
  import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
7
7
  import { createOneBot11EndpointManagement } from './endpoint-management.js';
8
8
  import { buildSendAction, formatInboundContent, formatInboundMetadata, formatOutboundSegments, isMessageEvent, onebot11InboundConversation, senderDisplayName, senderUserId, } from './protocol.js';
9
9
  import { receiveOneBot11SideEvent } from './side-event-dispatch.js';
10
10
  import { createOneBot11ContentPort } from './content-port.js';
11
11
  import { verifyOneBotAccessToken } from './wss-auth.js';
12
- import { callOneBot11WsAction, handleOneBot11WsMessage, rejectAllPending, } from './ws-transport.js';
12
+ import { createOneBot11Rpc } from './ws-transport.js';
13
13
  import { probeOnebot11SelfId, resolveOnebot11SelfId } from './self-id.js';
14
- import { callOnebot11Client, createOnebot11EndpointClient, forwardOnebot11ClientEvents } from './client.js';
15
- export class OneBot11WssEndpoint extends ClientEndpoint {
16
- client;
14
+ import { callOnebot11Client, readOnebot11Response, onebot11ClientOptions, forwardOnebot11ClientEvents, Onebot11Client } from './client.js';
15
+ export class OneBot11WssEndpoint extends Onebot11Client {
16
+ [endpointSpec];
17
+ #open = false;
17
18
  #logger;
18
19
  #options;
19
- management;
20
- control = createRecallEndpointControl((id) => this.recallMessage(id));
21
- content;
20
+ #management;
21
+ #control = createRecallEndpointControl((id, signal) => this.#recallMessage(id, signal));
22
+ #content;
23
+ #releaseClientEvents;
22
24
  #ws;
23
25
  #wsRelease;
24
26
  #connectionLifecycle;
25
27
  #connectionTask = Promise.resolve();
26
- #requestId = { value: 0 };
27
- #pending = new Map();
28
+ #rpc;
28
29
  #started = false;
29
30
  #stopped = false;
30
31
  constructor(options) {
31
- super();
32
+ const rpc = createOneBot11Rpc({ endpointId: options.config.id, signal: options.signal,
33
+ socket: () => this.#connectionLifecycle.state === 'closed' ? undefined : this.#ws,
34
+ });
35
+ super(onebot11ClientOptions(options.config, (action, params) => rpc.call({ action, params })));
36
+ this.#rpc = rpc;
32
37
  this.#logger = getAdapterLogger('onebot11', options.config.id);
33
38
  this.#options = options;
34
39
  this.#connectionLifecycle = createEndpointLifecycle({
@@ -36,16 +41,29 @@ export class OneBot11WssEndpoint extends ClientEndpoint {
36
41
  reconnect: false,
37
42
  heartbeat: { intervalMs: options.config.heartbeat_interval, watchdogMisses: 2 },
38
43
  });
39
- this.client = createOnebot11EndpointClient(options.config, (action, params) => this.#callApi(action, params));
40
- const callApi = (action, params) => callOnebot11Client(this.client, action, params);
41
- this.management = createOneBot11EndpointManagement({ callApi });
42
- this.content = createOneBot11ContentPort(callApi);
43
- this.bindClientEvents((receive) => forwardOnebot11ClientEvents(this.client, receive), (name, payload) => {
44
- if (name === 'event')
45
- this.#admitRaw(payload);
46
- }, (_name, error) => this.#warnPlatformEvent(error));
44
+ const callApi = (request) => this.#rpc.call(request).then(readOnebot11Response);
45
+ this.#management = createOneBot11EndpointManagement({ callApi });
46
+ this.#content = createOneBot11ContentPort(callApi);
47
+ const readTransportState = () => this.#transportState;
48
+ this[endpointSpec] = createEndpointSpecification(this, {
49
+ name: options.config.id,
50
+ start: () => this.#start(), open: () => { this.#open = true; },
51
+ close: () => { this.#open = false; }, stop: () => this.#stop(),
52
+ send: request => this.#send(request),
53
+ get transportState() { return readTransportState(); },
54
+ management: this.#management, control: this.#control, content: this.#content,
55
+ });
56
+ this.#releaseClientEvents = forwardOnebot11ClientEvents(this, (name, payload) => {
57
+ void this[endpointSpec].events.run(() => {
58
+ if (this.#stopped || (!this.#open && !this.#started))
59
+ return;
60
+ void this[endpointSpec].events.platform(name, payload).catch(error => this.#warnPlatformEvent(error));
61
+ if (name === 'event')
62
+ this.#admitRaw(payload);
63
+ }).catch(error => this.#warnPlatformEvent(error));
64
+ });
47
65
  }
48
- get transportState() {
66
+ get #transportState() {
49
67
  if (this.#stopped)
50
68
  return 'stopped';
51
69
  if (!this.#started)
@@ -56,7 +74,7 @@ export class OneBot11WssEndpoint extends ClientEndpoint {
56
74
  return 'open';
57
75
  return this.#connectionLifecycle.state === 'connecting' ? 'connecting' : 'idle';
58
76
  }
59
- async start() {
77
+ async #start() {
60
78
  if (this.#started)
61
79
  return;
62
80
  this.#stopped = false;
@@ -89,47 +107,56 @@ export class OneBot11WssEndpoint extends ClientEndpoint {
89
107
  path: this.#options.config.path,
90
108
  }));
91
109
  }
92
- async stop() {
110
+ async #stop() {
93
111
  this.#stopped = true;
94
- this.close();
112
+ this.#open = false;
95
113
  this.#wsRelease?.();
96
114
  this.#wsRelease = undefined;
97
- await this.#connectionTask;
98
- await this.#connectionLifecycle.stop();
99
- rejectAllPending(this.#pending);
100
- this.#ws = undefined;
101
- this.#started = false;
102
- this.reportSelfUserId(undefined);
115
+ try {
116
+ await this.#connectionTask;
117
+ await this.#connectionLifecycle.stop();
118
+ }
119
+ finally {
120
+ this.#rpc.dispose();
121
+ this.#releaseClientEvents();
122
+ this.#ws = undefined;
123
+ this.#started = false;
124
+ this[endpointSpec].reportSelfUserId(undefined);
125
+ }
103
126
  }
104
- async send({ conversation, payload }) {
127
+ async #send({ conversation, payload, signal }) {
105
128
  const message = formatOutboundSegments(payload);
106
129
  const { action, params } = buildSendAction(conversation, message);
107
- const data = await callOnebot11Client(this.client, action, params);
130
+ const data = readOnebot11Response(await this.#rpc.call({ action, params, signal }));
108
131
  const rawId = data?.message_id;
109
132
  if ((typeof rawId !== 'number' && typeof rawId !== 'string') || String(rawId).trim() === '' || (typeof rawId === 'number' && !Number.isFinite(rawId))) {
110
133
  throw new EndpointDeliveryError('delivery_unconfirmed', 'Message delivery is unconfirmed: platform returned no message_id', 'unknown');
111
134
  }
112
135
  return String(rawId);
113
136
  }
114
- async recallMessage(messageId) {
137
+ async #recallMessage(messageId, signal) {
115
138
  if (!messageId)
116
139
  return;
117
- await callOnebot11Client(this.client, 'delete_msg', { message_id: Number(messageId) });
118
- }
119
- #callApi(action, params = {}) {
120
- return callOneBot11WsAction(this.#connectionLifecycle.state === 'closed' ? undefined : this.#ws, this.#pending, this.#requestId, action, params);
140
+ await this.#rpc.call({ action: 'delete_msg', params: { message_id: Number(messageId) }, signal }).then(readOnebot11Response);
121
141
  }
122
142
  #admitRaw(ev) {
123
143
  const selfId = resolveOnebot11SelfId(ev.self_id);
124
- if (!this.#stopped && selfId && selfId !== this.selfUserId)
125
- this.reportSelfUserId(selfId);
144
+ if (!this.#stopped && selfId)
145
+ this[endpointSpec].reportSelfUserId(selfId);
126
146
  if (!isMessageEvent(ev)) {
127
- receiveOneBot11SideEvent((name, payload) => this.emit(name, payload), this.#options.config.id, { callApi: (action, params) => callOnebot11Client(this.client, action, params) }, ev, this.#logger);
147
+ receiveOneBot11SideEvent(ev, {
148
+ emit: (name, payload) => this[endpointSpec].events.emit(name, payload),
149
+ endpointKey: this.#options.config.id,
150
+ caller: { callApi: (action, params) => callOnebot11Client({ client: this, action, params }) },
151
+ logger: this.#logger,
152
+ });
128
153
  return;
129
154
  }
130
- const conversation = onebot11InboundConversation(String(this.#options.id), ev);
155
+ const conversation = { ...onebot11InboundConversation(String(this.#options.id), ev), endpoint: this[endpointSpec].identity };
131
156
  const inbound = formatInboundMetadata(ev, this.#options.config.id);
132
- void this.emit('message.receive', {
157
+ void this[endpointSpec].events.message({
158
+ event: ev,
159
+ ...(typeof ev.time === 'number' ? { platformSentAt: ev.time * 1000 } : {}),
133
160
  conversation,
134
161
  message: { conversation, id: String(ev.message_id) },
135
162
  content: formatInboundContent(ev),
@@ -152,16 +179,16 @@ export class OneBot11WssEndpoint extends ClientEndpoint {
152
179
  }
153
180
  /** Best-effort 登录账号探测;失败仅 debug,不影响连接。 */
154
181
  async #probeSelfUserId() {
155
- const selfId = await probeOnebot11SelfId(this.client, (error) => {
182
+ const selfId = await probeOnebot11SelfId(this, (error) => {
156
183
  this.#logger.debug(formatCompact({
157
184
  op: 'onebot11_get_login_info_failed',
158
185
  error: error instanceof Error ? error.message : String(error),
159
186
  }));
160
187
  });
161
- if (this.#stopped || this.transportState !== 'open')
188
+ if (this.#stopped || this.#transportState !== 'open')
162
189
  return;
163
- if (selfId && selfId !== this.selfUserId)
164
- this.reportSelfUserId(selfId);
190
+ if (selfId)
191
+ this[endpointSpec].reportSelfUserId(selfId);
165
192
  }
166
193
  #warnPlatformEvent(error) {
167
194
  this.#logger.warn(formatCompact({
@@ -180,12 +207,12 @@ export class OneBot11WssEndpoint extends ClientEndpoint {
180
207
  }
181
208
  const socket = connection.socket;
182
209
  await this.#connectionLifecycle.stop();
183
- rejectAllPending(this.#pending, '连接已替换');
210
+ this.#rpc.disconnect('连接已替换');
184
211
  await this.#connectionLifecycle.start(async (lifecycleHandle) => {
185
212
  this.#ws = socket;
186
213
  lifecycleHandle.onForceClose(() => {
187
214
  if (this.#ws === socket) {
188
- rejectAllPending(this.#pending);
215
+ this.#rpc.disconnect();
189
216
  }
190
217
  try {
191
218
  socket.close();
@@ -212,11 +239,7 @@ export class OneBot11WssEndpoint extends ClientEndpoint {
212
239
  if (this.#ws !== socket || this.#connectionLifecycle.state === 'closed')
213
240
  return;
214
241
  this.#connectionLifecycle.notifyHeartbeatAck();
215
- handleOneBot11WsMessage(data, {
216
- endpointId: this.#options.config.id,
217
- pending: this.#pending,
218
- ingest: (ev) => this.client.ingest(ev),
219
- });
242
+ this.#rpc.receive(data, (ev) => this.ingest(ev));
220
243
  });
221
244
  socket.on('error', (error) => {
222
245
  if (this.#ws !== socket)
@@ -228,7 +251,7 @@ export class OneBot11WssEndpoint extends ClientEndpoint {
228
251
  return;
229
252
  if (this.#ws === socket) {
230
253
  this.#ws = undefined;
231
- rejectAllPending(this.#pending);
254
+ this.#rpc.disconnect();
232
255
  }
233
256
  lifecycleHandle.notifyClosed(new Error('OneBot11 reverse WebSocket closed'));
234
257
  });