@izhimu/qq 0.3.0 → 0.3.2

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.
@@ -105,7 +105,6 @@ export const qqPlugin = {
105
105
  probeAccount: async () => {
106
106
  const status = await getStatus();
107
107
  setContextStatus({
108
- running: true,
109
108
  lastProbeAt: Date.now(),
110
109
  });
111
110
  return {
@@ -139,11 +138,12 @@ export const qqPlugin = {
139
138
  setContext(ctx);
140
139
  const { account } = ctx;
141
140
  log.info('gateway', `Starting gateway`);
142
- // Update start time
143
- setContextStatus({
144
- running: true,
145
- lastStartAt: Date.now(),
146
- });
141
+ // 检查是否已存在连接
142
+ const existingConnection = getConnection();
143
+ if (existingConnection) {
144
+ log.warn('gateway', `A connection is already running`);
145
+ return;
146
+ }
147
147
  // Create new connection manager
148
148
  const connection = new ConnectionManager(account);
149
149
  connection.on("event", (event) => eventListener(event));
@@ -151,12 +151,14 @@ export const qqPlugin = {
151
151
  log.info('gateway', `State: ${status.state}`);
152
152
  if (status.state === "connected") {
153
153
  setContextStatus({
154
+ linked: true,
154
155
  connected: true,
155
156
  lastConnectedAt: Date.now(),
156
157
  });
157
158
  }
158
159
  else if (status.state === "disconnected" || status.state === "failed") {
159
160
  setContextStatus({
161
+ linked: false,
160
162
  connected: false,
161
163
  lastError: status.error,
162
164
  });
@@ -165,14 +167,34 @@ export const qqPlugin = {
165
167
  connection.on("reconnecting", (info) => {
166
168
  log.info('gateway', `Reconnecting: ${info.reason}, attempt ${info.totalAttempts}`);
167
169
  setContextStatus({
170
+ linked: false,
168
171
  connected: false,
169
172
  lastError: `Reconnecting (${info.reason})`,
170
173
  reconnectAttempts: info.totalAttempts,
171
174
  });
172
175
  });
173
- await connection.start();
174
- setConnection(connection);
175
- log.info('gateway', `Started gateway`);
176
+ try {
177
+ await connection.start();
178
+ setConnection(connection);
179
+ // Update start time
180
+ setContextStatus({
181
+ running: true,
182
+ linked: true,
183
+ connected: true,
184
+ lastStartAt: Date.now(),
185
+ });
186
+ log.info('gateway', `Started gateway`);
187
+ }
188
+ catch (error) {
189
+ log.error('gateway', `Failed to start gateway:`, error);
190
+ setContextStatus({
191
+ running: false,
192
+ linked: false,
193
+ connected: false,
194
+ lastError: error instanceof Error ? error.message : 'Failed to start gateway',
195
+ });
196
+ throw error;
197
+ }
176
198
  },
177
199
  stopAccount: async (_ctx) => {
178
200
  const connection = getConnection();
@@ -182,35 +204,12 @@ export const qqPlugin = {
182
204
  }
183
205
  setContextStatus({
184
206
  running: false,
207
+ linked: false,
185
208
  connected: false,
186
209
  lastStopAt: Date.now(),
187
210
  });
188
211
  clearContext();
189
212
  },
190
- },
191
- heartbeat: {
192
- checkReady: async () => {
193
- const status = await getStatus();
194
- if (status.status === "ok" && status.data?.online && status.data?.good) {
195
- setContextStatus({
196
- linked: true,
197
- });
198
- return {
199
- ok: true,
200
- reason: 'ok'
201
- };
202
- }
203
- else {
204
- log.warn('heartbeat', `Heartbeat failed, status: ${status.status}, data: ${status.data}`);
205
- setContextStatus({
206
- linked: false,
207
- });
208
- return {
209
- ok: false,
210
- reason: status.msg
211
- };
212
- }
213
- }
214
213
  }
215
214
  };
216
215
  async function outboundSend(ctx) {
@@ -7,8 +7,8 @@ import EventEmitter from 'events';
7
7
  import { Logger as log, generateEchoId, calculateBackoff, getCloseCodeMessage, } from '../utils/index.js';
8
8
  const MAX_RECONNECT_ATTEMPTS = -1;
9
9
  const REQUEST_TIMEOUT = 30000; // 30 seconds
10
- const HEARTBEAT_TIMEOUT = 60000; // 60 seconds - time without heartbeat before reconnecting
11
- const HEARTBEAT_CHECK_INTERVAL = 30000; // 30 seconds - how often to check for heartbeat timeout
10
+ const HEARTBEAT_TIMEOUT = 120000; // 120 seconds - time without heartbeat before reconnecting (increased for NapCat compatibility)
11
+ const HEARTBEAT_CHECK_INTERVAL = 60000; // 60 seconds - how often to check for heartbeat timeout
12
12
  /**
13
13
  * Connection Manager for a single NapCat account
14
14
  */
@@ -67,6 +67,11 @@ export class ConnectionManager extends EventEmitter {
67
67
  if (this.ws?.readyState === WebSocket.OPEN) {
68
68
  return;
69
69
  }
70
+ // 防御性清理:确保旧连接和监听器被清理,避免潜在的内存泄漏
71
+ if (this.ws) {
72
+ this.ws.removeAllListeners();
73
+ this.ws = null;
74
+ }
70
75
  this.setState('connecting');
71
76
  try {
72
77
  // Build WebSocket URL with access_token query parameter (NapCat OneBot 11 standard)
@@ -148,6 +153,7 @@ export class ConnectionManager extends EventEmitter {
148
153
  };
149
154
  this.emit('heartbeat', this.healthStatus);
150
155
  // Close connection and trigger immediate reconnect
156
+ this.setState('disconnected');
151
157
  this.close('Heartbeat timeout').then(() => {
152
158
  if (this.shouldReconnect) {
153
159
  // Increment total reconnect attempts
@@ -254,6 +260,12 @@ export class ConnectionManager extends EventEmitter {
254
260
  handleClose(code, reason) {
255
261
  const reasonStr = reason.toString() || getCloseCodeMessage(code);
256
262
  log.warn('connection', `Connection closed: ${code} - ${reasonStr}`);
263
+ // 停止心跳检测
264
+ this.stopHeartbeatCheck();
265
+ // 如果 ws 已经为 null,说明是主动关闭(如心跳超时),不需要再处理
266
+ if (this.ws === null) {
267
+ return;
268
+ }
257
269
  if (this.shouldReconnect && !this.isNormalClosure(code)) {
258
270
  this.scheduleReconnect();
259
271
  }
@@ -3,7 +3,7 @@
3
3
  * Stores the PluginRuntime for access in gateway handlers
4
4
  */
5
5
  import type { ChannelAccountSnapshot, ChannelGatewayContext, PluginRuntime } from "openclaw/plugin-sdk";
6
- import type { QQConfig } from "../types/index.js";
6
+ import type { QQConfig } from "../types";
7
7
  import { ConnectionManager } from "./connection.js";
8
8
  export declare function setRuntime(next: PluginRuntime): void;
9
9
  export declare function getRuntime(): PluginRuntime | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@izhimu/qq",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "A QQ channel plugin for OpenClaw using NapCat WebSocket",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",