fix: heartbeat falls back to last session contact
This commit is contained in:
@@ -105,6 +105,37 @@ describe("runWebHeartbeatOnce", () => {
|
|||||||
});
|
});
|
||||||
expect(sender).toHaveBeenCalledWith("+1555", "ALERT", { verbose: false });
|
expect(sender).toHaveBeenCalledWith("+1555", "ALERT", { verbose: false });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("falls back to most recent session when no to is provided", async () => {
|
||||||
|
const sender: typeof sendMessageWeb = vi
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue({ messageId: "m1", toJid: "jid" });
|
||||||
|
const resolver = vi.fn(async () => ({ text: "ALERT" }));
|
||||||
|
// Seed session store
|
||||||
|
const now = Date.now();
|
||||||
|
const store = {
|
||||||
|
"+1222": { sessionId: "s1", updatedAt: now - 1000 },
|
||||||
|
"+1333": { sessionId: "s2", updatedAt: now },
|
||||||
|
};
|
||||||
|
const storePath = resolveStorePath();
|
||||||
|
await fs.mkdir(resolveStorePath().replace("sessions.json", ""), {
|
||||||
|
recursive: true,
|
||||||
|
});
|
||||||
|
await fs.writeFile(storePath, JSON.stringify(store));
|
||||||
|
setLoadConfigMock({
|
||||||
|
inbound: {
|
||||||
|
allowFrom: ["+1999"],
|
||||||
|
reply: { mode: "command", session: {} },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await runWebHeartbeatOnce({
|
||||||
|
to: "+1999",
|
||||||
|
verbose: false,
|
||||||
|
sender,
|
||||||
|
replyResolver: resolver,
|
||||||
|
});
|
||||||
|
expect(sender).toHaveBeenCalledWith("+1333", "ALERT", { verbose: false });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("web auto-reply", () => {
|
describe("web auto-reply", () => {
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ import { getReplyFromConfig } from "../auto-reply/reply.js";
|
|||||||
import type { ReplyPayload } from "../auto-reply/types.js";
|
import type { ReplyPayload } from "../auto-reply/types.js";
|
||||||
import { waitForever } from "../cli/wait.js";
|
import { waitForever } from "../cli/wait.js";
|
||||||
import { loadConfig } from "../config/config.js";
|
import { loadConfig } from "../config/config.js";
|
||||||
|
import { loadSessionStore, resolveStorePath } from "../config/sessions.js";
|
||||||
import { danger, isVerbose, logVerbose, success } from "../globals.js";
|
import { danger, isVerbose, logVerbose, success } from "../globals.js";
|
||||||
import { logInfo } from "../logger.js";
|
import { logInfo } from "../logger.js";
|
||||||
import { getChildLogger } from "../logging.js";
|
import { getChildLogger } from "../logging.js";
|
||||||
import { defaultRuntime, type RuntimeEnv } from "../runtime.js";
|
import { defaultRuntime, type RuntimeEnv } from "../runtime.js";
|
||||||
|
import { normalizeE164 } from "../utils.js";
|
||||||
import { monitorWebInbox } from "./inbound.js";
|
import { monitorWebInbox } from "./inbound.js";
|
||||||
import { loadWebMedia } from "./media.js";
|
import { loadWebMedia } from "./media.js";
|
||||||
import { sendMessageWeb } from "./outbound.js";
|
import { sendMessageWeb } from "./outbound.js";
|
||||||
@@ -141,6 +143,23 @@ export async function runWebHeartbeatOnce(opts: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getFallbackRecipient(cfg: ReturnType<typeof loadConfig>) {
|
||||||
|
const sessionCfg = cfg.inbound?.reply?.session;
|
||||||
|
const storePath = resolveStorePath(sessionCfg?.store);
|
||||||
|
const store = loadSessionStore(storePath);
|
||||||
|
const candidates = Object.entries(store).filter(([key]) => key !== "global");
|
||||||
|
if (candidates.length === 0) {
|
||||||
|
return (
|
||||||
|
(Array.isArray(cfg.inbound?.allowFrom) && cfg.inbound.allowFrom[0]) ||
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const mostRecent = candidates.sort(
|
||||||
|
(a, b) => (b[1]?.updatedAt ?? 0) - (a[1]?.updatedAt ?? 0),
|
||||||
|
)[0];
|
||||||
|
return mostRecent ? normalizeE164(mostRecent[0]) : null;
|
||||||
|
}
|
||||||
|
|
||||||
async function deliverWebReply(params: {
|
async function deliverWebReply(params: {
|
||||||
replyResult: ReplyPayload;
|
replyResult: ReplyPayload;
|
||||||
msg: WebInboundMsg;
|
msg: WebInboundMsg;
|
||||||
@@ -437,15 +456,31 @@ export async function monitorWebProvider(
|
|||||||
if (!replyHeartbeatMinutes) return;
|
if (!replyHeartbeatMinutes) return;
|
||||||
const tickStart = Date.now();
|
const tickStart = Date.now();
|
||||||
if (!lastInboundMsg) {
|
if (!lastInboundMsg) {
|
||||||
heartbeatLogger.info(
|
const fallbackTo = getFallbackRecipient(cfg);
|
||||||
{
|
if (!fallbackTo) {
|
||||||
connectionId,
|
heartbeatLogger.info(
|
||||||
reason: "no-recent-inbound",
|
{
|
||||||
durationMs: Date.now() - tickStart,
|
connectionId,
|
||||||
},
|
reason: "no-recent-inbound",
|
||||||
"reply heartbeat skipped",
|
durationMs: Date.now() - tickStart,
|
||||||
);
|
},
|
||||||
console.log(success("heartbeat: skipped (no recent inbound)"));
|
"reply heartbeat skipped",
|
||||||
|
);
|
||||||
|
console.log(success("heartbeat: skipped (no recent inbound)"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isVerbose()) {
|
||||||
|
heartbeatLogger.info(
|
||||||
|
{ connectionId, to: fallbackTo, reason: "fallback-session" },
|
||||||
|
"reply heartbeat start",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await runWebHeartbeatOnce({
|
||||||
|
to: fallbackTo,
|
||||||
|
verbose,
|
||||||
|
replyResolver,
|
||||||
|
runtime,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user