feat: retries for webhook bring-up and send --json docs

This commit is contained in:
Peter Steinberger
2025-11-25 04:10:20 +01:00
parent d1923e6efe
commit 28277a298a
6 changed files with 36 additions and 19 deletions

View File

@@ -36,6 +36,7 @@ You can also talk to WhatsApp directly with a personal WhatsApp Web session (QR
## Common Commands ## Common Commands
- Send: `pnpm warelay send --to +12345550000 --message "Hello" --wait 20 --poll 2` - Send: `pnpm warelay send --to +12345550000 --message "Hello" --wait 20 --poll 2`
- Send (JSON output): `pnpm warelay send --to +12345550000 --message "Hello" --json`
- Send via personal WhatsApp Web: first `pnpm warelay web:login` (alias: `pnpm warelay login`, scan QR), then `pnpm warelay send --provider web --to +12345550000 --message "Hi"` - Send via personal WhatsApp Web: first `pnpm warelay web:login` (alias: `pnpm warelay login`, scan QR), then `pnpm warelay send --provider web --to +12345550000 --message "Hi"`
- Auto-replies (auto provider): `pnpm warelay relay` (uses web if logged in, otherwise twilio poll) - Auto-replies (auto provider): `pnpm warelay relay` (uses web if logged in, otherwise twilio poll)
- Auto-replies (force web): `pnpm warelay relay --provider web` - Auto-replies (force web): `pnpm warelay relay --provider web`

View File

@@ -16,6 +16,7 @@ This is a living note capturing the cleanups underway to keep `warelay` small an
- README updated to document direct WhatsApp Web support and Claude output handling. - README updated to document direct WhatsApp Web support and Claude output handling.
- Added zod validation for `~/.warelay/warelay.json` and a `--dry-run` flag for `send`. - Added zod validation for `~/.warelay/warelay.json` and a `--dry-run` flag for `send`.
- Introduced a tiny logger (`src/logger.ts`) and backoff retry in Twilio polling. - Introduced a tiny logger (`src/logger.ts`) and backoff retry in Twilio polling.
- Added `--json` for `send`, logger adoption for web/twilio sends, and retries for webhook bring-up.
## In this pass ## In this pass
- Wire more modules to the logger; keep colors/verbosity consistent. - Wire more modules to the logger; keep colors/verbosity consistent.

View File

@@ -122,10 +122,7 @@ Examples:
const provider = await pickProvider(providerPref as Provider | "auto"); const provider = await pickProvider(providerPref as Provider | "auto");
if (provider === "web") { if (provider === "web") {
defaultRuntime.log( logWebSelfId(defaultRuntime, true);
info("Provider: web (personal WhatsApp Web session)"),
);
logWebSelfId();
try { try {
await monitorWebProvider(Boolean(opts.verbose)); await monitorWebProvider(Boolean(opts.verbose));
return; return;

View File

@@ -1,6 +1,7 @@
import type { CliDeps } from "../cli/deps.js"; import type { CliDeps } from "../cli/deps.js";
import type { RuntimeEnv } from "../runtime.js"; import type { RuntimeEnv } from "../runtime.js";
import { waitForever as defaultWaitForever } from "../cli/wait.js"; import { waitForever as defaultWaitForever } from "../cli/wait.js";
import { retryAsync } from "../infra/retry.js";
export async function upCommand( export async function upCommand(
opts: { port: string; path: string; verbose?: boolean; yes?: boolean; dryRun?: boolean }, opts: { port: string; path: string; verbose?: boolean; yes?: boolean; dryRun?: boolean },
@@ -23,17 +24,22 @@ export async function upCommand(
return { server: undefined, publicUrl, senderSid: undefined, waiter }; return { server: undefined, publicUrl, senderSid: undefined, waiter };
} }
await deps.ensureBinary("tailscale", undefined, runtime); await deps.ensureBinary("tailscale", undefined, runtime);
await deps.ensureFunnel(port, undefined, runtime); await retryAsync(() => deps.ensureFunnel(port, undefined, runtime), 3, 500);
const host = await deps.getTailnetHostname(); const host = await deps.getTailnetHostname();
const publicUrl = `https://${host}${opts.path}`; const publicUrl = `https://${host}${opts.path}`;
runtime.log(`🌐 Public webhook URL (via Funnel): ${publicUrl}`); runtime.log(`🌐 Public webhook URL (via Funnel): ${publicUrl}`);
const server = await deps.startWebhook( const server = await retryAsync(
port, () =>
opts.path, deps.startWebhook(
undefined, port,
Boolean(opts.verbose), opts.path,
runtime, undefined,
Boolean(opts.verbose),
runtime,
),
3,
300,
); );
if (!deps.createClient) { if (!deps.createClient) {

View File

@@ -1,5 +1,6 @@
import type { CliDeps } from "../cli/deps.js"; import type { CliDeps } from "../cli/deps.js";
import type { RuntimeEnv } from "../runtime.js"; import type { RuntimeEnv } from "../runtime.js";
import { retryAsync } from "../infra/retry.js";
export async function webhookCommand( export async function webhookCommand(
opts: { opts: {
@@ -21,12 +22,17 @@ export async function webhookCommand(
runtime.log(`[dry-run] would start webhook on port ${port} path ${opts.path}`); runtime.log(`[dry-run] would start webhook on port ${port} path ${opts.path}`);
return undefined; return undefined;
} }
const server = await deps.startWebhook( const server = await retryAsync(
port, () =>
opts.path, deps.startWebhook(
opts.reply, port,
Boolean(opts.verbose), opts.path,
runtime, opts.reply,
Boolean(opts.verbose),
runtime,
),
3,
300,
); );
return server; return server;
} }

View File

@@ -364,14 +364,20 @@ function readWebSelfId() {
} }
} }
export function logWebSelfId(runtime: RuntimeEnv = defaultRuntime) { export function logWebSelfId(
runtime: RuntimeEnv = defaultRuntime,
includeProviderPrefix = false,
) {
// Human-friendly log of the currently linked personal web session. // Human-friendly log of the currently linked personal web session.
const { e164, jid } = readWebSelfId(); const { e164, jid } = readWebSelfId();
const details = const details =
e164 || jid e164 || jid
? `${e164 ?? "unknown"}${jid ? ` (jid ${jid})` : ""}` ? `${e164 ?? "unknown"}${jid ? ` (jid ${jid})` : ""}`
: "unknown"; : "unknown";
runtime.log(info(`Listening on web session: ${details}`)); const prefix = includeProviderPrefix
? "Provider: web (personal WhatsApp Web session) — "
: "";
runtime.log(info(`${prefix}Listening on web session: ${details}`));
} }
export async function pickProvider(pref: Provider | "auto"): Promise<Provider> { export async function pickProvider(pref: Provider | "auto"): Promise<Provider> {