@diegoaltoworks/chatter 0.65.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
  }
@@ -416,6 +422,11 @@ async function acquireSessionLease(sessionId, deps) {
416
422
  logger.error(
417
423
  `WhatsApp[${sessionId}]: lease heartbeats have been failing for ${failingForMs}ms, treating the lease as lost`
418
424
  );
425
+ deps.onStatusChange?.(
426
+ sessionId,
427
+ "disconnected",
428
+ `lease heartbeats failing for ${failingForMs}ms`
429
+ );
419
430
  demoteSelf();
420
431
  return;
421
432
  }
@@ -423,6 +434,7 @@ async function acquireSessionLease(sessionId, deps) {
423
434
  if (isStopped()) return;
424
435
  if (!stillHeld) {
425
436
  logger.error(`WhatsApp[${sessionId}]: lost the lease to another instance, disconnecting`);
437
+ deps.onStatusChange?.(sessionId, "disconnected", "lost the lease to another instance");
426
438
  demoteSelf();
427
439
  return;
428
440
  }
@@ -475,6 +487,26 @@ function createWhatsAppChannel(config) {
475
487
  const failureCounts = /* @__PURE__ */ new Map();
476
488
  const registeredSenderNames = /* @__PURE__ */ new Map();
477
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
+ }
478
510
  return {
479
511
  name: channelName,
480
512
  async start(deps) {
@@ -500,7 +532,8 @@ function createWhatsAppChannel(config) {
500
532
  waitMs,
501
533
  schedule,
502
534
  logger: log,
503
- onConnectFailed: recoverFromFailedConnect
535
+ onConnectFailed: recoverFromFailedConnect,
536
+ onStatusChange: setStatus
504
537
  };
505
538
  function nextRetryDelayMs(sessionId) {
506
539
  const failures = failureCounts.get(sessionId) ?? 0;
@@ -515,6 +548,7 @@ function createWhatsAppChannel(config) {
515
548
  const handle = claimSession(sessionId, sessions);
516
549
  if (!handle) return;
517
550
  const delay = nextRetryDelayMs(sessionId);
551
+ setStatus(sessionId, "disconnected", describeError(error));
518
552
  log.error(
519
553
  `WhatsApp[${sessionId}]: connect attempt failed, releasing the lease and retrying in ${Math.round(delay / 1e3)}s:`,
520
554
  error
@@ -534,6 +568,7 @@ function createWhatsAppChannel(config) {
534
568
  }
535
569
  async function connect(sessionId) {
536
570
  if (stopped || !sessions.has(sessionId)) return;
571
+ setStatus(sessionId, "connecting");
537
572
  const baileys = await loadBaileys2();
538
573
  const runtime = {
539
574
  bufferJSON: baileys.BufferJSON,
@@ -550,6 +585,7 @@ function createWhatsAppChannel(config) {
550
585
  log.warn(
551
586
  `WhatsApp[${sessionId}]: no paired session found \u2014 pair it first (see docs/channels.md). Re-checking in 60s.`
552
587
  );
588
+ setStatus(sessionId, "disconnected", "no paired session - pair it first");
553
589
  schedule(() => attemptConnect(sessionId), 6e4);
554
590
  return;
555
591
  }
@@ -579,6 +615,7 @@ function createWhatsAppChannel(config) {
579
615
  const { connection, lastDisconnect } = update;
580
616
  if (connection === "open") {
581
617
  failureCounts.set(sessionId, 0);
618
+ setStatus(sessionId, "connected");
582
619
  log.info(`WhatsApp[${sessionId}]: connected as ${sock.user?.id ?? "unknown"}`);
583
620
  const senderName = senderNameFor(channelName, sessionId);
584
621
  const sender = {
@@ -609,6 +646,7 @@ function createWhatsAppChannel(config) {
609
646
  }
610
647
  const statusCode = lastDisconnect?.error?.output?.statusCode;
611
648
  if (statusCode === baileys.DisconnectReason.loggedOut) {
649
+ setStatus(sessionId, "disconnected", "session logged out - re-pair required");
612
650
  log.error(
613
651
  `WhatsApp[${sessionId}]: session logged out \u2014 re-pair to reconnect (see docs/channels.md).`
614
652
  );
@@ -617,6 +655,7 @@ function createWhatsAppChannel(config) {
617
655
  }
618
656
  if (!stopped && sessions.has(sessionId)) {
619
657
  const delay = nextRetryDelayMs(sessionId);
658
+ setStatus(sessionId, "disconnected", `connection closed (${statusCode ?? "?"})`);
620
659
  log.warn(
621
660
  `WhatsApp[${sessionId}]: connection closed (${statusCode ?? "?"}), reconnecting in ${Math.round(delay / 1e3)}s...`
622
661
  );
@@ -637,6 +676,33 @@ function createWhatsAppChannel(config) {
637
676
  });
638
677
  }
639
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
+ }
640
706
  for (const sessionId of sessionIds) {
641
707
  void acquireSessionLease(sessionId, leaseDeps).catch(
642
708
  (error) => log.error(`WhatsApp[${sessionId}]: failed to start:`, error)
@@ -645,11 +711,18 @@ function createWhatsAppChannel(config) {
645
711
  },
646
712
  async stop() {
647
713
  stopped = true;
714
+ if (healthCheckInterval) clearInterval(healthCheckInterval);
715
+ for (const sessionId of sessionIds) setStatus(sessionId, "disconnected", "channel stopped");
648
716
  for (const senderName of registeredSenderNames.values()) {
649
717
  senders?.unregister(senderName);
650
718
  }
651
719
  registeredSenderNames.clear();
652
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
+ );
653
726
  }
654
727
  };
655
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
  }
@@ -351,6 +357,11 @@ async function acquireSessionLease(sessionId, deps) {
351
357
  logger.error(
352
358
  `WhatsApp[${sessionId}]: lease heartbeats have been failing for ${failingForMs}ms, treating the lease as lost`
353
359
  );
360
+ deps.onStatusChange?.(
361
+ sessionId,
362
+ "disconnected",
363
+ `lease heartbeats failing for ${failingForMs}ms`
364
+ );
354
365
  demoteSelf();
355
366
  return;
356
367
  }
@@ -358,6 +369,7 @@ async function acquireSessionLease(sessionId, deps) {
358
369
  if (isStopped()) return;
359
370
  if (!stillHeld) {
360
371
  logger.error(`WhatsApp[${sessionId}]: lost the lease to another instance, disconnecting`);
372
+ deps.onStatusChange?.(sessionId, "disconnected", "lost the lease to another instance");
361
373
  demoteSelf();
362
374
  return;
363
375
  }
@@ -410,6 +422,26 @@ function createWhatsAppChannel(config) {
410
422
  const failureCounts = /* @__PURE__ */ new Map();
411
423
  const registeredSenderNames = /* @__PURE__ */ new Map();
412
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
+ }
413
445
  return {
414
446
  name: channelName,
415
447
  async start(deps) {
@@ -435,7 +467,8 @@ function createWhatsAppChannel(config) {
435
467
  waitMs,
436
468
  schedule,
437
469
  logger: log,
438
- onConnectFailed: recoverFromFailedConnect
470
+ onConnectFailed: recoverFromFailedConnect,
471
+ onStatusChange: setStatus
439
472
  };
440
473
  function nextRetryDelayMs(sessionId) {
441
474
  const failures = failureCounts.get(sessionId) ?? 0;
@@ -450,6 +483,7 @@ function createWhatsAppChannel(config) {
450
483
  const handle = claimSession(sessionId, sessions);
451
484
  if (!handle) return;
452
485
  const delay = nextRetryDelayMs(sessionId);
486
+ setStatus(sessionId, "disconnected", describeError(error));
453
487
  log.error(
454
488
  `WhatsApp[${sessionId}]: connect attempt failed, releasing the lease and retrying in ${Math.round(delay / 1e3)}s:`,
455
489
  error
@@ -469,6 +503,7 @@ function createWhatsAppChannel(config) {
469
503
  }
470
504
  async function connect(sessionId) {
471
505
  if (stopped || !sessions.has(sessionId)) return;
506
+ setStatus(sessionId, "connecting");
472
507
  const baileys = await loadBaileys2();
473
508
  const runtime = {
474
509
  bufferJSON: baileys.BufferJSON,
@@ -485,6 +520,7 @@ function createWhatsAppChannel(config) {
485
520
  log.warn(
486
521
  `WhatsApp[${sessionId}]: no paired session found \u2014 pair it first (see docs/channels.md). Re-checking in 60s.`
487
522
  );
523
+ setStatus(sessionId, "disconnected", "no paired session - pair it first");
488
524
  schedule(() => attemptConnect(sessionId), 6e4);
489
525
  return;
490
526
  }
@@ -514,6 +550,7 @@ function createWhatsAppChannel(config) {
514
550
  const { connection, lastDisconnect } = update;
515
551
  if (connection === "open") {
516
552
  failureCounts.set(sessionId, 0);
553
+ setStatus(sessionId, "connected");
517
554
  log.info(`WhatsApp[${sessionId}]: connected as ${sock.user?.id ?? "unknown"}`);
518
555
  const senderName = senderNameFor(channelName, sessionId);
519
556
  const sender = {
@@ -544,6 +581,7 @@ function createWhatsAppChannel(config) {
544
581
  }
545
582
  const statusCode = lastDisconnect?.error?.output?.statusCode;
546
583
  if (statusCode === baileys.DisconnectReason.loggedOut) {
584
+ setStatus(sessionId, "disconnected", "session logged out - re-pair required");
547
585
  log.error(
548
586
  `WhatsApp[${sessionId}]: session logged out \u2014 re-pair to reconnect (see docs/channels.md).`
549
587
  );
@@ -552,6 +590,7 @@ function createWhatsAppChannel(config) {
552
590
  }
553
591
  if (!stopped && sessions.has(sessionId)) {
554
592
  const delay = nextRetryDelayMs(sessionId);
593
+ setStatus(sessionId, "disconnected", `connection closed (${statusCode ?? "?"})`);
555
594
  log.warn(
556
595
  `WhatsApp[${sessionId}]: connection closed (${statusCode ?? "?"}), reconnecting in ${Math.round(delay / 1e3)}s...`
557
596
  );
@@ -572,6 +611,33 @@ function createWhatsAppChannel(config) {
572
611
  });
573
612
  }
574
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
+ }
575
641
  for (const sessionId of sessionIds) {
576
642
  void acquireSessionLease(sessionId, leaseDeps).catch(
577
643
  (error) => log.error(`WhatsApp[${sessionId}]: failed to start:`, error)
@@ -580,11 +646,18 @@ function createWhatsAppChannel(config) {
580
646
  },
581
647
  async stop() {
582
648
  stopped = true;
649
+ if (healthCheckInterval) clearInterval(healthCheckInterval);
650
+ for (const sessionId of sessionIds) setStatus(sessionId, "disconnected", "channel stopped");
583
651
  for (const senderName of registeredSenderNames.values()) {
584
652
  senders?.unregister(senderName);
585
653
  }
586
654
  registeredSenderNames.clear();
587
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
+ );
588
661
  }
589
662
  };
590
663
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegoaltoworks/chatter",
3
- "version": "0.65.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",