@interactive-inc/flume 0.9.1 → 0.9.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.
Files changed (2) hide show
  1. package/dist/slack.js +48 -0
  2. package/package.json +1 -1
package/dist/slack.js CHANGED
@@ -177,6 +177,8 @@ const FlumeSlackEnvelopeSchema = z.object({
177
177
  });
178
178
  //#endregion
179
179
  //#region lib/slack/slack-socket-mode.ts
180
+ const DEFAULT_IDLE_TIMEOUT_MS = 9e4;
181
+ const IDLE_CHECK_INTERVAL_MS = 15e3;
180
182
  const WS_OPEN = 1;
181
183
  /**
182
184
  * Slack Socket Mode の最小 WebSocket 実装。`apps.connections.open` で URL を取得し
@@ -191,6 +193,8 @@ var FlumeSlackSocketMode = class {
191
193
  hasConnected = false;
192
194
  pendingResolve = null;
193
195
  pendingResolved = false;
196
+ lastFrameAt = 0;
197
+ idleWatchdog = null;
194
198
  constructor(props) {
195
199
  this.props = props;
196
200
  this.log = new FlumeLogger({
@@ -249,9 +253,50 @@ var FlumeSlackSocketMode = class {
249
253
  message: "stopping socket mode"
250
254
  });
251
255
  this.isStoppedFlag = true;
256
+ this.disarmIdleWatchdog();
252
257
  this.closeSocket(this.ws);
253
258
  this.ws = null;
254
259
  }
260
+ armIdleWatchdog() {
261
+ this.disarmIdleWatchdog();
262
+ const idleLimit = this.props.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
263
+ const handle = attempt(() => this.props.deps.setInterval(() => this.checkIdle(idleLimit), IDLE_CHECK_INTERVAL_MS));
264
+ if (handle instanceof Error) {
265
+ this.log.error({
266
+ action: "idle.watchdog.schedule.error",
267
+ message: safeErrorMessage({ error: handle }),
268
+ error: handle
269
+ });
270
+ return;
271
+ }
272
+ this.idleWatchdog = handle;
273
+ }
274
+ disarmIdleWatchdog() {
275
+ if (!this.idleWatchdog) return;
276
+ const handle = this.idleWatchdog;
277
+ this.idleWatchdog = null;
278
+ const result = attempt(() => this.props.deps.clearInterval(handle));
279
+ if (result instanceof Error) this.log.error({
280
+ action: "idle.watchdog.clear.error",
281
+ message: safeErrorMessage({ error: result }),
282
+ error: result
283
+ });
284
+ }
285
+ checkIdle(idleLimit) {
286
+ if (!this.hasConnected || this.isStoppedFlag) return;
287
+ const elapsed = this.props.deps.now() - this.lastFrameAt;
288
+ if (elapsed < idleLimit) return;
289
+ this.log.warn({
290
+ action: "idle.timeout",
291
+ message: `no frames for ${elapsed}ms (limit ${idleLimit}ms) — force-closing socket`,
292
+ detail: {
293
+ elapsedMs: elapsed,
294
+ limitMs: idleLimit
295
+ }
296
+ });
297
+ this.disarmIdleWatchdog();
298
+ this.closeSocket(this.ws);
299
+ }
255
300
  isConnected() {
256
301
  return this.ws !== null && this.ws.readyState === WS_OPEN;
257
302
  }
@@ -357,12 +402,14 @@ var FlumeSlackSocketMode = class {
357
402
  message: `type=${typeof json.type === "string" ? json.type : "-"} length=${raw.length}`,
358
403
  detail: { length: raw.length }
359
404
  });
405
+ this.lastFrameAt = this.props.deps.now();
360
406
  if (json.type === "hello") {
361
407
  this.log.info({
362
408
  action: "socket.hello",
363
409
  message: "connection ready"
364
410
  });
365
411
  this.hasConnected = true;
412
+ this.armIdleWatchdog();
366
413
  this.props.onConnected();
367
414
  this.completeConnect(null);
368
415
  return;
@@ -419,6 +466,7 @@ var FlumeSlackSocketMode = class {
419
466
  reason: ev.reason
420
467
  }
421
468
  });
469
+ this.disarmIdleWatchdog();
422
470
  this.ws = null;
423
471
  if (this.hasConnected) this.props.onDisconnected();
424
472
  if (!this.pendingResolved) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@interactive-inc/flume",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "description": "Unified notification listener for Discord, Slack, and GitHub. Raw WebSocket + fetch + Zod. No SDK dependencies.",
5
5
  "keywords": [
6
6
  "discord",