mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 22:20:42 +00:00
fix: patch Baileys runtime hotfixes for npm installs
This commit is contained in:
@@ -55,6 +55,30 @@ const BAILEYS_MEDIA_HOTFIX_REPLACEMENT = [
|
||||
" await Promise.all([encFinishPromise, originalFinishPromise]);",
|
||||
" logger?.debug('encrypted data successfully');",
|
||||
].join("\n");
|
||||
const BAILEYS_MEDIA_DISPATCHER_NEEDLE = [
|
||||
" const response = await fetch(url, {",
|
||||
" dispatcher: fetchAgent,",
|
||||
" method: 'POST',",
|
||||
].join("\n");
|
||||
const BAILEYS_MEDIA_DISPATCHER_REPLACEMENT = [
|
||||
" const response = await fetch(url, {",
|
||||
" method: 'POST',",
|
||||
].join("\n");
|
||||
const BAILEYS_MEDIA_DISPATCHER_HEADER_NEEDLE = [
|
||||
" 'Content-Type': 'application/octet-stream',",
|
||||
" Origin: DEFAULT_ORIGIN",
|
||||
" },",
|
||||
].join("\n");
|
||||
const BAILEYS_MEDIA_DISPATCHER_HEADER_REPLACEMENT = [
|
||||
" 'Content-Type': 'application/octet-stream',",
|
||||
" Origin: DEFAULT_ORIGIN",
|
||||
" },",
|
||||
" // Baileys passes a generic agent here in some runtimes. Undici's",
|
||||
" // `dispatcher` only works with Dispatcher-compatible implementations,",
|
||||
" // so only wire it through when the object actually implements",
|
||||
" // `dispatch`.",
|
||||
" ...(fetchAgent?.dispatch ? { dispatcher: fetchAgent } : {}),",
|
||||
].join("\n");
|
||||
const BAILEYS_MEDIA_ONCE_IMPORT_RE = /import\s+\{\s*once\s*\}\s+from\s+['"]events['"]/u;
|
||||
const BAILEYS_MEDIA_ASYNC_CONTEXT_RE =
|
||||
/async\s+function\s+encryptedStream|encryptedStream\s*=\s*async/u;
|
||||
@@ -243,23 +267,45 @@ export function applyBaileysEncryptedStreamFinishHotfix(params = {}) {
|
||||
}
|
||||
|
||||
const currentText = readFile(targetPath, "utf8");
|
||||
if (currentText.includes(BAILEYS_MEDIA_HOTFIX_REPLACEMENT)) {
|
||||
return { applied: false, reason: "already_patched" };
|
||||
}
|
||||
if (!currentText.includes(BAILEYS_MEDIA_HOTFIX_NEEDLE)) {
|
||||
return { applied: false, reason: "unexpected_content" };
|
||||
}
|
||||
if (!BAILEYS_MEDIA_ONCE_IMPORT_RE.test(currentText)) {
|
||||
return { applied: false, reason: "missing_once_import", targetPath };
|
||||
}
|
||||
if (!BAILEYS_MEDIA_ASYNC_CONTEXT_RE.test(currentText)) {
|
||||
return { applied: false, reason: "not_async_context", targetPath };
|
||||
let patchedText = currentText;
|
||||
let applied = false;
|
||||
|
||||
if (!patchedText.includes(BAILEYS_MEDIA_HOTFIX_REPLACEMENT)) {
|
||||
if (!patchedText.includes(BAILEYS_MEDIA_HOTFIX_NEEDLE)) {
|
||||
return { applied: false, reason: "unexpected_content" };
|
||||
}
|
||||
if (!BAILEYS_MEDIA_ONCE_IMPORT_RE.test(patchedText)) {
|
||||
return { applied: false, reason: "missing_once_import", targetPath };
|
||||
}
|
||||
if (!BAILEYS_MEDIA_ASYNC_CONTEXT_RE.test(patchedText)) {
|
||||
return { applied: false, reason: "not_async_context", targetPath };
|
||||
}
|
||||
patchedText = patchedText.replace(
|
||||
BAILEYS_MEDIA_HOTFIX_NEEDLE,
|
||||
BAILEYS_MEDIA_HOTFIX_REPLACEMENT,
|
||||
);
|
||||
applied = true;
|
||||
}
|
||||
|
||||
const patchedText = currentText.replace(
|
||||
BAILEYS_MEDIA_HOTFIX_NEEDLE,
|
||||
BAILEYS_MEDIA_HOTFIX_REPLACEMENT,
|
||||
);
|
||||
if (!patchedText.includes("...(fetchAgent?.dispatch ? { dispatcher: fetchAgent } : {}),")) {
|
||||
if (!patchedText.includes(BAILEYS_MEDIA_DISPATCHER_NEEDLE)) {
|
||||
return { applied: false, reason: "unexpected_content" };
|
||||
}
|
||||
if (!patchedText.includes(BAILEYS_MEDIA_DISPATCHER_HEADER_NEEDLE)) {
|
||||
return { applied: false, reason: "unexpected_content" };
|
||||
}
|
||||
patchedText = patchedText
|
||||
.replace(BAILEYS_MEDIA_DISPATCHER_NEEDLE, BAILEYS_MEDIA_DISPATCHER_REPLACEMENT)
|
||||
.replace(
|
||||
BAILEYS_MEDIA_DISPATCHER_HEADER_NEEDLE,
|
||||
BAILEYS_MEDIA_DISPATCHER_HEADER_REPLACEMENT,
|
||||
);
|
||||
applied = true;
|
||||
}
|
||||
|
||||
if (!applied) {
|
||||
return { applied: false, reason: "already_patched" };
|
||||
}
|
||||
const tempPath = createTempPath(targetPath);
|
||||
const tempFd = openFile(tempPath, "wx", initialTargetValidation.mode);
|
||||
let tempFdClosed = false;
|
||||
@@ -298,12 +344,12 @@ function applyBundledPluginRuntimeHotfixes(params = {}) {
|
||||
const log = params.log ?? console;
|
||||
const baileysResult = applyBaileysEncryptedStreamFinishHotfix(params);
|
||||
if (baileysResult.applied) {
|
||||
log.log("[postinstall] patched @whiskeysockets/baileys encryptedStream flush ordering");
|
||||
log.log("[postinstall] patched @whiskeysockets/baileys runtime hotfixes");
|
||||
return;
|
||||
}
|
||||
if (baileysResult.reason !== "missing" && baileysResult.reason !== "already_patched") {
|
||||
log.warn(
|
||||
`[postinstall] could not patch @whiskeysockets/baileys encryptedStream: ${baileysResult.reason}`,
|
||||
`[postinstall] could not patch @whiskeysockets/baileys runtime hotfixes: ${baileysResult.reason}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,68 @@ function writeRepoFile(repoRoot: string, relativePath: string, value: string) {
|
||||
fs.writeFileSync(fullPath, value, "utf8");
|
||||
}
|
||||
|
||||
function createBaileysMessagesMediaSource(params?: {
|
||||
dispatcherPatched?: boolean;
|
||||
encryptedStreamPatched?: boolean;
|
||||
}) {
|
||||
const encryptedLines = params?.encryptedStreamPatched
|
||||
? [
|
||||
" encFileWriteStream.write(mac);",
|
||||
" const encFinishPromise = once(encFileWriteStream, 'finish');",
|
||||
" const originalFinishPromise = originalFileStream ? once(originalFileStream, 'finish') : Promise.resolve();",
|
||||
" encFileWriteStream.end();",
|
||||
" originalFileStream?.end?.();",
|
||||
" stream.destroy();",
|
||||
" await Promise.all([encFinishPromise, originalFinishPromise]);",
|
||||
" logger?.debug('encrypted data successfully');",
|
||||
]
|
||||
: [
|
||||
" encFileWriteStream.write(mac);",
|
||||
" encFileWriteStream.end();",
|
||||
" originalFileStream?.end?.();",
|
||||
" stream.destroy();",
|
||||
" logger?.debug('encrypted data successfully');",
|
||||
];
|
||||
const dispatcherLines = params?.dispatcherPatched
|
||||
? [
|
||||
" const response = await fetch(url, {",
|
||||
" method: 'POST',",
|
||||
" body: stream,",
|
||||
" headers: {",
|
||||
" 'Content-Type': 'application/octet-stream',",
|
||||
" Origin: DEFAULT_ORIGIN",
|
||||
" },",
|
||||
" // Baileys passes a generic agent here in some runtimes. Undici's",
|
||||
" // `dispatcher` only works with Dispatcher-compatible implementations,",
|
||||
" // so only wire it through when the object actually implements",
|
||||
" // `dispatch`.",
|
||||
" ...(fetchAgent?.dispatch ? { dispatcher: fetchAgent } : {}),",
|
||||
" duplex: 'half',",
|
||||
" });",
|
||||
]
|
||||
: [
|
||||
" const response = await fetch(url, {",
|
||||
" dispatcher: fetchAgent,",
|
||||
" method: 'POST',",
|
||||
" body: stream,",
|
||||
" headers: {",
|
||||
" 'Content-Type': 'application/octet-stream',",
|
||||
" Origin: DEFAULT_ORIGIN",
|
||||
" },",
|
||||
" duplex: 'half',",
|
||||
" });",
|
||||
];
|
||||
return [
|
||||
"import { once } from 'events';",
|
||||
"const encryptedStream = async () => {",
|
||||
...encryptedLines,
|
||||
"};",
|
||||
"const upload = async () => {",
|
||||
...dispatcherLines,
|
||||
"};",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
cleanupTrackedTempDirs(tempDirs);
|
||||
});
|
||||
@@ -208,16 +270,7 @@ describe("stageBundledPluginRuntimeDeps", () => {
|
||||
writeRepoFile(
|
||||
repoRoot,
|
||||
"node_modules/@whiskeysockets/baileys/lib/Utils/messages-media.js",
|
||||
[
|
||||
"import { once } from 'events';",
|
||||
"const encryptedStream = async () => {",
|
||||
" encFileWriteStream.write(mac);",
|
||||
" encFileWriteStream.end();",
|
||||
" originalFileStream?.end?.();",
|
||||
" stream.destroy();",
|
||||
" logger?.debug('encrypted data successfully');",
|
||||
"};",
|
||||
].join("\n"),
|
||||
createBaileysMessagesMediaSource(),
|
||||
);
|
||||
|
||||
const { applyBaileysEncryptedStreamFinishHotfix } = await loadPostinstallBundledPluginsModule();
|
||||
@@ -234,6 +287,43 @@ describe("stageBundledPluginRuntimeDeps", () => {
|
||||
expect(fs.readFileSync(targetPath, "utf8")).toContain(
|
||||
"await Promise.all([encFinishPromise, originalFinishPromise]);",
|
||||
);
|
||||
expect(fs.readFileSync(targetPath, "utf8")).toContain(
|
||||
"...(fetchAgent?.dispatch ? { dispatcher: fetchAgent } : {}),",
|
||||
);
|
||||
expect(fs.readFileSync(targetPath, "utf8")).not.toContain("dispatcher: fetchAgent,");
|
||||
});
|
||||
|
||||
it("patches the Baileys dispatcher guard when the flush hotfix is already present", async () => {
|
||||
const repoRoot = makeRepoRoot("openclaw-stage-bundled-runtime-hotfix-dispatcher-");
|
||||
const targetPath = path.join(
|
||||
repoRoot,
|
||||
"node_modules",
|
||||
"@whiskeysockets",
|
||||
"baileys",
|
||||
"lib",
|
||||
"Utils",
|
||||
"messages-media.js",
|
||||
);
|
||||
writeRepoFile(
|
||||
repoRoot,
|
||||
"node_modules/@whiskeysockets/baileys/lib/Utils/messages-media.js",
|
||||
createBaileysMessagesMediaSource({ encryptedStreamPatched: true }),
|
||||
);
|
||||
|
||||
const { applyBaileysEncryptedStreamFinishHotfix } = await loadPostinstallBundledPluginsModule();
|
||||
const result = applyBaileysEncryptedStreamFinishHotfix({ packageRoot: repoRoot });
|
||||
|
||||
expect(result).toEqual({
|
||||
applied: true,
|
||||
reason: "patched",
|
||||
targetPath,
|
||||
});
|
||||
expect(fs.readFileSync(targetPath, "utf8")).toContain(
|
||||
"await Promise.all([encFinishPromise, originalFinishPromise]);",
|
||||
);
|
||||
expect(fs.readFileSync(targetPath, "utf8")).toContain(
|
||||
"...(fetchAgent?.dispatch ? { dispatcher: fetchAgent } : {}),",
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves the original module read mode when replacing Baileys", async () => {
|
||||
@@ -250,16 +340,7 @@ describe("stageBundledPluginRuntimeDeps", () => {
|
||||
writeRepoFile(
|
||||
repoRoot,
|
||||
"node_modules/@whiskeysockets/baileys/lib/Utils/messages-media.js",
|
||||
[
|
||||
"import { once } from 'events';",
|
||||
"const encryptedStream = async () => {",
|
||||
" encFileWriteStream.write(mac);",
|
||||
" encFileWriteStream.end();",
|
||||
" originalFileStream?.end?.();",
|
||||
" stream.destroy();",
|
||||
" logger?.debug('encrypted data successfully');",
|
||||
"};",
|
||||
].join("\n"),
|
||||
createBaileysMessagesMediaSource(),
|
||||
);
|
||||
fs.chmodSync(targetPath, 0o644);
|
||||
|
||||
@@ -315,16 +396,7 @@ describe("stageBundledPluginRuntimeDeps", () => {
|
||||
writeRepoFile(
|
||||
repoRoot,
|
||||
"node_modules/@whiskeysockets/baileys/lib/Utils/messages-media.js",
|
||||
[
|
||||
"import { once } from 'events';",
|
||||
"const encryptedStream = async () => {",
|
||||
" encFileWriteStream.write(mac);",
|
||||
" encFileWriteStream.end();",
|
||||
" originalFileStream?.end?.();",
|
||||
" stream.destroy();",
|
||||
" logger?.debug('encrypted data successfully');",
|
||||
"};",
|
||||
].join("\n"),
|
||||
createBaileysMessagesMediaSource(),
|
||||
);
|
||||
|
||||
const { applyBaileysEncryptedStreamFinishHotfix } = await loadPostinstallBundledPluginsModule();
|
||||
@@ -341,7 +413,7 @@ describe("stageBundledPluginRuntimeDeps", () => {
|
||||
targetPath,
|
||||
error: "read-only filesystem",
|
||||
});
|
||||
expect(fs.readFileSync(targetPath, "utf8")).toContain("encFileWriteStream.end();");
|
||||
expect(fs.readFileSync(targetPath, "utf8")).toContain("dispatcher: fetchAgent,");
|
||||
});
|
||||
|
||||
it("refuses pre-created symlink temp paths instead of following them", async () => {
|
||||
@@ -363,16 +435,7 @@ describe("stageBundledPluginRuntimeDeps", () => {
|
||||
writeRepoFile(
|
||||
repoRoot,
|
||||
"node_modules/@whiskeysockets/baileys/lib/Utils/messages-media.js",
|
||||
[
|
||||
"import { once } from 'events';",
|
||||
"const encryptedStream = async () => {",
|
||||
" encFileWriteStream.write(mac);",
|
||||
" encFileWriteStream.end();",
|
||||
" originalFileStream?.end?.();",
|
||||
" stream.destroy();",
|
||||
" logger?.debug('encrypted data successfully');",
|
||||
"};",
|
||||
].join("\n"),
|
||||
createBaileysMessagesMediaSource(),
|
||||
);
|
||||
writeRepoFile(repoRoot, "redirected-temp-target.js", "const untouched = true;\n");
|
||||
fs.symlinkSync(redirectedTarget, attackerTempPath);
|
||||
@@ -389,6 +452,6 @@ describe("stageBundledPluginRuntimeDeps", () => {
|
||||
expect(result.reason).toBe("error");
|
||||
expect(result.error).toContain("EEXIST");
|
||||
expect(fs.readFileSync(redirectedTarget, "utf8")).toBe("const untouched = true;\n");
|
||||
expect(fs.readFileSync(targetPath, "utf8")).toContain("encFileWriteStream.end();");
|
||||
expect(fs.readFileSync(targetPath, "utf8")).toContain("dispatcher: fetchAgent,");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user