@heylemon/lemonade 2026.2.26 → 2026.2.27
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.
package/dist/build-info.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
97f5c46c50948d0e879a973bf28450f752b5c3beda881fe18db3cdebcb14f395
|
|
@@ -110,6 +110,7 @@ export function createChannelManager(opts) {
|
|
|
110
110
|
log.error?.(`[${id}] channel exited: ${message}`);
|
|
111
111
|
})
|
|
112
112
|
.finally(() => {
|
|
113
|
+
const wasAborted = abort.signal.aborted;
|
|
113
114
|
store.aborts.delete(id);
|
|
114
115
|
store.tasks.delete(id);
|
|
115
116
|
setRuntime(channelId, id, {
|
|
@@ -117,6 +118,23 @@ export function createChannelManager(opts) {
|
|
|
117
118
|
running: false,
|
|
118
119
|
lastStopAt: Date.now(),
|
|
119
120
|
});
|
|
121
|
+
// Auto-restart channels that exit unexpectedly (not intentionally
|
|
122
|
+
// stopped via abort). This is a safety net: the inner reconnect
|
|
123
|
+
// loop inside monitorWebChannel handles transient failures, but if
|
|
124
|
+
// the channel exits entirely (e.g., max reconnect attempts
|
|
125
|
+
// exhausted, or an unexpected throw), we restart it at this level.
|
|
126
|
+
if (!wasAborted) {
|
|
127
|
+
const RESTART_DELAY_MS = 30_000;
|
|
128
|
+
log.info?.(`[${id}] scheduling auto-restart in ${RESTART_DELAY_MS / 1000}s`);
|
|
129
|
+
setTimeout(() => {
|
|
130
|
+
if (store.tasks.has(id))
|
|
131
|
+
return; // already restarted
|
|
132
|
+
log.info?.(`[${id}] auto-restarting channel`);
|
|
133
|
+
void startChannel(channelId, id).catch((restartErr) => {
|
|
134
|
+
log.error?.(`[${id}] auto-restart failed: ${formatErrorMessage(restartErr)}`);
|
|
135
|
+
});
|
|
136
|
+
}, RESTART_DELAY_MS);
|
|
137
|
+
}
|
|
120
138
|
});
|
|
121
139
|
store.tasks.set(id, tracked);
|
|
122
140
|
}));
|
|
@@ -16,7 +16,7 @@ import { resolveWhatsAppAccount } from "../accounts.js";
|
|
|
16
16
|
import { setActiveWebListener } from "../active-listener.js";
|
|
17
17
|
import { monitorWebInbox } from "../inbound.js";
|
|
18
18
|
import { computeBackoff, newConnectionId, resolveHeartbeatSeconds, resolveReconnectPolicy, sleepWithAbort, } from "../reconnect.js";
|
|
19
|
-
import { formatError, getWebAuthAgeMs, readWebSelfId } from "../session.js";
|
|
19
|
+
import { formatError, getStatusCode, getWebAuthAgeMs, readWebSelfId } from "../session.js";
|
|
20
20
|
import { DEFAULT_WEB_MEDIA_BYTES } from "./constants.js";
|
|
21
21
|
import { whatsappHeartbeatLog, whatsappLog } from "./loggers.js";
|
|
22
22
|
import { buildMentionConfig } from "./mentions.js";
|
|
@@ -142,24 +142,70 @@ export async function monitorWebChannel(verbose, listenerFactory = monitorWebInb
|
|
|
142
142
|
return false;
|
|
143
143
|
return !hasControlCommand(msg.body, cfg);
|
|
144
144
|
};
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
145
|
+
let listener;
|
|
146
|
+
try {
|
|
147
|
+
listener = await (listenerFactory ?? monitorWebInbox)({
|
|
148
|
+
verbose,
|
|
149
|
+
accountId: account.accountId,
|
|
150
|
+
authDir: account.authDir,
|
|
151
|
+
mediaMaxMb: account.mediaMaxMb,
|
|
152
|
+
sendReadReceipts: account.sendReadReceipts,
|
|
153
|
+
debounceMs: inboundDebounceMs,
|
|
154
|
+
shouldDebounce,
|
|
155
|
+
onMessage: async (msg) => {
|
|
156
|
+
handledMessages += 1;
|
|
157
|
+
lastMessageAt = Date.now();
|
|
158
|
+
status.lastMessageAt = lastMessageAt;
|
|
159
|
+
status.lastEventAt = lastMessageAt;
|
|
160
|
+
emitStatus();
|
|
161
|
+
_lastInboundMsg = msg;
|
|
162
|
+
await onMessage(msg);
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
// Initial socket creation or WebSocket handshake failed (e.g., 408
|
|
168
|
+
// timeout, 428 Connection Terminated). Without this catch the error
|
|
169
|
+
// escapes the while-loop and the reconnect backoff is never reached.
|
|
170
|
+
const errStatusCode = getStatusCode(err);
|
|
171
|
+
const errorStr = formatError(err);
|
|
172
|
+
reconnectLogger.warn({ connectionId, error: errorStr, status: errStatusCode }, "web reconnect: initial connection failed");
|
|
173
|
+
status.connected = false;
|
|
174
|
+
status.lastError = errorStr;
|
|
175
|
+
status.lastDisconnect = {
|
|
176
|
+
at: Date.now(),
|
|
177
|
+
status: typeof errStatusCode === "number" ? errStatusCode : undefined,
|
|
178
|
+
error: errorStr,
|
|
179
|
+
loggedOut: errStatusCode === 401,
|
|
180
|
+
};
|
|
181
|
+
emitStatus();
|
|
182
|
+
if (errStatusCode === 401) {
|
|
183
|
+
runtime.error(`WhatsApp session logged out. Run \`${formatCliCommand("lemonade channels login --channel web")}\` to relink.`);
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
reconnectAttempts += 1;
|
|
187
|
+
status.reconnectAttempts = reconnectAttempts;
|
|
188
|
+
emitStatus();
|
|
189
|
+
if (reconnectPolicy.maxAttempts > 0 && reconnectAttempts >= reconnectPolicy.maxAttempts) {
|
|
190
|
+
reconnectLogger.warn({
|
|
191
|
+
connectionId,
|
|
192
|
+
status: errStatusCode,
|
|
193
|
+
reconnectAttempts,
|
|
194
|
+
maxAttempts: reconnectPolicy.maxAttempts,
|
|
195
|
+
}, "web reconnect: max attempts reached after connection failure");
|
|
196
|
+
runtime.error(`WhatsApp Web reconnect: max attempts reached (${reconnectAttempts}/${reconnectPolicy.maxAttempts}). Stopping web monitoring.`);
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
const delay = computeBackoff(reconnectPolicy, reconnectAttempts);
|
|
200
|
+
runtime.error(`WhatsApp Web connection failed (status ${errStatusCode ?? "unknown"}). Retry ${reconnectAttempts}/${reconnectPolicy.maxAttempts || "∞"} in ${formatDurationMs(delay)}… (${errorStr})`);
|
|
201
|
+
try {
|
|
202
|
+
await sleep(delay, abortSignal);
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
163
209
|
status.connected = true;
|
|
164
210
|
status.lastConnectedAt = Date.now();
|
|
165
211
|
status.lastEventAt = status.lastConnectedAt;
|