mattermost-claude-code 0.3.2 → 0.3.3

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.
@@ -17,6 +17,10 @@ export declare class MattermostClient extends EventEmitter {
17
17
  private userCache;
18
18
  private botUserId;
19
19
  private debug;
20
+ private pingInterval;
21
+ private lastMessageAt;
22
+ private readonly PING_INTERVAL_MS;
23
+ private readonly PING_TIMEOUT_MS;
20
24
  constructor(config: Config);
21
25
  private log;
22
26
  private api;
@@ -28,6 +32,8 @@ export declare class MattermostClient extends EventEmitter {
28
32
  connect(): Promise<void>;
29
33
  private handleEvent;
30
34
  private scheduleReconnect;
35
+ private startHeartbeat;
36
+ private stopHeartbeat;
31
37
  isUserAllowed(username: string): boolean;
32
38
  isBotMentioned(message: string): boolean;
33
39
  extractPrompt(message: string): string;
@@ -9,6 +9,11 @@ export class MattermostClient extends EventEmitter {
9
9
  userCache = new Map();
10
10
  botUserId = null;
11
11
  debug = process.env.DEBUG === '1' || process.argv.includes('--debug');
12
+ // Heartbeat to detect dead connections
13
+ pingInterval = null;
14
+ lastMessageAt = Date.now();
15
+ PING_INTERVAL_MS = 30000; // Send ping every 30s
16
+ PING_TIMEOUT_MS = 60000; // Reconnect if no message for 60s
12
17
  constructor(config) {
13
18
  super();
14
19
  this.config = config;
@@ -99,12 +104,14 @@ export class MattermostClient extends EventEmitter {
99
104
  }));
100
105
  });
101
106
  this.ws.on('message', (data) => {
107
+ this.lastMessageAt = Date.now(); // Track activity for heartbeat
102
108
  try {
103
109
  const event = JSON.parse(data.toString());
104
110
  this.handleEvent(event);
105
111
  // Authentication success
106
112
  if (event.event === 'hello') {
107
113
  this.reconnectAttempts = 0;
114
+ this.startHeartbeat();
108
115
  this.emit('connected');
109
116
  resolve();
110
117
  }
@@ -115,6 +122,7 @@ export class MattermostClient extends EventEmitter {
115
122
  });
116
123
  this.ws.on('close', () => {
117
124
  this.log('WebSocket disconnected');
125
+ this.stopHeartbeat();
118
126
  this.emit('disconnected');
119
127
  this.scheduleReconnect();
120
128
  });
@@ -123,6 +131,10 @@ export class MattermostClient extends EventEmitter {
123
131
  this.emit('error', err);
124
132
  reject(err);
125
133
  });
134
+ this.ws.on('pong', () => {
135
+ this.lastMessageAt = Date.now(); // Pong received, connection is alive
136
+ this.log('Pong received');
137
+ });
126
138
  });
127
139
  }
128
140
  handleEvent(event) {
@@ -183,6 +195,33 @@ export class MattermostClient extends EventEmitter {
183
195
  });
184
196
  }, delay);
185
197
  }
198
+ startHeartbeat() {
199
+ this.stopHeartbeat(); // Clear any existing
200
+ this.lastMessageAt = Date.now();
201
+ this.pingInterval = setInterval(() => {
202
+ const silentFor = Date.now() - this.lastMessageAt;
203
+ // If no message received for too long, connection is dead
204
+ if (silentFor > this.PING_TIMEOUT_MS) {
205
+ console.log(` 💔 Connection dead (no activity for ${Math.round(silentFor / 1000)}s), reconnecting...`);
206
+ this.stopHeartbeat();
207
+ if (this.ws) {
208
+ this.ws.terminate(); // Force close (triggers reconnect via 'close' event)
209
+ }
210
+ return;
211
+ }
212
+ // Send ping to keep connection alive and verify it's working
213
+ if (this.ws?.readyState === WebSocket.OPEN) {
214
+ this.ws.ping();
215
+ this.log(`Ping sent (last activity ${Math.round(silentFor / 1000)}s ago)`);
216
+ }
217
+ }, this.PING_INTERVAL_MS);
218
+ }
219
+ stopHeartbeat() {
220
+ if (this.pingInterval) {
221
+ clearInterval(this.pingInterval);
222
+ this.pingInterval = null;
223
+ }
224
+ }
186
225
  // Check if user is allowed to use the bot
187
226
  isUserAllowed(username) {
188
227
  if (this.config.allowedUsers.length === 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mattermost-claude-code",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "Share Claude Code sessions live in a Mattermost channel with interactive features",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",