@zhin.js/adapter-onebot12 1.1.17 → 1.1.19

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 (61) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +33 -4
  3. package/adapters/onebot12/index.js +3 -32
  4. package/adapters/onebot12/index.ts +2 -38
  5. package/commands/onebot12/endpoint/definition.js +2 -2
  6. package/commands/onebot12/endpoint/definition.ts +2 -2
  7. package/lib/adapter.d.ts +21 -0
  8. package/lib/adapter.js +34 -0
  9. package/lib/client.d.ts +9 -11
  10. package/lib/client.js +23 -13
  11. package/lib/console-spec.d.ts +12 -0
  12. package/lib/console-spec.js +15 -0
  13. package/lib/console.d.ts +8 -0
  14. package/lib/console.js +13 -0
  15. package/lib/content-port.d.ts +2 -2
  16. package/lib/content-port.js +1 -1
  17. package/lib/endpoint-management.d.ts +5 -5
  18. package/lib/endpoint-management.js +17 -17
  19. package/lib/identity.d.ts +4 -0
  20. package/lib/identity.js +53 -0
  21. package/lib/index.d.ts +3 -2
  22. package/lib/index.js +2 -1
  23. package/lib/protocol.d.ts +32 -4
  24. package/lib/protocol.js +54 -53
  25. package/lib/response.d.ts +5 -0
  26. package/lib/response.js +17 -0
  27. package/lib/side-event-dispatch.d.ts +8 -1
  28. package/lib/side-event-dispatch.js +2 -1
  29. package/lib/webhook.d.ts +5 -11
  30. package/lib/webhook.js +72 -37
  31. package/lib/ws-endpoint.d.ts +5 -11
  32. package/lib/ws-endpoint.js +79 -45
  33. package/lib/ws-transport.d.ts +18 -10
  34. package/lib/ws-transport.js +26 -45
  35. package/lib/ws-types.d.ts +0 -6
  36. package/lib/wss-endpoint.d.ts +5 -11
  37. package/lib/wss-endpoint.js +82 -48
  38. package/package.json +22 -14
  39. package/pages/index/index.js +15 -0
  40. package/pages/index/index.ts +16 -0
  41. package/plugin.js +3 -5
  42. package/schema.json +15 -3
  43. package/src/adapter.ts +36 -0
  44. package/src/client.ts +26 -29
  45. package/src/console-spec.ts +20 -0
  46. package/src/console.ts +18 -0
  47. package/src/content-port.ts +3 -3
  48. package/src/endpoint-management.ts +25 -24
  49. package/src/identity.ts +52 -0
  50. package/src/index.ts +4 -1
  51. package/src/protocol.ts +71 -62
  52. package/src/response.ts +19 -0
  53. package/src/side-event-dispatch.ts +9 -7
  54. package/src/webhook.ts +70 -55
  55. package/src/ws-endpoint.ts +75 -63
  56. package/src/ws-transport.ts +37 -73
  57. package/src/ws-types.ts +0 -7
  58. package/src/wss-endpoint.ts +80 -66
  59. package/lib/onebot12-runtime-state.d.ts +0 -1
  60. package/lib/onebot12-runtime-state.js +0 -6
  61. package/src/onebot12-runtime-state.ts +0 -7
@@ -3,7 +3,8 @@
3
3
  */
4
4
  import WebSocket from 'ws';
5
5
  import {
6
- ClientEndpoint,
6
+ createEndpointSpecification, endpointSpec,
7
+ type Endpoint, type EndpointSpecification,
7
8
  createRecallEndpointControl,
8
9
  createEndpointLifecycle,
9
10
  type EndpointConnectHandle,
@@ -11,12 +12,14 @@ import {
11
12
  type EndpointLifecycle,
12
13
  type EndpointManagement,
13
14
  type EndpointSendRequest,
15
+ type PendingRpcRequest,
14
16
  } from 'zhin.js/adapter';
15
17
  import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
16
18
  import type { CapabilityId } from 'zhin.js';
17
19
  import { createOneBot12EndpointManagement } from './endpoint-management.js';
18
20
  import {
19
21
  buildSendMessageParams,
22
+ matchesOneBot12Self,
20
23
  buildWsConnectOptions,
21
24
  formatInboundContent,
22
25
  formatOutboundSegments,
@@ -31,19 +34,15 @@ import {
31
34
  } from './protocol.js';
32
35
  import { receiveOneBot12SideEvent } from './side-event-dispatch.js';
33
36
  import { createOneBot12ContentPort } from './content-port.js';
34
- import { callOnebot12Client, createOnebot12EndpointClient, forwardOnebot12ClientEvents, type Onebot12Client } from './client.js';
37
+ import { callOnebot12Client, readOnebot12Response, onebot12ClientOptions, forwardOnebot12ClientEvents, Onebot12Client } from './client.js';
35
38
  import {
36
- type OneBot12PendingAction,
37
39
  type OneBot12WsCreateOptions,
38
40
  type OneBot12WsSocket,
39
41
  } from './ws-types.js';
40
- import {
41
- callOneBot12WsAction,
42
- handleOneBot12WsMessage,
43
- rejectAllPending,
44
- } from './ws-transport.js';
42
+ import { createOneBot12Rpc, type OneBot12Rpc } from './ws-transport.js';
45
43
 
46
44
  export interface OneBot12WsEndpointOptions {
45
+ readonly signal?: AbortSignal;
47
46
  readonly id: CapabilityId;
48
47
  readonly config: OneBot12WsConfig;
49
48
  readonly createWebSocket?: (
@@ -52,34 +51,48 @@ export interface OneBot12WsEndpointOptions {
52
51
  ) => OneBot12WsSocket;
53
52
  }
54
53
 
55
- export class OneBot12WsEndpoint extends ClientEndpoint<Onebot12Client> {
56
- readonly client: Onebot12Client;
54
+ export class OneBot12WsEndpoint extends Onebot12Client implements Endpoint {
55
+ readonly [endpointSpec]: EndpointSpecification;
56
+ #open = false;
57
57
  readonly #logger!: ReturnType<typeof getAdapterLogger>;
58
58
 
59
59
  readonly #options: OneBot12WsEndpointOptions;
60
- readonly management: EndpointManagement;
61
- readonly control: EndpointControl = createRecallEndpointControl((id) => this.recallMessage(id));
62
- readonly content;
60
+ readonly #management: EndpointManagement;
61
+ readonly #control: EndpointControl = createRecallEndpointControl((id, signal?: AbortSignal) => this.#recallMessage(id, signal));
62
+ readonly #content;
63
+ readonly #releaseClientEvents: () => void;
63
64
  readonly #lifecycle: EndpointLifecycle;
64
65
  #ws?: OneBot12WsSocket;
65
- #requestId = { value: 0 };
66
- #pending = new Map<string, OneBot12PendingAction>();
66
+ readonly #rpc: OneBot12Rpc;
67
67
 
68
68
  constructor(options: OneBot12WsEndpointOptions) {
69
- super();
69
+ const rpc = createOneBot12Rpc({ endpointId: options.config.id, signal: options.signal,
70
+ self: { platform: options.config.platform, user_id: options.config.userId },
71
+ socket: () => this.#lifecycle.state === 'open' ? this.#ws : undefined,
72
+ });
73
+ super(onebot12ClientOptions(options.config, (action, params) => rpc.call({ action, params })));
74
+ this.#rpc = rpc;
70
75
  this.#logger = getAdapterLogger('onebot12', options.config.id);
71
76
  this.#options = options;
72
- this.client = createOnebot12EndpointClient(options.config, (action, params) => this.#callAction(action, params ?? {}));
73
- const callApi = (action: string, params?: Record<string, unknown>) => callOnebot12Client(this.client, action, params);
74
- this.management = createOneBot12EndpointManagement({ callApi });
75
- this.content = createOneBot12ContentPort(callApi);
76
- this.bindClientEvents(
77
- (receive) => forwardOnebot12ClientEvents(this.client, receive),
78
- (name, payload) => {
77
+ const callApi = (request: PendingRpcRequest) => this.#rpc.call(request).then(readOnebot12Response);
78
+ this.#management = createOneBot12EndpointManagement({ callApi });
79
+ this.#content = createOneBot12ContentPort(callApi);
80
+ const readTransportState = () => this.#lifecycle.state;
81
+ this[endpointSpec] = createEndpointSpecification(this, {
82
+ name: options.config.id,
83
+ start: () => this.#start(), open: () => { this.#open = true; },
84
+ close: () => { this.#open = false; }, stop: () => this.#stop(),
85
+ send: request => this.#send(request),
86
+ get transportState() { return readTransportState(); },
87
+ management: this.#management, control: this.#control, content: this.#content,
88
+ });
89
+ this.#releaseClientEvents = forwardOnebot12ClientEvents(this, (name, payload) => {
90
+ void this[endpointSpec].events.run(() => {
91
+ if (!this.#open && !this.#lifecycle.started) return;
92
+ void this[endpointSpec].events.platform(name, payload).catch(error => this.#warnPlatformEvent(error));
79
93
  if (name === 'event') this.#admitRaw(payload as OneBot12Event);
80
- },
81
- (_name, error) => this.#warnPlatformEvent(error),
82
- );
94
+ }).catch(error => this.#warnPlatformEvent(error));
95
+ });
83
96
  const { config } = options;
84
97
  this.#lifecycle = createEndpointLifecycle({
85
98
  name: config.id,
@@ -94,7 +107,7 @@ export class OneBot12WsEndpoint extends ClientEndpoint<Onebot12Client> {
94
107
  });
95
108
  }
96
109
 
97
- async start(): Promise<void> {
110
+ async #start(): Promise<void> {
98
111
  if (this.#lifecycle.started) return;
99
112
  try {
100
113
  await this.#lifecycle.start((handle) => this.#connect(handle));
@@ -112,29 +125,29 @@ export class OneBot12WsEndpoint extends ClientEndpoint<Onebot12Client> {
112
125
  }
113
126
  }
114
127
 
115
- async stop(): Promise<void> {
116
- this.close();
128
+ async #stop(): Promise<void> {
129
+ this.#open = false;
117
130
  // 基座负责:清重连/心跳定时器、强关 ws、唤醒 stop-during-connect 竞态
118
- await this.#lifecycle.stop();
119
- rejectAllPending(this.#pending);
120
- this.#ws = undefined;
131
+ try { await this.#lifecycle.stop(); }
132
+ finally {
133
+ this.#rpc.dispose();
134
+ this.#releaseClientEvents();
135
+ this.#ws = undefined;
136
+ }
121
137
  }
122
138
 
123
- async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
124
- const materialized = await uploadOneBot12MediaSegments(
125
- payload,
126
- (action, params) => callOnebot12Client(this.client, action, params),
127
- (error) => {
128
- this.#logger.warn(formatCompact({
129
- op: 'onebot12_upload_failed',
130
- endpoint: this.#options.config.id,
131
- error: error instanceof Error ? error.message : String(error),
132
- }));
139
+ async #send({ conversation, payload, signal }: EndpointSendRequest): Promise<string> {
140
+ const materialized = await uploadOneBot12MediaSegments({
141
+ payload, signal,
142
+ callAction: (action, params) => this.#rpc.call({ action, params, signal }).then(readOnebot12Response),
143
+ onUploadFailed: error => {
144
+ this.#logger.warn(formatCompact({ op: 'onebot12_upload_failed', endpoint: this.#options.config.id,
145
+ error: error instanceof Error ? error.message : String(error) }));
133
146
  },
134
- );
147
+ });
135
148
  const message = formatOutboundSegments(materialized);
136
149
  const params = buildSendMessageParams(conversation, message);
137
- const data = await callOnebot12Client<{ message_id?: string }>(this.client, 'send_message', params);
150
+ const data = readOnebot12Response<{ message_id?: string }>(await this.#rpc.call({ action: 'send_message', params, signal }));
138
151
  const messageId = data?.message_id ?? '';
139
152
  this.#logger.debug(formatCompact({
140
153
  op: 'onebot12_send',
@@ -146,27 +159,30 @@ export class OneBot12WsEndpoint extends ClientEndpoint<Onebot12Client> {
146
159
  return messageId;
147
160
  }
148
161
 
149
- async recallMessage(messageId: string): Promise<void> {
162
+ async #recallMessage(messageId: string, signal?: AbortSignal): Promise<void> {
150
163
  if (!messageId) return;
151
- await callOnebot12Client(this.client, 'delete_message', { message_id: messageId });
164
+ await this.#rpc.call({ action: 'delete_message', params: { message_id: messageId }, signal }).then(readOnebot12Response);
152
165
  }
153
166
 
154
167
  #admitRaw(ev: OneBot12Event): void {
168
+ if (!matchesOneBot12Self(this.#options.config, ev)) return;
169
+ this[endpointSpec].reportSelfUserId(ev.self.user_id);
155
170
  if (!isMessageEvent(ev)) {
156
- receiveOneBot12SideEvent(
157
- (name, payload) => this.emit(name, payload),
158
- this.#options.config.id,
159
- { callApi: (action, params) => callOnebot12Client(this.client, action, params) },
160
- ev,
161
- this.#logger,
162
- );
171
+ receiveOneBot12SideEvent(ev, {
172
+ emit: (name, payload) => this[endpointSpec].events.emit(name, payload),
173
+ endpointKey: this.#options.config.id,
174
+ caller: { callApi: (action, params) => callOnebot12Client({ client: this, action, params }) },
175
+ logger: this.#logger,
176
+ });
163
177
  return;
164
178
  }
165
- const conversation = onebot12InboundConversation(String(this.#options.id), ev);
179
+ const conversation = { ...onebot12InboundConversation(String(this.#options.id), ev), endpoint: this[endpointSpec].identity };
166
180
  const content = formatInboundContent(ev);
167
181
  const nickname = senderNickname(ev);
168
182
  const mentioned = isBotMentioned(ev);
169
- void this.emit('message.receive', {
183
+ void this[endpointSpec].events.message({
184
+ event: ev,
185
+ ...(typeof ev.time === 'number' ? { platformSentAt: ev.time * 1000 } : {}),
170
186
  conversation,
171
187
  message: { conversation, id: ev.message_id },
172
188
  content,
@@ -234,12 +250,8 @@ export class OneBot12WsEndpoint extends ClientEndpoint<Onebot12Client> {
234
250
  ws.on('message', (data) => {
235
251
  if (this.#ws !== ws) return;
236
252
  this.#lifecycle.notifyHeartbeatAck();
237
- handleOneBot12WsMessage(data, {
238
- endpointId: this.#options.config.id,
239
- pending: this.#pending,
240
- ingest: (event) => this.client.ingest(
241
- event as Parameters<Onebot12Client['ingest']>[0],
242
- ),
253
+ this.#rpc.receive(data, event => {
254
+ if (matchesOneBot12Self(this.#options.config, event)) this.ingest(event as Parameters<Onebot12Client['ingest']>[0]);
243
255
  });
244
256
  });
245
257
 
@@ -256,7 +268,7 @@ export class OneBot12WsEndpoint extends ClientEndpoint<Onebot12Client> {
256
268
  }
257
269
  if (this.#ws === ws) {
258
270
  this.#ws = undefined;
259
- rejectAllPending(this.#pending);
271
+ this.#rpc.disconnect();
260
272
  }
261
273
  // 断开日志与重连武装均由基座负责;仅曾 open 的连接才会武装重连,
262
274
  // 初始连接失败由 start() 的拒绝路径复位,不产生僵尸重连。
@@ -280,6 +292,6 @@ export class OneBot12WsEndpoint extends ClientEndpoint<Onebot12Client> {
280
292
  }
281
293
 
282
294
  #callAction(action: string, params: Record<string, unknown>) {
283
- return callOneBot12WsAction(this.#ws, this.#pending, this.#requestId, action, params);
295
+ return this.#rpc.call({ action, params });
284
296
  }
285
297
  }
@@ -1,86 +1,50 @@
1
- import { clearTimeout, setTimeout } from 'node:timers';
1
+ import { createPendingRpc, type PendingRpc, type PendingRpcRequest } from 'zhin.js/adapter';
2
2
  import { formatCompact, getLogger } from '@zhin.js/logger';
3
- import type {
4
- OneBot12ActionRequest,
5
- OneBot12ActionResponse,
6
- OneBot12Event,
7
- } from './protocol.js';
8
- import {
9
- type OneBot12PendingAction,
10
- type OneBot12WsSocket,
11
- WS_OPEN,
12
- } from './ws-types.js';
3
+ import type { OneBot12ActionResponse, OneBot12Event, OneBot12Self } from './protocol.js';
4
+ import { WS_OPEN, type OneBot12WsSocket } from './ws-types.js';
13
5
 
14
6
  const logger = getLogger('onebot12');
15
-
16
- export interface OneBot12WsMessageOptions {
7
+ export interface OneBot12RpcOptions {
17
8
  readonly endpointId: string;
18
- readonly pending: Map<string, OneBot12PendingAction>;
19
- readonly ingest: (event: OneBot12Event) => void;
9
+ readonly signal?: AbortSignal;
10
+ /** Reads the lifecycle's selected socket; RPC never opens or reconnects it. */
11
+ readonly socket: () => OneBot12WsSocket | undefined;
12
+ readonly self: OneBot12Self;
20
13
  }
21
-
22
- export function handleOneBot12WsMessage(
23
- data: unknown,
24
- options: OneBot12WsMessageOptions,
25
- ): void {
26
- try {
27
- const message = JSON.parse(decodeOneBot12WsPayload(data)) as
28
- | OneBot12Event
29
- | OneBot12ActionResponse;
30
- if ('echo' in message && typeof message.echo === 'string') {
31
- const response = message as OneBot12ActionResponse;
32
- const pending = options.pending.get(response.echo!);
33
- if (pending) {
34
- options.pending.delete(response.echo!);
35
- clearTimeout(pending.timeout);
36
- pending.resolve(response);
37
- }
38
- return;
39
- }
40
- options.ingest(message as OneBot12Event);
41
- } catch (error) {
42
- logger.warn(formatCompact({
43
- op: 'onebot12_parse_failed',
44
- endpoint: options.endpointId,
45
- error: error instanceof Error ? error.message : String(error),
46
- }));
47
- }
14
+ export interface OneBot12Rpc {
15
+ call(request: PendingRpcRequest): Promise<OneBot12ActionResponse>;
16
+ receive(data: unknown, ingest: (event: OneBot12Event) => void): void;
17
+ disconnect: PendingRpc<OneBot12ActionResponse>['disconnect'];
18
+ dispose(): void;
19
+ readonly pendingCount: number;
48
20
  }
49
21
 
50
- export function callOneBot12WsAction(
51
- ws: OneBot12WsSocket | undefined,
52
- pending: Map<string, OneBot12PendingAction>,
53
- requestId: { value: number },
54
- action: string,
55
- params: Record<string, unknown>,
56
- ): Promise<OneBot12ActionResponse> {
57
- if (!ws || ws.readyState !== WS_OPEN) {
58
- return Promise.reject(new Error('WebSocket 未连接'));
59
- }
60
- const echo = `ob12_${++requestId.value}`;
61
- const request: OneBot12ActionRequest = { action, params, echo };
62
- return new Promise((resolve, reject) => {
63
- const timeout = setTimeout(() => {
64
- pending.delete(echo);
65
- reject(new Error(`OneBot12 动作超时: ${action}`));
66
- }, 30_000);
67
- pending.set(echo, { resolve, reject, timeout });
68
- ws.send(JSON.stringify(request));
22
+ /** A Client's one request owner serves native SDK actions and framework operations. */
23
+ export function createOneBot12Rpc(options: OneBot12RpcOptions): OneBot12Rpc {
24
+ const pending = createPendingRpc<OneBot12ActionResponse>({
25
+ signal: options.signal, requestPrefix: 'ob12_', timeoutMs: 30_000,
26
+ transport: () => { const socket = options.socket(); return socket?.readyState === WS_OPEN ? socket : undefined; },
27
+ serialize: request => JSON.stringify({ action: request.action, params: request.params ?? {}, echo: request.id, self: options.self }),
28
+ });
29
+ return Object.freeze({
30
+ call: pending.call, disconnect: pending.disconnect, dispose: pending.dispose,
31
+ get pendingCount() { return pending.pendingCount; },
32
+ receive(data: unknown, ingest: (event: OneBot12Event) => void) {
33
+ try {
34
+ const message = JSON.parse(decodeWsPayload(data)) as OneBot12Event | OneBot12ActionResponse;
35
+ if ('echo' in message && typeof message.echo === 'string') {
36
+ pending.accept({ id: message.echo, value: message as OneBot12ActionResponse }); return;
37
+ }
38
+ ingest(message as OneBot12Event);
39
+ } catch (error) {
40
+ logger.warn(formatCompact({ op: 'onebot12_parse_failed', endpoint: options.endpointId,
41
+ error: error instanceof Error ? error.message : String(error) }));
42
+ }
43
+ },
69
44
  });
70
45
  }
71
46
 
72
- export function rejectAllPending(
73
- pending: Map<string, OneBot12PendingAction>,
74
- message = '连接已关闭',
75
- ): void {
76
- for (const [, entry] of pending) {
77
- clearTimeout(entry.timeout);
78
- entry.reject(new Error(message));
79
- }
80
- pending.clear();
81
- }
82
-
83
- function decodeOneBot12WsPayload(data: unknown): string {
47
+ export function decodeWsPayload(data: unknown): string {
84
48
  if (typeof data === 'string') return data;
85
49
  if (Buffer.isBuffer(data)) return data.toString();
86
50
  if (data instanceof ArrayBuffer) return new TextDecoder().decode(data);
package/src/ws-types.ts CHANGED
@@ -1,4 +1,3 @@
1
- import type { OneBot12ActionResponse } from './protocol.js';
2
1
 
3
2
  /** Minimal WS surface used by the endpoint (real `ws` or test mock). */
4
3
  export interface OneBot12WsSocket {
@@ -13,9 +12,3 @@ export interface OneBot12WsCreateOptions {
13
12
  }
14
13
 
15
14
  export const WS_OPEN = 1;
16
-
17
- export interface OneBot12PendingAction {
18
- resolve: (value: OneBot12ActionResponse) => void;
19
- reject: (err: Error) => void;
20
- timeout: NodeJS.Timeout;
21
- }
@@ -2,13 +2,15 @@
2
2
  * OneBot12 reverse WSS endpoint — accepts inbound WebSocket from OneBot implementation.
3
3
  */
4
4
  import {
5
- ClientEndpoint,
5
+ createEndpointSpecification, endpointSpec,
6
+ type Endpoint, type EndpointSpecification,
6
7
  createEndpointLifecycle,
7
8
  createRecallEndpointControl,
8
9
  type EndpointLifecycle,
9
10
  type EndpointControl,
10
11
  type EndpointManagement,
11
12
  type EndpointSendRequest,
13
+ type PendingRpcRequest,
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';
@@ -16,6 +18,7 @@ import type { CapabilityId } from 'zhin.js';
16
18
  import { createOneBot12EndpointManagement } from './endpoint-management.js';
17
19
  import {
18
20
  buildSendMessageParams,
21
+ matchesOneBot12Self,
19
22
  formatInboundContent,
20
23
  formatOutboundSegments,
21
24
  isBotMentioned,
@@ -29,59 +32,70 @@ import {
29
32
  } from './protocol.js';
30
33
  import { receiveOneBot12SideEvent } from './side-event-dispatch.js';
31
34
  import { createOneBot12ContentPort } from './content-port.js';
32
- import { callOnebot12Client, createOnebot12EndpointClient, forwardOnebot12ClientEvents, type Onebot12Client } from './client.js';
35
+ import { callOnebot12Client, readOnebot12Response, onebot12ClientOptions, forwardOnebot12ClientEvents, Onebot12Client } from './client.js';
33
36
  import { verifyOneBotAccessToken } from './wss-auth.js';
34
- import { type OneBot12PendingAction, type OneBot12WsSocket } from './ws-types.js';
35
- import {
36
- callOneBot12WsAction,
37
- handleOneBot12WsMessage,
38
- rejectAllPending,
39
- } from './ws-transport.js';
37
+ import { type OneBot12WsSocket } from './ws-types.js';
38
+ import { createOneBot12Rpc, type OneBot12Rpc } from './ws-transport.js';
40
39
 
41
40
  export interface OneBot12WssEndpointOptions {
41
+ readonly signal?: AbortSignal;
42
42
  readonly id: CapabilityId;
43
43
  readonly http: HttpHost;
44
44
  readonly config: OneBot12WssConfig;
45
45
  }
46
46
 
47
- export class OneBot12WssEndpoint extends ClientEndpoint<Onebot12Client> {
48
- readonly client: Onebot12Client;
47
+ export class OneBot12WssEndpoint extends Onebot12Client implements Endpoint {
48
+ readonly [endpointSpec]: EndpointSpecification;
49
+ #open = false;
49
50
  readonly #logger!: ReturnType<typeof getAdapterLogger>;
50
51
 
51
52
  readonly #options: OneBot12WssEndpointOptions;
52
- readonly management: EndpointManagement;
53
- readonly control: EndpointControl = createRecallEndpointControl((id) => this.recallMessage(id));
54
- readonly content;
53
+ readonly #management: EndpointManagement;
54
+ readonly #control: EndpointControl = createRecallEndpointControl((id, signal?: AbortSignal) => this.#recallMessage(id, signal));
55
+ readonly #content;
56
+ readonly #releaseClientEvents: () => void;
55
57
  #ws?: OneBot12WsSocket;
56
58
  #wsRelease?: () => void;
57
59
  readonly #connectionLifecycle: EndpointLifecycle;
58
60
  #connectionTask = Promise.resolve();
59
- #requestId = { value: 0 };
60
- #pending = new Map<string, OneBot12PendingAction>();
61
+ readonly #rpc: OneBot12Rpc;
61
62
  #started = false;
62
63
 
63
64
  constructor(options: OneBot12WssEndpointOptions) {
64
- super();
65
+ const rpc = createOneBot12Rpc({ endpointId: options.config.id, signal: options.signal,
66
+ self: { platform: options.config.platform, user_id: options.config.userId },
67
+ socket: () => this.#connectionLifecycle.state === 'closed' ? undefined : this.#ws,
68
+ });
69
+ super(onebot12ClientOptions(options.config, (action, params) => rpc.call({ action, params })));
70
+ this.#rpc = rpc;
65
71
  this.#logger = getAdapterLogger('onebot12', options.config.id);
66
72
  this.#options = options;
67
73
  this.#connectionLifecycle = createEndpointLifecycle({
68
74
  name: `${options.config.id}:inbound`,
69
75
  reconnect: false,
70
76
  });
71
- this.client = createOnebot12EndpointClient(options.config, (action, params) => this.#callAction(action, params ?? {}));
72
- const callApi = (action: string, params?: Record<string, unknown>) => callOnebot12Client(this.client, action, params);
73
- this.management = createOneBot12EndpointManagement({ callApi });
74
- this.content = createOneBot12ContentPort(callApi);
75
- this.bindClientEvents(
76
- (receive) => forwardOnebot12ClientEvents(this.client, receive),
77
- (name, payload) => {
77
+ const callApi = (request: PendingRpcRequest) => this.#rpc.call(request).then(readOnebot12Response);
78
+ this.#management = createOneBot12EndpointManagement({ callApi });
79
+ this.#content = createOneBot12ContentPort(callApi);
80
+ const readTransportState = () => this.#connectionLifecycle.state;
81
+ this[endpointSpec] = createEndpointSpecification(this, {
82
+ name: options.config.id,
83
+ start: () => this.#start(), open: () => { this.#open = true; },
84
+ close: () => { this.#open = false; }, stop: () => this.#stop(),
85
+ send: request => this.#send(request),
86
+ get transportState() { return readTransportState(); },
87
+ management: this.#management, control: this.#control, content: this.#content,
88
+ });
89
+ this.#releaseClientEvents = forwardOnebot12ClientEvents(this, (name, payload) => {
90
+ void this[endpointSpec].events.run(() => {
91
+ if (!this.#open && !this.#started) return;
92
+ void this[endpointSpec].events.platform(name, payload).catch(error => this.#warnPlatformEvent(error));
78
93
  if (name === 'event') this.#admitRaw(payload as OneBot12Event);
79
- },
80
- (_name, error) => this.#warnPlatformEvent(error),
81
- );
94
+ }).catch(error => this.#warnPlatformEvent(error));
95
+ });
82
96
  }
83
97
 
84
- async start(): Promise<void> {
98
+ async #start(): Promise<void> {
85
99
  if (this.#started) return;
86
100
  if (!this.#options.config.access_token) {
87
101
  // wss 模式未配 access_token 时任何连接都会被放行(verifyOneBotAccessToken 直接 return true)
@@ -113,55 +127,59 @@ export class OneBot12WssEndpoint extends ClientEndpoint<Onebot12Client> {
113
127
  }));
114
128
  }
115
129
 
116
- async stop(): Promise<void> {
117
- this.close();
130
+ async #stop(): Promise<void> {
131
+ this.#open = false;
118
132
  this.#wsRelease?.();
119
133
  this.#wsRelease = undefined;
120
- await this.#connectionTask;
121
- await this.#connectionLifecycle.stop();
122
- rejectAllPending(this.#pending);
123
- this.#ws = undefined;
124
- this.#started = false;
134
+ try {
135
+ await this.#connectionTask;
136
+ await this.#connectionLifecycle.stop();
137
+ } finally {
138
+ this.#rpc.dispose();
139
+ this.#releaseClientEvents();
140
+ this.#ws = undefined;
141
+ this.#started = false;
142
+ }
125
143
  }
126
144
 
127
- async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
128
- const materialized = await uploadOneBot12MediaSegments(
129
- payload,
130
- (action, params) => callOnebot12Client(this.client, action, params),
131
- (error) => {
132
- this.#logger.warn(formatCompact({
133
- op: 'onebot12_upload_failed',
134
- endpoint: this.#options.config.id,
135
- error: error instanceof Error ? error.message : String(error),
136
- }));
145
+ async #send({ conversation, payload, signal }: EndpointSendRequest): Promise<string> {
146
+ const materialized = await uploadOneBot12MediaSegments({
147
+ payload, signal,
148
+ callAction: (action, params) => this.#rpc.call({ action, params, signal }).then(readOnebot12Response),
149
+ onUploadFailed: error => {
150
+ this.#logger.warn(formatCompact({ op: 'onebot12_upload_failed', endpoint: this.#options.config.id,
151
+ error: error instanceof Error ? error.message : String(error) }));
137
152
  },
138
- );
153
+ });
139
154
  const message = formatOutboundSegments(materialized);
140
155
  const params = buildSendMessageParams(conversation, message);
141
- const data = await callOnebot12Client<{ message_id?: string }>(this.client, 'send_message', params);
156
+ const data = readOnebot12Response<{ message_id?: string }>(await this.#rpc.call({ action: 'send_message', params, signal }));
142
157
  return data?.message_id ?? '';
143
158
  }
144
159
 
145
- async recallMessage(messageId: string): Promise<void> {
160
+ async #recallMessage(messageId: string, signal?: AbortSignal): Promise<void> {
146
161
  if (!messageId) return;
147
- await callOnebot12Client(this.client, 'delete_message', { message_id: messageId });
162
+ await this.#rpc.call({ action: 'delete_message', params: { message_id: messageId }, signal }).then(readOnebot12Response);
148
163
  }
149
164
 
150
165
  #admitRaw(ev: OneBot12Event): void {
166
+ if (!matchesOneBot12Self(this.#options.config, ev)) return;
167
+ this[endpointSpec].reportSelfUserId(ev.self.user_id);
151
168
  if (!isMessageEvent(ev)) {
152
- receiveOneBot12SideEvent(
153
- (name, payload) => this.emit(name, payload),
154
- this.#options.config.id,
155
- { callApi: (action, params) => callOnebot12Client(this.client, action, params) },
156
- ev,
157
- this.#logger,
158
- );
169
+ receiveOneBot12SideEvent(ev, {
170
+ emit: (name, payload) => this[endpointSpec].events.emit(name, payload),
171
+ endpointKey: this.#options.config.id,
172
+ caller: { callApi: (action, params) => callOnebot12Client({ client: this, action, params }) },
173
+ logger: this.#logger,
174
+ });
159
175
  return;
160
176
  }
161
- const conversation = onebot12InboundConversation(String(this.#options.id), ev);
177
+ const conversation = { ...onebot12InboundConversation(String(this.#options.id), ev), endpoint: this[endpointSpec].identity };
162
178
  const nickname = senderNickname(ev);
163
179
  const mentioned = isBotMentioned(ev);
164
- void this.emit('message.receive', {
180
+ void this[endpointSpec].events.message({
181
+ event: ev,
182
+ ...(typeof ev.time === 'number' ? { platformSentAt: ev.time * 1000 } : {}),
165
183
  conversation,
166
184
  message: { conversation, id: ev.message_id },
167
185
  content: formatInboundContent(ev),
@@ -201,7 +219,7 @@ export class OneBot12WssEndpoint extends ClientEndpoint<Onebot12Client> {
201
219
  }
202
220
  const socket = connection.socket as unknown as OneBot12WsSocket;
203
221
  await this.#connectionLifecycle.stop();
204
- rejectAllPending(this.#pending, '连接已替换');
222
+ this.#rpc.disconnect('连接已替换');
205
223
  await this.#connectionLifecycle.start(async (lifecycleHandle) => {
206
224
  this.#ws = socket;
207
225
  lifecycleHandle.onForceClose(() => {
@@ -218,18 +236,14 @@ export class OneBot12WssEndpoint extends ClientEndpoint<Onebot12Client> {
218
236
  socket.on('message', (data) => {
219
237
  if (this.#ws !== socket) return;
220
238
  this.#connectionLifecycle.notifyHeartbeatAck();
221
- handleOneBot12WsMessage(data, {
222
- endpointId: this.#options.config.id,
223
- pending: this.#pending,
224
- ingest: (event) => this.client.ingest(
225
- event as Parameters<Onebot12Client['ingest']>[0],
226
- ),
239
+ this.#rpc.receive(data, event => {
240
+ if (matchesOneBot12Self(this.#options.config, event)) this.ingest(event as Parameters<Onebot12Client['ingest']>[0]);
227
241
  });
228
242
  });
229
243
  socket.on('close', () => {
230
244
  if (this.#ws === socket) {
231
245
  this.#ws = undefined;
232
- rejectAllPending(this.#pending);
246
+ this.#rpc.disconnect();
233
247
  }
234
248
  lifecycleHandle.notifyClosed(new Error('OneBot12 reverse WebSocket closed'));
235
249
  });
@@ -242,7 +256,7 @@ export class OneBot12WssEndpoint extends ClientEndpoint<Onebot12Client> {
242
256
  }
243
257
 
244
258
  #callAction(action: string, params: Record<string, unknown>) {
245
- return callOneBot12WsAction(this.#ws, this.#pending, this.#requestId, action, params);
259
+ return this.#rpc.call({ action, params });
246
260
  }
247
261
 
248
262
  }
@@ -1 +0,0 @@
1
- export declare const onebot12RuntimeStateToken: import("zhin.js").Token<import("@zhin.js/adapter").EndpointRuntimeState>;
@@ -1,6 +0,0 @@
1
- /**
2
- * OneBot12 插件实例的运行时状态:adapter create() 注册的 endpoint 列表。
3
- * 由 plugin.ts setup() provide,adapter create 与 `onebot12 endpoint` 命令共享(同一 owner generation)。
4
- */
5
- import { defineEndpointRuntimeStateToken } from 'zhin.js/adapter';
6
- export const onebot12RuntimeStateToken = defineEndpointRuntimeStateToken('onebot12');
@@ -1,7 +0,0 @@
1
- /**
2
- * OneBot12 插件实例的运行时状态:adapter create() 注册的 endpoint 列表。
3
- * 由 plugin.ts setup() provide,adapter create 与 `onebot12 endpoint` 命令共享(同一 owner generation)。
4
- */
5
- import { defineEndpointRuntimeStateToken } from 'zhin.js/adapter';
6
-
7
- export const onebot12RuntimeStateToken = defineEndpointRuntimeStateToken('onebot12');