matrix: bootstrap crypto runtime when npm scripts are skipped

This commit is contained in:
bmendonca3
2026-03-02 10:46:37 -07:00
committed by Peter Steinberger
parent dbbd41a2ed
commit 66c1da45d4
3 changed files with 144 additions and 2 deletions

View File

@@ -1,6 +1,6 @@
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
import { matrixPlugin } from "./src/channel.js";
import { ensureMatrixCryptoRuntime } from "./src/matrix/deps.js";
import { setMatrixRuntime } from "./src/runtime.js";
const plugin = {
@@ -8,8 +8,10 @@ const plugin = {
name: "Matrix",
description: "Matrix channel plugin (matrix-js-sdk)",
configSchema: emptyPluginConfigSchema(),
register(api: OpenClawPluginApi) {
async register(api: OpenClawPluginApi) {
setMatrixRuntime(api.runtime);
await ensureMatrixCryptoRuntime();
const { matrixPlugin } = await import("./src/channel.js");
api.registerChannel({ plugin: matrixPlugin });
},
};

View File

@@ -0,0 +1,74 @@
import { describe, expect, it, vi } from "vitest";
import { ensureMatrixCryptoRuntime } from "./deps.js";
const logStub = vi.fn();
describe("ensureMatrixCryptoRuntime", () => {
it("returns immediately when matrix SDK loads", async () => {
const runCommand = vi.fn();
const requireFn = vi.fn(() => ({}));
await ensureMatrixCryptoRuntime({
log: logStub,
requireFn,
runCommand,
resolveFn: () => "/tmp/download-lib.js",
nodeExecutable: "/usr/bin/node",
});
expect(requireFn).toHaveBeenCalledTimes(1);
expect(runCommand).not.toHaveBeenCalled();
});
it("bootstraps missing crypto runtime and retries matrix SDK load", async () => {
let bootstrapped = false;
const requireFn = vi.fn(() => {
if (!bootstrapped) {
throw new Error(
"Cannot find module '@matrix-org/matrix-sdk-crypto-nodejs-linux-x64-gnu' (required by matrix sdk)",
);
}
return {};
});
const runCommand = vi.fn(async () => {
bootstrapped = true;
return { code: 0, stdout: "", stderr: "" };
});
await ensureMatrixCryptoRuntime({
log: logStub,
requireFn,
runCommand,
resolveFn: () => "/tmp/download-lib.js",
nodeExecutable: "/usr/bin/node",
});
expect(runCommand).toHaveBeenCalledWith({
argv: ["/usr/bin/node", "/tmp/download-lib.js"],
cwd: "/tmp",
timeoutMs: 300_000,
env: { COREPACK_ENABLE_DOWNLOAD_PROMPT: "0" },
});
expect(requireFn).toHaveBeenCalledTimes(2);
});
it("rethrows non-crypto module errors without bootstrapping", async () => {
const runCommand = vi.fn();
const requireFn = vi.fn(() => {
throw new Error("Cannot find module '@vector-im/matrix-bot-sdk'");
});
await expect(
ensureMatrixCryptoRuntime({
log: logStub,
requireFn,
runCommand,
resolveFn: () => "/tmp/download-lib.js",
nodeExecutable: "/usr/bin/node",
}),
).rejects.toThrow("Cannot find module '@vector-im/matrix-bot-sdk'");
expect(runCommand).not.toHaveBeenCalled();
expect(requireFn).toHaveBeenCalledTimes(1);
});
});

View File

@@ -5,6 +5,27 @@ import { fileURLToPath } from "node:url";
import { runPluginCommandWithTimeout, type RuntimeEnv } from "openclaw/plugin-sdk";
const MATRIX_SDK_PACKAGE = "@vector-im/matrix-bot-sdk";
const MATRIX_CRYPTO_DOWNLOAD_HELPER = "@matrix-org/matrix-sdk-crypto-nodejs/download-lib.js";
function formatCommandError(result: { stderr: string; stdout: string }): string {
const stderr = result.stderr.trim();
if (stderr) {
return stderr;
}
const stdout = result.stdout.trim();
if (stdout) {
return stdout;
}
return "unknown error";
}
function isMissingMatrixCryptoRuntimeError(err: unknown): boolean {
const message = err instanceof Error ? err.message : String(err ?? "");
return (
message.includes("Cannot find module") &&
message.includes("@matrix-org/matrix-sdk-crypto-nodejs-")
);
}
export function isMatrixSdkAvailable(): boolean {
try {
@@ -21,6 +42,51 @@ function resolvePluginRoot(): string {
return path.resolve(currentDir, "..", "..");
}
export async function ensureMatrixCryptoRuntime(
params: {
log?: (message: string) => void;
requireFn?: (id: string) => unknown;
resolveFn?: (id: string) => string;
runCommand?: typeof runPluginCommandWithTimeout;
nodeExecutable?: string;
} = {},
): Promise<void> {
const req = createRequire(import.meta.url);
const requireFn = params.requireFn ?? ((id: string) => req(id));
const resolveFn = params.resolveFn ?? ((id: string) => req.resolve(id));
const runCommand = params.runCommand ?? runPluginCommandWithTimeout;
const nodeExecutable = params.nodeExecutable ?? process.execPath;
try {
requireFn(MATRIX_SDK_PACKAGE);
return;
} catch (err) {
if (!isMissingMatrixCryptoRuntimeError(err)) {
throw err;
}
}
const scriptPath = resolveFn(MATRIX_CRYPTO_DOWNLOAD_HELPER);
params.log?.("matrix: crypto runtime missing; downloading platform library…");
const result = await runCommand({
argv: [nodeExecutable, scriptPath],
cwd: path.dirname(scriptPath),
timeoutMs: 300_000,
env: { COREPACK_ENABLE_DOWNLOAD_PROMPT: "0" },
});
if (result.code !== 0) {
throw new Error(`Matrix crypto runtime bootstrap failed: ${formatCommandError(result)}`);
}
try {
requireFn(MATRIX_SDK_PACKAGE);
} catch (err) {
throw new Error(
`Matrix crypto runtime remains unavailable after bootstrap: ${err instanceof Error ? err.message : String(err)}`,
);
}
}
export async function ensureMatrixSdkInstalled(params: {
runtime: RuntimeEnv;
confirm?: (message: string) => Promise<boolean>;