fix(qr): replace qrcode-terminal with qrcode-tui

Replace legacy qrcode-terminal usage with shared qrcode-tui media helpers, bound QR PNG rendering options, and raise bundled plugin host floors for the new SDK runtime surface.
This commit is contained in:
Vincent Koc
2026-04-23 13:06:14 -07:00
committed by GitHub
parent 6f74763f1d
commit ea25d7ed5b
29 changed files with 336 additions and 193 deletions

View File

@@ -6,7 +6,6 @@
"dependencies": {
"@whiskeysockets/baileys": "7.0.0-rc.9",
"jimp": "^1.6.1",
"qrcode-terminal": "^0.12.0",
"typebox": "1.1.28",
"undici": "8.1.0"
},
@@ -15,7 +14,7 @@
"openclaw": "workspace:*"
},
"peerDependencies": {
"openclaw": ">=2026.4.20"
"openclaw": ">=2026.4.23"
},
"peerDependenciesMeta": {
"openclaw": {
@@ -54,10 +53,10 @@
"install": {
"npmSpec": "@openclaw/whatsapp",
"defaultChoice": "npm",
"minHostVersion": ">=2026.4.10"
"minHostVersion": ">=2026.4.23"
},
"compat": {
"pluginApi": ">=2026.4.20"
"pluginApi": ">=2026.4.23"
},
"bundle": {
"stageRuntimeDependencies": true

View File

@@ -1,6 +1,4 @@
import { EventEmitter } from "node:events";
import { readFile } from "node:fs/promises";
import { resolve } from "node:path";
import { resetLogger, setLoggerOverride } from "openclaw/plugin-sdk/runtime-env";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { renderQrPngBase64 } from "./qr-image.js";
@@ -88,13 +86,4 @@ describe("renderQrPngBase64", () => {
const buf = Buffer.from(b64, "base64");
expect(buf.subarray(0, 8).toString("hex")).toBe("89504e470d0a1a0a");
});
it("avoids dynamic require of qrcode-terminal vendor modules", async () => {
const sourcePath = resolve(process.cwd(), "src/media/qr-image.ts");
const source = await readFile(sourcePath, "utf-8");
expect(source).not.toContain("createRequire(");
expect(source).not.toContain('require("qrcode-terminal/vendor/QRCode")');
expect(source).toContain("qrcode-terminal/vendor/QRCode/index.js");
expect(source).toContain("qrcode-terminal/vendor/QRCode/QRErrorCorrectLevel.js");
});
});

View File

@@ -0,0 +1 @@
export { renderQrTerminal } from "openclaw/plugin-sdk/media-runtime";

View File

@@ -1,12 +0,0 @@
declare module "qrcode-terminal" {
type GenerateOptions = {
small?: boolean;
};
type QrCodeTerminal = {
generate: (input: string, options?: GenerateOptions, cb?: (output: string) => void) => void;
};
const qrcode: QrCodeTerminal;
export default qrcode;
}

View File

@@ -21,6 +21,7 @@ import {
writeCredsJsonAtomically,
type CredsQueueWaitResult,
} from "./creds-persistence.js";
import { renderQrTerminal } from "./qr-terminal.js";
import { formatError, getStatusCode } from "./session-errors.js";
import {
DisconnectReason,
@@ -60,11 +61,6 @@ const LOGGED_OUT_STATUS = DisconnectReason?.loggedOut ?? 401;
const CREDS_FLUSH_TIMEOUT_MESSAGE =
"Queued WhatsApp creds save did not finish before auth bootstrap; skipping repair and continuing with primary creds.";
async function loadQrTerminal() {
const mod = await import("qrcode-terminal");
return mod.default ?? mod;
}
function enqueueSaveCreds(
authDir: string,
saveCreds: () => Promise<void> | void,
@@ -113,6 +109,11 @@ async function safeSaveCreds(
}
}
async function printTerminalQr(qr: string): Promise<void> {
const output = await renderQrTerminal(qr, { small: true });
process.stdout.write(output.endsWith("\n") ? output : `${output}\n`);
}
/**
* Create a Baileys socket backed by the multi-file auth store we keep on disk.
* Consumers can opt into QR printing for interactive login flows.
@@ -172,8 +173,9 @@ export async function createWaSocket(
opts.onQr?.(qr);
if (printQr) {
console.log("Scan this QR in WhatsApp (Linked Devices):");
const qrcode = await loadQrTerminal();
qrcode.generate(qr, { small: true });
void printTerminalQr(qr).catch((err) => {
sessionLogger.warn({ error: String(err) }, "failed rendering WhatsApp QR");
});
}
}
if (connection === "close") {

View File

@@ -610,9 +610,8 @@ vi.mock("./session.runtime.js", () => {
};
});
vi.mock("qrcode-terminal", () => ({
default: { generate: vi.fn() },
generate: vi.fn(),
vi.mock("./qr-terminal.js", () => ({
renderQrTerminal: vi.fn(async () => "ASCII-QR"),
}));
export const baileys = await import("./session.runtime.js");