docs: finalize web refactor and coverage
This commit is contained in:
11
README.md
11
README.md
@@ -18,7 +18,7 @@ Install from npm (global): `npm install -g warelay` (Node 22+). Then choose **on
|
||||
**A) Personal WhatsApp Web (preferred: no Twilio creds, fastest setup)**
|
||||
1. Link your account: `warelay login` (scan the QR).
|
||||
2. Send a message: `warelay send --to +12345550000 --message "Hi from warelay"` (add `--provider web` if you want to force the web session).
|
||||
3. Stay online & auto-reply: `warelay relay --verbose` (defaults to Web when logged in, falls back to Twilio otherwise).
|
||||
3. Stay online & auto-reply: `warelay relay --verbose` (uses Web when you're logged in; if you're not linked, start it with `--provider twilio`). When a Web session drops, the relay exits instead of silently falling back so you notice and re-login.
|
||||
|
||||
**B) Twilio WhatsApp number (for delivery status + webhooks)**
|
||||
1. Copy `.env.example` → `.env`; set `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN` **or** `TWILIO_API_KEY`/`TWILIO_API_SECRET`, and `TWILIO_WHATSAPP_FROM=whatsapp:+19995550123` (optional `TWILIO_SENDER_SID`).
|
||||
@@ -79,8 +79,8 @@ Install from npm (global): `npm install -g warelay` (Node 22+). Then choose **on
|
||||
|
||||
## Providers
|
||||
- **Twilio (default):** needs `.env` creds + WhatsApp-enabled number; supports delivery tracking, polling, webhooks, and auto-reply typing indicators.
|
||||
- **Web (`--provider web`):** uses your personal WhatsApp via Baileys; supports send/receive + auto-reply, but no delivery-status wait; cache lives in `~/.warelay/credentials/` (rerun `login` if logged out).
|
||||
- **Auto-select (`relay` only):** `--provider auto` uses Web when logged in, otherwise Twilio polling.
|
||||
- **Web (`--provider web`):** uses your personal WhatsApp via Baileys; supports send/receive + auto-reply, but no delivery-status wait; cache lives in `~/.warelay/credentials/` (rerun `login` if logged out). If the Web socket closes, the relay exits instead of pivoting to Twilio.
|
||||
- **Auto-select (`relay` only):** `--provider auto` picks Web when a cache exists at start, otherwise Twilio polling. It will not swap from Web to Twilio mid-run if the Web session drops.
|
||||
|
||||
Best practice: use a dedicated WhatsApp account (separate SIM/eSIM or business account) for automation instead of your primary personal account to avoid unexpected logouts or rate limits.
|
||||
|
||||
@@ -171,6 +171,11 @@ Templating tokens: `{{Body}}`, `{{BodyStripped}}`, `{{From}}`, `{{To}}`, `{{Mess
|
||||
- Web provider dropped: rerun `pnpm warelay login`; credentials live in `~/.warelay/credentials/`.
|
||||
- Tailscale Funnel errors: update tailscale/tailscaled; check admin console that Funnel is enabled for this device.
|
||||
|
||||
### Maintainer notes (web provider internals)
|
||||
- Web logic lives under `src/web/`: `session.ts` (auth/cache + provider pick), `login.ts` (QR login/logout), `outbound.ts`/`inbound.ts` (send/receive plumbing), `auto-reply.ts` (relay loop + reconnect/backoff), `media.ts` (download/resize helpers), and `reconnect.ts` (shared retry math). `test-helpers.ts` provides fixtures.
|
||||
- The public surface remains the `src/provider-web.ts` barrel so existing imports keep working.
|
||||
- Reconnects are capped and logged; no Twilio fallback occurs after a Web disconnect—restart the relay after re-linking.
|
||||
|
||||
## FAQ & Safety
|
||||
- Twilio errors: **63016 “permission to send an SMS has not been enabled”** → ensure your number is WhatsApp-enabled; **63007 template not approved** → send a free-form session message within 24h or use an approved template; **63112 policy violation** → adjust content, shorten to <1600 chars, avoid links that trigger spam filters. Re-run `pnpm warelay status` to see the exact Twilio response body.
|
||||
- Does this store my messages? warelay only writes `~/.warelay/warelay.json` (config), `~/.warelay/credentials/` (WhatsApp Web auth), and `~/.warelay/sessions.json` (session IDs + timestamps). It does **not** persist message bodies beyond the session store. Logs stream to stdout/stderr and also `/tmp/warelay/warelay.log` (configurable via `logging.file`).
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
# Web Provider Refactor (Nov 26, 2025)
|
||||
|
||||
Context: `src/provider-web.ts` was a 900+ line ball of mud mixing session management, outbound sends, inbound handling, auto-replies, and media helpers. We split it into focused modules under `src/web/` and adjusted tests/CLI behavior.
|
||||
|
||||
## What changed
|
||||
- New modules: `session.ts`, `login.ts`, `outbound.ts`, `inbound.ts`, `auto-reply.ts`, `media.ts`; barrel remains `src/provider-web.ts`.
|
||||
- CLI adds `warelay logout` to clear `~/.warelay/credentials`; tested in `src/web/logout.test.ts`.
|
||||
- Relay now **exits instead of falling back to Twilio** when the web provider fails (even in `--provider auto`), so outages are visible.
|
||||
- Tests split accordingly; all suites green.
|
||||
- Structured logging + heartbeats: web relay now emits structured logs with `runId`/`connectionId` plus periodic heartbeats (default every 60s) that include auth age and message counts.
|
||||
- Bounded reconnects: web relay uses capped exponential backoff (default 2s→30s, max 12 attempts). CLI knobs `--web-retries`, `--web-retry-initial`, `--web-retry-max`, `--web-heartbeat` and config `web.reconnect`/`web.heartbeatSeconds` tune the behavior.
|
||||
- Backoff reset after healthy uptime; logged-out state still exits immediately.
|
||||
- Extracted reconnect/heartbeat helpers to `src/web/reconnect.ts` with unit tests.
|
||||
- Added troubleshooting guide at `docs/refactor/web-relay-troubleshooting.md` (common errors, knobs, logs).
|
||||
|
||||
## How to use
|
||||
- Link: `warelay login --provider web`
|
||||
- Logout: `warelay logout` (deletes `~/.warelay/credentials`)
|
||||
- Run relay web-only: `warelay relay --provider web --verbose`
|
||||
|
||||
## Follow-ups worth doing
|
||||
- Document the new module boundaries in README/docs; add a one-liner explaining the no-fallback behavior.
|
||||
- Add bounded backoff/jitter in `monitorWebProvider` reconnect loop with clearer exit codes. ✅
|
||||
- Tighten config validation (`mediaMaxMb`, etc.) on load. ✅ (schema now includes `web.*` knobs)
|
||||
- Emit structured logs for reconnect/close reasons to help ops triage (status, isLoggedOut). ✅
|
||||
- Add quick troubleshooting snippets (how to read logs, restart relay, rotate creds).
|
||||
15
src/provider-web.barrel.test.ts
Normal file
15
src/provider-web.barrel.test.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import * as mod from "./provider-web.js";
|
||||
|
||||
describe("provider-web barrel", () => {
|
||||
it("exports the expected web helpers", () => {
|
||||
expect(mod.createWaSocket).toBeTypeOf("function");
|
||||
expect(mod.loginWeb).toBeTypeOf("function");
|
||||
expect(mod.monitorWebProvider).toBeTypeOf("function");
|
||||
expect(mod.sendMessageWeb).toBeTypeOf("function");
|
||||
expect(mod.monitorWebInbox).toBeTypeOf("function");
|
||||
expect(mod.pickProvider).toBeTypeOf("function");
|
||||
expect(mod.WA_WEB_AUTH_DIR).toBeTruthy();
|
||||
});
|
||||
});
|
||||
74
src/web/login.coverage.test.ts
Normal file
74
src/web/login.coverage.test.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import fs from "node:fs/promises";
|
||||
|
||||
import { DisconnectReason } from "@whiskeysockets/baileys";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.useFakeTimers();
|
||||
|
||||
const rmMock = vi.spyOn(fs, "rm");
|
||||
|
||||
vi.mock("./session.js", () => {
|
||||
const sockA = { ws: { close: vi.fn() } };
|
||||
const sockB = { ws: { close: vi.fn() } };
|
||||
const createWaSocket = vi.fn(async () =>
|
||||
createWaSocket.mock.calls.length === 0 ? sockA : sockB,
|
||||
);
|
||||
const waitForWaConnection = vi.fn();
|
||||
const formatError = vi.fn((err: unknown) => `formatted:${String(err)}`);
|
||||
return {
|
||||
createWaSocket,
|
||||
waitForWaConnection,
|
||||
formatError,
|
||||
WA_WEB_AUTH_DIR: "/tmp/wa-creds",
|
||||
};
|
||||
});
|
||||
|
||||
const { createWaSocket, waitForWaConnection, formatError } = await import(
|
||||
"./session.js"
|
||||
);
|
||||
const { loginWeb } = await import("./login.js");
|
||||
|
||||
describe("loginWeb coverage", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
rmMock.mockClear();
|
||||
});
|
||||
|
||||
it("restarts once when WhatsApp requests code 515", async () => {
|
||||
waitForWaConnection
|
||||
.mockRejectedValueOnce({ output: { statusCode: 515 } })
|
||||
.mockResolvedValueOnce(undefined);
|
||||
|
||||
const runtime = { log: vi.fn(), error: vi.fn() } as never;
|
||||
await loginWeb(false, waitForWaConnection as never, runtime);
|
||||
|
||||
expect(createWaSocket).toHaveBeenCalledTimes(2);
|
||||
const firstSock = await createWaSocket.mock.results[0].value;
|
||||
expect(firstSock.ws.close).toHaveBeenCalled();
|
||||
vi.runAllTimers();
|
||||
const secondSock = await createWaSocket.mock.results[1].value;
|
||||
expect(secondSock.ws.close).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("clears creds and throws when logged out", async () => {
|
||||
waitForWaConnection.mockRejectedValueOnce({
|
||||
output: { statusCode: DisconnectReason.loggedOut },
|
||||
});
|
||||
|
||||
await expect(loginWeb(false, waitForWaConnection as never)).rejects.toThrow(
|
||||
/cache cleared/i,
|
||||
);
|
||||
expect(rmMock).toHaveBeenCalledWith("/tmp/wa-creds", {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("formats and rethrows generic errors", async () => {
|
||||
waitForWaConnection.mockRejectedValueOnce(new Error("boom"));
|
||||
await expect(loginWeb(false, waitForWaConnection as never)).rejects.toThrow(
|
||||
"formatted:Error: boom",
|
||||
);
|
||||
expect(formatError).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user