fix(whatsapp): upgrade Baileys 6.7→6.17, fix proto import and 515 restart

Baileys 6.7.21 silently failed the pairing handshake. Upgrade to 6.17.16
which fixes this. Three related issues:

1. proto is no longer a named ESM export in 6.17.x — use createRequire
   to import via CJS (matching the proven v1 pattern).
2. Setup auth script didn't handle the 515 stream restart that WhatsApp
   sends after successful pairing. Refactored to reconnect (matching v1's
   connectSocket(isReconnect) pattern) instead of hanging until timeout.
3. Added succeeded guard and process.exit(0) to prevent timeout race
   after successful auth.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
gavrielc
2026-04-16 21:01:55 +03:00
parent cdf18e608f
commit e55ed0f4e8
12 changed files with 1189 additions and 307 deletions

View File

@@ -20,11 +20,7 @@ export interface ChannelSetup {
conversations: ConversationConfig[];
/** Called when an inbound message arrives from the platform. */
onInbound(
platformId: string,
threadId: string | null,
message: InboundMessage,
): void | Promise<void>;
onInbound(platformId: string, threadId: string | null, message: InboundMessage): void | Promise<void>;
/** Called when the adapter discovers metadata about a conversation. */
onMetadata(platformId: string, name?: string, isGroup?: boolean): void;

View File

@@ -181,7 +181,12 @@ export function createChatSdkBridge(config: ChatSdkBridgeConfig): ChannelAdapter
// (is_group=0 short-circuits the per-thread escalation).
chat.onDirectMessage(async (thread, message) => {
const channelId = adapter.channelIdFromThreadId(thread.id);
log.info('Inbound DM received', { adapter: adapter.name, channelId, sender: (message.author as any)?.fullName ?? (message.author as any)?.userId ?? 'unknown', threadId: thread.id });
log.info('Inbound DM received', {
adapter: adapter.name,
channelId,
sender: (message.author as any)?.fullName ?? (message.author as any)?.userId ?? 'unknown',
threadId: thread.id,
});
await setupConfig.onInbound(channelId, thread.id, await messageToInbound(message));
await thread.subscribe();
});

View File

@@ -310,9 +310,7 @@ export async function waitForPairing(code: string, opts: WaitForPairingOptions =
?.slice()
.reverse()
.find((a) => !a.matched);
reject(new Error(
`Pairing ${code} invalidated by wrong code${lastMiss ? ` (${lastMiss.candidate})` : ''}`
));
reject(new Error(`Pairing ${code} invalidated by wrong code${lastMiss ? ` (${lastMiss.candidate})` : ''}`));
return;
}
};

View File

@@ -6,7 +6,7 @@
* getMessage fallback, outgoing queue, group metadata cache, LID mapping,
* reconnection with backoff.
*
* Auth credentials persist in data/whatsapp-auth/. On first run:
* Auth credentials persist in store/auth/. On first run:
* - If WHATSAPP_PHONE_NUMBER is set → pairing code (printed to log)
* - Otherwise → QR code (printed to log)
* Subsequent restarts reuse the saved session automatically.
@@ -24,7 +24,6 @@ import {
makeCacheableSignalKeyStore,
normalizeMessageContent,
useMultiFileAuthState,
proto,
} from '@whiskeysockets/baileys';
import type { GroupMetadata, WAMessageKey, WAMessage, WASocket } from '@whiskeysockets/baileys';
@@ -46,8 +45,10 @@ import type {
// Fixed in Baileys 7.x but not backported. Without this, pairing codes fail with
// "couldn't link device" because WhatsApp receives an invalid platform ID.
// Must use createRequire — ESM `import *` creates a read-only namespace.
// proto is not available as a named ESM export — use createRequire (same as v1)
import { createRequire } from 'module';
const _require = createRequire(import.meta.url);
const { proto } = _require('@whiskeysockets/baileys') as { proto: any };
try {
const _generics = _require('@whiskeysockets/baileys/lib/Utils/generics') as Record<string, unknown>;
_generics.getPlatformId = (browser: string): string => {
@@ -63,7 +64,7 @@ try {
const baileysLogger = pino({ level: 'silent' });
const AUTH_DIR_NAME = 'whatsapp-auth';
const AUTH_DIR = path.join(process.cwd(), 'store', 'auth');
const GROUP_SYNC_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24h
const GROUP_METADATA_CACHE_TTL_MS = 60_000; // 1 min for outbound sends
const SENT_MESSAGE_CACHE_MAX = 256;
@@ -148,13 +149,13 @@ function buildMediaMessage(data: Buffer, filename: string, ext: string, caption?
registerChannelAdapter('whatsapp', {
factory: () => {
const env = readEnvFile(['WHATSAPP_PHONE_NUMBER']);
const env = readEnvFile(['WHATSAPP_PHONE_NUMBER', 'WHATSAPP_ENABLED']);
const phoneNumber = env.WHATSAPP_PHONE_NUMBER;
const authDir = path.join(DATA_DIR, AUTH_DIR_NAME);
const authDir = AUTH_DIR;
// Skip if no existing auth and no phone number for pairing
// Skip if no existing auth, no phone number for pairing, and not explicitly enabled (QR mode)
const hasAuth = fs.existsSync(path.join(authDir, 'creds.json'));
if (!hasAuth && !phoneNumber) return null;
if (!hasAuth && !phoneNumber && !env.WHATSAPP_ENABLED) return null;
fs.mkdirSync(authDir, { recursive: true });
@@ -173,7 +174,7 @@ registerChannelAdapter('whatsapp', {
let flushing = false;
// Sent message cache for retry/re-encrypt requests
const sentMessageCache = new Map<string, proto.IMessage>();
const sentMessageCache = new Map<string, any>();
// Group metadata cache with TTL
const groupMetadataCache = new Map<string, { metadata: GroupMetadata; expiresAt: number }>();
@@ -197,7 +198,7 @@ registerChannelAdapter('whatsapp', {
let rejectFirstOpen: ((err: Error) => void) | undefined;
// Pairing code file for the setup skill to poll
const pairingCodeFile = path.join(DATA_DIR, 'whatsapp-pairing-code.txt');
const pairingCodeFile = path.join(process.cwd(), 'store', 'pairing-code.txt');
// --- Helpers ---

View File

@@ -15,7 +15,15 @@ export interface Migration {
up: (db: Database.Database) => void;
}
const migrations: Migration[] = [migration001, migration002, migration003, migration004, migration005, migration007, migration008];
const migrations: Migration[] = [
migration001,
migration002,
migration003,
migration004,
migration005,
migration007,
migration008,
];
export function runMigrations(db: Database.Database): void {
db.exec(`

View File

@@ -314,4 +314,3 @@ function safeParseContent(raw: string): { text?: string; sender?: string; sender
return { text: raw };
}
}