mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
Matrix: extract startup maintenance flow
This commit is contained in:
@@ -19,18 +19,14 @@ import {
|
||||
resolveSharedMatrixClient,
|
||||
stopSharedClientForAccount,
|
||||
} from "../client.js";
|
||||
import { updateMatrixAccountConfig } from "../config-update.js";
|
||||
import { summarizeMatrixDeviceHealth } from "../device-health.js";
|
||||
import { syncMatrixOwnProfile } from "../profile.js";
|
||||
import { createMatrixThreadBindingManager } from "../thread-bindings.js";
|
||||
import { registerMatrixAutoJoin } from "./auto-join.js";
|
||||
import { resolveMatrixMonitorConfig } from "./config.js";
|
||||
import { createDirectRoomTracker } from "./direct.js";
|
||||
import { registerMatrixMonitorEvents } from "./events.js";
|
||||
import { createMatrixRoomMessageHandler } from "./handler.js";
|
||||
import { maybeRestoreLegacyMatrixBackup } from "./legacy-crypto-restore.js";
|
||||
import { createMatrixRoomInfoResolver } from "./room-info.js";
|
||||
import { ensureMatrixStartupVerification } from "./startup-verification.js";
|
||||
import { runMatrixStartupMaintenance } from "./startup.js";
|
||||
|
||||
export type MonitorMatrixOpts = {
|
||||
runtime?: RuntimeEnv;
|
||||
@@ -235,127 +231,19 @@ export async function monitorMatrixProvider(opts: MonitorMatrixOpts = {}): Promi
|
||||
// Shared client is already started via resolveSharedMatrixClient.
|
||||
logger.info(`matrix: logged in as ${auth.userId}`);
|
||||
|
||||
try {
|
||||
const profileSync = await syncMatrixOwnProfile({
|
||||
client,
|
||||
userId: auth.userId,
|
||||
displayName: accountConfig.name,
|
||||
avatarUrl: accountConfig.avatarUrl,
|
||||
loadAvatarFromUrl: async (url, maxBytes) => await core.media.loadWebMedia(url, maxBytes),
|
||||
});
|
||||
if (profileSync.displayNameUpdated) {
|
||||
logger.info(`matrix: profile display name updated for ${auth.userId}`);
|
||||
}
|
||||
if (profileSync.avatarUpdated) {
|
||||
logger.info(`matrix: profile avatar updated for ${auth.userId}`);
|
||||
}
|
||||
if (
|
||||
profileSync.convertedAvatarFromHttp &&
|
||||
profileSync.resolvedAvatarUrl &&
|
||||
accountConfig.avatarUrl !== profileSync.resolvedAvatarUrl
|
||||
) {
|
||||
const latestCfg = core.config.loadConfig() as CoreConfig;
|
||||
const updatedCfg = updateMatrixAccountConfig(latestCfg, account.accountId, {
|
||||
avatarUrl: profileSync.resolvedAvatarUrl,
|
||||
});
|
||||
await core.config.writeConfigFile(updatedCfg as never);
|
||||
logVerboseMessage(
|
||||
`matrix: persisted converted avatar URL for account ${account.accountId} (${profileSync.resolvedAvatarUrl})`,
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.warn("matrix: failed to sync profile from config", { error: String(err) });
|
||||
}
|
||||
|
||||
// If E2EE is enabled, report device verification status and request self-verification
|
||||
// when configured and the device is still unverified.
|
||||
if (auth.encryption && client.crypto) {
|
||||
try {
|
||||
const deviceHealth = summarizeMatrixDeviceHealth(await client.listOwnDevices());
|
||||
if (deviceHealth.staleOpenClawDevices.length > 0) {
|
||||
logger.warn(
|
||||
`matrix: stale OpenClaw devices detected for ${auth.userId}: ${deviceHealth.staleOpenClawDevices.map((device) => device.deviceId).join(", ")}. Run 'openclaw matrix devices prune-stale --account ${effectiveAccountId}' to keep encrypted-room trust healthy.`,
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.debug?.("Failed to inspect matrix device hygiene (non-fatal)", {
|
||||
error: String(err),
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const startupVerification = await ensureMatrixStartupVerification({
|
||||
client,
|
||||
auth,
|
||||
accountConfig,
|
||||
env: process.env,
|
||||
});
|
||||
if (startupVerification.kind === "verified") {
|
||||
logger.info("matrix: device is verified by its owner and ready for encrypted rooms");
|
||||
} else if (
|
||||
startupVerification.kind === "disabled" ||
|
||||
startupVerification.kind === "cooldown" ||
|
||||
startupVerification.kind === "pending" ||
|
||||
startupVerification.kind === "request-failed"
|
||||
) {
|
||||
logger.info(
|
||||
"matrix: device not verified — run 'openclaw matrix verify device <key>' to enable E2EE",
|
||||
);
|
||||
if (startupVerification.kind === "pending") {
|
||||
logger.info(
|
||||
"matrix: startup verification request is already pending; finish it in another Matrix client",
|
||||
);
|
||||
} else if (startupVerification.kind === "cooldown") {
|
||||
logVerboseMessage(
|
||||
`matrix: skipped startup verification request due to cooldown (retryAfterMs=${startupVerification.retryAfterMs ?? 0})`,
|
||||
);
|
||||
} else if (startupVerification.kind === "request-failed") {
|
||||
logger.debug?.("Matrix startup verification request failed (non-fatal)", {
|
||||
error: startupVerification.error ?? "unknown",
|
||||
});
|
||||
}
|
||||
} else if (startupVerification.kind === "requested") {
|
||||
logger.info(
|
||||
"matrix: device not verified — requested verification in another Matrix client",
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.debug?.("Failed to resolve matrix verification status (non-fatal)", {
|
||||
error: String(err),
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const legacyCryptoRestore = await maybeRestoreLegacyMatrixBackup({
|
||||
client,
|
||||
auth,
|
||||
env: process.env,
|
||||
});
|
||||
if (legacyCryptoRestore.kind === "restored") {
|
||||
logger.info(
|
||||
`matrix: restored ${legacyCryptoRestore.imported}/${legacyCryptoRestore.total} room key(s) from legacy encrypted-state backup`,
|
||||
);
|
||||
if (legacyCryptoRestore.localOnlyKeys > 0) {
|
||||
logger.warn(
|
||||
`matrix: ${legacyCryptoRestore.localOnlyKeys} legacy local-only room key(s) were never backed up and could not be restored automatically`,
|
||||
);
|
||||
}
|
||||
} else if (legacyCryptoRestore.kind === "failed") {
|
||||
logger.warn(
|
||||
`matrix: failed restoring room keys from legacy encrypted-state backup: ${legacyCryptoRestore.error}`,
|
||||
);
|
||||
if (legacyCryptoRestore.localOnlyKeys > 0) {
|
||||
logger.warn(
|
||||
`matrix: ${legacyCryptoRestore.localOnlyKeys} legacy local-only room key(s) were never backed up and may remain unavailable until manually recovered`,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.warn("matrix: failed restoring legacy encrypted-state backup", {
|
||||
error: String(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
await runMatrixStartupMaintenance({
|
||||
client,
|
||||
auth,
|
||||
accountId: account.accountId,
|
||||
effectiveAccountId,
|
||||
accountConfig,
|
||||
logger,
|
||||
logVerboseMessage,
|
||||
loadConfig: () => core.config.loadConfig() as CoreConfig,
|
||||
writeConfigFile: async (nextCfg) => await core.config.writeConfigFile(nextCfg),
|
||||
loadWebMedia: async (url, maxBytes) => await core.media.loadWebMedia(url, maxBytes),
|
||||
env: process.env,
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
const onAbort = () => {
|
||||
|
||||
188
extensions/matrix/src/matrix/monitor/startup.test.ts
Normal file
188
extensions/matrix/src/matrix/monitor/startup.test.ts
Normal file
@@ -0,0 +1,188 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { runMatrixStartupMaintenance } from "./startup.js";
|
||||
|
||||
const hoisted = vi.hoisted(() => ({
|
||||
maybeRestoreLegacyMatrixBackup: vi.fn(async () => ({ kind: "skipped" as const })),
|
||||
summarizeMatrixDeviceHealth: vi.fn(() => ({
|
||||
staleOpenClawDevices: [] as Array<{ deviceId: string }>,
|
||||
})),
|
||||
syncMatrixOwnProfile: vi.fn(async () => ({
|
||||
skipped: false,
|
||||
displayNameUpdated: false,
|
||||
avatarUpdated: false,
|
||||
resolvedAvatarUrl: null,
|
||||
uploadedAvatarSource: null,
|
||||
convertedAvatarFromHttp: false,
|
||||
})),
|
||||
ensureMatrixStartupVerification: vi.fn(async () => ({ kind: "verified" as const })),
|
||||
updateMatrixAccountConfig: vi.fn((cfg: unknown) => cfg),
|
||||
}));
|
||||
|
||||
vi.mock("../config-update.js", () => ({
|
||||
updateMatrixAccountConfig: (...args: unknown[]) => hoisted.updateMatrixAccountConfig(...args),
|
||||
}));
|
||||
|
||||
vi.mock("../device-health.js", () => ({
|
||||
summarizeMatrixDeviceHealth: (...args: unknown[]) => hoisted.summarizeMatrixDeviceHealth(...args),
|
||||
}));
|
||||
|
||||
vi.mock("../profile.js", () => ({
|
||||
syncMatrixOwnProfile: (...args: unknown[]) => hoisted.syncMatrixOwnProfile(...args),
|
||||
}));
|
||||
|
||||
vi.mock("./legacy-crypto-restore.js", () => ({
|
||||
maybeRestoreLegacyMatrixBackup: (...args: unknown[]) =>
|
||||
hoisted.maybeRestoreLegacyMatrixBackup(...args),
|
||||
}));
|
||||
|
||||
vi.mock("./startup-verification.js", () => ({
|
||||
ensureMatrixStartupVerification: (...args: unknown[]) =>
|
||||
hoisted.ensureMatrixStartupVerification(...args),
|
||||
}));
|
||||
|
||||
describe("runMatrixStartupMaintenance", () => {
|
||||
beforeEach(() => {
|
||||
hoisted.maybeRestoreLegacyMatrixBackup.mockClear().mockResolvedValue({ kind: "skipped" });
|
||||
hoisted.summarizeMatrixDeviceHealth.mockClear().mockReturnValue({ staleOpenClawDevices: [] });
|
||||
hoisted.syncMatrixOwnProfile.mockClear().mockResolvedValue({
|
||||
skipped: false,
|
||||
displayNameUpdated: false,
|
||||
avatarUpdated: false,
|
||||
resolvedAvatarUrl: null,
|
||||
uploadedAvatarSource: null,
|
||||
convertedAvatarFromHttp: false,
|
||||
});
|
||||
hoisted.ensureMatrixStartupVerification.mockClear().mockResolvedValue({ kind: "verified" });
|
||||
hoisted.updateMatrixAccountConfig.mockClear().mockImplementation((cfg: unknown) => cfg);
|
||||
});
|
||||
|
||||
function createParams() {
|
||||
return {
|
||||
client: {
|
||||
crypto: {},
|
||||
listOwnDevices: vi.fn(async () => []),
|
||||
} as never,
|
||||
auth: {
|
||||
accountId: "ops",
|
||||
homeserver: "https://matrix.example.org",
|
||||
userId: "@bot:example.org",
|
||||
accessToken: "token",
|
||||
encryption: false,
|
||||
},
|
||||
accountId: "ops",
|
||||
effectiveAccountId: "ops",
|
||||
accountConfig: {
|
||||
name: "Ops Bot",
|
||||
avatarUrl: "https://example.org/avatar.png",
|
||||
},
|
||||
logger: {
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
error: vi.fn(),
|
||||
},
|
||||
logVerboseMessage: vi.fn(),
|
||||
loadConfig: vi.fn(() => ({ channels: { matrix: {} } })),
|
||||
writeConfigFile: vi.fn(async () => {}),
|
||||
loadWebMedia: vi.fn(async () => ({
|
||||
buffer: Buffer.from("avatar"),
|
||||
contentType: "image/png",
|
||||
fileName: "avatar.png",
|
||||
})),
|
||||
env: {},
|
||||
} as const;
|
||||
}
|
||||
|
||||
it("persists converted avatar URLs after profile sync", async () => {
|
||||
const params = createParams();
|
||||
const updatedCfg = { channels: { matrix: { avatarUrl: "mxc://avatar" } } };
|
||||
hoisted.syncMatrixOwnProfile.mockResolvedValue({
|
||||
skipped: false,
|
||||
displayNameUpdated: false,
|
||||
avatarUpdated: true,
|
||||
resolvedAvatarUrl: "mxc://avatar",
|
||||
uploadedAvatarSource: "http",
|
||||
convertedAvatarFromHttp: true,
|
||||
});
|
||||
hoisted.updateMatrixAccountConfig.mockReturnValue(updatedCfg);
|
||||
|
||||
await runMatrixStartupMaintenance(params);
|
||||
|
||||
expect(hoisted.syncMatrixOwnProfile).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
userId: "@bot:example.org",
|
||||
displayName: "Ops Bot",
|
||||
avatarUrl: "https://example.org/avatar.png",
|
||||
}),
|
||||
);
|
||||
expect(hoisted.updateMatrixAccountConfig).toHaveBeenCalledWith(
|
||||
{ channels: { matrix: {} } },
|
||||
"ops",
|
||||
{ avatarUrl: "mxc://avatar" },
|
||||
);
|
||||
expect(params.writeConfigFile).toHaveBeenCalledWith(updatedCfg as never);
|
||||
expect(params.logVerboseMessage).toHaveBeenCalledWith(
|
||||
"matrix: persisted converted avatar URL for account ops (mxc://avatar)",
|
||||
);
|
||||
});
|
||||
|
||||
it("reports stale devices, pending verification, and restored legacy backups", async () => {
|
||||
const params = createParams();
|
||||
params.auth.encryption = true;
|
||||
hoisted.summarizeMatrixDeviceHealth.mockReturnValue({
|
||||
staleOpenClawDevices: [{ deviceId: "DEV123" }],
|
||||
});
|
||||
hoisted.ensureMatrixStartupVerification.mockResolvedValue({ kind: "pending" });
|
||||
hoisted.maybeRestoreLegacyMatrixBackup.mockResolvedValue({
|
||||
kind: "restored",
|
||||
imported: 2,
|
||||
total: 3,
|
||||
localOnlyKeys: 1,
|
||||
});
|
||||
|
||||
await runMatrixStartupMaintenance(params);
|
||||
|
||||
expect(params.logger.warn).toHaveBeenCalledWith(
|
||||
"matrix: stale OpenClaw devices detected for @bot:example.org: DEV123. Run 'openclaw matrix devices prune-stale --account ops' to keep encrypted-room trust healthy.",
|
||||
);
|
||||
expect(params.logger.info).toHaveBeenCalledWith(
|
||||
"matrix: device not verified — run 'openclaw matrix verify device <key>' to enable E2EE",
|
||||
);
|
||||
expect(params.logger.info).toHaveBeenCalledWith(
|
||||
"matrix: startup verification request is already pending; finish it in another Matrix client",
|
||||
);
|
||||
expect(params.logger.info).toHaveBeenCalledWith(
|
||||
"matrix: restored 2/3 room key(s) from legacy encrypted-state backup",
|
||||
);
|
||||
expect(params.logger.warn).toHaveBeenCalledWith(
|
||||
"matrix: 1 legacy local-only room key(s) were never backed up and could not be restored automatically",
|
||||
);
|
||||
});
|
||||
|
||||
it("logs cooldown and request-failure verification outcomes without throwing", async () => {
|
||||
const params = createParams();
|
||||
params.auth.encryption = true;
|
||||
hoisted.ensureMatrixStartupVerification.mockResolvedValueOnce({
|
||||
kind: "cooldown",
|
||||
retryAfterMs: 321,
|
||||
});
|
||||
|
||||
await runMatrixStartupMaintenance(params);
|
||||
|
||||
expect(params.logVerboseMessage).toHaveBeenCalledWith(
|
||||
"matrix: skipped startup verification request due to cooldown (retryAfterMs=321)",
|
||||
);
|
||||
|
||||
hoisted.ensureMatrixStartupVerification.mockResolvedValueOnce({
|
||||
kind: "request-failed",
|
||||
error: "boom",
|
||||
});
|
||||
|
||||
await runMatrixStartupMaintenance(params);
|
||||
|
||||
expect(params.logger.debug).toHaveBeenCalledWith(
|
||||
"Matrix startup verification request failed (non-fatal)",
|
||||
{ error: "boom" },
|
||||
);
|
||||
});
|
||||
});
|
||||
159
extensions/matrix/src/matrix/monitor/startup.ts
Normal file
159
extensions/matrix/src/matrix/monitor/startup.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
import type { RuntimeLogger } from "openclaw/plugin-sdk/matrix";
|
||||
import type { CoreConfig, MatrixConfig } from "../../types.js";
|
||||
import type { MatrixAuth } from "../client.js";
|
||||
import { updateMatrixAccountConfig } from "../config-update.js";
|
||||
import { summarizeMatrixDeviceHealth } from "../device-health.js";
|
||||
import { syncMatrixOwnProfile } from "../profile.js";
|
||||
import type { MatrixClient } from "../sdk.js";
|
||||
import { maybeRestoreLegacyMatrixBackup } from "./legacy-crypto-restore.js";
|
||||
import { ensureMatrixStartupVerification } from "./startup-verification.js";
|
||||
|
||||
type MatrixStartupClient = Pick<
|
||||
MatrixClient,
|
||||
| "crypto"
|
||||
| "getUserProfile"
|
||||
| "listOwnDevices"
|
||||
| "restoreRoomKeyBackup"
|
||||
| "setAvatarUrl"
|
||||
| "setDisplayName"
|
||||
| "uploadContent"
|
||||
>;
|
||||
|
||||
export async function runMatrixStartupMaintenance(params: {
|
||||
client: MatrixStartupClient;
|
||||
auth: MatrixAuth;
|
||||
accountId: string;
|
||||
effectiveAccountId: string;
|
||||
accountConfig: MatrixConfig;
|
||||
logger: RuntimeLogger;
|
||||
logVerboseMessage: (message: string) => void;
|
||||
loadConfig: () => CoreConfig;
|
||||
writeConfigFile: (cfg: never) => Promise<void>;
|
||||
loadWebMedia: (
|
||||
url: string,
|
||||
maxBytes: number,
|
||||
) => Promise<{ buffer: Buffer; contentType?: string; fileName?: string }>;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): Promise<void> {
|
||||
try {
|
||||
const profileSync = await syncMatrixOwnProfile({
|
||||
client: params.client,
|
||||
userId: params.auth.userId,
|
||||
displayName: params.accountConfig.name,
|
||||
avatarUrl: params.accountConfig.avatarUrl,
|
||||
loadAvatarFromUrl: async (url, maxBytes) => await params.loadWebMedia(url, maxBytes),
|
||||
});
|
||||
if (profileSync.displayNameUpdated) {
|
||||
params.logger.info(`matrix: profile display name updated for ${params.auth.userId}`);
|
||||
}
|
||||
if (profileSync.avatarUpdated) {
|
||||
params.logger.info(`matrix: profile avatar updated for ${params.auth.userId}`);
|
||||
}
|
||||
if (
|
||||
profileSync.convertedAvatarFromHttp &&
|
||||
profileSync.resolvedAvatarUrl &&
|
||||
params.accountConfig.avatarUrl !== profileSync.resolvedAvatarUrl
|
||||
) {
|
||||
const latestCfg = params.loadConfig();
|
||||
const updatedCfg = updateMatrixAccountConfig(latestCfg, params.accountId, {
|
||||
avatarUrl: profileSync.resolvedAvatarUrl,
|
||||
});
|
||||
await params.writeConfigFile(updatedCfg as never);
|
||||
params.logVerboseMessage(
|
||||
`matrix: persisted converted avatar URL for account ${params.accountId} (${profileSync.resolvedAvatarUrl})`,
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
params.logger.warn("matrix: failed to sync profile from config", { error: String(err) });
|
||||
}
|
||||
|
||||
if (!(params.auth.encryption && params.client.crypto)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const deviceHealth = summarizeMatrixDeviceHealth(await params.client.listOwnDevices());
|
||||
if (deviceHealth.staleOpenClawDevices.length > 0) {
|
||||
params.logger.warn(
|
||||
`matrix: stale OpenClaw devices detected for ${params.auth.userId}: ${deviceHealth.staleOpenClawDevices.map((device) => device.deviceId).join(", ")}. Run 'openclaw matrix devices prune-stale --account ${params.effectiveAccountId}' to keep encrypted-room trust healthy.`,
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
params.logger.debug?.("Failed to inspect matrix device hygiene (non-fatal)", {
|
||||
error: String(err),
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const startupVerification = await ensureMatrixStartupVerification({
|
||||
client: params.client,
|
||||
auth: params.auth,
|
||||
accountConfig: params.accountConfig,
|
||||
env: params.env,
|
||||
});
|
||||
if (startupVerification.kind === "verified") {
|
||||
params.logger.info("matrix: device is verified by its owner and ready for encrypted rooms");
|
||||
} else if (
|
||||
startupVerification.kind === "disabled" ||
|
||||
startupVerification.kind === "cooldown" ||
|
||||
startupVerification.kind === "pending" ||
|
||||
startupVerification.kind === "request-failed"
|
||||
) {
|
||||
params.logger.info(
|
||||
"matrix: device not verified — run 'openclaw matrix verify device <key>' to enable E2EE",
|
||||
);
|
||||
if (startupVerification.kind === "pending") {
|
||||
params.logger.info(
|
||||
"matrix: startup verification request is already pending; finish it in another Matrix client",
|
||||
);
|
||||
} else if (startupVerification.kind === "cooldown") {
|
||||
params.logVerboseMessage(
|
||||
`matrix: skipped startup verification request due to cooldown (retryAfterMs=${startupVerification.retryAfterMs ?? 0})`,
|
||||
);
|
||||
} else if (startupVerification.kind === "request-failed") {
|
||||
params.logger.debug?.("Matrix startup verification request failed (non-fatal)", {
|
||||
error: startupVerification.error ?? "unknown",
|
||||
});
|
||||
}
|
||||
} else if (startupVerification.kind === "requested") {
|
||||
params.logger.info(
|
||||
"matrix: device not verified — requested verification in another Matrix client",
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
params.logger.debug?.("Failed to resolve matrix verification status (non-fatal)", {
|
||||
error: String(err),
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const legacyCryptoRestore = await maybeRestoreLegacyMatrixBackup({
|
||||
client: params.client,
|
||||
auth: params.auth,
|
||||
env: params.env,
|
||||
});
|
||||
if (legacyCryptoRestore.kind === "restored") {
|
||||
params.logger.info(
|
||||
`matrix: restored ${legacyCryptoRestore.imported}/${legacyCryptoRestore.total} room key(s) from legacy encrypted-state backup`,
|
||||
);
|
||||
if (legacyCryptoRestore.localOnlyKeys > 0) {
|
||||
params.logger.warn(
|
||||
`matrix: ${legacyCryptoRestore.localOnlyKeys} legacy local-only room key(s) were never backed up and could not be restored automatically`,
|
||||
);
|
||||
}
|
||||
} else if (legacyCryptoRestore.kind === "failed") {
|
||||
params.logger.warn(
|
||||
`matrix: failed restoring room keys from legacy encrypted-state backup: ${legacyCryptoRestore.error}`,
|
||||
);
|
||||
if (legacyCryptoRestore.localOnlyKeys > 0) {
|
||||
params.logger.warn(
|
||||
`matrix: ${legacyCryptoRestore.localOnlyKeys} legacy local-only room key(s) were never backed up and may remain unavailable until manually recovered`,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
params.logger.warn("matrix: failed restoring legacy encrypted-state backup", {
|
||||
error: String(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user