Add IPC to prevent Signal session corruption from concurrent connections

When the relay is running, `warelay send` and `warelay heartbeat` now
communicate via Unix socket IPC (~/.warelay/relay.sock) to send messages
through the relay's existing WhatsApp connection.

Previously, these commands created new Baileys sockets that wrote to the
same auth state files, corrupting the Signal session ratchet and causing
the relay's subsequent sends to fail silently.

Changes:
- Add src/web/ipc.ts with Unix socket server/client
- Relay starts IPC server after connecting
- send command tries IPC first, falls back to direct
- heartbeat uses sendWithIpcFallback helper
- inbound.ts exposes sendMessage on listener object
- Messages sent via IPC are added to echo detection set
This commit is contained in:
Peter Steinberger
2025-12-02 06:31:01 +00:00
parent 2fc3a822c8
commit e86b507da7
5 changed files with 354 additions and 3 deletions

View File

@@ -1,7 +1,8 @@
import type { CliDeps } from "../cli/deps.js";
import { info } from "../globals.js";
import { info, success } from "../globals.js";
import type { RuntimeEnv } from "../runtime.js";
import type { Provider } from "../utils.js";
import { sendViaIpc } from "../web/ipc.js";
export async function sendCommand(
opts: {
@@ -39,6 +40,34 @@ export async function sendCommand(
if (waitSeconds !== 0) {
runtime.log(info("Wait/poll are Twilio-only; ignored for provider=web."));
}
// Try to send via IPC to running relay first (avoids Signal session corruption)
const ipcResult = await sendViaIpc(opts.to, opts.message, opts.media);
if (ipcResult) {
if (ipcResult.success) {
runtime.log(success(`✅ Sent via relay IPC. Message ID: ${ipcResult.messageId}`));
if (opts.json) {
runtime.log(
JSON.stringify(
{
provider: "web",
via: "ipc",
to: opts.to,
messageId: ipcResult.messageId,
mediaUrl: opts.media ?? null,
},
null,
2,
),
);
}
return;
}
// IPC failed but relay is running - warn and fall back
runtime.log(info(`IPC send failed (${ipcResult.error}), falling back to direct connection`));
}
// Fall back to direct connection (creates new Baileys socket)
const res = await deps
.sendMessageWeb(opts.to, opts.message, {
verbose: false,
@@ -53,6 +82,7 @@ export async function sendCommand(
JSON.stringify(
{
provider: "web",
via: "direct",
to: opts.to,
messageId: res.messageId,
mediaUrl: opts.media ?? null,