From cc9acd7f3ac27762feae07a3f573e015054d3c4b Mon Sep 17 00:00:00 2001 From: Yudhi Armyndharis Date: Wed, 12 Aug 2026 18:11:19 +0700 Subject: [PATCH] fix: survive a malformed upstream response and an empty chat list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit typebot-connector checked `messages` and `clientSideActions` for null but not for shape. A Typebot server returning either as an object or a string raised a TypeError inside the message hook rather than degrading to no bubbles. Both are array-checked now — the upstream is third-party, and its response is untrusted input like any other. chatwoot-adapter marked its one-time bulk backfill complete even when the engine returned no chats. The engine returns an empty list while it is still warming up, and the marker is durable, so a sweep at the wrong moment retired bulk backfill for that session permanently. It now leaves the marker unset and logs, so a later enable tries again. --- chatwoot-adapter/CHANGELOG.md | 4 ++++ chatwoot-adapter/backfill.ts | 7 +++++++ typebot-connector/CHANGELOG.md | 7 +++++++ typebot-connector/typebot-client.ts | 6 ++++-- 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/chatwoot-adapter/CHANGELOG.md b/chatwoot-adapter/CHANGELOG.md index 540759f..f88e980 100644 --- a/chatwoot-adapter/CHANGELOG.md +++ b/chatwoot-adapter/CHANGELOG.md @@ -8,6 +8,10 @@ All notable changes to the Chatwoot Adapter plugin are documented here. The form ### Fixed +- **An empty chat list no longer retires bulk backfill permanently.** The engine returns an empty list + while it is still warming up, and the completion marker is durable — so a sweep that ran at the wrong + moment disabled bulk backfill for that session forever. The marker is left unset so a later enable + tries again. - **Chatwoot request errors no longer carry the customer's phone number.** These errors surface in `healthCheck`, which the dashboard renders, and the contact-search URL carries the number in its query string. The query is dropped and the response body is no longer appended; the path still says which diff --git a/chatwoot-adapter/backfill.ts b/chatwoot-adapter/backfill.ts index d6b2bfd..333a13d 100644 --- a/chatwoot-adapter/backfill.ts +++ b/chatwoot-adapter/backfill.ts @@ -85,6 +85,13 @@ export async function backfillAllChats(deps: InboundDeps, sessionId: string): Pr try { if (await deps.store.isBulkBackfilled(sessionId)) return; const chats = (await deps.engine.getChats(sessionId)) as ChatSummary[]; + // An empty list is what the engine returns while it is still warming up, and marking the sweep done + // on it retires bulk backfill permanently for this session — the marker is durable. Leave it unset + // so the next enable tries again. + if (!Array.isArray(chats) || chats.length === 0) { + deps.log('chatwoot-adapter: no chats returned; leaving bulk backfill unmarked for a later attempt'); + return; + } for (const chat of chats) { if (chat.isGroup && !deps.relayGroups) continue; await deps.lock.run(`${sessionId}:${chat.id}`, async () => { diff --git a/typebot-connector/CHANGELOG.md b/typebot-connector/CHANGELOG.md index cd1ca14..41bd160 100644 --- a/typebot-connector/CHANGELOG.md +++ b/typebot-connector/CHANGELOG.md @@ -6,6 +6,13 @@ All notable changes to the Typebot Connector plugin are documented here. The for ## [Unreleased] +### Fixed + +- **A malformed upstream response no longer throws out of the hook.** `messages` and `clientSideActions` + were only checked for null, so a Typebot server returning either as an object or a string raised a + TypeError inside the message handler. They are array-checked now and degrade to no bubbles. + + ## [0.2.2] — 2026-08-12 ### Changed diff --git a/typebot-connector/typebot-client.ts b/typebot-connector/typebot-client.ts index 29709a2..b332fce 100644 --- a/typebot-connector/typebot-client.ts +++ b/typebot-connector/typebot-client.ts @@ -79,9 +79,11 @@ export class TypebotClient { // ── normalization ───────────────────────────────────────────────────────────────────── function normalize(raw: any): NormalizedResponse { - const bubbles = (raw?.messages ?? []).map(normalizeBubble).filter((b: Bubble | null): b is Bubble => b !== null); + // Array-check, not just nullish: the upstream is a third-party server and `messages` arriving as an + // object or a string would otherwise throw TypeError out of a hook rather than degrade to no bubbles. + const bubbles = (Array.isArray(raw?.messages) ? raw.messages : []).map(normalizeBubble).filter((b: Bubble | null): b is Bubble => b !== null); const input = raw?.input ? normalizeInput(raw.input) : undefined; - const redirect = (raw?.clientSideActions ?? []).find((a: any) => a?.redirect)?.redirect; + const redirect = (Array.isArray(raw?.clientSideActions) ? raw.clientSideActions : []).find((a: any) => a?.redirect)?.redirect; return { sessionId: raw?.sessionId, bubbles, input, redirectUrl: redirect?.url }; }