diff --git a/extensions/matrix/src/matrix/client/logging.ts b/extensions/matrix/src/matrix/client/logging.ts index a260aab4619..386ca295eb6 100644 --- a/extensions/matrix/src/matrix/client/logging.ts +++ b/extensions/matrix/src/matrix/client/logging.ts @@ -1,12 +1,8 @@ -import { logger as matrixJsSdkRootLogger } from "matrix-js-sdk/lib/logger.js"; import { ConsoleLogger, LogService, setMatrixConsoleLogging } from "../sdk/logger.js"; let matrixSdkLoggingConfigured = false; let matrixSdkLogMode: "default" | "quiet" = "default"; const matrixSdkBaseLogger = new ConsoleLogger(); -const matrixSdkSilentMethodFactory = () => () => {}; -let matrixSdkRootMethodFactory: unknown; -let matrixSdkRootLoggerInitialized = false; type MatrixJsSdkLogger = { trace: (...messageOrObject: unknown[]) => void; @@ -52,22 +48,7 @@ export function createMatrixJsSdkClientLogger(prefix = "matrix"): MatrixJsSdkLog return createMatrixJsSdkLoggerInstance(prefix); } -function applyMatrixJsSdkRootLoggerMode(): void { - const rootLogger = matrixJsSdkRootLogger as { - methodFactory?: unknown; - rebuild?: () => void; - }; - if (!matrixSdkRootLoggerInitialized) { - matrixSdkRootMethodFactory = rootLogger.methodFactory; - matrixSdkRootLoggerInitialized = true; - } - rootLogger.methodFactory = - matrixSdkLogMode === "quiet" ? matrixSdkSilentMethodFactory : matrixSdkRootMethodFactory; - rootLogger.rebuild?.(); -} - function applyMatrixSdkLogger(): void { - applyMatrixJsSdkRootLoggerMode(); if (matrixSdkLogMode === "quiet") { LogService.setLogger({ trace: () => {}, diff --git a/extensions/matrix/src/matrix/sdk.test.ts b/extensions/matrix/src/matrix/sdk.test.ts index d54b2855131..90e8de38ab6 100644 --- a/extensions/matrix/src/matrix/sdk.test.ts +++ b/extensions/matrix/src/matrix/sdk.test.ts @@ -1404,6 +1404,26 @@ describe("MatrixClient crypto bootstrapping", () => { expect(logger?.getChild).toBeTypeOf("function"); }); + it("passes a custom sync filter to matrix-js-sdk startup", async () => { + const client = new MatrixClient("https://matrix.example.org", "token", { + userId: "@bot:example.org", + syncFilter: { room: { ephemeral: { not_types: ["m.receipt"] } } }, + }); + + await client.start(); + + const startOpts = matrixJsClient.startClient.mock.calls[0]?.[0] as + | { filter?: { getDefinition?: () => unknown } } + | undefined; + expect(startOpts?.filter?.getDefinition?.()).toEqual({ + room: { + ephemeral: { + not_types: ["m.receipt"], + }, + }, + }); + }); + it("schedules periodic crypto snapshot persistence with fake timers", async () => { vi.useFakeTimers(); const databasesSpy = vi.spyOn(indexedDB, "databases").mockResolvedValue([]); diff --git a/extensions/matrix/src/matrix/sdk.ts b/extensions/matrix/src/matrix/sdk.ts index 59d9b11a1c9..3259af650a5 100644 --- a/extensions/matrix/src/matrix/sdk.ts +++ b/extensions/matrix/src/matrix/sdk.ts @@ -1,9 +1,11 @@ import { EventEmitter } from "node:events"; import { ClientEvent, + Filter, MatrixEventEvent, Preset, createClient as createMatrixJsClient, + type IFilterDefinition, type MatrixClient as MatrixJsClient, type MatrixEvent, } from "matrix-js-sdk/lib/matrix.js"; @@ -216,6 +218,7 @@ export class MatrixClient { private readonly httpClient: MatrixAuthedHttpClient; private readonly localTimeoutMs: number; private readonly initialSyncLimit?: number; + private readonly syncFilter?: IFilterDefinition; private readonly encryptionEnabled: boolean; private readonly password?: string; private readonly syncStore?: FileBackedMatrixSyncStore; @@ -258,6 +261,7 @@ export class MatrixClient { localTimeoutMs?: number; encryption?: boolean; initialSyncLimit?: number; + syncFilter?: IFilterDefinition; storagePath?: string; recoveryKeyPath?: string; idbSnapshotPath?: string; @@ -275,6 +279,7 @@ export class MatrixClient { }); this.localTimeoutMs = Math.max(1, opts.localTimeoutMs ?? 60_000); this.initialSyncLimit = opts.initialSyncLimit; + this.syncFilter = opts.syncFilter; this.encryptionEnabled = opts.encryption === true; this.password = opts.password; this.syncStore = opts.storagePath ? new FileBackedMatrixSyncStore(opts.storagePath) : undefined; @@ -496,6 +501,7 @@ export class MatrixClient { await this.client.startClient({ initialSyncLimit: this.initialSyncLimit, + ...(this.syncFilter ? { filter: Filter.fromJson(this.selfUserId, "", this.syncFilter) } : {}), }); await this.waitForInitialSyncReady({ abortSignal: opts.abortSignal, @@ -1674,6 +1680,12 @@ export class MatrixClient { "MatrixClientLite", "No room key backup version found on server, creating one via secret storage bootstrap", ); + // matrix-js-sdk 41.3.0 can log a transient PerSessionKeyBackupDownloader + // "current backup version ... undefined" warning while setupNewKeyBackup creates + // the backup: resetKeyBackup emits key-backup cache events before its async + // checkKeyBackupAndEnable pass has populated active backup state. Keep the + // explicit server re-check below and do not hide the SDK logs; if this needs + // fixing in code, upstream a minimal Matrix SDK repro instead of patching here. await this.recoveryKeyStore.bootstrapSecretStorageWithRecoveryKey(crypto, { setupNewKeyBackup: true, }); diff --git a/extensions/matrix/src/matrix/sdk/crypto-bootstrap.test.ts b/extensions/matrix/src/matrix/sdk/crypto-bootstrap.test.ts index 99db1115302..accacfb7ecc 100644 --- a/extensions/matrix/src/matrix/sdk/crypto-bootstrap.test.ts +++ b/extensions/matrix/src/matrix/sdk/crypto-bootstrap.test.ts @@ -222,6 +222,26 @@ describe("MatrixCryptoBootstrapper", () => { ); }); + it("refreshes published cross-signing keys before importing private keys from secret storage", async () => { + const bootstrapCrossSigning = vi.fn(async () => {}); + const userHasCrossSigningKeys = vi.fn(async () => true); + const { bootstrapper, crypto } = createBootstrapperHarness({ + bootstrapCrossSigning, + getDeviceVerificationStatus: vi.fn(async () => createVerifiedDeviceStatus()), + isCrossSigningReady: vi.fn(async () => true), + userHasCrossSigningKeys, + }); + + await bootstrapper.bootstrap(crypto, { + allowAutomaticCrossSigningReset: false, + }); + + expect(userHasCrossSigningKeys).toHaveBeenCalledWith("@bot:example.org", true); + expect(userHasCrossSigningKeys.mock.invocationCallOrder[0]).toBeLessThan( + bootstrapCrossSigning.mock.invocationCallOrder[0], + ); + }); + it("passes explicit secret-storage repair allowance only when requested", async () => { const deps = createBootstrapperDeps(); const crypto = createCryptoApi({ diff --git a/extensions/matrix/src/matrix/sdk/crypto-bootstrap.ts b/extensions/matrix/src/matrix/sdk/crypto-bootstrap.ts index 3686800c626..cbcd942dabe 100644 --- a/extensions/matrix/src/matrix/sdk/crypto-bootstrap.ts +++ b/extensions/matrix/src/matrix/sdk/crypto-bootstrap.ts @@ -142,6 +142,16 @@ export class MatrixCryptoBootstrapper { return false; } }; + const refreshPublishedCrossSigningKeys = async (): Promise => { + if (typeof crypto.userHasCrossSigningKeys !== "function") { + return; + } + try { + await crypto.userHasCrossSigningKeys(userId, true); + } catch { + // The normal bootstrap flow below handles missing or unavailable keys. + } + }; const isCrossSigningReady = async (): Promise => { if (typeof crypto.isCrossSigningReady !== "function") { return true; @@ -212,6 +222,7 @@ export class MatrixCryptoBootstrapper { // First pass: preserve existing cross-signing identity and ensure public keys are uploaded. try { + await refreshPublishedCrossSigningKeys(); await crypto.bootstrapCrossSigning({ authUploadDeviceSigningKeys, });