mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-07 22:09:57 +00:00
matrix-js: stabilize verification flow and rename verify device command
This commit is contained in:
@@ -147,10 +147,10 @@ Bootstrap cross-signing and verification state:
|
||||
openclaw matrix-js verify bootstrap
|
||||
```
|
||||
|
||||
Verify with a recovery key:
|
||||
Verify this device with a recovery key:
|
||||
|
||||
```bash
|
||||
openclaw matrix-js verify recovery-key "<your-recovery-key>"
|
||||
openclaw matrix-js verify device "<your-recovery-key>"
|
||||
```
|
||||
|
||||
Use `openclaw matrix-js verify status --json` when scripting verification checks.
|
||||
|
||||
82
extensions/matrix-js/src/cli.test.ts
Normal file
82
extensions/matrix-js/src/cli.test.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { Command } from "commander";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const bootstrapMatrixVerificationMock = vi.fn();
|
||||
const getMatrixVerificationStatusMock = vi.fn();
|
||||
const verifyMatrixRecoveryKeyMock = vi.fn();
|
||||
|
||||
vi.mock("./matrix/actions/verification.js", () => ({
|
||||
bootstrapMatrixVerification: (...args: unknown[]) => bootstrapMatrixVerificationMock(...args),
|
||||
getMatrixVerificationStatus: (...args: unknown[]) => getMatrixVerificationStatusMock(...args),
|
||||
verifyMatrixRecoveryKey: (...args: unknown[]) => verifyMatrixRecoveryKeyMock(...args),
|
||||
}));
|
||||
|
||||
let registerMatrixJsCli: typeof import("./cli.js").registerMatrixJsCli;
|
||||
|
||||
function buildProgram(): Command {
|
||||
const program = new Command();
|
||||
registerMatrixJsCli({ program });
|
||||
return program;
|
||||
}
|
||||
|
||||
describe("matrix-js CLI verification commands", () => {
|
||||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
vi.clearAllMocks();
|
||||
process.exitCode = undefined;
|
||||
({ registerMatrixJsCli } = await import("./cli.js"));
|
||||
vi.spyOn(console, "log").mockImplementation(() => {});
|
||||
vi.spyOn(console, "error").mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
process.exitCode = undefined;
|
||||
});
|
||||
|
||||
it("sets non-zero exit code for device verification failures in JSON mode", async () => {
|
||||
verifyMatrixRecoveryKeyMock.mockResolvedValue({
|
||||
success: false,
|
||||
error: "invalid key",
|
||||
});
|
||||
const program = buildProgram();
|
||||
|
||||
await program.parseAsync(["matrix-js", "verify", "device", "bad-key", "--json"], {
|
||||
from: "user",
|
||||
});
|
||||
|
||||
expect(process.exitCode).toBe(1);
|
||||
});
|
||||
|
||||
it("sets non-zero exit code for bootstrap failures in JSON mode", async () => {
|
||||
bootstrapMatrixVerificationMock.mockResolvedValue({
|
||||
success: false,
|
||||
error: "bootstrap failed",
|
||||
verification: {},
|
||||
crossSigning: {},
|
||||
pendingVerifications: 0,
|
||||
cryptoBootstrap: null,
|
||||
});
|
||||
const program = buildProgram();
|
||||
|
||||
await program.parseAsync(["matrix-js", "verify", "bootstrap", "--json"], { from: "user" });
|
||||
|
||||
expect(process.exitCode).toBe(1);
|
||||
});
|
||||
|
||||
it("keeps zero exit code for successful bootstrap in JSON mode", async () => {
|
||||
process.exitCode = 0;
|
||||
bootstrapMatrixVerificationMock.mockResolvedValue({
|
||||
success: true,
|
||||
verification: {},
|
||||
crossSigning: {},
|
||||
pendingVerifications: 0,
|
||||
cryptoBootstrap: {},
|
||||
});
|
||||
const program = buildProgram();
|
||||
|
||||
await program.parseAsync(["matrix-js", "verify", "bootstrap", "--json"], { from: "user" });
|
||||
|
||||
expect(process.exitCode).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -5,6 +5,23 @@ import {
|
||||
verifyMatrixRecoveryKey,
|
||||
} from "./matrix/actions/verification.js";
|
||||
|
||||
let matrixJsCliExitScheduled = false;
|
||||
|
||||
function scheduleMatrixJsCliExit(): void {
|
||||
if (matrixJsCliExitScheduled || process.env.VITEST) {
|
||||
return;
|
||||
}
|
||||
matrixJsCliExitScheduled = true;
|
||||
// matrix-js-sdk rust crypto can leave background async work alive after command completion.
|
||||
setTimeout(() => {
|
||||
process.exit(process.exitCode ?? 0);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function markCliFailure(): void {
|
||||
process.exitCode = 1;
|
||||
}
|
||||
|
||||
function printVerificationStatus(status: {
|
||||
verified: boolean;
|
||||
userId: string | null;
|
||||
@@ -25,7 +42,7 @@ function printVerificationStatus(status: {
|
||||
console.log("Verified: no");
|
||||
console.log(`User: ${status.userId ?? "unknown"}`);
|
||||
console.log(`Device: ${status.deviceId ?? "unknown"}`);
|
||||
console.log("Run 'openclaw matrix-js verify recovery-key <key>' to verify this device.");
|
||||
console.log("Run 'openclaw matrix-js verify device <key>' to verify this device.");
|
||||
}
|
||||
console.log(`Recovery key stored: ${status.recoveryKeyStored ? "yes" : "no"}`);
|
||||
if (status.recoveryKeyCreatedAt) {
|
||||
@@ -66,7 +83,9 @@ export function registerMatrixJsCli(params: { program: Command }): void {
|
||||
} else {
|
||||
console.error(`Error: ${message}`);
|
||||
}
|
||||
process.exitCode = 1;
|
||||
markCliFailure();
|
||||
} finally {
|
||||
scheduleMatrixJsCliExit();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -92,6 +111,9 @@ export function registerMatrixJsCli(params: { program: Command }): void {
|
||||
});
|
||||
if (options.json) {
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
if (!result.success) {
|
||||
markCliFailure();
|
||||
}
|
||||
return;
|
||||
}
|
||||
console.log(`Bootstrap success: ${result.success ? "yes" : "no"}`);
|
||||
@@ -106,7 +128,7 @@ export function registerMatrixJsCli(params: { program: Command }): void {
|
||||
);
|
||||
console.log(`Pending verifications: ${result.pendingVerifications}`);
|
||||
if (!result.success) {
|
||||
process.exitCode = 1;
|
||||
markCliFailure();
|
||||
}
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
@@ -115,13 +137,15 @@ export function registerMatrixJsCli(params: { program: Command }): void {
|
||||
} else {
|
||||
console.error(`Verification bootstrap failed: ${message}`);
|
||||
}
|
||||
process.exitCode = 1;
|
||||
markCliFailure();
|
||||
} finally {
|
||||
scheduleMatrixJsCliExit();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
verify
|
||||
.command("recovery-key <key>")
|
||||
.command("device <key>")
|
||||
.description("Verify device using a Matrix recovery key")
|
||||
.option("--account <id>", "Account ID (for multi-account setups)")
|
||||
.option("--json", "Output as JSON")
|
||||
@@ -130,6 +154,9 @@ export function registerMatrixJsCli(params: { program: Command }): void {
|
||||
const result = await verifyMatrixRecoveryKey(key, { accountId: options.account });
|
||||
if (options.json) {
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
if (!result.success) {
|
||||
markCliFailure();
|
||||
}
|
||||
} else if (result.success) {
|
||||
console.log("Device verification completed successfully.");
|
||||
console.log(`User: ${result.userId ?? "unknown"}`);
|
||||
@@ -139,7 +166,7 @@ export function registerMatrixJsCli(params: { program: Command }): void {
|
||||
}
|
||||
} else {
|
||||
console.error(`Verification failed: ${result.error ?? "unknown error"}`);
|
||||
process.exitCode = 1;
|
||||
markCliFailure();
|
||||
}
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
@@ -148,7 +175,9 @@ export function registerMatrixJsCli(params: { program: Command }): void {
|
||||
} else {
|
||||
console.error(`Verification failed: ${message}`);
|
||||
}
|
||||
process.exitCode = 1;
|
||||
markCliFailure();
|
||||
} finally {
|
||||
scheduleMatrixJsCliExit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
87
extensions/matrix-js/src/matrix/actions/client.test.ts
Normal file
87
extensions/matrix-js/src/matrix/actions/client.test.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { MatrixClient } from "../sdk.js";
|
||||
|
||||
const loadConfigMock = vi.fn(() => ({}));
|
||||
const getActiveMatrixClientMock = vi.fn();
|
||||
const createMatrixClientMock = vi.fn();
|
||||
const isBunRuntimeMock = vi.fn(() => false);
|
||||
const resolveMatrixAuthMock = vi.fn();
|
||||
|
||||
vi.mock("../../runtime.js", () => ({
|
||||
getMatrixRuntime: () => ({
|
||||
config: {
|
||||
loadConfig: (...args: unknown[]) => loadConfigMock(...args),
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("../active-client.js", () => ({
|
||||
getActiveMatrixClient: (...args: unknown[]) => getActiveMatrixClientMock(...args),
|
||||
}));
|
||||
|
||||
vi.mock("../client.js", () => ({
|
||||
createMatrixClient: (...args: unknown[]) => createMatrixClientMock(...args),
|
||||
isBunRuntime: () => isBunRuntimeMock(),
|
||||
resolveMatrixAuth: (...args: unknown[]) => resolveMatrixAuthMock(...args),
|
||||
}));
|
||||
|
||||
let resolveActionClient: typeof import("./client.js").resolveActionClient;
|
||||
|
||||
function createMockMatrixClient(): MatrixClient {
|
||||
return {
|
||||
prepareForOneOff: vi.fn(async () => undefined),
|
||||
} as unknown as MatrixClient;
|
||||
}
|
||||
|
||||
describe("resolveActionClient", () => {
|
||||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
vi.clearAllMocks();
|
||||
getActiveMatrixClientMock.mockReturnValue(null);
|
||||
isBunRuntimeMock.mockReturnValue(false);
|
||||
resolveMatrixAuthMock.mockResolvedValue({
|
||||
homeserver: "https://matrix.example.org",
|
||||
userId: "@bot:example.org",
|
||||
accessToken: "token",
|
||||
password: undefined,
|
||||
deviceId: "DEVICE123",
|
||||
encryption: false,
|
||||
});
|
||||
createMatrixClientMock.mockResolvedValue(createMockMatrixClient());
|
||||
|
||||
({ resolveActionClient } = await import("./client.js"));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
it("creates a one-off client even when OPENCLAW_GATEWAY_PORT is set", async () => {
|
||||
vi.stubEnv("OPENCLAW_GATEWAY_PORT", "18799");
|
||||
|
||||
const result = await resolveActionClient({ accountId: "default" });
|
||||
|
||||
expect(getActiveMatrixClientMock).toHaveBeenCalledWith("default");
|
||||
expect(resolveMatrixAuthMock).toHaveBeenCalledTimes(1);
|
||||
expect(createMatrixClientMock).toHaveBeenCalledTimes(1);
|
||||
expect(createMatrixClientMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
autoBootstrapCrypto: false,
|
||||
}),
|
||||
);
|
||||
const oneOffClient = await createMatrixClientMock.mock.results[0]?.value;
|
||||
expect(oneOffClient.prepareForOneOff).toHaveBeenCalledTimes(1);
|
||||
expect(result.stopOnDone).toBe(true);
|
||||
});
|
||||
|
||||
it("reuses active monitor client when available", async () => {
|
||||
const activeClient = createMockMatrixClient();
|
||||
getActiveMatrixClientMock.mockReturnValue(activeClient);
|
||||
|
||||
const result = await resolveActionClient({ accountId: "default" });
|
||||
|
||||
expect(result).toEqual({ client: activeClient, stopOnDone: false });
|
||||
expect(resolveMatrixAuthMock).not.toHaveBeenCalled();
|
||||
expect(createMatrixClientMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,11 +1,6 @@
|
||||
import { getMatrixRuntime } from "../../runtime.js";
|
||||
import { getActiveMatrixClient } from "../active-client.js";
|
||||
import {
|
||||
createMatrixClient,
|
||||
isBunRuntime,
|
||||
resolveMatrixAuth,
|
||||
resolveSharedMatrixClient,
|
||||
} from "../client.js";
|
||||
import { createMatrixClient, isBunRuntime, resolveMatrixAuth } from "../client.js";
|
||||
import type { CoreConfig } from "../types.js";
|
||||
import type { MatrixActionClient, MatrixActionClientOpts } from "./types.js";
|
||||
|
||||
@@ -26,15 +21,6 @@ export async function resolveActionClient(
|
||||
if (active) {
|
||||
return { client: active, stopOnDone: false };
|
||||
}
|
||||
const shouldShareClient = Boolean(process.env.OPENCLAW_GATEWAY_PORT);
|
||||
if (shouldShareClient) {
|
||||
const client = await resolveSharedMatrixClient({
|
||||
cfg: getMatrixRuntime().config.loadConfig() as CoreConfig,
|
||||
timeoutMs: opts.timeoutMs,
|
||||
accountId: opts.accountId,
|
||||
});
|
||||
return { client, stopOnDone: false };
|
||||
}
|
||||
const auth = await resolveMatrixAuth({
|
||||
cfg: getMatrixRuntime().config.loadConfig() as CoreConfig,
|
||||
accountId: opts.accountId,
|
||||
@@ -48,15 +34,8 @@ export async function resolveActionClient(
|
||||
encryption: auth.encryption,
|
||||
localTimeoutMs: opts.timeoutMs,
|
||||
accountId: opts.accountId,
|
||||
autoBootstrapCrypto: false,
|
||||
});
|
||||
if (auth.encryption && client.crypto) {
|
||||
try {
|
||||
const joinedRooms = await client.getJoinedRooms();
|
||||
await client.crypto.prepare(joinedRooms);
|
||||
} catch {
|
||||
// Ignore crypto prep failures for one-off actions.
|
||||
}
|
||||
}
|
||||
await client.start();
|
||||
await client.prepareForOneOff();
|
||||
return { client, stopOnDone: true };
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ export async function createMatrixClient(params: {
|
||||
localTimeoutMs?: number;
|
||||
initialSyncLimit?: number;
|
||||
accountId?: string | null;
|
||||
autoBootstrapCrypto?: boolean;
|
||||
}): Promise<MatrixClient> {
|
||||
ensureMatrixSdkLoggingConfigured();
|
||||
const env = process.env;
|
||||
@@ -52,5 +53,6 @@ export async function createMatrixClient(params: {
|
||||
recoveryKeyPath: storagePaths.recoveryKeyPath,
|
||||
idbSnapshotPath: storagePaths.idbSnapshotPath,
|
||||
cryptoDatabasePrefix,
|
||||
autoBootstrapCrypto: params.autoBootstrapCrypto,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -337,7 +337,7 @@ export async function monitorMatrixProvider(opts: MonitorMatrixOpts = {}): Promi
|
||||
logger.info("matrix: device is verified and ready for encrypted rooms");
|
||||
} else {
|
||||
logger.info(
|
||||
"matrix: device not verified — run 'openclaw matrix-js verify recovery-key <key>' to enable E2EE",
|
||||
"matrix: device not verified — run 'openclaw matrix-js verify device <key>' to enable E2EE",
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
@@ -947,4 +947,46 @@ describe("MatrixClient crypto bootstrapping", () => {
|
||||
expect(result.crossSigning.published).toBe(true);
|
||||
expect(result.cryptoBootstrap).not.toBeNull();
|
||||
});
|
||||
|
||||
it("does not report bootstrap errors when final verification state is healthy", async () => {
|
||||
matrixJsClient.getUserId = vi.fn(() => "@bot:example.org");
|
||||
matrixJsClient.getDeviceId = vi.fn(() => "DEVICE123");
|
||||
matrixJsClient.getCrypto = vi.fn(() => ({
|
||||
on: vi.fn(),
|
||||
bootstrapCrossSigning: vi.fn(async () => {}),
|
||||
bootstrapSecretStorage: vi.fn(async () => {}),
|
||||
requestOwnUserVerification: vi.fn(async () => null),
|
||||
isCrossSigningReady: vi.fn(async () => true),
|
||||
userHasCrossSigningKeys: vi.fn(async () => true),
|
||||
getSecretStorageStatus: vi.fn(async () => ({
|
||||
ready: true,
|
||||
defaultKeyId: "SSSSKEY",
|
||||
secretStorageKeyValidityMap: { SSSSKEY: true },
|
||||
})),
|
||||
getDeviceVerificationStatus: vi.fn(async () => ({
|
||||
isVerified: () => true,
|
||||
localVerified: true,
|
||||
crossSigningVerified: true,
|
||||
signedByOwner: true,
|
||||
})),
|
||||
}));
|
||||
|
||||
const client = new MatrixClient("https://matrix.example.org", "token", undefined, undefined, {
|
||||
encryption: true,
|
||||
});
|
||||
vi.spyOn(client, "getOwnCrossSigningPublicationStatus").mockResolvedValue({
|
||||
userId: "@bot:example.org",
|
||||
masterKeyPublished: true,
|
||||
selfSigningKeyPublished: true,
|
||||
userSigningKeyPublished: true,
|
||||
published: true,
|
||||
});
|
||||
|
||||
const result = await client.bootstrapOwnDeviceVerification({
|
||||
recoveryKey: "not-a-valid-recovery-key",
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.error).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -109,6 +109,7 @@ export class MatrixClient {
|
||||
private readonly verificationManager = new MatrixVerificationManager();
|
||||
private readonly recoveryKeyStore: MatrixRecoveryKeyStore;
|
||||
private readonly cryptoBootstrapper: MatrixCryptoBootstrapper<MatrixRawEvent>;
|
||||
private readonly autoBootstrapCrypto: boolean;
|
||||
|
||||
readonly dms = {
|
||||
update: async (): Promise<void> => {
|
||||
@@ -134,6 +135,7 @@ export class MatrixClient {
|
||||
recoveryKeyPath?: string;
|
||||
idbSnapshotPath?: string;
|
||||
cryptoDatabasePrefix?: string;
|
||||
autoBootstrapCrypto?: boolean;
|
||||
} = {},
|
||||
) {
|
||||
this.httpClient = new MatrixAuthedHttpClient(homeserver, accessToken);
|
||||
@@ -144,6 +146,7 @@ export class MatrixClient {
|
||||
this.idbSnapshotPath = opts.idbSnapshotPath;
|
||||
this.cryptoDatabasePrefix = opts.cryptoDatabasePrefix;
|
||||
this.selfUserId = opts.userId?.trim() || null;
|
||||
this.autoBootstrapCrypto = opts.autoBootstrapCrypto !== false;
|
||||
this.recoveryKeyStore = new MatrixRecoveryKeyStore(opts.recoveryKeyPath);
|
||||
const cryptoCallbacks = this.encryptionEnabled
|
||||
? this.recoveryKeyStore.buildCryptoCallbacks()
|
||||
@@ -229,12 +232,30 @@ export class MatrixClient {
|
||||
await this.client.startClient({
|
||||
initialSyncLimit: this.initialSyncLimit,
|
||||
});
|
||||
await this.bootstrapCryptoIfNeeded();
|
||||
if (this.autoBootstrapCrypto) {
|
||||
await this.bootstrapCryptoIfNeeded();
|
||||
}
|
||||
this.started = true;
|
||||
this.emitOutstandingInviteEvents();
|
||||
await this.refreshDmCache().catch(noop);
|
||||
}
|
||||
|
||||
async prepareForOneOff(): Promise<void> {
|
||||
if (!this.encryptionEnabled) {
|
||||
return;
|
||||
}
|
||||
await this.initializeCryptoIfNeeded();
|
||||
if (!this.crypto) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const joinedRooms = await this.getJoinedRooms();
|
||||
await this.crypto.prepare(joinedRooms);
|
||||
} catch {
|
||||
// One-off commands should continue even if crypto room prep is incomplete.
|
||||
}
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
if (this.idbPersistTimer) {
|
||||
clearInterval(this.idbPersistTimer);
|
||||
@@ -709,11 +730,10 @@ export class MatrixClient {
|
||||
const verification = await this.getOwnDeviceVerificationStatus();
|
||||
const crossSigning = await this.getOwnCrossSigningPublicationStatus();
|
||||
const success = verification.verified && crossSigning.published;
|
||||
const error =
|
||||
bootstrapError ??
|
||||
(success
|
||||
? undefined
|
||||
: "Matrix verification bootstrap did not produce a verified device with published cross-signing keys");
|
||||
const error = success
|
||||
? undefined
|
||||
: (bootstrapError ??
|
||||
"Matrix verification bootstrap did not produce a verified device with published cross-signing keys");
|
||||
return {
|
||||
success,
|
||||
error,
|
||||
|
||||
78
extensions/matrix-js/src/matrix/send/client.test.ts
Normal file
78
extensions/matrix-js/src/matrix/send/client.test.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { MatrixClient } from "../sdk.js";
|
||||
|
||||
const getActiveMatrixClientMock = vi.fn();
|
||||
const createMatrixClientMock = vi.fn();
|
||||
const isBunRuntimeMock = vi.fn(() => false);
|
||||
const resolveMatrixAuthMock = vi.fn();
|
||||
|
||||
vi.mock("../active-client.js", () => ({
|
||||
getActiveMatrixClient: (...args: unknown[]) => getActiveMatrixClientMock(...args),
|
||||
}));
|
||||
|
||||
vi.mock("../client.js", () => ({
|
||||
createMatrixClient: (...args: unknown[]) => createMatrixClientMock(...args),
|
||||
isBunRuntime: () => isBunRuntimeMock(),
|
||||
resolveMatrixAuth: (...args: unknown[]) => resolveMatrixAuthMock(...args),
|
||||
}));
|
||||
|
||||
let resolveMatrixClient: typeof import("./client.js").resolveMatrixClient;
|
||||
|
||||
function createMockMatrixClient(): MatrixClient {
|
||||
return {
|
||||
prepareForOneOff: vi.fn(async () => undefined),
|
||||
} as unknown as MatrixClient;
|
||||
}
|
||||
|
||||
describe("resolveMatrixClient", () => {
|
||||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
vi.clearAllMocks();
|
||||
getActiveMatrixClientMock.mockReturnValue(null);
|
||||
isBunRuntimeMock.mockReturnValue(false);
|
||||
resolveMatrixAuthMock.mockResolvedValue({
|
||||
homeserver: "https://matrix.example.org",
|
||||
userId: "@bot:example.org",
|
||||
accessToken: "token",
|
||||
password: undefined,
|
||||
deviceId: "DEVICE123",
|
||||
encryption: false,
|
||||
});
|
||||
createMatrixClientMock.mockResolvedValue(createMockMatrixClient());
|
||||
|
||||
({ resolveMatrixClient } = await import("./client.js"));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
it("creates a one-off client even when OPENCLAW_GATEWAY_PORT is set", async () => {
|
||||
vi.stubEnv("OPENCLAW_GATEWAY_PORT", "18799");
|
||||
|
||||
const result = await resolveMatrixClient({ accountId: "default" });
|
||||
|
||||
expect(getActiveMatrixClientMock).toHaveBeenCalledWith("default");
|
||||
expect(resolveMatrixAuthMock).toHaveBeenCalledTimes(1);
|
||||
expect(createMatrixClientMock).toHaveBeenCalledTimes(1);
|
||||
expect(createMatrixClientMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
autoBootstrapCrypto: false,
|
||||
}),
|
||||
);
|
||||
const oneOffClient = await createMatrixClientMock.mock.results[0]?.value;
|
||||
expect(oneOffClient.prepareForOneOff).toHaveBeenCalledTimes(1);
|
||||
expect(result.stopOnDone).toBe(true);
|
||||
});
|
||||
|
||||
it("reuses active monitor client when available", async () => {
|
||||
const activeClient = createMockMatrixClient();
|
||||
getActiveMatrixClientMock.mockReturnValue(activeClient);
|
||||
|
||||
const result = await resolveMatrixClient({ accountId: "default" });
|
||||
|
||||
expect(result).toEqual({ client: activeClient, stopOnDone: false });
|
||||
expect(resolveMatrixAuthMock).not.toHaveBeenCalled();
|
||||
expect(createMatrixClientMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,11 +1,6 @@
|
||||
import { getMatrixRuntime } from "../../runtime.js";
|
||||
import { getActiveMatrixClient } from "../active-client.js";
|
||||
import {
|
||||
createMatrixClient,
|
||||
isBunRuntime,
|
||||
resolveMatrixAuth,
|
||||
resolveSharedMatrixClient,
|
||||
} from "../client.js";
|
||||
import { createMatrixClient, isBunRuntime, resolveMatrixAuth } from "../client.js";
|
||||
import type { MatrixClient } from "../sdk.js";
|
||||
import type { CoreConfig } from "../types.js";
|
||||
|
||||
@@ -38,14 +33,6 @@ export async function resolveMatrixClient(opts: {
|
||||
if (active) {
|
||||
return { client: active, stopOnDone: false };
|
||||
}
|
||||
const shouldShareClient = Boolean(process.env.OPENCLAW_GATEWAY_PORT);
|
||||
if (shouldShareClient) {
|
||||
const client = await resolveSharedMatrixClient({
|
||||
timeoutMs: opts.timeoutMs,
|
||||
accountId: opts.accountId,
|
||||
});
|
||||
return { client, stopOnDone: false };
|
||||
}
|
||||
const auth = await resolveMatrixAuth({ accountId: opts.accountId });
|
||||
const client = await createMatrixClient({
|
||||
homeserver: auth.homeserver,
|
||||
@@ -56,15 +43,8 @@ export async function resolveMatrixClient(opts: {
|
||||
encryption: auth.encryption,
|
||||
localTimeoutMs: opts.timeoutMs,
|
||||
accountId: opts.accountId,
|
||||
autoBootstrapCrypto: false,
|
||||
});
|
||||
if (auth.encryption && client.crypto) {
|
||||
try {
|
||||
const joinedRooms = await client.getJoinedRooms();
|
||||
await client.crypto.prepare(joinedRooms);
|
||||
} catch {
|
||||
// Ignore crypto prep failures for one-off sends; normal sync will retry.
|
||||
}
|
||||
}
|
||||
await client.start();
|
||||
await client.prepareForOneOff();
|
||||
return { client, stopOnDone: true };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user