diff --git a/extensions/matrix/src/cli.test.ts b/extensions/matrix/src/cli.test.ts index bd6d142bc49..da5ae86cc33 100644 --- a/extensions/matrix/src/cli.test.ts +++ b/extensions/matrix/src/cli.test.ts @@ -388,6 +388,53 @@ describe("matrix CLI verification commands", () => { ); }); + it("prints DM lookup details in Matrix verification follow-up commands", async () => { + requestMatrixVerificationMock.mockResolvedValue( + mockMatrixVerificationSummary({ + id: "dm-verify-1", + transactionId: "txn-dm", + roomId: "!room-'$(x):example.org", + otherUserId: "@alice:example.org", + isSelfVerification: false, + hasSas: false, + sas: undefined, + }), + ); + const program = buildProgram(); + + await program.parseAsync( + [ + "matrix", + "verify", + "request", + "--user-id", + "@alice:example.org", + "--room-id", + "!room-'$(x):example.org", + ], + { from: "user" }, + ); + + expect(requestMatrixVerificationMock).toHaveBeenCalledWith({ + accountId: "default", + cfg: {}, + ownUser: undefined, + userId: "@alice:example.org", + deviceId: undefined, + roomId: "!room-'$(x):example.org", + }); + expect(consoleLogMock).toHaveBeenCalledWith("Room id: !room-'$(x):example.org"); + expect(consoleLogMock).toHaveBeenCalledWith( + "- Then run openclaw matrix verify start txn-dm --user-id @alice:example.org --room-id '!room-'\\''$(x):example.org' to start SAS verification.", + ); + expect(consoleLogMock).toHaveBeenCalledWith( + "- Run openclaw matrix verify sas txn-dm --user-id @alice:example.org --room-id '!room-'\\''$(x):example.org' to display the SAS emoji or decimals.", + ); + expect(consoleLogMock).toHaveBeenCalledWith( + "- When the SAS matches, run openclaw matrix verify confirm-sas txn-dm --user-id @alice:example.org --room-id '!room-'\\''$(x):example.org'.", + ); + }); + it("rejects ambiguous Matrix verification request targets", async () => { const program = buildProgram(); @@ -529,6 +576,45 @@ describe("matrix CLI verification commands", () => { ); }); + it("passes DM lookup details through Matrix verification follow-up commands", async () => { + startMatrixVerificationMock.mockResolvedValue( + mockMatrixVerificationSummary({ + id: "dm-verify-1", + transactionId: "txn-dm", + roomId: "!dm:example.org", + otherUserId: "@alice:example.org", + }), + ); + const program = buildProgram(); + + await program.parseAsync( + [ + "matrix", + "verify", + "start", + "txn-dm", + "--user-id", + "@alice:example.org", + "--room-id", + "!dm:example.org", + "--account", + "ops", + ], + { from: "user" }, + ); + + expect(startMatrixVerificationMock).toHaveBeenCalledWith("txn-dm", { + accountId: "ops", + cfg: {}, + method: "sas", + verificationDmUserId: "@alice:example.org", + verificationDmRoomId: "!dm:example.org", + }); + expect(consoleLogMock).toHaveBeenCalledWith( + "- If they match, run openclaw matrix verify confirm-sas txn-dm --user-id @alice:example.org --room-id '!dm:example.org' --account ops.", + ); + }); + it("prints stable transaction ids in follow-up commands after accepting verification", async () => { acceptMatrixVerificationMock.mockResolvedValue( mockMatrixVerificationSummary({ diff --git a/extensions/matrix/src/cli.ts b/extensions/matrix/src/cli.ts index 374ee70df16..28123f1d75c 100644 --- a/extensions/matrix/src/cli.ts +++ b/extensions/matrix/src/cli.ts @@ -544,6 +544,8 @@ type MatrixCliVerificationStatus = { type MatrixCliVerificationCommandOptions = { account?: string; + userId?: string; + roomId?: string; verbose?: boolean; json?: boolean; }; @@ -557,6 +559,7 @@ type MatrixCliSelfVerificationCommandOptions = { type MatrixCliVerificationSummary = { id: string; transactionId?: string; + roomId?: string; otherUserId: string; otherDeviceId?: string; isSelfVerification: boolean; @@ -789,6 +792,9 @@ function printMatrixVerificationSummary(summary: MatrixCliVerificationSummary): if (summary.transactionId) { console.log(`Transaction id: ${sanitizeMatrixCliText(summary.transactionId)}`); } + if (summary.roomId) { + console.log(`Room id: ${sanitizeMatrixCliText(summary.roomId)}`); + } console.log(`Other user: ${sanitizeMatrixCliText(summary.otherUserId)}`); console.log(`Other device: ${sanitizeMatrixCliText(summary.otherDeviceId ?? "unknown")}`); console.log(`Self-verification: ${summary.isSelfVerification ? "yes" : "no"}`); @@ -837,11 +843,87 @@ function printMatrixVerificationSas(sas: MatrixCliVerificationSas): void { } } -function printMatrixVerificationSasGuidance(requestId: string, accountId?: string): void { +function matrixCliVerificationDmLookupOptions(options: MatrixCliVerificationCommandOptions): { + verificationDmRoomId?: string; + verificationDmUserId?: string; +} { + const lookup: { + verificationDmRoomId?: string; + verificationDmUserId?: string; + } = {}; + if (options.roomId !== undefined) { + lookup.verificationDmRoomId = options.roomId; + } + if (options.userId !== undefined) { + lookup.verificationDmUserId = options.userId; + } + return lookup; +} + +function formatMatrixVerificationDmFollowupParts(params: { + roomId?: string; + userId?: string; +}): string[] { + if (!params.roomId || !params.userId) { + return []; + } + return [ + "--user-id", + sanitizeMatrixCliText(params.userId), + "--room-id", + sanitizeMatrixCliText(params.roomId), + ]; +} + +function formatMatrixVerificationSummaryDmFollowupParts( + summary: MatrixCliVerificationSummary, +): string[] { + return formatMatrixVerificationDmFollowupParts({ + roomId: summary.roomId, + userId: summary.otherUserId, + }); +} + +function formatMatrixVerificationOptionsDmFollowupParts( + options: MatrixCliVerificationCommandOptions, +): string[] { + return formatMatrixVerificationDmFollowupParts({ + roomId: options.roomId, + userId: options.userId, + }); +} + +function formatMatrixVerificationPreferredDmFollowupParts( + summary: MatrixCliVerificationSummary, + options: MatrixCliVerificationCommandOptions, +): string[] { + const summaryParts = formatMatrixVerificationSummaryDmFollowupParts(summary); + return summaryParts.length + ? summaryParts + : formatMatrixVerificationOptionsDmFollowupParts(options); +} + +function formatMatrixVerificationFollowupCommand(params: { + action: string; + requestId: string; + accountId?: string; + dmParts?: string[]; +}): string { + return formatMatrixCliCommandParts( + ["verify", params.action, params.requestId, ...(params.dmParts ?? [])], + params.accountId, + ); +} + +function printMatrixVerificationSasGuidance( + requestId: string, + accountId?: string, + dmParts: string[] = [], +): void { printGuidance([ `Compare the emoji or decimals with the other Matrix client.`, - `If they match, run ${formatMatrixCliCommandParts(["verify", "confirm-sas", requestId], accountId)}.`, - `If they do not match, run ${formatMatrixCliCommandParts(["verify", "mismatch-sas", requestId], accountId)}.`, + `If they match, run ${formatMatrixVerificationFollowupCommand({ action: "confirm-sas", requestId, accountId, dmParts })}.`, + `If they do not match, run ${formatMatrixVerificationFollowupCommand({ action: "mismatch-sas", requestId, accountId, dmParts })}.`, ]); } @@ -863,12 +945,17 @@ async function promptMatrixVerificationSasMatch(): Promise { } } -function printMatrixVerificationRequestGuidance(requestId: string, accountId?: string): void { +function printMatrixVerificationRequestGuidance( + summary: MatrixCliVerificationSummary, + accountId?: string, +): void { + const requestId = formatMatrixVerificationCommandId(summary); + const dmParts = formatMatrixVerificationSummaryDmFollowupParts(summary); printGuidance([ `Accept the verification request in another Matrix client for this account.`, - `Then run ${formatMatrixCliCommandParts(["verify", "start", requestId], accountId)} to start SAS verification.`, - `Run ${formatMatrixCliCommandParts(["verify", "sas", requestId], accountId)} to display the SAS emoji or decimals.`, - `When the SAS matches, run ${formatMatrixCliCommandParts(["verify", "confirm-sas", requestId], accountId)}.`, + `Then run ${formatMatrixVerificationFollowupCommand({ action: "start", requestId, accountId, dmParts })} to start SAS verification.`, + `Run ${formatMatrixVerificationFollowupCommand({ action: "sas", requestId, accountId, dmParts })} to display the SAS emoji or decimals.`, + `When the SAS matches, run ${formatMatrixVerificationFollowupCommand({ action: "confirm-sas", requestId, accountId, dmParts })}.`, ]); } @@ -1357,10 +1444,7 @@ export function registerMatrixCli(params: { program: Command }): void { onText: (summary) => { printAccountLabel(accountId); printMatrixVerificationSummary(summary); - printMatrixVerificationRequestGuidance( - formatMatrixVerificationCommandId(summary), - accountId, - ); + printMatrixVerificationRequestGuidance(summary, accountId); }, errorPrefix: "Verification request failed", }); @@ -1371,15 +1455,24 @@ export function registerMatrixCli(params: { program: Command }): void { .command("accept ") .description("Accept an inbound Matrix verification request") .option("--account ", "Account ID (for multi-account setups)") + .option("--user-id ", "Matrix user ID for DM verification follow-up") + .option("--room-id ", "Matrix direct-message room ID for verification follow-up") .option("--verbose", "Show detailed diagnostics") .option("--json", "Output as JSON") .action(async (id: string, options: MatrixCliVerificationCommandOptions) => { await runMatrixCliVerificationSummaryCommand({ options, - run: async (accountId, cfg) => await acceptMatrixVerification(id, { accountId, cfg }), + run: async (accountId, cfg) => + await acceptMatrixVerification(id, { + accountId, + cfg, + ...matrixCliVerificationDmLookupOptions(options), + }), afterText: (summary, accountId) => { + const requestId = formatMatrixVerificationCommandId(summary); + const dmParts = formatMatrixVerificationPreferredDmFollowupParts(summary, options); printGuidance([ - `Run ${formatMatrixCliCommandParts(["verify", "start", formatMatrixVerificationCommandId(summary)], accountId)} to start SAS verification.`, + `Run ${formatMatrixVerificationFollowupCommand({ action: "start", requestId, accountId, dmParts })} to start SAS verification.`, ]); }, errorPrefix: "Verification accept failed", @@ -1390,15 +1483,26 @@ export function registerMatrixCli(params: { program: Command }): void { .command("start ") .description("Start SAS verification for a Matrix verification request") .option("--account ", "Account ID (for multi-account setups)") + .option("--user-id ", "Matrix user ID for DM verification follow-up") + .option("--room-id ", "Matrix direct-message room ID for verification follow-up") .option("--verbose", "Show detailed diagnostics") .option("--json", "Output as JSON") .action(async (id: string, options: MatrixCliVerificationCommandOptions) => { await runMatrixCliVerificationSummaryCommand({ options, run: async (accountId, cfg) => - await startMatrixVerification(id, { accountId, cfg, method: "sas" }), + await startMatrixVerification(id, { + accountId, + cfg, + method: "sas", + ...matrixCliVerificationDmLookupOptions(options), + }), afterText: (summary, accountId) => - printMatrixVerificationSasGuidance(formatMatrixVerificationCommandId(summary), accountId), + printMatrixVerificationSasGuidance( + formatMatrixVerificationCommandId(summary), + accountId, + formatMatrixVerificationPreferredDmFollowupParts(summary, options), + ), errorPrefix: "Verification start failed", }); }); @@ -1407,6 +1511,8 @@ export function registerMatrixCli(params: { program: Command }): void { .command("sas ") .description("Show SAS emoji or decimals for a Matrix verification request") .option("--account ", "Account ID (for multi-account setups)") + .option("--user-id ", "Matrix user ID for DM verification follow-up") + .option("--room-id ", "Matrix direct-message room ID for verification follow-up") .option("--verbose", "Show detailed diagnostics") .option("--json", "Output as JSON") .action(async (id: string, options: MatrixCliVerificationCommandOptions) => { @@ -1414,12 +1520,22 @@ export function registerMatrixCli(params: { program: Command }): void { await runMatrixCliCommand({ verbose: options.verbose === true, json: options.json === true, - run: async () => await getMatrixVerificationSas(id, { accountId, cfg }), + run: async () => + await getMatrixVerificationSas(id, { + accountId, + cfg, + ...matrixCliVerificationDmLookupOptions(options), + }), onText: (sas) => { + const requestId = formatMatrixCliText(id); printAccountLabel(accountId); - console.log(`Verification id: ${formatMatrixCliText(id)}`); + console.log(`Verification id: ${requestId}`); printMatrixVerificationSas(sas); - printMatrixVerificationSasGuidance(id, accountId); + printMatrixVerificationSasGuidance( + requestId, + accountId, + formatMatrixVerificationOptionsDmFollowupParts(options), + ); }, errorPrefix: "Verification SAS lookup failed", }); @@ -1429,12 +1545,19 @@ export function registerMatrixCli(params: { program: Command }): void { .command("confirm-sas ") .description("Confirm matching SAS emoji or decimals for a Matrix verification request") .option("--account ", "Account ID (for multi-account setups)") + .option("--user-id ", "Matrix user ID for DM verification follow-up") + .option("--room-id ", "Matrix direct-message room ID for verification follow-up") .option("--verbose", "Show detailed diagnostics") .option("--json", "Output as JSON") .action(async (id: string, options: MatrixCliVerificationCommandOptions) => { await runMatrixCliVerificationSummaryCommand({ options, - run: async (accountId, cfg) => await confirmMatrixVerificationSas(id, { accountId, cfg }), + run: async (accountId, cfg) => + await confirmMatrixVerificationSas(id, { + accountId, + cfg, + ...matrixCliVerificationDmLookupOptions(options), + }), errorPrefix: "Verification SAS confirm failed", }); }); @@ -1443,12 +1566,19 @@ export function registerMatrixCli(params: { program: Command }): void { .command("mismatch-sas ") .description("Reject a Matrix SAS verification when the emoji or decimals do not match") .option("--account ", "Account ID (for multi-account setups)") + .option("--user-id ", "Matrix user ID for DM verification follow-up") + .option("--room-id ", "Matrix direct-message room ID for verification follow-up") .option("--verbose", "Show detailed diagnostics") .option("--json", "Output as JSON") .action(async (id: string, options: MatrixCliVerificationCommandOptions) => { await runMatrixCliVerificationSummaryCommand({ options, - run: async (accountId, cfg) => await mismatchMatrixVerificationSas(id, { accountId, cfg }), + run: async (accountId, cfg) => + await mismatchMatrixVerificationSas(id, { + accountId, + cfg, + ...matrixCliVerificationDmLookupOptions(options), + }), errorPrefix: "Verification SAS mismatch failed", }); }); @@ -1457,6 +1587,8 @@ export function registerMatrixCli(params: { program: Command }): void { .command("cancel ") .description("Cancel a Matrix verification request") .option("--account ", "Account ID (for multi-account setups)") + .option("--user-id ", "Matrix user ID for DM verification follow-up") + .option("--room-id ", "Matrix direct-message room ID for verification follow-up") .option("--reason ", "Cancellation reason") .option("--code ", "Matrix cancellation code") .option("--verbose", "Show detailed diagnostics") @@ -1477,6 +1609,7 @@ export function registerMatrixCli(params: { program: Command }): void { cfg, reason: options.reason, code: options.code, + ...matrixCliVerificationDmLookupOptions(options), }), errorPrefix: "Verification cancel failed", }); diff --git a/extensions/matrix/src/matrix/actions/verification.test.ts b/extensions/matrix/src/matrix/actions/verification.test.ts index c3096b1b180..93fb0286655 100644 --- a/extensions/matrix/src/matrix/actions/verification.test.ts +++ b/extensions/matrix/src/matrix/actions/verification.test.ts @@ -36,6 +36,7 @@ let getMatrixEncryptionStatus: typeof import("./verification.js").getMatrixEncry let getMatrixRoomKeyBackupStatus: typeof import("./verification.js").getMatrixRoomKeyBackupStatus; let getMatrixVerificationStatus: typeof import("./verification.js").getMatrixVerificationStatus; let runMatrixSelfVerification: typeof import("./verification.js").runMatrixSelfVerification; +let startMatrixVerification: typeof import("./verification.js").startMatrixVerification; describe("matrix verification actions", () => { beforeAll(async () => { @@ -45,6 +46,7 @@ describe("matrix verification actions", () => { getMatrixVerificationStatus, listMatrixVerifications, runMatrixSelfVerification, + startMatrixVerification, } = await import("./verification.js")); }); @@ -250,6 +252,63 @@ describe("matrix verification actions", () => { expect(withStartedActionClientMock).not.toHaveBeenCalled(); }); + it("rehydrates DM verification requests before follow-up actions", async () => { + const tracked = { + completed: false, + hasSas: false, + id: "verification-1", + phaseName: "requested", + transactionId: "txn-dm", + }; + const started = { + ...tracked, + chosenMethod: "m.sas.v1", + phaseName: "started", + }; + const crypto = { + ensureVerificationDmTracked: vi.fn(async () => tracked), + startVerification: vi.fn(async () => started), + }; + withStartedActionClientMock.mockImplementation(async (_opts, run) => { + return await run({ crypto }); + }); + + await expect( + startMatrixVerification("txn-dm", { + verificationDmRoomId: "!dm:example.org", + verificationDmUserId: "@alice:example.org", + }), + ).resolves.toMatchObject({ + id: "verification-1", + phaseName: "started", + }); + + expect(crypto.ensureVerificationDmTracked).toHaveBeenCalledWith({ + roomId: "!dm:example.org", + userId: "@alice:example.org", + }); + expect(crypto.startVerification).toHaveBeenCalledWith("txn-dm", "sas"); + }); + + it("requires complete DM lookup details for verification follow-up actions", async () => { + const crypto = { + ensureVerificationDmTracked: vi.fn(), + startVerification: vi.fn(), + }; + withStartedActionClientMock.mockImplementation(async (_opts, run) => { + return await run({ crypto }); + }); + + await expect( + startMatrixVerification("txn-dm", { + verificationDmRoomId: "!dm:example.org", + }), + ).rejects.toThrow("--user-id and --room-id must be provided together"); + + expect(crypto.ensureVerificationDmTracked).not.toHaveBeenCalled(); + expect(crypto.startVerification).not.toHaveBeenCalled(); + }); + it("keeps self-verification in one started Matrix client session", async () => { const requested = { completed: false, @@ -420,6 +479,47 @@ describe("matrix verification actions", () => { expect(crypto.startVerification).not.toHaveBeenCalled(); }); + it("fails immediately when an already-started self-verification uses a non-SAS method", async () => { + const requested = { + completed: false, + hasSas: false, + id: "verification-1", + phaseName: "requested", + transactionId: "tx-self", + }; + const started = { + ...requested, + chosenMethod: "m.reciprocate.v1", + phaseName: "started", + }; + const cancelled = { + ...started, + phaseName: "cancelled", + }; + const crypto = { + cancelVerification: vi.fn(async () => cancelled), + listVerifications: vi.fn(async () => [started]), + requestVerification: vi.fn(async () => requested), + startVerification: vi.fn(), + }; + withStartedActionClientMock.mockImplementation(async (_opts, run) => { + return await run({ crypto }); + }); + + await expect( + runMatrixSelfVerification({ confirmSas: vi.fn(async () => true), timeoutMs: 500 }), + ).rejects.toThrow( + "Matrix self-verification started without SAS while waiting to show SAS emoji or decimals (method: m.reciprocate.v1)", + ); + + expect(crypto.listVerifications).toHaveBeenCalledTimes(1); + expect(crypto.startVerification).not.toHaveBeenCalled(); + expect(crypto.cancelVerification).toHaveBeenCalledWith("verification-1", { + code: "m.user", + reason: "OpenClaw self-verification did not complete", + }); + }); + it("finalizes completed non-SAS self-verification without waiting for SAS", async () => { const completed = { completed: true, diff --git a/extensions/matrix/src/matrix/actions/verification.ts b/extensions/matrix/src/matrix/actions/verification.ts index 3c8b1143e12..392614314cc 100644 --- a/extensions/matrix/src/matrix/actions/verification.ts +++ b/extensions/matrix/src/matrix/actions/verification.ts @@ -12,6 +12,10 @@ const DEFAULT_MATRIX_SELF_VERIFICATION_TIMEOUT_MS = 180_000; type MatrixCryptoActionFacade = NonNullable; type MatrixActionClient = import("../sdk.js").MatrixClient; +type MatrixVerificationDmLookupOpts = { + verificationDmRoomId?: string; + verificationDmUserId?: string; +}; export type MatrixSelfVerificationResult = MatrixVerificationSummary & { deviceOwnerVerified: boolean; @@ -42,6 +46,26 @@ function resolveVerificationId(input: string): string { return normalized; } +async function ensureMatrixVerificationDmTracked( + crypto: MatrixCryptoActionFacade, + opts: MatrixVerificationDmLookupOpts, +): Promise { + const roomId = normalizeOptionalString(opts.verificationDmRoomId); + const userId = normalizeOptionalString(opts.verificationDmUserId); + if (Boolean(roomId) !== Boolean(userId)) { + throw new Error("--user-id and --room-id must be provided together for Matrix DM verification"); + } + if (!roomId || !userId) { + return; + } + const tracked = await crypto.ensureVerificationDmTracked({ roomId, userId }); + if (!tracked) { + throw new Error( + `Matrix DM verification request not found for room ${roomId} and user ${userId}`, + ); + } +} + function isSameMatrixVerification( left: MatrixVerificationSummary, right: MatrixVerificationSummary, @@ -69,12 +93,38 @@ function isMatrixVerificationCancelled(summary: MatrixVerificationSummary): bool return summary.phaseName === "cancelled"; } +function isMatrixSasMethod(method: string | null | undefined): boolean { + return method === "m.sas.v1" || method === "sas"; +} + +function getMatrixVerificationSasWaitFailure( + summary: MatrixVerificationSummary, + label: string, +): string | null { + if (summary.hasSas || summary.phaseName === "cancelled") { + return null; + } + const method = summary.chosenMethod ? ` (method: ${summary.chosenMethod})` : ""; + if (summary.completed) { + return `Matrix self-verification completed without SAS while waiting to ${label}${method}`; + } + if ( + summary.phaseName === "started" && + summary.chosenMethod && + !isMatrixSasMethod(summary.chosenMethod) + ) { + return `Matrix self-verification started without SAS while waiting to ${label}${method}`; + } + return null; +} + async function waitForMatrixVerificationSummary(params: { crypto: MatrixCryptoActionFacade; label: string; request: MatrixVerificationSummary; timeoutMs: number; predicate: (summary: MatrixVerificationSummary) => boolean; + reject?: (summary: MatrixVerificationSummary) => string | null; }): Promise { const startedAt = Date.now(); let last: MatrixVerificationSummary | undefined; @@ -93,6 +143,10 @@ async function waitForMatrixVerificationSummary(params: { }`, ); } + const rejection = params.reject?.(found); + if (rejection) { + throw new Error(rejection); + } } await sleep(Math.min(250, Math.max(25, params.timeoutMs - (Date.now() - startedAt)))); } @@ -246,12 +300,21 @@ export async function runMatrixSelfVerification( : ready; let sasSummary = started; if (!sasSummary.hasSas) { + const sasFailure = getMatrixVerificationSasWaitFailure( + sasSummary, + "show SAS emoji or decimals", + ); + if (sasFailure) { + throw new Error(sasFailure); + } sasSummary = await waitForMatrixVerificationSummary({ crypto, label: "show SAS emoji or decimals", request: started, timeoutMs, predicate: (summary) => summary.hasSas, + reject: (summary) => + getMatrixVerificationSasWaitFailure(summary, "show SAS emoji or decimals"), }); } if (!sasSummary.sas) { @@ -289,20 +352,23 @@ export async function runMatrixSelfVerification( export async function acceptMatrixVerification( requestId: string, - opts: MatrixActionClientOpts = {}, + opts: MatrixActionClientOpts & MatrixVerificationDmLookupOpts = {}, ) { return await withStartedActionClient(opts, async (client) => { const crypto = requireCrypto(client, opts); + await ensureMatrixVerificationDmTracked(crypto, opts); return await crypto.acceptVerification(resolveVerificationId(requestId)); }); } export async function cancelMatrixVerification( requestId: string, - opts: MatrixActionClientOpts & { reason?: string; code?: string } = {}, + opts: MatrixActionClientOpts & + MatrixVerificationDmLookupOpts & { reason?: string; code?: string } = {}, ) { return await withStartedActionClient(opts, async (client) => { const crypto = requireCrypto(client, opts); + await ensureMatrixVerificationDmTracked(crypto, opts); return await crypto.cancelVerification(resolveVerificationId(requestId), { reason: normalizeOptionalString(opts.reason), code: normalizeOptionalString(opts.code), @@ -312,20 +378,22 @@ export async function cancelMatrixVerification( export async function startMatrixVerification( requestId: string, - opts: MatrixActionClientOpts & { method?: "sas" } = {}, + opts: MatrixActionClientOpts & MatrixVerificationDmLookupOpts & { method?: "sas" } = {}, ) { return await withStartedActionClient(opts, async (client) => { const crypto = requireCrypto(client, opts); + await ensureMatrixVerificationDmTracked(crypto, opts); return await crypto.startVerification(resolveVerificationId(requestId), opts.method ?? "sas"); }); } export async function generateMatrixVerificationQr( requestId: string, - opts: MatrixActionClientOpts = {}, + opts: MatrixActionClientOpts & MatrixVerificationDmLookupOpts = {}, ) { return await withStartedActionClient(opts, async (client) => { const crypto = requireCrypto(client, opts); + await ensureMatrixVerificationDmTracked(crypto, opts); return await crypto.generateVerificationQr(resolveVerificationId(requestId)); }); } @@ -333,10 +401,11 @@ export async function generateMatrixVerificationQr( export async function scanMatrixVerificationQr( requestId: string, qrDataBase64: string, - opts: MatrixActionClientOpts = {}, + opts: MatrixActionClientOpts & MatrixVerificationDmLookupOpts = {}, ) { return await withStartedActionClient(opts, async (client) => { const crypto = requireCrypto(client, opts); + await ensureMatrixVerificationDmTracked(crypto, opts); const payload = qrDataBase64.trim(); if (!payload) { throw new Error("Matrix QR data is required"); @@ -347,40 +416,44 @@ export async function scanMatrixVerificationQr( export async function getMatrixVerificationSas( requestId: string, - opts: MatrixActionClientOpts = {}, + opts: MatrixActionClientOpts & MatrixVerificationDmLookupOpts = {}, ) { return await withStartedActionClient(opts, async (client) => { const crypto = requireCrypto(client, opts); + await ensureMatrixVerificationDmTracked(crypto, opts); return await crypto.getVerificationSas(resolveVerificationId(requestId)); }); } export async function confirmMatrixVerificationSas( requestId: string, - opts: MatrixActionClientOpts = {}, + opts: MatrixActionClientOpts & MatrixVerificationDmLookupOpts = {}, ) { return await withStartedActionClient(opts, async (client) => { const crypto = requireCrypto(client, opts); + await ensureMatrixVerificationDmTracked(crypto, opts); return await crypto.confirmVerificationSas(resolveVerificationId(requestId)); }); } export async function mismatchMatrixVerificationSas( requestId: string, - opts: MatrixActionClientOpts = {}, + opts: MatrixActionClientOpts & MatrixVerificationDmLookupOpts = {}, ) { return await withStartedActionClient(opts, async (client) => { const crypto = requireCrypto(client, opts); + await ensureMatrixVerificationDmTracked(crypto, opts); return await crypto.mismatchVerificationSas(resolveVerificationId(requestId)); }); } export async function confirmMatrixVerificationReciprocateQr( requestId: string, - opts: MatrixActionClientOpts = {}, + opts: MatrixActionClientOpts & MatrixVerificationDmLookupOpts = {}, ) { return await withStartedActionClient(opts, async (client) => { const crypto = requireCrypto(client, opts); + await ensureMatrixVerificationDmTracked(crypto, opts); return await crypto.confirmVerificationReciprocateQr(resolveVerificationId(requestId)); }); }