@diegoaltoworks/chatter 0.64.0 → 0.66.0

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.
@@ -30,6 +30,39 @@ export interface WhatsAppMessageEvent {
30
30
  sock: WASocket;
31
31
  message: WAMessage;
32
32
  }
33
+ /**
34
+ * A session's connection state, derived from the same `connection.update`
35
+ * and lease transitions the channel already reacts to - not a new event
36
+ * source. `connecting` covers both the first connect after acquiring the
37
+ * lease and every reconnect attempt; `waiting_for_lease` covers not
38
+ * currently holding this session's lease, whether another instance holds it
39
+ * or acquiring/renewing it is failing.
40
+ */
41
+ export type WaConnectionState = "connected" | "connecting" | "waiting_for_lease" | "disconnected";
42
+ export interface WaSessionStatus {
43
+ sessionId: string;
44
+ state: WaConnectionState;
45
+ /** `Date.now()`-style timestamp of the last state transition (not of the last read). */
46
+ since: number;
47
+ /** The most recent failure description; cleared once the session reconnects. */
48
+ lastError?: string;
49
+ }
50
+ /**
51
+ * Optional liveness escalation: `onUnhealthy` fires once a session has been
52
+ * anything but `connected` for at least `unhealthyAfterMs`, and rearms the
53
+ * moment it reconnects. That includes `waiting_for_lease` - legitimate
54
+ * during a brief multi-instance deploy overlap, so size `unhealthyAfterMs`
55
+ * comfortably above how long that overlap is expected to last, or an
56
+ * instance that never wins the lease (a stuck rollout) will trip the
57
+ * watchdog repeatedly. Off by default - an unconfigured host still gets a
58
+ * bounded, grep-able error log while unhealthy (see `createWhatsAppChannel`'s
59
+ * status checker), just no callback. A host can use `onUnhealthy` to alert,
60
+ * or to `process.exit` so the platform restarts the container.
61
+ */
62
+ export interface WhatsAppWatchdogConfig {
63
+ unhealthyAfterMs: number;
64
+ onUnhealthy?: (status: WaSessionStatus) => void;
65
+ }
33
66
  export interface WhatsAppChannelConfig {
34
67
  /** Passphrase that encrypts auth state at rest (AES-256-GCM key material). Keep it stable across restarts and deploys — losing it means re-pairing. */
35
68
  sessionSecret: string;
@@ -60,6 +93,8 @@ export interface WhatsAppChannelConfig {
60
93
  leaseStore?: WaLeaseStore;
61
94
  /** Baileys auth-state persistence, encrypted above this seam. Defaults to a `wa_auth` table in `deps.db`; inject your own to run this channel without a libsql database. */
62
95
  authStore?: WaAuthKV;
96
+ /** Liveness escalation for a session stuck unhealthy. Off by default (see {@link WhatsAppWatchdogConfig}). */
97
+ watchdog?: WhatsAppWatchdogConfig;
63
98
  }
64
99
  /** A live, leased session: what `stop()` (or losing the lease) must tear down. */
65
100
  export interface WaSessionHandle {
@@ -99,6 +134,8 @@ export interface LeaseGatedConnectDeps {
99
134
  * no live socket and nothing pending never recovers on its own.
100
135
  */
101
136
  onConnectFailed?: (sessionId: string, error: unknown) => void;
137
+ /** Reports a connection-state transition, for the channel's status/watchdog seam. Optional - direct callers of `acquireSessionLease` in tests don't need it. */
138
+ onStatusChange?: (sessionId: string, state: WaConnectionState, lastError?: string) => void;
102
139
  }
103
140
  /**
104
141
  * Acquire `sessionId`'s lease before connecting — two instances must never
@@ -130,5 +167,10 @@ export interface WhatsAppMediaPayload {
130
167
  * `false` rather than sending Baileys a `{ image: { url: undefined } }`.
131
168
  */
132
169
  export declare function normalizeWaMediaPayload(payload: unknown): WhatsAppMediaPayload;
133
- export declare function createWhatsAppChannel(config: WhatsAppChannelConfig): Channel;
170
+ /** `createWhatsAppChannel`'s return type: the {@link Channel} SPI plus a way to poll per-session connection health without log-scraping. */
171
+ export interface WhatsAppChannel extends Channel {
172
+ /** Per-session status, in the order `sessionIds` was configured. */
173
+ getStatus(): WaSessionStatus[];
174
+ }
175
+ export declare function createWhatsAppChannel(config: WhatsAppChannelConfig): WhatsAppChannel;
134
176
  //# sourceMappingURL=channel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/channel.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/channel.ts"],"names":[],"mappings":"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"}
@@ -27,7 +27,7 @@ export type { AuthStateRuntime, TursoAuthStateResult, WaAuthKV } from "./authSta
27
27
  export { createTursoWaAuthKV, useAuthState, useTursoAuthState } from "./authState";
28
28
  export type { Baileys } from "./baileys";
29
29
  export { loadBaileys } from "./baileys";
30
- export { acquireSessionLease, createWhatsAppChannel, type LeaseGatedConnectDeps, normalizeWaMediaPayload, reconnectDelayMs, senderNameFor, shutdownWaSessions, type WaSessionHandle, type WhatsAppChannelConfig, type WhatsAppMediaPayload, type WhatsAppMessageEvent, } from "./channel";
30
+ export { acquireSessionLease, createWhatsAppChannel, type LeaseGatedConnectDeps, normalizeWaMediaPayload, reconnectDelayMs, senderNameFor, shutdownWaSessions, type WaConnectionState, type WaSessionHandle, type WaSessionStatus, type WhatsAppChannel, type WhatsAppChannelConfig, type WhatsAppMediaPayload, type WhatsAppMessageEvent, type WhatsAppWatchdogConfig, } from "./channel";
31
31
  export { decrypt, encrypt } from "./crypto";
32
32
  export { createWhatsAppImageHandler, type DrawRequestDetectorConfig, drawSubject, isDrawRequest, type WhatsAppImageHandler, type WhatsAppImageHandlerDeps, type WhatsAppImageRequestContext, type WhatsAppImageStrings, } from "./images";
33
33
  export { createWhatsAppInboundHandler, extractText, isGroupJid, jidsMatch, jidToPhoneNumber, messageContext, type ResolvedWaMessage, resolveWaMessage, senderPhoneFor, stripOwnMentions, type WaMessageContext, type WhatsAppInboundConfig, } from "./inbound";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACpF,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACnF,YAAY,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,KAAK,qBAAqB,EAC1B,uBAAuB,EACvB,gBAAgB,EAChB,aAAa,EACb,kBAAkB,EAClB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,GAC1B,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EACL,0BAA0B,EAC1B,KAAK,yBAAyB,EAC9B,WAAW,EACX,aAAa,EACb,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,2BAA2B,EAChC,KAAK,oBAAoB,GAC1B,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,4BAA4B,EAC5B,WAAW,EACX,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,cAAc,EACd,KAAK,iBAAiB,EACtB,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,GAC3B,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,KAAK,UAAU,EACf,KAAK,YAAY,GAClB,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,2BAA2B,EAC3B,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,2BAA2B,GACjC,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACpF,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACnF,YAAY,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,KAAK,qBAAqB,EAC1B,uBAAuB,EACvB,gBAAgB,EAChB,aAAa,EACb,kBAAkB,EAClB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,GAC5B,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EACL,0BAA0B,EAC1B,KAAK,yBAAyB,EAC9B,WAAW,EACX,aAAa,EACb,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,2BAA2B,EAChC,KAAK,oBAAoB,GAC1B,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,4BAA4B,EAC5B,WAAW,EACX,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,cAAc,EACd,KAAK,iBAAiB,EACtB,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,GAC3B,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,KAAK,UAAU,EACf,KAAK,YAAY,GAClB,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,2BAA2B,EAC3B,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,2BAA2B,GACjC,MAAM,UAAU,CAAC"}
@@ -348,9 +348,13 @@ function createTursoWaLeaseStore(client) {
348
348
  // src/channels/whatsapp/channel.ts
349
349
  var RECONNECT_DELAY_MS = 5e3;
350
350
  var RECONNECT_MAX_DELAY_MS = 10 * 60 * 1e3;
351
+ var UNHEALTHY_LOG_INTERVAL_MS = 5 * 60 * 1e3;
351
352
  function reconnectDelayMs(failures) {
352
353
  return exponentialBackoffMs(RECONNECT_DELAY_MS, RECONNECT_MAX_DELAY_MS, failures);
353
354
  }
355
+ function describeError(error) {
356
+ return error instanceof Error ? error.message : String(error);
357
+ }
354
358
  async function shutdownWaSessions(sessions) {
355
359
  await Promise.all(
356
360
  [...sessions.values()].map(async (handle) => {
@@ -381,6 +385,7 @@ async function acquireSessionLease(sessionId, deps) {
381
385
  const schedule = deps.schedule ?? ((fn, ms) => void setTimeout(fn, ms));
382
386
  const logger = deps.logger ?? createConsoleLogger();
383
387
  if (isStopped()) return;
388
+ deps.onStatusChange?.(sessionId, "waiting_for_lease");
384
389
  const retry = () => {
385
390
  schedule(
386
391
  () => void acquireSessionLease(sessionId, deps).catch(
@@ -394,6 +399,7 @@ async function acquireSessionLease(sessionId, deps) {
394
399
  acquired = await leaseStore.tryAcquire(sessionId, instanceId, now(), staleMs);
395
400
  } catch (error) {
396
401
  logger.warn(`WhatsApp[${sessionId}]: lease acquire failed, retrying:`, error);
402
+ deps.onStatusChange?.(sessionId, "waiting_for_lease", describeError(error));
397
403
  retry();
398
404
  return;
399
405
  }
@@ -402,13 +408,37 @@ async function acquireSessionLease(sessionId, deps) {
402
408
  retry();
403
409
  return;
404
410
  }
411
+ const demoteSelf = () => {
412
+ void loseSession(sessionId, sessions).then((lost) => {
413
+ if (lost) retry();
414
+ });
415
+ };
416
+ let lastHeartbeatSuccessAt = now();
405
417
  const heartbeat = setInterval(() => {
406
- void leaseStore.tryAcquire(sessionId, instanceId, now(), staleMs).then((stillHeld) => {
407
- if (stillHeld || isStopped()) return;
408
- logger.error(`WhatsApp[${sessionId}]: lost the lease to another instance \u2014 disconnecting`);
409
- void loseSession(sessionId, sessions).then((lost) => {
410
- if (lost) retry();
411
- });
418
+ if (isStopped()) return;
419
+ const attemptAt = now();
420
+ const failingForMs = attemptAt - lastHeartbeatSuccessAt;
421
+ if (failingForMs >= staleMs) {
422
+ logger.error(
423
+ `WhatsApp[${sessionId}]: lease heartbeats have been failing for ${failingForMs}ms, treating the lease as lost`
424
+ );
425
+ deps.onStatusChange?.(
426
+ sessionId,
427
+ "disconnected",
428
+ `lease heartbeats failing for ${failingForMs}ms`
429
+ );
430
+ demoteSelf();
431
+ return;
432
+ }
433
+ void leaseStore.tryAcquire(sessionId, instanceId, attemptAt, staleMs).then((stillHeld) => {
434
+ if (isStopped()) return;
435
+ if (!stillHeld) {
436
+ logger.error(`WhatsApp[${sessionId}]: lost the lease to another instance, disconnecting`);
437
+ deps.onStatusChange?.(sessionId, "disconnected", "lost the lease to another instance");
438
+ demoteSelf();
439
+ return;
440
+ }
441
+ lastHeartbeatSuccessAt = Math.max(lastHeartbeatSuccessAt, attemptAt);
412
442
  }).catch((error) => logger.warn(`WhatsApp[${sessionId}]: lease heartbeat failed:`, error));
413
443
  }, heartbeatMs);
414
444
  if (typeof heartbeat === "object" && "unref" in heartbeat) heartbeat.unref();
@@ -457,6 +487,26 @@ function createWhatsAppChannel(config) {
457
487
  const failureCounts = /* @__PURE__ */ new Map();
458
488
  const registeredSenderNames = /* @__PURE__ */ new Map();
459
489
  let senders;
490
+ const statuses = new Map(
491
+ sessionIds.map((id) => [id, { sessionId: id, state: "waiting_for_lease", since: now() }])
492
+ );
493
+ const unhealthySince = /* @__PURE__ */ new Map();
494
+ const watchdogFired = /* @__PURE__ */ new Set();
495
+ const lastUnhealthyLogAt = /* @__PURE__ */ new Map();
496
+ let healthCheckInterval;
497
+ function setStatus(sessionId, state, lastError) {
498
+ const prev = statuses.get(sessionId);
499
+ const since = prev && prev.state === state ? prev.since : now();
500
+ const resolvedLastError = lastError !== void 0 ? lastError : state === "connected" ? void 0 : prev?.lastError;
501
+ statuses.set(sessionId, { sessionId, state, since, lastError: resolvedLastError });
502
+ if (state === "connected") {
503
+ unhealthySince.delete(sessionId);
504
+ watchdogFired.delete(sessionId);
505
+ lastUnhealthyLogAt.delete(sessionId);
506
+ } else if (!unhealthySince.has(sessionId)) {
507
+ unhealthySince.set(sessionId, since);
508
+ }
509
+ }
460
510
  return {
461
511
  name: channelName,
462
512
  async start(deps) {
@@ -482,7 +532,8 @@ function createWhatsAppChannel(config) {
482
532
  waitMs,
483
533
  schedule,
484
534
  logger: log,
485
- onConnectFailed: recoverFromFailedConnect
535
+ onConnectFailed: recoverFromFailedConnect,
536
+ onStatusChange: setStatus
486
537
  };
487
538
  function nextRetryDelayMs(sessionId) {
488
539
  const failures = failureCounts.get(sessionId) ?? 0;
@@ -497,6 +548,7 @@ function createWhatsAppChannel(config) {
497
548
  const handle = claimSession(sessionId, sessions);
498
549
  if (!handle) return;
499
550
  const delay = nextRetryDelayMs(sessionId);
551
+ setStatus(sessionId, "disconnected", describeError(error));
500
552
  log.error(
501
553
  `WhatsApp[${sessionId}]: connect attempt failed, releasing the lease and retrying in ${Math.round(delay / 1e3)}s:`,
502
554
  error
@@ -516,6 +568,7 @@ function createWhatsAppChannel(config) {
516
568
  }
517
569
  async function connect(sessionId) {
518
570
  if (stopped || !sessions.has(sessionId)) return;
571
+ setStatus(sessionId, "connecting");
519
572
  const baileys = await loadBaileys2();
520
573
  const runtime = {
521
574
  bufferJSON: baileys.BufferJSON,
@@ -532,6 +585,7 @@ function createWhatsAppChannel(config) {
532
585
  log.warn(
533
586
  `WhatsApp[${sessionId}]: no paired session found \u2014 pair it first (see docs/channels.md). Re-checking in 60s.`
534
587
  );
588
+ setStatus(sessionId, "disconnected", "no paired session - pair it first");
535
589
  schedule(() => attemptConnect(sessionId), 6e4);
536
590
  return;
537
591
  }
@@ -561,6 +615,7 @@ function createWhatsAppChannel(config) {
561
615
  const { connection, lastDisconnect } = update;
562
616
  if (connection === "open") {
563
617
  failureCounts.set(sessionId, 0);
618
+ setStatus(sessionId, "connected");
564
619
  log.info(`WhatsApp[${sessionId}]: connected as ${sock.user?.id ?? "unknown"}`);
565
620
  const senderName = senderNameFor(channelName, sessionId);
566
621
  const sender = {
@@ -591,6 +646,7 @@ function createWhatsAppChannel(config) {
591
646
  }
592
647
  const statusCode = lastDisconnect?.error?.output?.statusCode;
593
648
  if (statusCode === baileys.DisconnectReason.loggedOut) {
649
+ setStatus(sessionId, "disconnected", "session logged out - re-pair required");
594
650
  log.error(
595
651
  `WhatsApp[${sessionId}]: session logged out \u2014 re-pair to reconnect (see docs/channels.md).`
596
652
  );
@@ -599,6 +655,7 @@ function createWhatsAppChannel(config) {
599
655
  }
600
656
  if (!stopped && sessions.has(sessionId)) {
601
657
  const delay = nextRetryDelayMs(sessionId);
658
+ setStatus(sessionId, "disconnected", `connection closed (${statusCode ?? "?"})`);
602
659
  log.warn(
603
660
  `WhatsApp[${sessionId}]: connection closed (${statusCode ?? "?"}), reconnecting in ${Math.round(delay / 1e3)}s...`
604
661
  );
@@ -619,6 +676,33 @@ function createWhatsAppChannel(config) {
619
676
  });
620
677
  }
621
678
  }
679
+ function checkHealth() {
680
+ const nowMs = now();
681
+ for (const sessionId of sessionIds) {
682
+ const unhealthySinceMs = unhealthySince.get(sessionId);
683
+ if (unhealthySinceMs === void 0) continue;
684
+ const status = statuses.get(sessionId);
685
+ if (!status) continue;
686
+ const watchdog = config.watchdog;
687
+ if (watchdog && nowMs - unhealthySinceMs >= watchdog.unhealthyAfterMs && !watchdogFired.has(sessionId)) {
688
+ watchdogFired.add(sessionId);
689
+ watchdog.onUnhealthy?.(status);
690
+ }
691
+ if (!watchdog?.onUnhealthy) {
692
+ const lastLogged = lastUnhealthyLogAt.get(sessionId) ?? unhealthySinceMs;
693
+ if (nowMs - lastLogged >= UNHEALTHY_LOG_INTERVAL_MS) {
694
+ lastUnhealthyLogAt.set(sessionId, nowMs);
695
+ log.error(
696
+ `WhatsApp[${sessionId}]: unhealthy (${status.state}) since ${new Date(status.since).toISOString()}` + (status.lastError ? ` - ${status.lastError}` : "")
697
+ );
698
+ }
699
+ }
700
+ }
701
+ }
702
+ healthCheckInterval = setInterval(checkHealth, heartbeatMs);
703
+ if (typeof healthCheckInterval === "object" && "unref" in healthCheckInterval) {
704
+ healthCheckInterval.unref();
705
+ }
622
706
  for (const sessionId of sessionIds) {
623
707
  void acquireSessionLease(sessionId, leaseDeps).catch(
624
708
  (error) => log.error(`WhatsApp[${sessionId}]: failed to start:`, error)
@@ -627,11 +711,18 @@ function createWhatsAppChannel(config) {
627
711
  },
628
712
  async stop() {
629
713
  stopped = true;
714
+ if (healthCheckInterval) clearInterval(healthCheckInterval);
715
+ for (const sessionId of sessionIds) setStatus(sessionId, "disconnected", "channel stopped");
630
716
  for (const senderName of registeredSenderNames.values()) {
631
717
  senders?.unregister(senderName);
632
718
  }
633
719
  registeredSenderNames.clear();
634
720
  await shutdownWaSessions(sessions);
721
+ },
722
+ getStatus() {
723
+ return sessionIds.map(
724
+ (id) => statuses.get(id) ?? { sessionId: id, state: "waiting_for_lease", since: now() }
725
+ );
635
726
  }
636
727
  };
637
728
  }
@@ -283,9 +283,13 @@ function createTursoWaLeaseStore(client) {
283
283
  // src/channels/whatsapp/channel.ts
284
284
  var RECONNECT_DELAY_MS = 5e3;
285
285
  var RECONNECT_MAX_DELAY_MS = 10 * 60 * 1e3;
286
+ var UNHEALTHY_LOG_INTERVAL_MS = 5 * 60 * 1e3;
286
287
  function reconnectDelayMs(failures) {
287
288
  return exponentialBackoffMs(RECONNECT_DELAY_MS, RECONNECT_MAX_DELAY_MS, failures);
288
289
  }
290
+ function describeError(error) {
291
+ return error instanceof Error ? error.message : String(error);
292
+ }
289
293
  async function shutdownWaSessions(sessions) {
290
294
  await Promise.all(
291
295
  [...sessions.values()].map(async (handle) => {
@@ -316,6 +320,7 @@ async function acquireSessionLease(sessionId, deps) {
316
320
  const schedule = deps.schedule ?? ((fn, ms) => void setTimeout(fn, ms));
317
321
  const logger = deps.logger ?? createConsoleLogger();
318
322
  if (isStopped()) return;
323
+ deps.onStatusChange?.(sessionId, "waiting_for_lease");
319
324
  const retry = () => {
320
325
  schedule(
321
326
  () => void acquireSessionLease(sessionId, deps).catch(
@@ -329,6 +334,7 @@ async function acquireSessionLease(sessionId, deps) {
329
334
  acquired = await leaseStore.tryAcquire(sessionId, instanceId, now(), staleMs);
330
335
  } catch (error) {
331
336
  logger.warn(`WhatsApp[${sessionId}]: lease acquire failed, retrying:`, error);
337
+ deps.onStatusChange?.(sessionId, "waiting_for_lease", describeError(error));
332
338
  retry();
333
339
  return;
334
340
  }
@@ -337,13 +343,37 @@ async function acquireSessionLease(sessionId, deps) {
337
343
  retry();
338
344
  return;
339
345
  }
346
+ const demoteSelf = () => {
347
+ void loseSession(sessionId, sessions).then((lost) => {
348
+ if (lost) retry();
349
+ });
350
+ };
351
+ let lastHeartbeatSuccessAt = now();
340
352
  const heartbeat = setInterval(() => {
341
- void leaseStore.tryAcquire(sessionId, instanceId, now(), staleMs).then((stillHeld) => {
342
- if (stillHeld || isStopped()) return;
343
- logger.error(`WhatsApp[${sessionId}]: lost the lease to another instance \u2014 disconnecting`);
344
- void loseSession(sessionId, sessions).then((lost) => {
345
- if (lost) retry();
346
- });
353
+ if (isStopped()) return;
354
+ const attemptAt = now();
355
+ const failingForMs = attemptAt - lastHeartbeatSuccessAt;
356
+ if (failingForMs >= staleMs) {
357
+ logger.error(
358
+ `WhatsApp[${sessionId}]: lease heartbeats have been failing for ${failingForMs}ms, treating the lease as lost`
359
+ );
360
+ deps.onStatusChange?.(
361
+ sessionId,
362
+ "disconnected",
363
+ `lease heartbeats failing for ${failingForMs}ms`
364
+ );
365
+ demoteSelf();
366
+ return;
367
+ }
368
+ void leaseStore.tryAcquire(sessionId, instanceId, attemptAt, staleMs).then((stillHeld) => {
369
+ if (isStopped()) return;
370
+ if (!stillHeld) {
371
+ logger.error(`WhatsApp[${sessionId}]: lost the lease to another instance, disconnecting`);
372
+ deps.onStatusChange?.(sessionId, "disconnected", "lost the lease to another instance");
373
+ demoteSelf();
374
+ return;
375
+ }
376
+ lastHeartbeatSuccessAt = Math.max(lastHeartbeatSuccessAt, attemptAt);
347
377
  }).catch((error) => logger.warn(`WhatsApp[${sessionId}]: lease heartbeat failed:`, error));
348
378
  }, heartbeatMs);
349
379
  if (typeof heartbeat === "object" && "unref" in heartbeat) heartbeat.unref();
@@ -392,6 +422,26 @@ function createWhatsAppChannel(config) {
392
422
  const failureCounts = /* @__PURE__ */ new Map();
393
423
  const registeredSenderNames = /* @__PURE__ */ new Map();
394
424
  let senders;
425
+ const statuses = new Map(
426
+ sessionIds.map((id) => [id, { sessionId: id, state: "waiting_for_lease", since: now() }])
427
+ );
428
+ const unhealthySince = /* @__PURE__ */ new Map();
429
+ const watchdogFired = /* @__PURE__ */ new Set();
430
+ const lastUnhealthyLogAt = /* @__PURE__ */ new Map();
431
+ let healthCheckInterval;
432
+ function setStatus(sessionId, state, lastError) {
433
+ const prev = statuses.get(sessionId);
434
+ const since = prev && prev.state === state ? prev.since : now();
435
+ const resolvedLastError = lastError !== void 0 ? lastError : state === "connected" ? void 0 : prev?.lastError;
436
+ statuses.set(sessionId, { sessionId, state, since, lastError: resolvedLastError });
437
+ if (state === "connected") {
438
+ unhealthySince.delete(sessionId);
439
+ watchdogFired.delete(sessionId);
440
+ lastUnhealthyLogAt.delete(sessionId);
441
+ } else if (!unhealthySince.has(sessionId)) {
442
+ unhealthySince.set(sessionId, since);
443
+ }
444
+ }
395
445
  return {
396
446
  name: channelName,
397
447
  async start(deps) {
@@ -417,7 +467,8 @@ function createWhatsAppChannel(config) {
417
467
  waitMs,
418
468
  schedule,
419
469
  logger: log,
420
- onConnectFailed: recoverFromFailedConnect
470
+ onConnectFailed: recoverFromFailedConnect,
471
+ onStatusChange: setStatus
421
472
  };
422
473
  function nextRetryDelayMs(sessionId) {
423
474
  const failures = failureCounts.get(sessionId) ?? 0;
@@ -432,6 +483,7 @@ function createWhatsAppChannel(config) {
432
483
  const handle = claimSession(sessionId, sessions);
433
484
  if (!handle) return;
434
485
  const delay = nextRetryDelayMs(sessionId);
486
+ setStatus(sessionId, "disconnected", describeError(error));
435
487
  log.error(
436
488
  `WhatsApp[${sessionId}]: connect attempt failed, releasing the lease and retrying in ${Math.round(delay / 1e3)}s:`,
437
489
  error
@@ -451,6 +503,7 @@ function createWhatsAppChannel(config) {
451
503
  }
452
504
  async function connect(sessionId) {
453
505
  if (stopped || !sessions.has(sessionId)) return;
506
+ setStatus(sessionId, "connecting");
454
507
  const baileys = await loadBaileys2();
455
508
  const runtime = {
456
509
  bufferJSON: baileys.BufferJSON,
@@ -467,6 +520,7 @@ function createWhatsAppChannel(config) {
467
520
  log.warn(
468
521
  `WhatsApp[${sessionId}]: no paired session found \u2014 pair it first (see docs/channels.md). Re-checking in 60s.`
469
522
  );
523
+ setStatus(sessionId, "disconnected", "no paired session - pair it first");
470
524
  schedule(() => attemptConnect(sessionId), 6e4);
471
525
  return;
472
526
  }
@@ -496,6 +550,7 @@ function createWhatsAppChannel(config) {
496
550
  const { connection, lastDisconnect } = update;
497
551
  if (connection === "open") {
498
552
  failureCounts.set(sessionId, 0);
553
+ setStatus(sessionId, "connected");
499
554
  log.info(`WhatsApp[${sessionId}]: connected as ${sock.user?.id ?? "unknown"}`);
500
555
  const senderName = senderNameFor(channelName, sessionId);
501
556
  const sender = {
@@ -526,6 +581,7 @@ function createWhatsAppChannel(config) {
526
581
  }
527
582
  const statusCode = lastDisconnect?.error?.output?.statusCode;
528
583
  if (statusCode === baileys.DisconnectReason.loggedOut) {
584
+ setStatus(sessionId, "disconnected", "session logged out - re-pair required");
529
585
  log.error(
530
586
  `WhatsApp[${sessionId}]: session logged out \u2014 re-pair to reconnect (see docs/channels.md).`
531
587
  );
@@ -534,6 +590,7 @@ function createWhatsAppChannel(config) {
534
590
  }
535
591
  if (!stopped && sessions.has(sessionId)) {
536
592
  const delay = nextRetryDelayMs(sessionId);
593
+ setStatus(sessionId, "disconnected", `connection closed (${statusCode ?? "?"})`);
537
594
  log.warn(
538
595
  `WhatsApp[${sessionId}]: connection closed (${statusCode ?? "?"}), reconnecting in ${Math.round(delay / 1e3)}s...`
539
596
  );
@@ -554,6 +611,33 @@ function createWhatsAppChannel(config) {
554
611
  });
555
612
  }
556
613
  }
614
+ function checkHealth() {
615
+ const nowMs = now();
616
+ for (const sessionId of sessionIds) {
617
+ const unhealthySinceMs = unhealthySince.get(sessionId);
618
+ if (unhealthySinceMs === void 0) continue;
619
+ const status = statuses.get(sessionId);
620
+ if (!status) continue;
621
+ const watchdog = config.watchdog;
622
+ if (watchdog && nowMs - unhealthySinceMs >= watchdog.unhealthyAfterMs && !watchdogFired.has(sessionId)) {
623
+ watchdogFired.add(sessionId);
624
+ watchdog.onUnhealthy?.(status);
625
+ }
626
+ if (!watchdog?.onUnhealthy) {
627
+ const lastLogged = lastUnhealthyLogAt.get(sessionId) ?? unhealthySinceMs;
628
+ if (nowMs - lastLogged >= UNHEALTHY_LOG_INTERVAL_MS) {
629
+ lastUnhealthyLogAt.set(sessionId, nowMs);
630
+ log.error(
631
+ `WhatsApp[${sessionId}]: unhealthy (${status.state}) since ${new Date(status.since).toISOString()}` + (status.lastError ? ` - ${status.lastError}` : "")
632
+ );
633
+ }
634
+ }
635
+ }
636
+ }
637
+ healthCheckInterval = setInterval(checkHealth, heartbeatMs);
638
+ if (typeof healthCheckInterval === "object" && "unref" in healthCheckInterval) {
639
+ healthCheckInterval.unref();
640
+ }
557
641
  for (const sessionId of sessionIds) {
558
642
  void acquireSessionLease(sessionId, leaseDeps).catch(
559
643
  (error) => log.error(`WhatsApp[${sessionId}]: failed to start:`, error)
@@ -562,11 +646,18 @@ function createWhatsAppChannel(config) {
562
646
  },
563
647
  async stop() {
564
648
  stopped = true;
649
+ if (healthCheckInterval) clearInterval(healthCheckInterval);
650
+ for (const sessionId of sessionIds) setStatus(sessionId, "disconnected", "channel stopped");
565
651
  for (const senderName of registeredSenderNames.values()) {
566
652
  senders?.unregister(senderName);
567
653
  }
568
654
  registeredSenderNames.clear();
569
655
  await shutdownWaSessions(sessions);
656
+ },
657
+ getStatus() {
658
+ return sessionIds.map(
659
+ (id) => statuses.get(id) ?? { sessionId: id, state: "waiting_for_lease", since: now() }
660
+ );
570
661
  }
571
662
  };
572
663
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegoaltoworks/chatter",
3
- "version": "0.64.0",
3
+ "version": "0.66.0",
4
4
  "description": "Embeddable AI chatbot framework with RAG, authentication, and customizable widgets",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",