@kodelyth/matrix 2026.5.39 → 2026.5.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +321 -0
- package/SPEC-SUPPORT.md +116 -0
- package/api.ts +38 -0
- package/auth-presence.ts +56 -0
- package/channel-plugin-api.ts +3 -0
- package/cli-metadata.ts +11 -0
- package/contract-api.ts +17 -0
- package/dist/account-selection-Y50DNJ2l.js +158 -0
- package/dist/active-client-CmFdvPdO.js +20 -0
- package/dist/api.js +12 -0
- package/dist/approval-handler.runtime-BIi4fL0R.js +377 -0
- package/dist/approval-ids-BGHK7PnZ.js +7 -0
- package/dist/approval-reaction-auth-CL0-nCNV.js +27 -0
- package/dist/approval-reactions-nDm2x-K5.js +162 -0
- package/dist/async-lock-SsmtFXtt.js +19 -0
- package/dist/auth-presence.js +26 -0
- package/dist/backup-health-3BHbHxyd.js +60 -0
- package/dist/channel-C0kCyTNB.js +1380 -0
- package/dist/channel-plugin-api.js +2 -0
- package/dist/channel.runtime-CdrdEN-0.js +250 -0
- package/dist/cli-FtY6Nuzw.js +1338 -0
- package/dist/cli-metadata-Dkwua7CB.js +22 -0
- package/dist/cli-metadata.js +2 -0
- package/dist/client-BnohYygh.js +25 -0
- package/dist/client-PhrTwuC4.js +30 -0
- package/dist/client-bootstrap-Mcj8ChJ5.js +114 -0
- package/dist/config-paths-DVvt6vM3.js +114 -0
- package/dist/config-schema-BMGOlhdI.js +308 -0
- package/dist/config-secret-input.runtime-Dv_4Br_f.js +2 -0
- package/dist/contract-api.js +8 -0
- package/dist/create-client-J0htTaRj.js +64 -0
- package/dist/credentials-B7GsBbgQ.js +56 -0
- package/dist/credentials-read-8fE4qoWs.js +112 -0
- package/dist/credentials-write.runtime-BibplB4Y.js +17 -0
- package/dist/crypto-node.runtime-D9qxgRPa.js +12 -0
- package/dist/crypto-runtime-1pKW4O2F.js +1214 -0
- package/dist/deps-DVpDS81G.js +208 -0
- package/dist/device-health-Ct2wDSPG.js +16 -0
- package/dist/directory-live-i3T8uORc.js +150 -0
- package/dist/doctor-contract-BLzYHl_9.js +246 -0
- package/dist/doctor-contract-api.js +2 -0
- package/dist/doctor-diR5gE7D.js +153 -0
- package/dist/draft-stream-HpPJ_VJt.js +143 -0
- package/dist/encryption-guidance-BNEgckrZ.js +15 -0
- package/dist/env-auth-UFiTGkDM.js +63 -0
- package/dist/env-vars-EQKQv-FE.js +63 -0
- package/dist/errors-BETj3zr9.js +17 -0
- package/dist/exec-approval-resolver-BxPorU_t.js +15 -0
- package/dist/helper-api.js +4 -0
- package/dist/http-client-DoQgbQsU.js +331 -0
- package/dist/index.js +46 -0
- package/dist/legacy-crypto-inspector-zK0hDCbt.js +41 -0
- package/dist/legacy-crypto-restore-DSFIXuDo.js +85 -0
- package/dist/logging-Df7aPD1z.js +99 -0
- package/dist/matrix-migration.runtime-BNoT1Prt.js +525 -0
- package/dist/media-text-ZhGA8Pcs.js +146 -0
- package/dist/messages-CRA9WGg0.js +140 -0
- package/dist/migration-snapshot-backup-BR-xD7Ew.js +69 -0
- package/dist/migration-snapshot.runtime-BLcy_Nvw.js +2 -0
- package/dist/monitor-DQm7_13y.js +4331 -0
- package/dist/plugin-entry.handlers.runtime.js +51 -0
- package/dist/probe.runtime-CjJS53Kz.js +3 -0
- package/dist/profile-update-DqkPgZ1P.js +68 -0
- package/dist/reaction-common-CmVLzP-u.js +71 -0
- package/dist/reaction-events-D0nUJuZV.js +121 -0
- package/dist/record-shared-DGvSFn5M.js +2 -0
- package/dist/resolve-targets-ChECUzD2.js +140 -0
- package/dist/resolver.runtime-hdY3n0GO.js +5 -0
- package/dist/rolldown-runtime-DUslC3ob.js +14 -0
- package/dist/route-xRKj_ESW.js +161 -0
- package/dist/runtime-B-Fyrmxo.js +8 -0
- package/dist/runtime-api-BYXXkxq2.js +24 -0
- package/dist/runtime-api.js +25 -0
- package/dist/runtime-heavy-api.js +3 -0
- package/dist/runtime-lwTSy9Yt.js +6 -0
- package/dist/runtime-setter-api.js +2 -0
- package/dist/sdk-Jhq7mLtD.js +1704 -0
- package/dist/secret-contract-DEMcDsjl.js +120 -0
- package/dist/secret-contract-api.js +2 -0
- package/dist/send-CJunc6QM.js +1517 -0
- package/dist/setup-bootstrap-rJ0qZWPe.js +62 -0
- package/dist/setup-core-BEYoXF3J.js +677 -0
- package/dist/setup-entry.js +19 -0
- package/dist/setup-plugin-api.js +43 -0
- package/dist/setup-surface-c28ON6jq.js +537 -0
- package/dist/shared-D6MFMnpG.js +642 -0
- package/dist/startup-abort-B2J3MU_h.js +109 -0
- package/dist/startup-verification-CkD4Cwce.js +132 -0
- package/dist/storage-nyO0DOFE.js +281 -0
- package/dist/storage-paths-BTAketfg.js +52 -0
- package/dist/subagent-hooks-api-Dr_xnMRG.js +170 -0
- package/dist/subagent-hooks-api.js +2 -0
- package/dist/sync-state-Bx0gPaGA.js +12 -0
- package/dist/target-ids-Bsazo8si.js +77 -0
- package/dist/test-api.js +4 -0
- package/dist/thread-binding-api-IGU0-L70.js +17 -0
- package/dist/thread-binding-api.js +2 -0
- package/dist/thread-bindings-FjAZmDUP.js +352 -0
- package/dist/thread-bindings-runtime.js +2 -0
- package/dist/thread-bindings-shared-fvfP7jVs.js +97 -0
- package/dist/timeout-abort-signal-DpSHDHhR.js +2 -0
- package/dist/tool-actions.runtime-Cbo7YcYZ.js +532 -0
- package/dist/url-validation-DlrXNjAE.js +36 -0
- package/dist/verification-7tDPRpJU.js +345 -0
- package/doctor-contract-api.ts +1 -0
- package/helper-api.ts +3 -0
- package/index.ts +55 -0
- package/klaw.plugin.json +3 -891
- package/package.json +4 -4
- package/plugin-entry.handlers.runtime.ts +1 -0
- package/runtime-api.ts +72 -0
- package/runtime-heavy-api.ts +1 -0
- package/runtime-setter-api.ts +3 -0
- package/secret-contract-api.ts +5 -0
- package/setup-entry.ts +17 -0
- package/setup-plugin-api.ts +3 -0
- package/src/account-selection.ts +223 -0
- package/src/actions.ts +346 -0
- package/src/approval-auth.ts +25 -0
- package/src/approval-handler.runtime.ts +592 -0
- package/src/approval-ids.ts +6 -0
- package/src/approval-native.ts +345 -0
- package/src/approval-reaction-auth.ts +45 -0
- package/src/approval-reactions.ts +313 -0
- package/src/auth-precedence.ts +61 -0
- package/src/channel-account-paths.ts +97 -0
- package/src/channel.runtime.ts +17 -0
- package/src/channel.setup.ts +48 -0
- package/src/channel.ts +667 -0
- package/src/cli-metadata.ts +19 -0
- package/src/cli.ts +2298 -0
- package/src/config-adapter.ts +41 -0
- package/src/config-schema.ts +159 -0
- package/src/config-ui-hints.ts +56 -0
- package/src/directory-live.ts +238 -0
- package/src/doctor-contract.ts +287 -0
- package/src/doctor.ts +262 -0
- package/src/env-vars.ts +92 -0
- package/src/exec-approval-resolver.ts +23 -0
- package/src/exec-approvals.ts +287 -0
- package/src/group-mentions.ts +41 -0
- package/src/legacy-crypto-inspector-availability.ts +60 -0
- package/src/legacy-crypto.ts +531 -0
- package/src/legacy-state.ts +156 -0
- package/src/matrix/account-config.ts +175 -0
- package/src/matrix/accounts.ts +194 -0
- package/src/matrix/actions/client.ts +31 -0
- package/src/matrix/actions/devices.ts +34 -0
- package/src/matrix/actions/limits.ts +6 -0
- package/src/matrix/actions/messages.ts +129 -0
- package/src/matrix/actions/pins.ts +63 -0
- package/src/matrix/actions/polls.ts +109 -0
- package/src/matrix/actions/profile.ts +37 -0
- package/src/matrix/actions/reactions.ts +59 -0
- package/src/matrix/actions/room.ts +71 -0
- package/src/matrix/actions/summary.ts +88 -0
- package/src/matrix/actions/types.ts +63 -0
- package/src/matrix/actions/verification.ts +589 -0
- package/src/matrix/actions.ts +37 -0
- package/src/matrix/active-client.ts +26 -0
- package/src/matrix/async-lock.ts +18 -0
- package/src/matrix/backup-health.ts +124 -0
- package/src/matrix/client/config-runtime-api.ts +9 -0
- package/src/matrix/client/config-secret-input.runtime.ts +1 -0
- package/src/matrix/client/config.ts +853 -0
- package/src/matrix/client/create-client.ts +105 -0
- package/src/matrix/client/env-auth.ts +95 -0
- package/src/matrix/client/file-sync-store.ts +289 -0
- package/src/matrix/client/logging.ts +140 -0
- package/src/matrix/client/migration-snapshot.runtime.ts +1 -0
- package/src/matrix/client/private-network-host.ts +1 -0
- package/src/matrix/client/runtime.ts +4 -0
- package/src/matrix/client/shared.ts +316 -0
- package/src/matrix/client/storage.ts +543 -0
- package/src/matrix/client/types.ts +50 -0
- package/src/matrix/client/url-validation.ts +73 -0
- package/src/matrix/client-bootstrap.ts +173 -0
- package/src/matrix/client.ts +23 -0
- package/src/matrix/config-paths.ts +31 -0
- package/src/matrix/config-update.ts +292 -0
- package/src/matrix/credentials-read.ts +208 -0
- package/src/matrix/credentials-write.runtime.ts +35 -0
- package/src/matrix/credentials.ts +95 -0
- package/src/matrix/deps.ts +309 -0
- package/src/matrix/device-health.ts +29 -0
- package/src/matrix/direct-management.ts +349 -0
- package/src/matrix/direct-room.ts +128 -0
- package/src/matrix/draft-stream.ts +225 -0
- package/src/matrix/encryption-guidance.ts +24 -0
- package/src/matrix/errors.ts +21 -0
- package/src/matrix/format.ts +426 -0
- package/src/matrix/legacy-crypto-inspector.ts +95 -0
- package/src/matrix/media-errors.ts +20 -0
- package/src/matrix/media-text.ts +162 -0
- package/src/matrix/monitor/access-state.ts +145 -0
- package/src/matrix/monitor/ack-config.ts +27 -0
- package/src/matrix/monitor/allowlist.ts +89 -0
- package/src/matrix/monitor/auto-join.ts +86 -0
- package/src/matrix/monitor/config.ts +569 -0
- package/src/matrix/monitor/context-summary.ts +43 -0
- package/src/matrix/monitor/direct.ts +296 -0
- package/src/matrix/monitor/events.ts +397 -0
- package/src/matrix/monitor/handler.ts +2266 -0
- package/src/matrix/monitor/inbound-dedupe.ts +267 -0
- package/src/matrix/monitor/index.ts +540 -0
- package/src/matrix/monitor/legacy-crypto-restore.ts +139 -0
- package/src/matrix/monitor/location.ts +108 -0
- package/src/matrix/monitor/media.ts +119 -0
- package/src/matrix/monitor/mentions.ts +256 -0
- package/src/matrix/monitor/reaction-events.ts +197 -0
- package/src/matrix/monitor/recent-invite.ts +30 -0
- package/src/matrix/monitor/replies.ts +136 -0
- package/src/matrix/monitor/reply-context.ts +92 -0
- package/src/matrix/monitor/room-history.ts +301 -0
- package/src/matrix/monitor/room-info.ts +126 -0
- package/src/matrix/monitor/rooms.ts +52 -0
- package/src/matrix/monitor/route.ts +179 -0
- package/src/matrix/monitor/runtime-api.ts +28 -0
- package/src/matrix/monitor/startup-verification.ts +237 -0
- package/src/matrix/monitor/startup.ts +218 -0
- package/src/matrix/monitor/status.ts +120 -0
- package/src/matrix/monitor/sync-lifecycle.ts +91 -0
- package/src/matrix/monitor/task-runner.ts +38 -0
- package/src/matrix/monitor/test-events.ts +21 -0
- package/src/matrix/monitor/thread-context.ts +108 -0
- package/src/matrix/monitor/threads.ts +85 -0
- package/src/matrix/monitor/types.ts +30 -0
- package/src/matrix/monitor/verification-events.ts +643 -0
- package/src/matrix/monitor/verification-utils.ts +46 -0
- package/src/matrix/outbound-media-runtime.ts +1 -0
- package/src/matrix/poll-summary.ts +110 -0
- package/src/matrix/poll-types.ts +429 -0
- package/src/matrix/probe.runtime.ts +4 -0
- package/src/matrix/probe.ts +97 -0
- package/src/matrix/profile.ts +184 -0
- package/src/matrix/reaction-common.ts +147 -0
- package/src/matrix/sdk/crypto-bootstrap.ts +438 -0
- package/src/matrix/sdk/crypto-facade.ts +242 -0
- package/src/matrix/sdk/crypto-node.runtime.ts +17 -0
- package/src/matrix/sdk/crypto-runtime.ts +14 -0
- package/src/matrix/sdk/decrypt-bridge.ts +410 -0
- package/src/matrix/sdk/event-helpers.ts +83 -0
- package/src/matrix/sdk/http-client.ts +87 -0
- package/src/matrix/sdk/idb-persistence-lock.ts +51 -0
- package/src/matrix/sdk/idb-persistence.ts +288 -0
- package/src/matrix/sdk/logger.ts +108 -0
- package/src/matrix/sdk/read-response-with-limit.ts +19 -0
- package/src/matrix/sdk/recovery-key-store.ts +453 -0
- package/src/matrix/sdk/timeout-abort-signal.ts +1 -0
- package/src/matrix/sdk/transport-runtime-api.ts +18 -0
- package/src/matrix/sdk/transport.ts +352 -0
- package/src/matrix/sdk/types.ts +245 -0
- package/src/matrix/sdk/verification-manager.ts +795 -0
- package/src/matrix/sdk/verification-status.ts +23 -0
- package/src/matrix/sdk.ts +2152 -0
- package/src/matrix/send/client.ts +93 -0
- package/src/matrix/send/formatting.ts +189 -0
- package/src/matrix/send/media.ts +244 -0
- package/src/matrix/send/targets.ts +104 -0
- package/src/matrix/send/types.ts +131 -0
- package/src/matrix/send.ts +660 -0
- package/src/matrix/session-store-metadata.ts +108 -0
- package/src/matrix/startup-abort.ts +44 -0
- package/src/matrix/subagent-hooks.ts +308 -0
- package/src/matrix/sync-state.ts +27 -0
- package/src/matrix/target-ids.ts +79 -0
- package/src/matrix/thread-bindings-shared.ts +206 -0
- package/src/matrix/thread-bindings.ts +580 -0
- package/src/matrix-migration.runtime.ts +9 -0
- package/src/migration-config.ts +243 -0
- package/src/migration-snapshot-backup.ts +116 -0
- package/src/migration-snapshot.ts +53 -0
- package/src/onboarding.ts +775 -0
- package/src/outbound.ts +248 -0
- package/src/plugin-entry.runtime.js +115 -0
- package/src/plugin-entry.runtime.ts +70 -0
- package/src/profile-update.ts +71 -0
- package/src/record-shared.ts +3 -0
- package/src/resolve-targets.ts +175 -0
- package/src/resolver.runtime.ts +5 -0
- package/src/resolver.ts +21 -0
- package/src/runtime-api.ts +106 -0
- package/src/runtime.ts +13 -0
- package/src/secret-contract.ts +174 -0
- package/src/session-route.ts +126 -0
- package/src/setup-bootstrap.ts +102 -0
- package/src/setup-config.ts +222 -0
- package/src/setup-contract.ts +90 -0
- package/src/setup-core.ts +146 -0
- package/src/setup-dm-policy.ts +15 -0
- package/src/setup-surface.ts +4 -0
- package/src/startup-maintenance.ts +114 -0
- package/src/storage-paths.ts +92 -0
- package/src/thread-binding-api.ts +23 -0
- package/src/tool-actions.runtime.ts +1 -0
- package/src/tool-actions.ts +498 -0
- package/src/types.ts +257 -0
- package/subagent-hooks-api.ts +31 -0
- package/test-api.ts +21 -0
- package/thread-binding-api.ts +4 -0
- package/thread-bindings-runtime.ts +4 -0
- package/tsconfig.json +16 -0
- package/api.js +0 -7
- package/auth-presence.js +0 -7
- package/channel-plugin-api.js +0 -7
- package/cli-metadata.js +0 -7
- package/contract-api.js +0 -7
- package/doctor-contract-api.js +0 -7
- package/helper-api.js +0 -7
- package/index.js +0 -7
- package/plugin-entry.handlers.runtime.js +0 -7
- package/runtime-api.js +0 -7
- package/runtime-heavy-api.js +0 -7
- package/runtime-setter-api.js +0 -7
- package/secret-contract-api.js +0 -7
- package/setup-entry.js +0 -7
- package/setup-plugin-api.js +0 -7
- package/subagent-hooks-api.js +0 -7
- package/test-api.js +0 -7
- package/thread-binding-api.js +0 -7
- package/thread-bindings-runtime.js +0 -7
|
@@ -0,0 +1,1704 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-DUslC3ob.js";
|
|
2
|
+
import { t as isRecord } from "./record-shared-DGvSFn5M.js";
|
|
3
|
+
import { n as formatMatrixErrorReason, r as isMatrixNotFoundError, t as formatMatrixErrorMessage } from "./errors-BETj3zr9.js";
|
|
4
|
+
import { t as claimCurrentTokenStorageState } from "./storage-nyO0DOFE.js";
|
|
5
|
+
import { n as resolveMatrixRoomKeyBackupReadinessError } from "./backup-health-3BHbHxyd.js";
|
|
6
|
+
import { t as createAsyncLock } from "./async-lock-SsmtFXtt.js";
|
|
7
|
+
import { a as ConsoleLogger, i as throwIfMatrixStartupAborted, n as createMatrixStartupAbortError, o as LogService, s as noop } from "./startup-abort-B2J3MU_h.js";
|
|
8
|
+
import { t as createMatrixJsSdkClientLogger } from "./logging-Df7aPD1z.js";
|
|
9
|
+
import { a as matrixEventToRaw, n as createMatrixGuardedFetch, o as parseMxc, t as MatrixAuthedHttpClient } from "./http-client-DoQgbQsU.js";
|
|
10
|
+
import { n as isMatrixReadySyncState, r as isMatrixTerminalSyncState } from "./sync-state-Bx0gPaGA.js";
|
|
11
|
+
import { normalizeNullableString } from "klaw/plugin-sdk/string-coerce-runtime";
|
|
12
|
+
import { readFileSync } from "node:fs";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
import { loadJsonFile, saveJsonFile, writeJsonFileAtomically } from "klaw/plugin-sdk/json-store";
|
|
15
|
+
import { KeyedAsyncQueue } from "klaw/plugin-sdk/keyed-async-queue";
|
|
16
|
+
import { EventEmitter } from "node:events";
|
|
17
|
+
import { Category, ClientEvent, Filter, MatrixEventEvent, MemoryStore, Preset, SyncAccumulator, createClient } from "matrix-js-sdk/lib/matrix.js";
|
|
18
|
+
import { VerificationMethod } from "matrix-js-sdk/lib/types.js";
|
|
19
|
+
import fs$1 from "node:fs/promises";
|
|
20
|
+
import { decodeRecoveryKey } from "matrix-js-sdk/lib/crypto-api/recovery-key.js";
|
|
21
|
+
//#region extensions/matrix/src/matrix/client/file-sync-store.ts
|
|
22
|
+
const STORE_VERSION = 1;
|
|
23
|
+
const PERSIST_DEBOUNCE_MS = 250;
|
|
24
|
+
function normalizeRoomsData(value) {
|
|
25
|
+
if (!isRecord(value)) return null;
|
|
26
|
+
return {
|
|
27
|
+
[Category.Join]: isRecord(value[Category.Join]) ? value[Category.Join] : {},
|
|
28
|
+
[Category.Invite]: isRecord(value[Category.Invite]) ? value[Category.Invite] : {},
|
|
29
|
+
[Category.Leave]: isRecord(value[Category.Leave]) ? value[Category.Leave] : {},
|
|
30
|
+
[Category.Knock]: isRecord(value[Category.Knock]) ? value[Category.Knock] : {}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function toPersistedSyncData(value) {
|
|
34
|
+
if (!isRecord(value)) return null;
|
|
35
|
+
if (typeof value.nextBatch === "string" && value.nextBatch.trim()) {
|
|
36
|
+
const roomsData = normalizeRoomsData(value.roomsData);
|
|
37
|
+
if (!Array.isArray(value.accountData) || !roomsData) return null;
|
|
38
|
+
return {
|
|
39
|
+
nextBatch: value.nextBatch,
|
|
40
|
+
accountData: value.accountData,
|
|
41
|
+
roomsData
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
if (typeof value.next_batch === "string" && value.next_batch.trim()) {
|
|
45
|
+
const roomsData = normalizeRoomsData(value.rooms);
|
|
46
|
+
if (!roomsData) return null;
|
|
47
|
+
return {
|
|
48
|
+
nextBatch: value.next_batch,
|
|
49
|
+
accountData: isRecord(value.account_data) && Array.isArray(value.account_data.events) ? value.account_data.events : [],
|
|
50
|
+
roomsData
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
function readPersistedStore(raw) {
|
|
56
|
+
try {
|
|
57
|
+
const parsed = JSON.parse(raw);
|
|
58
|
+
const savedSync = toPersistedSyncData(parsed.savedSync);
|
|
59
|
+
if (parsed.version === STORE_VERSION) return {
|
|
60
|
+
version: STORE_VERSION,
|
|
61
|
+
savedSync,
|
|
62
|
+
clientOptions: isRecord(parsed.clientOptions) ? parsed.clientOptions : void 0,
|
|
63
|
+
cleanShutdown: parsed.cleanShutdown === true
|
|
64
|
+
};
|
|
65
|
+
return {
|
|
66
|
+
version: STORE_VERSION,
|
|
67
|
+
savedSync: toPersistedSyncData(parsed),
|
|
68
|
+
cleanShutdown: false
|
|
69
|
+
};
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function cloneJson(value) {
|
|
75
|
+
return structuredClone(value);
|
|
76
|
+
}
|
|
77
|
+
function syncDataToSyncResponse(syncData) {
|
|
78
|
+
return {
|
|
79
|
+
next_batch: syncData.nextBatch,
|
|
80
|
+
rooms: syncData.roomsData,
|
|
81
|
+
account_data: { events: syncData.accountData }
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
var FileBackedMatrixSyncStore = class extends MemoryStore {
|
|
85
|
+
constructor(storagePath) {
|
|
86
|
+
super();
|
|
87
|
+
this.storagePath = storagePath;
|
|
88
|
+
this.persistLock = createAsyncLock();
|
|
89
|
+
this.accumulator = new SyncAccumulator();
|
|
90
|
+
this.savedSync = null;
|
|
91
|
+
this.cleanShutdown = false;
|
|
92
|
+
this.dirty = false;
|
|
93
|
+
this.persistTimer = null;
|
|
94
|
+
this.persistPromise = null;
|
|
95
|
+
let restoredSavedSync = null;
|
|
96
|
+
let restoredClientOptions;
|
|
97
|
+
let restoredCleanShutdown = false;
|
|
98
|
+
try {
|
|
99
|
+
const persisted = readPersistedStore(readFileSync(this.storagePath, "utf8"));
|
|
100
|
+
restoredSavedSync = persisted?.savedSync ?? null;
|
|
101
|
+
restoredClientOptions = persisted?.clientOptions;
|
|
102
|
+
restoredCleanShutdown = persisted?.cleanShutdown === true;
|
|
103
|
+
} catch {}
|
|
104
|
+
this.savedSync = restoredSavedSync;
|
|
105
|
+
this.savedClientOptions = restoredClientOptions;
|
|
106
|
+
this.hadSavedSyncOnLoad = restoredSavedSync !== null;
|
|
107
|
+
this.hadCleanShutdownOnLoad = this.hadSavedSyncOnLoad && restoredCleanShutdown;
|
|
108
|
+
this.cleanShutdown = this.hadCleanShutdownOnLoad;
|
|
109
|
+
if (this.savedSync) {
|
|
110
|
+
this.accumulator.accumulate(syncDataToSyncResponse(this.savedSync), true);
|
|
111
|
+
super.setSyncToken(this.savedSync.nextBatch);
|
|
112
|
+
}
|
|
113
|
+
if (this.savedClientOptions) super.storeClientOptions(this.savedClientOptions);
|
|
114
|
+
}
|
|
115
|
+
hasSavedSync() {
|
|
116
|
+
return this.hadSavedSyncOnLoad;
|
|
117
|
+
}
|
|
118
|
+
hasSavedSyncFromCleanShutdown() {
|
|
119
|
+
return this.hadCleanShutdownOnLoad;
|
|
120
|
+
}
|
|
121
|
+
getSavedSync() {
|
|
122
|
+
return Promise.resolve(this.savedSync ? cloneJson(this.savedSync) : null);
|
|
123
|
+
}
|
|
124
|
+
getSavedSyncToken() {
|
|
125
|
+
return Promise.resolve(this.savedSync?.nextBatch ?? null);
|
|
126
|
+
}
|
|
127
|
+
setSyncData(syncData) {
|
|
128
|
+
this.accumulator.accumulate(syncData);
|
|
129
|
+
this.savedSync = this.accumulator.getJSON();
|
|
130
|
+
this.markDirtyAndSchedulePersist();
|
|
131
|
+
return Promise.resolve();
|
|
132
|
+
}
|
|
133
|
+
getClientOptions() {
|
|
134
|
+
return Promise.resolve(this.savedClientOptions ? cloneJson(this.savedClientOptions) : void 0);
|
|
135
|
+
}
|
|
136
|
+
storeClientOptions(options) {
|
|
137
|
+
this.savedClientOptions = cloneJson(options);
|
|
138
|
+
super.storeClientOptions(options);
|
|
139
|
+
this.markDirtyAndSchedulePersist();
|
|
140
|
+
return Promise.resolve();
|
|
141
|
+
}
|
|
142
|
+
save(force = false) {
|
|
143
|
+
if (force) return this.flush();
|
|
144
|
+
return Promise.resolve();
|
|
145
|
+
}
|
|
146
|
+
wantsSave() {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
async deleteAllData() {
|
|
150
|
+
if (this.persistTimer) {
|
|
151
|
+
clearTimeout(this.persistTimer);
|
|
152
|
+
this.persistTimer = null;
|
|
153
|
+
}
|
|
154
|
+
this.dirty = false;
|
|
155
|
+
await this.persistPromise?.catch(() => void 0);
|
|
156
|
+
await super.deleteAllData();
|
|
157
|
+
this.savedSync = null;
|
|
158
|
+
this.savedClientOptions = void 0;
|
|
159
|
+
this.cleanShutdown = false;
|
|
160
|
+
await fs$1.rm(this.storagePath, { force: true }).catch(() => void 0);
|
|
161
|
+
}
|
|
162
|
+
markCleanShutdown() {
|
|
163
|
+
this.cleanShutdown = true;
|
|
164
|
+
this.dirty = true;
|
|
165
|
+
}
|
|
166
|
+
async flush() {
|
|
167
|
+
if (this.persistTimer) {
|
|
168
|
+
clearTimeout(this.persistTimer);
|
|
169
|
+
this.persistTimer = null;
|
|
170
|
+
}
|
|
171
|
+
while (this.dirty || this.persistPromise) {
|
|
172
|
+
if (this.dirty && !this.persistPromise) this.persistPromise = this.persist().finally(() => {
|
|
173
|
+
this.persistPromise = null;
|
|
174
|
+
});
|
|
175
|
+
await this.persistPromise;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
markDirtyAndSchedulePersist() {
|
|
179
|
+
this.cleanShutdown = false;
|
|
180
|
+
this.dirty = true;
|
|
181
|
+
if (this.persistTimer) return;
|
|
182
|
+
this.persistTimer = setTimeout(() => {
|
|
183
|
+
this.persistTimer = null;
|
|
184
|
+
this.flush().catch((err) => {
|
|
185
|
+
LogService.warn("MatrixFileSyncStore", "Failed to persist Matrix sync store:", err);
|
|
186
|
+
});
|
|
187
|
+
}, PERSIST_DEBOUNCE_MS);
|
|
188
|
+
this.persistTimer.unref?.();
|
|
189
|
+
}
|
|
190
|
+
async persist() {
|
|
191
|
+
this.dirty = false;
|
|
192
|
+
const payload = {
|
|
193
|
+
version: STORE_VERSION,
|
|
194
|
+
savedSync: this.savedSync ? cloneJson(this.savedSync) : null,
|
|
195
|
+
cleanShutdown: this.cleanShutdown,
|
|
196
|
+
...this.savedClientOptions ? { clientOptions: cloneJson(this.savedClientOptions) } : {}
|
|
197
|
+
};
|
|
198
|
+
try {
|
|
199
|
+
await this.persistLock(async () => {
|
|
200
|
+
await writeJsonFileAtomically(this.storagePath, payload);
|
|
201
|
+
claimCurrentTokenStorageState({ rootDir: path.dirname(this.storagePath) });
|
|
202
|
+
});
|
|
203
|
+
} catch (err) {
|
|
204
|
+
this.dirty = true;
|
|
205
|
+
throw err;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
//#endregion
|
|
210
|
+
//#region extensions/matrix/src/matrix/sdk/idb-persistence-lock.ts
|
|
211
|
+
const MATRIX_IDB_PERSIST_INTERVAL_MS = 6e4;
|
|
212
|
+
const IDB_SNAPSHOT_LOCK_STALE_MS = 5 * 6e4;
|
|
213
|
+
const IDB_SNAPSHOT_LOCK_RETRY_BASE = {
|
|
214
|
+
factor: 2,
|
|
215
|
+
minTimeout: 50,
|
|
216
|
+
maxTimeout: 5e3,
|
|
217
|
+
randomize: true
|
|
218
|
+
};
|
|
219
|
+
function computeRetryDelayMs(retries, attempt) {
|
|
220
|
+
return Math.min(retries.maxTimeout, Math.max(retries.minTimeout, retries.minTimeout * retries.factor ** attempt));
|
|
221
|
+
}
|
|
222
|
+
function computeMinimumRetryWindowMs(retries) {
|
|
223
|
+
let total = 0;
|
|
224
|
+
const attempts = Math.max(1, retries.retries + 1);
|
|
225
|
+
for (let attempt = 0; attempt < attempts - 1; attempt += 1) total += computeRetryDelayMs(retries, attempt);
|
|
226
|
+
return total;
|
|
227
|
+
}
|
|
228
|
+
function resolveRetriesForMinimumWindowMs(retries, minimumWindowMs) {
|
|
229
|
+
const resolved = {
|
|
230
|
+
...retries,
|
|
231
|
+
retries: 0
|
|
232
|
+
};
|
|
233
|
+
while (computeMinimumRetryWindowMs(resolved) < minimumWindowMs) resolved.retries += 1;
|
|
234
|
+
return resolved;
|
|
235
|
+
}
|
|
236
|
+
const MATRIX_IDB_SNAPSHOT_LOCK_OPTIONS = {
|
|
237
|
+
retries: resolveRetriesForMinimumWindowMs(IDB_SNAPSHOT_LOCK_RETRY_BASE, MATRIX_IDB_PERSIST_INTERVAL_MS),
|
|
238
|
+
stale: IDB_SNAPSHOT_LOCK_STALE_MS
|
|
239
|
+
};
|
|
240
|
+
//#endregion
|
|
241
|
+
//#region extensions/matrix/src/matrix/sdk/recovery-key-store.ts
|
|
242
|
+
function isRepairableSecretStorageAccessError(err) {
|
|
243
|
+
const message = formatMatrixErrorReason(err);
|
|
244
|
+
if (!message) return false;
|
|
245
|
+
if (message.includes("getsecretstoragekey callback returned falsey")) return true;
|
|
246
|
+
if (message.includes("decrypting secret") && message.includes("bad mac")) return true;
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
var MatrixRecoveryKeyStore = class {
|
|
250
|
+
constructor(recoveryKeyPath) {
|
|
251
|
+
this.recoveryKeyPath = recoveryKeyPath;
|
|
252
|
+
this.secretStorageKeyCache = /* @__PURE__ */ new Map();
|
|
253
|
+
this.stagedRecoveryKey = null;
|
|
254
|
+
this.stagedRecoveryKeyUsed = false;
|
|
255
|
+
this.stagedCacheKeyIds = /* @__PURE__ */ new Set();
|
|
256
|
+
}
|
|
257
|
+
buildCryptoCallbacks() {
|
|
258
|
+
return {
|
|
259
|
+
getSecretStorageKey: async ({ keys }) => {
|
|
260
|
+
const requestedKeyIds = Object.keys(keys ?? {});
|
|
261
|
+
if (requestedKeyIds.length === 0) return null;
|
|
262
|
+
const staged = this.resolveStagedSecretStorageKey(requestedKeyIds);
|
|
263
|
+
if (staged) return staged;
|
|
264
|
+
for (const keyId of requestedKeyIds) {
|
|
265
|
+
const cached = this.secretStorageKeyCache.get(keyId);
|
|
266
|
+
if (cached) return [keyId, new Uint8Array(cached.key)];
|
|
267
|
+
}
|
|
268
|
+
const stored = this.loadStoredRecoveryKey();
|
|
269
|
+
if (!stored?.privateKeyBase64) return null;
|
|
270
|
+
const privateKey = new Uint8Array(Buffer.from(stored.privateKeyBase64, "base64"));
|
|
271
|
+
if (privateKey.length === 0) return null;
|
|
272
|
+
if (stored.keyId && requestedKeyIds.includes(stored.keyId)) {
|
|
273
|
+
this.rememberSecretStorageKey(stored.keyId, privateKey, stored.keyInfo);
|
|
274
|
+
return [stored.keyId, privateKey];
|
|
275
|
+
}
|
|
276
|
+
const firstRequestedKeyId = requestedKeyIds[0];
|
|
277
|
+
if (!firstRequestedKeyId) return null;
|
|
278
|
+
this.rememberSecretStorageKey(firstRequestedKeyId, privateKey, stored.keyInfo);
|
|
279
|
+
return [firstRequestedKeyId, privateKey];
|
|
280
|
+
},
|
|
281
|
+
cacheSecretStorageKey: (keyId, keyInfo, key) => {
|
|
282
|
+
const privateKey = new Uint8Array(key);
|
|
283
|
+
const normalizedKeyInfo = {
|
|
284
|
+
passphrase: keyInfo?.passphrase,
|
|
285
|
+
name: typeof keyInfo?.name === "string" ? keyInfo.name : void 0
|
|
286
|
+
};
|
|
287
|
+
this.rememberSecretStorageKey(keyId, privateKey, normalizedKeyInfo);
|
|
288
|
+
const stored = this.loadStoredRecoveryKey();
|
|
289
|
+
this.saveRecoveryKeyToDisk({
|
|
290
|
+
keyId,
|
|
291
|
+
keyInfo: normalizedKeyInfo,
|
|
292
|
+
privateKey,
|
|
293
|
+
encodedPrivateKey: stored?.encodedPrivateKey
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
getRecoveryKeySummary() {
|
|
299
|
+
const stored = this.loadStoredRecoveryKey();
|
|
300
|
+
if (!stored) return null;
|
|
301
|
+
return {
|
|
302
|
+
encodedPrivateKey: stored.encodedPrivateKey,
|
|
303
|
+
keyId: stored.keyId,
|
|
304
|
+
createdAt: stored.createdAt
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
resolveEncodedRecoveryKeyInput(params) {
|
|
308
|
+
const encodedPrivateKey = params.encodedPrivateKey.trim();
|
|
309
|
+
if (!encodedPrivateKey) throw new Error("Matrix recovery key is required");
|
|
310
|
+
let privateKey;
|
|
311
|
+
try {
|
|
312
|
+
privateKey = decodeRecoveryKey(encodedPrivateKey);
|
|
313
|
+
} catch (err) {
|
|
314
|
+
throw new Error(`Invalid Matrix recovery key: ${formatMatrixErrorMessage(err)}`, { cause: err });
|
|
315
|
+
}
|
|
316
|
+
const keyId = typeof params.keyId === "string" && params.keyId.trim() ? params.keyId.trim() : null;
|
|
317
|
+
return {
|
|
318
|
+
encodedPrivateKey,
|
|
319
|
+
privateKey,
|
|
320
|
+
keyId,
|
|
321
|
+
keyInfo: params.keyInfo ?? this.loadStoredRecoveryKey()?.keyInfo
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
storeEncodedRecoveryKey(params) {
|
|
325
|
+
const prepared = this.resolveEncodedRecoveryKeyInput(params);
|
|
326
|
+
this.saveRecoveryKeyToDisk({
|
|
327
|
+
keyId: prepared.keyId,
|
|
328
|
+
keyInfo: prepared.keyInfo,
|
|
329
|
+
privateKey: prepared.privateKey,
|
|
330
|
+
encodedPrivateKey: prepared.encodedPrivateKey
|
|
331
|
+
});
|
|
332
|
+
if (prepared.keyId) this.rememberSecretStorageKey(prepared.keyId, prepared.privateKey, prepared.keyInfo);
|
|
333
|
+
return this.getRecoveryKeySummary() ?? {};
|
|
334
|
+
}
|
|
335
|
+
stageEncodedRecoveryKey(params) {
|
|
336
|
+
const prepared = this.resolveEncodedRecoveryKeyInput(params);
|
|
337
|
+
this.discardStagedRecoveryKey();
|
|
338
|
+
this.stagedRecoveryKey = {
|
|
339
|
+
version: 1,
|
|
340
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
341
|
+
keyId: prepared.keyId,
|
|
342
|
+
encodedPrivateKey: prepared.encodedPrivateKey,
|
|
343
|
+
privateKeyBase64: Buffer.from(prepared.privateKey).toString("base64"),
|
|
344
|
+
keyInfo: prepared.keyInfo
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
hasStagedRecoveryKeyBeenUsed() {
|
|
348
|
+
return this.stagedRecoveryKeyUsed;
|
|
349
|
+
}
|
|
350
|
+
commitStagedRecoveryKey(params) {
|
|
351
|
+
if (!this.stagedRecoveryKey) return this.getRecoveryKeySummary();
|
|
352
|
+
const staged = this.stagedRecoveryKey;
|
|
353
|
+
const privateKey = new Uint8Array(Buffer.from(staged.privateKeyBase64, "base64"));
|
|
354
|
+
const keyId = typeof params?.keyId === "string" && params.keyId.trim() ? params.keyId.trim() : staged.keyId;
|
|
355
|
+
this.saveRecoveryKeyToDisk({
|
|
356
|
+
keyId,
|
|
357
|
+
keyInfo: params?.keyInfo ?? staged.keyInfo,
|
|
358
|
+
privateKey,
|
|
359
|
+
encodedPrivateKey: staged.encodedPrivateKey
|
|
360
|
+
});
|
|
361
|
+
this.clearStagedRecoveryKeyTracking();
|
|
362
|
+
return this.getRecoveryKeySummary();
|
|
363
|
+
}
|
|
364
|
+
discardStagedRecoveryKey() {
|
|
365
|
+
for (const keyId of this.stagedCacheKeyIds) this.secretStorageKeyCache.delete(keyId);
|
|
366
|
+
this.clearStagedRecoveryKeyTracking();
|
|
367
|
+
}
|
|
368
|
+
async bootstrapSecretStorageWithRecoveryKey(crypto, options = {}) {
|
|
369
|
+
let status = null;
|
|
370
|
+
const getSecretStorageStatus = crypto.getSecretStorageStatus;
|
|
371
|
+
if (typeof getSecretStorageStatus === "function") try {
|
|
372
|
+
status = await getSecretStorageStatus.call(crypto);
|
|
373
|
+
} catch (err) {
|
|
374
|
+
LogService.warn("MatrixClientLite", "Failed to read secret storage status:", err);
|
|
375
|
+
}
|
|
376
|
+
const hasDefaultSecretStorageKey = Boolean(status?.defaultKeyId);
|
|
377
|
+
const hasKnownInvalidSecrets = Object.values(status?.secretStorageKeyValidityMap ?? {}).some((valid) => !valid);
|
|
378
|
+
let generatedRecoveryKey = false;
|
|
379
|
+
const storedRecovery = this.loadStoredRecoveryKey();
|
|
380
|
+
const stagedRecovery = this.stagedRecoveryKey;
|
|
381
|
+
const sourceRecovery = options.forceNewRecoveryKey === true ? null : stagedRecovery ?? storedRecovery;
|
|
382
|
+
let recoveryKey = sourceRecovery ? {
|
|
383
|
+
keyInfo: sourceRecovery.keyInfo,
|
|
384
|
+
privateKey: new Uint8Array(Buffer.from(sourceRecovery.privateKeyBase64, "base64")),
|
|
385
|
+
encodedPrivateKey: sourceRecovery.encodedPrivateKey
|
|
386
|
+
} : null;
|
|
387
|
+
if (recoveryKey && status?.defaultKeyId) {
|
|
388
|
+
const defaultKeyId = status.defaultKeyId;
|
|
389
|
+
if (!stagedRecovery) {
|
|
390
|
+
this.rememberSecretStorageKey(defaultKeyId, recoveryKey.privateKey, recoveryKey.keyInfo);
|
|
391
|
+
if (storedRecovery && storedRecovery.keyId !== defaultKeyId) this.saveRecoveryKeyToDisk({
|
|
392
|
+
keyId: defaultKeyId,
|
|
393
|
+
keyInfo: recoveryKey.keyInfo,
|
|
394
|
+
privateKey: recoveryKey.privateKey,
|
|
395
|
+
encodedPrivateKey: recoveryKey.encodedPrivateKey
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
const ensureRecoveryKey = async () => {
|
|
400
|
+
if (recoveryKey) {
|
|
401
|
+
if (stagedRecovery) this.stagedRecoveryKeyUsed = true;
|
|
402
|
+
return recoveryKey;
|
|
403
|
+
}
|
|
404
|
+
if (typeof crypto.createRecoveryKeyFromPassphrase !== "function") throw new Error("Matrix crypto backend does not support recovery key generation (createRecoveryKeyFromPassphrase missing)");
|
|
405
|
+
recoveryKey = await crypto.createRecoveryKeyFromPassphrase();
|
|
406
|
+
this.saveRecoveryKeyToDisk(recoveryKey);
|
|
407
|
+
generatedRecoveryKey = true;
|
|
408
|
+
return recoveryKey;
|
|
409
|
+
};
|
|
410
|
+
const shouldRecreateSecretStorage = options.forceNewSecretStorage === true || !hasDefaultSecretStorageKey || !recoveryKey && status?.ready === false || hasKnownInvalidSecrets;
|
|
411
|
+
if (hasKnownInvalidSecrets) recoveryKey = null;
|
|
412
|
+
const secretStorageOptions = { setupNewKeyBackup: options.setupNewKeyBackup === true };
|
|
413
|
+
if (shouldRecreateSecretStorage) {
|
|
414
|
+
secretStorageOptions.setupNewSecretStorage = true;
|
|
415
|
+
secretStorageOptions.createSecretStorageKey = ensureRecoveryKey;
|
|
416
|
+
}
|
|
417
|
+
try {
|
|
418
|
+
await crypto.bootstrapSecretStorage(secretStorageOptions);
|
|
419
|
+
} catch (err) {
|
|
420
|
+
if (!(options.allowSecretStorageRecreateWithoutRecoveryKey === true && hasDefaultSecretStorageKey && isRepairableSecretStorageAccessError(err))) throw err;
|
|
421
|
+
recoveryKey = null;
|
|
422
|
+
LogService.warn("MatrixClientLite", "Secret storage exists on the server but local recovery material cannot unlock it; recreating secret storage during explicit bootstrap.");
|
|
423
|
+
await crypto.bootstrapSecretStorage({
|
|
424
|
+
setupNewSecretStorage: true,
|
|
425
|
+
setupNewKeyBackup: options.setupNewKeyBackup === true,
|
|
426
|
+
createSecretStorageKey: ensureRecoveryKey
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
if (generatedRecoveryKey && this.recoveryKeyPath) LogService.warn("MatrixClientLite", `Generated Matrix recovery key and saved it to ${this.recoveryKeyPath}. Keep this file secure.`);
|
|
430
|
+
}
|
|
431
|
+
clearStagedRecoveryKeyTracking() {
|
|
432
|
+
this.stagedRecoveryKey = null;
|
|
433
|
+
this.stagedRecoveryKeyUsed = false;
|
|
434
|
+
this.stagedCacheKeyIds.clear();
|
|
435
|
+
}
|
|
436
|
+
resolveStagedSecretStorageKey(requestedKeyIds) {
|
|
437
|
+
const staged = this.stagedRecoveryKey;
|
|
438
|
+
if (!staged?.privateKeyBase64) return null;
|
|
439
|
+
const privateKey = new Uint8Array(Buffer.from(staged.privateKeyBase64, "base64"));
|
|
440
|
+
if (privateKey.length === 0) return null;
|
|
441
|
+
const keyId = staged.keyId && requestedKeyIds.includes(staged.keyId) ? staged.keyId : requestedKeyIds[0];
|
|
442
|
+
if (!keyId) return null;
|
|
443
|
+
this.rememberStagedSecretStorageKey(keyId, privateKey, staged.keyInfo);
|
|
444
|
+
this.stagedCacheKeyIds.add(keyId);
|
|
445
|
+
return [keyId, privateKey];
|
|
446
|
+
}
|
|
447
|
+
rememberStagedSecretStorageKey(keyId, key, keyInfo) {
|
|
448
|
+
this.stagedRecoveryKeyUsed = true;
|
|
449
|
+
this.rememberSecretStorageKey(keyId, key, keyInfo);
|
|
450
|
+
}
|
|
451
|
+
rememberSecretStorageKey(keyId, key, keyInfo) {
|
|
452
|
+
if (!keyId.trim()) return;
|
|
453
|
+
this.secretStorageKeyCache.set(keyId, {
|
|
454
|
+
key: new Uint8Array(key),
|
|
455
|
+
keyInfo
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
loadStoredRecoveryKey() {
|
|
459
|
+
if (!this.recoveryKeyPath) return null;
|
|
460
|
+
try {
|
|
461
|
+
const parsed = loadJsonFile(this.recoveryKeyPath);
|
|
462
|
+
if (parsed?.version !== 1 || typeof parsed.createdAt !== "string" || typeof parsed.privateKeyBase64 !== "string" || !parsed.privateKeyBase64.trim()) return null;
|
|
463
|
+
return {
|
|
464
|
+
version: 1,
|
|
465
|
+
createdAt: parsed.createdAt,
|
|
466
|
+
keyId: typeof parsed.keyId === "string" ? parsed.keyId : null,
|
|
467
|
+
encodedPrivateKey: typeof parsed.encodedPrivateKey === "string" ? parsed.encodedPrivateKey : void 0,
|
|
468
|
+
privateKeyBase64: parsed.privateKeyBase64,
|
|
469
|
+
keyInfo: parsed.keyInfo && typeof parsed.keyInfo === "object" ? {
|
|
470
|
+
passphrase: parsed.keyInfo.passphrase,
|
|
471
|
+
name: typeof parsed.keyInfo.name === "string" ? parsed.keyInfo.name : void 0
|
|
472
|
+
} : void 0
|
|
473
|
+
};
|
|
474
|
+
} catch {
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
saveRecoveryKeyToDisk(params) {
|
|
479
|
+
if (!this.recoveryKeyPath) return;
|
|
480
|
+
try {
|
|
481
|
+
const payload = {
|
|
482
|
+
version: 1,
|
|
483
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
484
|
+
keyId: typeof params.keyId === "string" ? params.keyId : null,
|
|
485
|
+
encodedPrivateKey: params.encodedPrivateKey,
|
|
486
|
+
privateKeyBase64: Buffer.from(params.privateKey).toString("base64"),
|
|
487
|
+
keyInfo: params.keyInfo ? {
|
|
488
|
+
passphrase: params.keyInfo.passphrase,
|
|
489
|
+
name: params.keyInfo.name
|
|
490
|
+
} : void 0
|
|
491
|
+
};
|
|
492
|
+
saveJsonFile(this.recoveryKeyPath, payload);
|
|
493
|
+
} catch (err) {
|
|
494
|
+
LogService.warn("MatrixClientLite", "Failed to persist recovery key:", err);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
//#endregion
|
|
499
|
+
//#region extensions/matrix/src/matrix/sdk.ts
|
|
500
|
+
var sdk_exports = /* @__PURE__ */ __exportAll({
|
|
501
|
+
ConsoleLogger: () => ConsoleLogger,
|
|
502
|
+
LogService: () => LogService,
|
|
503
|
+
MatrixClient: () => MatrixClient
|
|
504
|
+
});
|
|
505
|
+
const MATRIX_STATUS_DIAGNOSTIC_TIMEOUT_MS = 1e4;
|
|
506
|
+
function unresolvedMatrixRoomKeyBackupStatus() {
|
|
507
|
+
return {
|
|
508
|
+
serverVersion: null,
|
|
509
|
+
activeVersion: null,
|
|
510
|
+
trusted: null,
|
|
511
|
+
matchesDecryptionKey: null,
|
|
512
|
+
decryptionKeyCached: null,
|
|
513
|
+
keyLoadAttempted: false,
|
|
514
|
+
keyLoadError: null
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
function unresolvedMatrixDeviceVerificationStatus(params) {
|
|
518
|
+
return {
|
|
519
|
+
encryptionEnabled: true,
|
|
520
|
+
userId: params.userId,
|
|
521
|
+
deviceId: params.deviceId,
|
|
522
|
+
verified: false,
|
|
523
|
+
localVerified: false,
|
|
524
|
+
crossSigningVerified: false,
|
|
525
|
+
signedByOwner: false
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
async function resolveMatrixDiagnostic(promise, timeoutMs) {
|
|
529
|
+
return (await resolveMatrixDiagnosticResult(promise, timeoutMs)).value;
|
|
530
|
+
}
|
|
531
|
+
async function resolveMatrixDiagnosticResult(promise, timeoutMs) {
|
|
532
|
+
let timeoutId;
|
|
533
|
+
try {
|
|
534
|
+
const guarded = promise.then((value) => ({
|
|
535
|
+
error: null,
|
|
536
|
+
timedOut: false,
|
|
537
|
+
value
|
|
538
|
+
})).catch((error) => ({
|
|
539
|
+
error,
|
|
540
|
+
timedOut: false,
|
|
541
|
+
value: null
|
|
542
|
+
}));
|
|
543
|
+
const timeout = new Promise((resolve) => {
|
|
544
|
+
timeoutId = setTimeout(() => resolve({
|
|
545
|
+
error: null,
|
|
546
|
+
timedOut: true,
|
|
547
|
+
value: null
|
|
548
|
+
}), timeoutMs);
|
|
549
|
+
timeoutId.unref?.();
|
|
550
|
+
});
|
|
551
|
+
return await Promise.race([guarded, timeout]);
|
|
552
|
+
} finally {
|
|
553
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
function isMatrixAccessTokenInvalidatedError(error) {
|
|
557
|
+
if (!error || typeof error !== "object") return false;
|
|
558
|
+
const err = error;
|
|
559
|
+
const errcode = err.body?.errcode ?? err.data?.errcode;
|
|
560
|
+
if (err.statusCode === 401 && errcode === "M_UNKNOWN_TOKEN") return true;
|
|
561
|
+
const reason = formatMatrixErrorReason(error);
|
|
562
|
+
return reason.includes("m_unknown_token") || reason.includes("unknown token") || reason.includes("access token") && (reason.includes("invalid") || reason.includes("unrecognized") || reason.includes("unknown"));
|
|
563
|
+
}
|
|
564
|
+
const MATRIX_INITIAL_CRYPTO_BOOTSTRAP_OPTIONS = { allowAutomaticCrossSigningReset: false };
|
|
565
|
+
const MATRIX_AUTOMATIC_REPAIR_BOOTSTRAP_OPTIONS = {
|
|
566
|
+
forceResetCrossSigning: true,
|
|
567
|
+
allowSecretStorageRecreateWithoutRecoveryKey: true,
|
|
568
|
+
strict: true
|
|
569
|
+
};
|
|
570
|
+
function createMatrixExplicitBootstrapOptions(params) {
|
|
571
|
+
return {
|
|
572
|
+
forceResetCrossSigning: params?.forceResetCrossSigning === true,
|
|
573
|
+
allowAutomaticCrossSigningReset: params?.allowAutomaticCrossSigningReset !== false,
|
|
574
|
+
allowSecretStorageRecreateWithoutRecoveryKey: true,
|
|
575
|
+
strict: params?.strict !== false
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
let loadedMatrixCryptoRuntime = null;
|
|
579
|
+
let matrixCryptoRuntimePromise = null;
|
|
580
|
+
async function loadMatrixCryptoRuntime() {
|
|
581
|
+
matrixCryptoRuntimePromise ??= import("./crypto-runtime-1pKW4O2F.js").then((runtime) => {
|
|
582
|
+
loadedMatrixCryptoRuntime = runtime;
|
|
583
|
+
return runtime;
|
|
584
|
+
});
|
|
585
|
+
return await matrixCryptoRuntimePromise;
|
|
586
|
+
}
|
|
587
|
+
const normalizeOptionalString$1 = normalizeNullableString;
|
|
588
|
+
function isUnsupportedAuthenticatedMediaEndpointError(err) {
|
|
589
|
+
const statusCode = err?.statusCode;
|
|
590
|
+
if (statusCode === 404 || statusCode === 405 || statusCode === 501) return true;
|
|
591
|
+
const message = formatMatrixErrorReason(err);
|
|
592
|
+
return message.includes("m_unrecognized") || message.includes("unrecognized request") || message.includes("method not allowed") || message.includes("not implemented");
|
|
593
|
+
}
|
|
594
|
+
var MatrixClient = class {
|
|
595
|
+
constructor(homeserver, accessToken, opts = {}) {
|
|
596
|
+
this.emitter = new EventEmitter();
|
|
597
|
+
this.bridgeRegistered = false;
|
|
598
|
+
this.started = false;
|
|
599
|
+
this.cryptoBootstrapped = false;
|
|
600
|
+
this.dmRoomIds = /* @__PURE__ */ new Set();
|
|
601
|
+
this.cryptoInitialized = false;
|
|
602
|
+
this.sendQueue = new KeyedAsyncQueue();
|
|
603
|
+
this.stopPersistPromise = null;
|
|
604
|
+
this.verificationSummaryListenerBound = false;
|
|
605
|
+
this.currentSyncState = null;
|
|
606
|
+
this.dms = {
|
|
607
|
+
update: async () => {
|
|
608
|
+
return await this.refreshDmCache();
|
|
609
|
+
},
|
|
610
|
+
isDm: (roomId) => this.dmRoomIds.has(roomId)
|
|
611
|
+
};
|
|
612
|
+
this.idbPersistTimer = null;
|
|
613
|
+
this.httpClient = new MatrixAuthedHttpClient({
|
|
614
|
+
homeserver,
|
|
615
|
+
accessToken,
|
|
616
|
+
ssrfPolicy: opts.ssrfPolicy,
|
|
617
|
+
dispatcherPolicy: opts.dispatcherPolicy
|
|
618
|
+
});
|
|
619
|
+
this.localTimeoutMs = Math.max(1, opts.localTimeoutMs ?? 6e4);
|
|
620
|
+
this.initialSyncLimit = opts.initialSyncLimit;
|
|
621
|
+
this.syncFilter = opts.syncFilter;
|
|
622
|
+
this.encryptionEnabled = opts.encryption === true;
|
|
623
|
+
this.password = opts.password;
|
|
624
|
+
this.syncStore = opts.storagePath ? new FileBackedMatrixSyncStore(opts.storagePath) : void 0;
|
|
625
|
+
this.idbSnapshotPath = opts.idbSnapshotPath;
|
|
626
|
+
this.cryptoDatabasePrefix = opts.cryptoDatabasePrefix;
|
|
627
|
+
this.selfUserId = opts.userId?.trim() || null;
|
|
628
|
+
this.autoBootstrapCrypto = opts.autoBootstrapCrypto !== false;
|
|
629
|
+
this.recoveryKeyStore = new MatrixRecoveryKeyStore(opts.recoveryKeyPath);
|
|
630
|
+
const cryptoCallbacks = this.encryptionEnabled ? this.recoveryKeyStore.buildCryptoCallbacks() : void 0;
|
|
631
|
+
this.client = createClient({
|
|
632
|
+
baseUrl: homeserver,
|
|
633
|
+
accessToken,
|
|
634
|
+
userId: opts.userId,
|
|
635
|
+
deviceId: opts.deviceId,
|
|
636
|
+
logger: createMatrixJsSdkClientLogger("MatrixClient"),
|
|
637
|
+
localTimeoutMs: this.localTimeoutMs,
|
|
638
|
+
fetchFn: createMatrixGuardedFetch({
|
|
639
|
+
ssrfPolicy: opts.ssrfPolicy,
|
|
640
|
+
dispatcherPolicy: opts.dispatcherPolicy
|
|
641
|
+
}),
|
|
642
|
+
store: this.syncStore,
|
|
643
|
+
cryptoCallbacks,
|
|
644
|
+
verificationMethods: [
|
|
645
|
+
VerificationMethod.Sas,
|
|
646
|
+
VerificationMethod.ShowQrCode,
|
|
647
|
+
VerificationMethod.ScanQrCode,
|
|
648
|
+
VerificationMethod.Reciprocate
|
|
649
|
+
]
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
on(eventName, listener) {
|
|
653
|
+
this.emitter.on(eventName, listener);
|
|
654
|
+
return this;
|
|
655
|
+
}
|
|
656
|
+
off(eventName, listener) {
|
|
657
|
+
this.emitter.off(eventName, listener);
|
|
658
|
+
return this;
|
|
659
|
+
}
|
|
660
|
+
async ensureCryptoSupportInitialized() {
|
|
661
|
+
if (this.decryptBridge && (!this.encryptionEnabled || this.verificationManager && this.cryptoBootstrapper && this.crypto)) return;
|
|
662
|
+
const runtime = await loadMatrixCryptoRuntime();
|
|
663
|
+
this.decryptBridge ??= new runtime.MatrixDecryptBridge({
|
|
664
|
+
client: this.client,
|
|
665
|
+
toRaw: (event) => matrixEventToRaw(event, { contentMode: "original" }),
|
|
666
|
+
emitDecryptedEvent: (roomId, event) => {
|
|
667
|
+
this.emitter.emit("room.decrypted_event", roomId, event);
|
|
668
|
+
},
|
|
669
|
+
emitMessage: (roomId, event) => {
|
|
670
|
+
this.emitter.emit("room.message", roomId, event);
|
|
671
|
+
},
|
|
672
|
+
emitFailedDecryption: (roomId, event, error) => {
|
|
673
|
+
this.emitter.emit("room.failed_decryption", roomId, event, error);
|
|
674
|
+
}
|
|
675
|
+
});
|
|
676
|
+
if (!this.encryptionEnabled) return;
|
|
677
|
+
this.verificationManager ??= new runtime.MatrixVerificationManager({ trustOwnDeviceAfterSas: async (deviceId) => {
|
|
678
|
+
const crypto = this.client.getCrypto();
|
|
679
|
+
if (typeof crypto?.crossSignDevice !== "function") return;
|
|
680
|
+
await crypto.crossSignDevice(deviceId);
|
|
681
|
+
} });
|
|
682
|
+
this.cryptoBootstrapper ??= new runtime.MatrixCryptoBootstrapper({
|
|
683
|
+
getUserId: () => this.getUserId(),
|
|
684
|
+
getPassword: () => this.password,
|
|
685
|
+
getDeviceId: () => this.client.getDeviceId(),
|
|
686
|
+
verificationManager: this.verificationManager,
|
|
687
|
+
recoveryKeyStore: this.recoveryKeyStore,
|
|
688
|
+
decryptBridge: this.decryptBridge
|
|
689
|
+
});
|
|
690
|
+
if (!this.crypto) this.crypto = runtime.createMatrixCryptoFacade({
|
|
691
|
+
client: this.client,
|
|
692
|
+
verificationManager: this.verificationManager,
|
|
693
|
+
recoveryKeyStore: this.recoveryKeyStore,
|
|
694
|
+
getRoomStateEvent: (roomId, eventType, stateKey = "") => this.getRoomStateEvent(roomId, eventType, stateKey),
|
|
695
|
+
downloadContent: (mxcUrl) => this.downloadContent(mxcUrl)
|
|
696
|
+
});
|
|
697
|
+
if (!this.verificationSummaryListenerBound) {
|
|
698
|
+
this.verificationSummaryListenerBound = true;
|
|
699
|
+
this.verificationManager.onSummaryChanged((summary) => {
|
|
700
|
+
this.emitter.emit("verification.summary", summary);
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
async start(opts = {}) {
|
|
705
|
+
await this.startSyncSession({
|
|
706
|
+
bootstrapCrypto: true,
|
|
707
|
+
abortSignal: opts.abortSignal,
|
|
708
|
+
readyTimeoutMs: opts.readyTimeoutMs
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
async waitForInitialSyncReady(params = {}) {
|
|
712
|
+
const timeoutMs = params.timeoutMs ?? 3e4;
|
|
713
|
+
if (isMatrixReadySyncState(this.currentSyncState)) return;
|
|
714
|
+
if (isMatrixTerminalSyncState(this.currentSyncState)) throw new Error(`Matrix sync entered ${this.currentSyncState} during startup`);
|
|
715
|
+
await new Promise((resolve, reject) => {
|
|
716
|
+
let settled = false;
|
|
717
|
+
let timeoutId;
|
|
718
|
+
const abortSignal = params.abortSignal;
|
|
719
|
+
const cleanup = () => {
|
|
720
|
+
this.off("sync.state", onSyncState);
|
|
721
|
+
this.off("sync.unexpected_error", onUnexpectedError);
|
|
722
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
723
|
+
if (timeoutId) {
|
|
724
|
+
clearTimeout(timeoutId);
|
|
725
|
+
timeoutId = void 0;
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
const settleResolve = () => {
|
|
729
|
+
if (settled) return;
|
|
730
|
+
settled = true;
|
|
731
|
+
cleanup();
|
|
732
|
+
resolve();
|
|
733
|
+
};
|
|
734
|
+
const settleReject = (error) => {
|
|
735
|
+
if (settled) return;
|
|
736
|
+
settled = true;
|
|
737
|
+
cleanup();
|
|
738
|
+
reject(error);
|
|
739
|
+
};
|
|
740
|
+
const onSyncState = (state, _prevState, error) => {
|
|
741
|
+
if (isMatrixReadySyncState(state)) {
|
|
742
|
+
settleResolve();
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
if (isMatrixTerminalSyncState(state)) settleReject(new Error(error instanceof Error && error.message ? error.message : `Matrix sync entered ${state} during startup`));
|
|
746
|
+
};
|
|
747
|
+
const onUnexpectedError = (error) => {
|
|
748
|
+
settleReject(error);
|
|
749
|
+
};
|
|
750
|
+
const onAbort = () => {
|
|
751
|
+
settleReject(createMatrixStartupAbortError());
|
|
752
|
+
};
|
|
753
|
+
this.on("sync.state", onSyncState);
|
|
754
|
+
this.on("sync.unexpected_error", onUnexpectedError);
|
|
755
|
+
if (abortSignal?.aborted) {
|
|
756
|
+
onAbort();
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
760
|
+
timeoutId = setTimeout(() => {
|
|
761
|
+
settleReject(/* @__PURE__ */ new Error(`Matrix client did not reach a ready sync state within ${timeoutMs}ms`));
|
|
762
|
+
}, timeoutMs);
|
|
763
|
+
timeoutId.unref?.();
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
async startSyncSession(opts) {
|
|
767
|
+
if (this.started) return;
|
|
768
|
+
throwIfMatrixStartupAborted(opts.abortSignal);
|
|
769
|
+
await this.ensureCryptoSupportInitialized();
|
|
770
|
+
throwIfMatrixStartupAborted(opts.abortSignal);
|
|
771
|
+
this.registerBridge();
|
|
772
|
+
await this.initializeCryptoIfNeeded(opts.abortSignal);
|
|
773
|
+
await this.client.startClient({
|
|
774
|
+
initialSyncLimit: this.initialSyncLimit,
|
|
775
|
+
...this.syncFilter ? { filter: Filter.fromJson(this.selfUserId, "", this.syncFilter) } : {}
|
|
776
|
+
});
|
|
777
|
+
await this.waitForInitialSyncReady({
|
|
778
|
+
abortSignal: opts.abortSignal,
|
|
779
|
+
timeoutMs: opts.readyTimeoutMs
|
|
780
|
+
});
|
|
781
|
+
throwIfMatrixStartupAborted(opts.abortSignal);
|
|
782
|
+
if (opts.bootstrapCrypto && this.autoBootstrapCrypto) await this.bootstrapCryptoIfNeeded(opts.abortSignal);
|
|
783
|
+
throwIfMatrixStartupAborted(opts.abortSignal);
|
|
784
|
+
this.started = true;
|
|
785
|
+
this.emitOutstandingInviteEvents();
|
|
786
|
+
await this.refreshDmCache().catch(noop);
|
|
787
|
+
}
|
|
788
|
+
async prepareForOneOff() {
|
|
789
|
+
if (!this.encryptionEnabled) return;
|
|
790
|
+
await this.ensureCryptoSupportInitialized();
|
|
791
|
+
await this.initializeCryptoIfNeeded();
|
|
792
|
+
if (!this.crypto) return;
|
|
793
|
+
try {
|
|
794
|
+
const joinedRooms = await this.getJoinedRooms();
|
|
795
|
+
await this.crypto.prepare(joinedRooms);
|
|
796
|
+
} catch {}
|
|
797
|
+
}
|
|
798
|
+
hasPersistedSyncState() {
|
|
799
|
+
return this.syncStore?.hasSavedSyncFromCleanShutdown() === true;
|
|
800
|
+
}
|
|
801
|
+
async ensureStartedForCryptoControlPlane() {
|
|
802
|
+
if (this.started) return;
|
|
803
|
+
await this.startSyncSession({ bootstrapCrypto: false });
|
|
804
|
+
}
|
|
805
|
+
stopSyncWithoutPersist() {
|
|
806
|
+
if (this.idbPersistTimer) {
|
|
807
|
+
clearInterval(this.idbPersistTimer);
|
|
808
|
+
this.idbPersistTimer = null;
|
|
809
|
+
}
|
|
810
|
+
this.currentSyncState = null;
|
|
811
|
+
this.client.stopClient();
|
|
812
|
+
this.started = false;
|
|
813
|
+
}
|
|
814
|
+
async drainPendingDecryptions(reason = "matrix client shutdown") {
|
|
815
|
+
await this.decryptBridge?.drainPendingDecryptions(reason);
|
|
816
|
+
}
|
|
817
|
+
stop() {
|
|
818
|
+
this.stopSyncWithoutPersist();
|
|
819
|
+
this.decryptBridge?.stop();
|
|
820
|
+
this.syncStore?.markCleanShutdown();
|
|
821
|
+
if (loadedMatrixCryptoRuntime) {
|
|
822
|
+
const { persistIdbToDisk } = loadedMatrixCryptoRuntime;
|
|
823
|
+
this.stopPersistPromise = Promise.all([persistIdbToDisk({
|
|
824
|
+
snapshotPath: this.idbSnapshotPath,
|
|
825
|
+
databasePrefix: this.cryptoDatabasePrefix
|
|
826
|
+
}).catch(noop), this.syncStore?.flush().catch(noop)]).then(() => void 0);
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
this.stopPersistPromise = loadMatrixCryptoRuntime().then(async ({ persistIdbToDisk }) => {
|
|
830
|
+
await Promise.all([persistIdbToDisk({
|
|
831
|
+
snapshotPath: this.idbSnapshotPath,
|
|
832
|
+
databasePrefix: this.cryptoDatabasePrefix
|
|
833
|
+
}).catch(noop), this.syncStore?.flush().catch(noop)]);
|
|
834
|
+
}).catch(noop).then(() => void 0);
|
|
835
|
+
}
|
|
836
|
+
async stopAndPersist() {
|
|
837
|
+
this.stop();
|
|
838
|
+
await this.stopPersistPromise;
|
|
839
|
+
}
|
|
840
|
+
stopWithoutPersist() {
|
|
841
|
+
this.stopSyncWithoutPersist();
|
|
842
|
+
this.decryptBridge?.stop();
|
|
843
|
+
this.stopPersistPromise = Promise.resolve();
|
|
844
|
+
}
|
|
845
|
+
async bootstrapCryptoIfNeeded(abortSignal) {
|
|
846
|
+
if (!this.encryptionEnabled || !this.cryptoInitialized || this.cryptoBootstrapped) return;
|
|
847
|
+
throwIfMatrixStartupAborted(abortSignal);
|
|
848
|
+
await this.ensureCryptoSupportInitialized();
|
|
849
|
+
const crypto = this.client.getCrypto();
|
|
850
|
+
if (!crypto) return;
|
|
851
|
+
const cryptoBootstrapper = this.cryptoBootstrapper;
|
|
852
|
+
if (!cryptoBootstrapper) return;
|
|
853
|
+
const initial = await cryptoBootstrapper.bootstrap(crypto, MATRIX_INITIAL_CRYPTO_BOOTSTRAP_OPTIONS);
|
|
854
|
+
throwIfMatrixStartupAborted(abortSignal);
|
|
855
|
+
if (!initial.crossSigningPublished || initial.ownDeviceVerified === false) if ((await this.getOwnDeviceVerificationStatus()).signedByOwner) LogService.warn("MatrixClientLite", "Cross-signing/bootstrap is incomplete for an already owner-signed device; skipping automatic reset and preserving the current identity. Restore the recovery key or run an explicit verification bootstrap if repair is needed.");
|
|
856
|
+
else try {
|
|
857
|
+
const repaired = await cryptoBootstrapper.bootstrap(crypto, MATRIX_AUTOMATIC_REPAIR_BOOTSTRAP_OPTIONS);
|
|
858
|
+
throwIfMatrixStartupAborted(abortSignal);
|
|
859
|
+
if (repaired.crossSigningPublished && repaired.ownDeviceVerified !== false) LogService.info("MatrixClientLite", "Cross-signing/bootstrap recovered after forced reset");
|
|
860
|
+
} catch (err) {
|
|
861
|
+
LogService.warn("MatrixClientLite", "Failed to recover cross-signing/bootstrap with forced reset:", err);
|
|
862
|
+
}
|
|
863
|
+
this.cryptoBootstrapped = true;
|
|
864
|
+
}
|
|
865
|
+
async initializeCryptoIfNeeded(abortSignal) {
|
|
866
|
+
if (!this.encryptionEnabled || this.cryptoInitialized) return;
|
|
867
|
+
throwIfMatrixStartupAborted(abortSignal);
|
|
868
|
+
const { persistIdbToDisk, restoreIdbFromDisk } = await loadMatrixCryptoRuntime();
|
|
869
|
+
await restoreIdbFromDisk(this.idbSnapshotPath);
|
|
870
|
+
throwIfMatrixStartupAborted(abortSignal);
|
|
871
|
+
try {
|
|
872
|
+
await this.client.initRustCrypto({ cryptoDatabasePrefix: this.cryptoDatabasePrefix });
|
|
873
|
+
this.cryptoInitialized = true;
|
|
874
|
+
throwIfMatrixStartupAborted(abortSignal);
|
|
875
|
+
await persistIdbToDisk({
|
|
876
|
+
snapshotPath: this.idbSnapshotPath,
|
|
877
|
+
databasePrefix: this.cryptoDatabasePrefix
|
|
878
|
+
});
|
|
879
|
+
throwIfMatrixStartupAborted(abortSignal);
|
|
880
|
+
this.idbPersistTimer = setInterval(() => {
|
|
881
|
+
persistIdbToDisk({
|
|
882
|
+
snapshotPath: this.idbSnapshotPath,
|
|
883
|
+
databasePrefix: this.cryptoDatabasePrefix
|
|
884
|
+
}).catch(noop);
|
|
885
|
+
}, MATRIX_IDB_PERSIST_INTERVAL_MS);
|
|
886
|
+
this.idbPersistTimer.unref?.();
|
|
887
|
+
} catch (err) {
|
|
888
|
+
LogService.warn("MatrixClientLite", "Failed to initialize rust crypto:", err);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
async getUserId() {
|
|
892
|
+
const fromClient = this.client.getUserId();
|
|
893
|
+
if (fromClient) {
|
|
894
|
+
this.selfUserId = fromClient;
|
|
895
|
+
return fromClient;
|
|
896
|
+
}
|
|
897
|
+
if (this.selfUserId) return this.selfUserId;
|
|
898
|
+
const resolved = (await this.doRequest("GET", "/_matrix/client/v3/account/whoami")).user_id?.trim();
|
|
899
|
+
if (!resolved) throw new Error("Matrix whoami did not return user_id");
|
|
900
|
+
this.selfUserId = resolved;
|
|
901
|
+
return resolved;
|
|
902
|
+
}
|
|
903
|
+
async getJoinedRooms() {
|
|
904
|
+
const joined = await this.doRequest("GET", "/_matrix/client/v3/joined_rooms");
|
|
905
|
+
return Array.isArray(joined.joined_rooms) ? joined.joined_rooms : [];
|
|
906
|
+
}
|
|
907
|
+
async getJoinedRoomMembers(roomId) {
|
|
908
|
+
const joined = (await this.client.getJoinedRoomMembers(roomId))?.joined;
|
|
909
|
+
if (!joined || typeof joined !== "object") return [];
|
|
910
|
+
return Object.keys(joined);
|
|
911
|
+
}
|
|
912
|
+
hasSyncedJoinedRoomMember(roomId, userId) {
|
|
913
|
+
return (this.client.getRoom?.(roomId))?.currentState?.getMember?.(userId)?.membership === "join";
|
|
914
|
+
}
|
|
915
|
+
async getRoomStateEvent(roomId, eventType, stateKey = "") {
|
|
916
|
+
return await this.client.getStateEvent(roomId, eventType, stateKey) ?? {};
|
|
917
|
+
}
|
|
918
|
+
async getAccountData(eventType) {
|
|
919
|
+
return this.client.getAccountData(eventType)?.getContent() ?? void 0;
|
|
920
|
+
}
|
|
921
|
+
async setAccountData(eventType, content) {
|
|
922
|
+
await this.client.setAccountData(eventType, content);
|
|
923
|
+
await this.refreshDmCache().catch(noop);
|
|
924
|
+
}
|
|
925
|
+
async resolveRoom(aliasOrRoomId) {
|
|
926
|
+
if (aliasOrRoomId.startsWith("!")) return aliasOrRoomId;
|
|
927
|
+
if (!aliasOrRoomId.startsWith("#")) return aliasOrRoomId;
|
|
928
|
+
try {
|
|
929
|
+
return (await this.client.getRoomIdForAlias(aliasOrRoomId)).room_id ?? null;
|
|
930
|
+
} catch {
|
|
931
|
+
return null;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
async createDirectRoom(remoteUserId, opts = {}) {
|
|
935
|
+
const initialState = opts.encrypted ? [{
|
|
936
|
+
type: "m.room.encryption",
|
|
937
|
+
state_key: "",
|
|
938
|
+
content: { algorithm: "m.megolm.v1.aes-sha2" }
|
|
939
|
+
}] : void 0;
|
|
940
|
+
return (await this.client.createRoom({
|
|
941
|
+
invite: [remoteUserId],
|
|
942
|
+
is_direct: true,
|
|
943
|
+
preset: Preset.TrustedPrivateChat,
|
|
944
|
+
initial_state: initialState
|
|
945
|
+
})).room_id;
|
|
946
|
+
}
|
|
947
|
+
async sendMessage(roomId, content) {
|
|
948
|
+
return await this.runSerializedRoomSend(roomId, async () => {
|
|
949
|
+
return (await this.client.sendMessage(roomId, content)).event_id;
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
async sendEvent(roomId, eventType, content) {
|
|
953
|
+
return await this.runSerializedRoomSend(roomId, async () => {
|
|
954
|
+
return (await this.client.sendEvent(roomId, eventType, content)).event_id;
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
async runSerializedRoomSend(roomId, task) {
|
|
958
|
+
return await this.sendQueue.enqueue(roomId, task);
|
|
959
|
+
}
|
|
960
|
+
async sendStateEvent(roomId, eventType, stateKey, content) {
|
|
961
|
+
return (await this.client.sendStateEvent(roomId, eventType, content, stateKey)).event_id;
|
|
962
|
+
}
|
|
963
|
+
async redactEvent(roomId, eventId, reason) {
|
|
964
|
+
return (await this.client.redactEvent(roomId, eventId, void 0, reason?.trim() ? { reason } : void 0)).event_id;
|
|
965
|
+
}
|
|
966
|
+
async doRequest(method, endpoint, qs, body, opts) {
|
|
967
|
+
return await this.httpClient.requestJson({
|
|
968
|
+
method,
|
|
969
|
+
endpoint,
|
|
970
|
+
qs,
|
|
971
|
+
body,
|
|
972
|
+
timeoutMs: this.localTimeoutMs,
|
|
973
|
+
allowAbsoluteEndpoint: opts?.allowAbsoluteEndpoint
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
async getUserProfile(userId) {
|
|
977
|
+
return await this.client.getProfileInfo(userId);
|
|
978
|
+
}
|
|
979
|
+
async setDisplayName(displayName) {
|
|
980
|
+
await this.client.setDisplayName(displayName);
|
|
981
|
+
}
|
|
982
|
+
async setAvatarUrl(avatarUrl) {
|
|
983
|
+
await this.client.setAvatarUrl(avatarUrl);
|
|
984
|
+
}
|
|
985
|
+
async joinRoom(roomId) {
|
|
986
|
+
await this.client.joinRoom(roomId);
|
|
987
|
+
}
|
|
988
|
+
mxcToHttp(mxcUrl) {
|
|
989
|
+
return this.client.mxcUrlToHttp(mxcUrl, void 0, void 0, void 0, true, false, true);
|
|
990
|
+
}
|
|
991
|
+
async downloadContent(mxcUrl, opts = {}) {
|
|
992
|
+
const parsed = parseMxc(mxcUrl);
|
|
993
|
+
if (!parsed) throw new Error(`Invalid Matrix content URI: ${mxcUrl}`);
|
|
994
|
+
const encodedServer = encodeURIComponent(parsed.server);
|
|
995
|
+
const encodedMediaId = encodeURIComponent(parsed.mediaId);
|
|
996
|
+
const request = async (endpoint) => await this.httpClient.requestRaw({
|
|
997
|
+
method: "GET",
|
|
998
|
+
endpoint,
|
|
999
|
+
qs: { allow_remote: opts.allowRemote ?? true },
|
|
1000
|
+
timeoutMs: this.localTimeoutMs,
|
|
1001
|
+
maxBytes: opts.maxBytes,
|
|
1002
|
+
readIdleTimeoutMs: opts.readIdleTimeoutMs
|
|
1003
|
+
});
|
|
1004
|
+
const authenticatedEndpoint = `/_matrix/client/v1/media/download/${encodedServer}/${encodedMediaId}`;
|
|
1005
|
+
try {
|
|
1006
|
+
return await request(authenticatedEndpoint);
|
|
1007
|
+
} catch (err) {
|
|
1008
|
+
if (!isUnsupportedAuthenticatedMediaEndpointError(err)) throw err;
|
|
1009
|
+
}
|
|
1010
|
+
return await request(`/_matrix/media/v3/download/${encodedServer}/${encodedMediaId}`);
|
|
1011
|
+
}
|
|
1012
|
+
async uploadContent(file, contentType, filename) {
|
|
1013
|
+
return (await this.client.uploadContent(new Uint8Array(file), {
|
|
1014
|
+
type: contentType || "application/octet-stream",
|
|
1015
|
+
name: filename,
|
|
1016
|
+
includeFilename: Boolean(filename)
|
|
1017
|
+
})).content_uri;
|
|
1018
|
+
}
|
|
1019
|
+
async getEvent(roomId, eventId) {
|
|
1020
|
+
const rawEvent = await this.client.fetchRoomEvent(roomId, eventId);
|
|
1021
|
+
if (rawEvent.type !== "m.room.encrypted") return rawEvent;
|
|
1022
|
+
const event = this.client.getEventMapper()(rawEvent);
|
|
1023
|
+
let decryptedEvent;
|
|
1024
|
+
const onDecrypted = (candidate) => {
|
|
1025
|
+
decryptedEvent = candidate;
|
|
1026
|
+
};
|
|
1027
|
+
event.once(MatrixEventEvent.Decrypted, onDecrypted);
|
|
1028
|
+
try {
|
|
1029
|
+
await this.client.decryptEventIfNeeded(event);
|
|
1030
|
+
} finally {
|
|
1031
|
+
event.off(MatrixEventEvent.Decrypted, onDecrypted);
|
|
1032
|
+
}
|
|
1033
|
+
return matrixEventToRaw(decryptedEvent ?? event);
|
|
1034
|
+
}
|
|
1035
|
+
async getRelations(roomId, eventId, relationType, eventType, opts = {}) {
|
|
1036
|
+
const result = await this.client.relations(roomId, eventId, relationType, eventType, opts);
|
|
1037
|
+
return {
|
|
1038
|
+
originalEvent: result.originalEvent ? matrixEventToRaw(result.originalEvent) : null,
|
|
1039
|
+
events: result.events.map((event) => matrixEventToRaw(event)),
|
|
1040
|
+
nextBatch: result.nextBatch ?? null,
|
|
1041
|
+
prevBatch: result.prevBatch ?? null
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
async hydrateEvents(roomId, events) {
|
|
1045
|
+
if (events.length === 0) return [];
|
|
1046
|
+
const mapper = this.client.getEventMapper();
|
|
1047
|
+
const mappedEvents = events.map((event) => mapper({
|
|
1048
|
+
room_id: roomId,
|
|
1049
|
+
...event
|
|
1050
|
+
}));
|
|
1051
|
+
await Promise.all(mappedEvents.map((event) => this.client.decryptEventIfNeeded(event)));
|
|
1052
|
+
return mappedEvents.map((event) => matrixEventToRaw(event));
|
|
1053
|
+
}
|
|
1054
|
+
async setTyping(roomId, typing, timeoutMs) {
|
|
1055
|
+
await this.client.sendTyping(roomId, typing, timeoutMs);
|
|
1056
|
+
}
|
|
1057
|
+
async sendReadReceipt(roomId, eventId) {
|
|
1058
|
+
await this.httpClient.requestJson({
|
|
1059
|
+
method: "POST",
|
|
1060
|
+
endpoint: `/_matrix/client/v3/rooms/${encodeURIComponent(roomId)}/receipt/m.read/${encodeURIComponent(eventId)}`,
|
|
1061
|
+
body: {},
|
|
1062
|
+
timeoutMs: this.localTimeoutMs
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
async getRoomKeyBackupStatus() {
|
|
1066
|
+
if (!this.encryptionEnabled) return {
|
|
1067
|
+
serverVersion: null,
|
|
1068
|
+
activeVersion: null,
|
|
1069
|
+
trusted: null,
|
|
1070
|
+
matchesDecryptionKey: null,
|
|
1071
|
+
decryptionKeyCached: null,
|
|
1072
|
+
keyLoadAttempted: false,
|
|
1073
|
+
keyLoadError: null
|
|
1074
|
+
};
|
|
1075
|
+
const crypto = this.client.getCrypto();
|
|
1076
|
+
const serverVersionFallback = await this.resolveRoomKeyBackupVersion();
|
|
1077
|
+
if (!crypto) return {
|
|
1078
|
+
serverVersion: serverVersionFallback,
|
|
1079
|
+
activeVersion: null,
|
|
1080
|
+
trusted: null,
|
|
1081
|
+
matchesDecryptionKey: null,
|
|
1082
|
+
decryptionKeyCached: null,
|
|
1083
|
+
keyLoadAttempted: false,
|
|
1084
|
+
keyLoadError: null
|
|
1085
|
+
};
|
|
1086
|
+
let { activeVersion, decryptionKeyCached } = await this.resolveRoomKeyBackupLocalState(crypto);
|
|
1087
|
+
let { serverVersion, trusted, matchesDecryptionKey } = await this.resolveRoomKeyBackupTrustState(crypto, serverVersionFallback);
|
|
1088
|
+
const shouldLoadBackupKey = Boolean(serverVersion) && (decryptionKeyCached === false || matchesDecryptionKey === false);
|
|
1089
|
+
const shouldActivateBackup = Boolean(serverVersion) && !activeVersion;
|
|
1090
|
+
let keyLoadAttempted = false;
|
|
1091
|
+
let keyLoadError = null;
|
|
1092
|
+
if (serverVersion && (shouldLoadBackupKey || shouldActivateBackup)) {
|
|
1093
|
+
if (shouldLoadBackupKey) if (typeof crypto.loadSessionBackupPrivateKeyFromSecretStorage === "function") {
|
|
1094
|
+
keyLoadAttempted = true;
|
|
1095
|
+
try {
|
|
1096
|
+
await crypto.loadSessionBackupPrivateKeyFromSecretStorage();
|
|
1097
|
+
} catch (err) {
|
|
1098
|
+
keyLoadError = formatMatrixErrorMessage(err);
|
|
1099
|
+
}
|
|
1100
|
+
} else keyLoadError = "Matrix crypto backend does not support loading backup keys from secret storage";
|
|
1101
|
+
if (!keyLoadError) await this.enableTrustedRoomKeyBackupIfPossible(crypto);
|
|
1102
|
+
({activeVersion, decryptionKeyCached} = await this.resolveRoomKeyBackupLocalState(crypto));
|
|
1103
|
+
({serverVersion, trusted, matchesDecryptionKey} = await this.resolveRoomKeyBackupTrustState(crypto, serverVersion));
|
|
1104
|
+
}
|
|
1105
|
+
return {
|
|
1106
|
+
serverVersion,
|
|
1107
|
+
activeVersion,
|
|
1108
|
+
trusted,
|
|
1109
|
+
matchesDecryptionKey,
|
|
1110
|
+
decryptionKeyCached,
|
|
1111
|
+
keyLoadAttempted,
|
|
1112
|
+
keyLoadError
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
async getDeviceVerificationStatus(userId, deviceId) {
|
|
1116
|
+
const normalizedUserId = userId?.trim() || null;
|
|
1117
|
+
const normalizedDeviceId = deviceId?.trim() || null;
|
|
1118
|
+
if (!this.encryptionEnabled) return {
|
|
1119
|
+
encryptionEnabled: false,
|
|
1120
|
+
userId: normalizedUserId,
|
|
1121
|
+
deviceId: normalizedDeviceId,
|
|
1122
|
+
verified: false,
|
|
1123
|
+
localVerified: false,
|
|
1124
|
+
crossSigningVerified: false,
|
|
1125
|
+
signedByOwner: false
|
|
1126
|
+
};
|
|
1127
|
+
const crypto = this.client.getCrypto();
|
|
1128
|
+
let deviceStatus = null;
|
|
1129
|
+
if (crypto && normalizedUserId && normalizedDeviceId && typeof crypto.getDeviceVerificationStatus === "function") deviceStatus = await crypto.getDeviceVerificationStatus(normalizedUserId, normalizedDeviceId).catch(() => null);
|
|
1130
|
+
const { isMatrixDeviceVerifiedInCurrentClient } = await loadMatrixCryptoRuntime();
|
|
1131
|
+
return {
|
|
1132
|
+
encryptionEnabled: true,
|
|
1133
|
+
userId: normalizedUserId,
|
|
1134
|
+
deviceId: normalizedDeviceId,
|
|
1135
|
+
verified: isMatrixDeviceVerifiedInCurrentClient(deviceStatus),
|
|
1136
|
+
localVerified: deviceStatus?.localVerified === true,
|
|
1137
|
+
crossSigningVerified: deviceStatus?.crossSigningVerified === true,
|
|
1138
|
+
signedByOwner: deviceStatus?.signedByOwner === true
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
async getOwnDeviceVerificationStatus() {
|
|
1142
|
+
const recoveryKey = this.recoveryKeyStore.getRecoveryKeySummary();
|
|
1143
|
+
const userId = this.client.getUserId() ?? this.selfUserId ?? null;
|
|
1144
|
+
const deviceId = this.client.getDeviceId()?.trim() || null;
|
|
1145
|
+
const diagnosticTimeoutMs = Math.min(this.localTimeoutMs, MATRIX_STATUS_DIAGNOSTIC_TIMEOUT_MS);
|
|
1146
|
+
const [backup, deviceVerification, ownDevices] = await Promise.all([
|
|
1147
|
+
resolveMatrixDiagnostic(this.getRoomKeyBackupStatus(), diagnosticTimeoutMs),
|
|
1148
|
+
resolveMatrixDiagnostic(this.getDeviceVerificationStatus(userId, deviceId), diagnosticTimeoutMs),
|
|
1149
|
+
resolveMatrixDiagnosticResult(this.listOwnDevices(), diagnosticTimeoutMs)
|
|
1150
|
+
]);
|
|
1151
|
+
const resolvedBackup = backup ?? unresolvedMatrixRoomKeyBackupStatus();
|
|
1152
|
+
const resolvedDeviceVerification = deviceVerification ?? unresolvedMatrixDeviceVerificationStatus({
|
|
1153
|
+
userId,
|
|
1154
|
+
deviceId
|
|
1155
|
+
});
|
|
1156
|
+
const serverDeviceKnown = deviceId ? ownDevices.value ? ownDevices.value.some((device) => device.deviceId === deviceId) : isMatrixAccessTokenInvalidatedError(ownDevices.error) ? false : null : null;
|
|
1157
|
+
return {
|
|
1158
|
+
...resolvedDeviceVerification,
|
|
1159
|
+
verified: resolvedDeviceVerification.crossSigningVerified,
|
|
1160
|
+
recoveryKeyStored: Boolean(recoveryKey),
|
|
1161
|
+
recoveryKeyCreatedAt: recoveryKey?.createdAt ?? null,
|
|
1162
|
+
recoveryKeyId: recoveryKey?.keyId ?? null,
|
|
1163
|
+
backupVersion: resolvedBackup.serverVersion,
|
|
1164
|
+
backup: resolvedBackup,
|
|
1165
|
+
serverDeviceKnown
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
async getOwnDeviceIdentityVerificationStatus() {
|
|
1169
|
+
const userId = this.client.getUserId() ?? this.selfUserId ?? null;
|
|
1170
|
+
const deviceId = this.client.getDeviceId()?.trim() || null;
|
|
1171
|
+
const deviceVerification = await this.getDeviceVerificationStatus(userId, deviceId);
|
|
1172
|
+
return {
|
|
1173
|
+
...deviceVerification,
|
|
1174
|
+
verified: deviceVerification.crossSigningVerified
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1177
|
+
async trustOwnIdentityAfterSelfVerification() {
|
|
1178
|
+
if (!this.encryptionEnabled) return;
|
|
1179
|
+
await this.ensureStartedForCryptoControlPlane();
|
|
1180
|
+
await this.ensureCryptoSupportInitialized();
|
|
1181
|
+
const crypto = this.client.getCrypto();
|
|
1182
|
+
const ownIdentity = crypto && typeof crypto.getOwnIdentity === "function" ? await crypto.getOwnIdentity().catch(() => void 0) : void 0;
|
|
1183
|
+
if (!ownIdentity) return;
|
|
1184
|
+
try {
|
|
1185
|
+
if (typeof ownIdentity.isVerified === "function" && ownIdentity.isVerified()) return;
|
|
1186
|
+
if (typeof ownIdentity.verify !== "function") return;
|
|
1187
|
+
await ownIdentity.verify();
|
|
1188
|
+
} finally {
|
|
1189
|
+
ownIdentity.free?.();
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
async verifyWithRecoveryKey(rawRecoveryKey) {
|
|
1193
|
+
const fail = async (error, fields = {}) => {
|
|
1194
|
+
const status = await this.getOwnDeviceVerificationStatus();
|
|
1195
|
+
return {
|
|
1196
|
+
success: false,
|
|
1197
|
+
recoveryKeyAccepted: fields.recoveryKeyAccepted ?? false,
|
|
1198
|
+
backupUsable: fields.backupUsable ?? false,
|
|
1199
|
+
deviceOwnerVerified: fields.deviceOwnerVerified ?? status.verified,
|
|
1200
|
+
error,
|
|
1201
|
+
...status
|
|
1202
|
+
};
|
|
1203
|
+
};
|
|
1204
|
+
if (!this.encryptionEnabled) return await fail("Matrix encryption is disabled for this client");
|
|
1205
|
+
await this.ensureStartedForCryptoControlPlane();
|
|
1206
|
+
await this.ensureCryptoSupportInitialized();
|
|
1207
|
+
const crypto = this.client.getCrypto();
|
|
1208
|
+
if (!crypto) return await fail("Matrix crypto is not available (start client with encryption enabled)");
|
|
1209
|
+
const backupUsableBeforeStagedRecovery = resolveMatrixRoomKeyBackupReadinessError(await this.getRoomKeyBackupStatus(), { requireServerBackup: true }) === null;
|
|
1210
|
+
const trimmedRecoveryKey = rawRecoveryKey.trim();
|
|
1211
|
+
if (!trimmedRecoveryKey) return await fail("Matrix recovery key is required");
|
|
1212
|
+
let stagedKeyId = null;
|
|
1213
|
+
try {
|
|
1214
|
+
stagedKeyId = await this.resolveDefaultSecretStorageKeyId(crypto) ?? null;
|
|
1215
|
+
this.recoveryKeyStore.stageEncodedRecoveryKey({
|
|
1216
|
+
encodedPrivateKey: trimmedRecoveryKey,
|
|
1217
|
+
keyId: stagedKeyId
|
|
1218
|
+
});
|
|
1219
|
+
} catch (err) {
|
|
1220
|
+
return await fail(formatMatrixErrorMessage(err));
|
|
1221
|
+
}
|
|
1222
|
+
const storedRecoveryKeyMatches = this.recoveryKeyStore.getRecoveryKeySummary()?.encodedPrivateKey?.trim() === trimmedRecoveryKey;
|
|
1223
|
+
if (backupUsableBeforeStagedRecovery && storedRecoveryKeyMatches) {
|
|
1224
|
+
const status = await this.getOwnDeviceVerificationStatus();
|
|
1225
|
+
const backupUsable = resolveMatrixRoomKeyBackupReadinessError(status.backup, { requireServerBackup: true }) === null;
|
|
1226
|
+
const backupError = resolveMatrixRoomKeyBackupReadinessError(status.backup, { requireServerBackup: false });
|
|
1227
|
+
const recoveryKeyAccepted = backupUsable;
|
|
1228
|
+
if (!status.verified) {
|
|
1229
|
+
if (recoveryKeyAccepted) this.recoveryKeyStore.commitStagedRecoveryKey({ keyId: stagedKeyId });
|
|
1230
|
+
else this.recoveryKeyStore.discardStagedRecoveryKey();
|
|
1231
|
+
return {
|
|
1232
|
+
success: false,
|
|
1233
|
+
recoveryKeyAccepted,
|
|
1234
|
+
backupUsable,
|
|
1235
|
+
deviceOwnerVerified: false,
|
|
1236
|
+
error: "Matrix recovery key was applied, but this device still lacks full Matrix identity trust. The recovery key can unlock usable backup material only when 'Backup usable' is yes; full identity trust still requires Matrix cross-signing verification.",
|
|
1237
|
+
...status
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
if (backupError) {
|
|
1241
|
+
this.recoveryKeyStore.discardStagedRecoveryKey();
|
|
1242
|
+
return {
|
|
1243
|
+
success: false,
|
|
1244
|
+
recoveryKeyAccepted,
|
|
1245
|
+
backupUsable,
|
|
1246
|
+
deviceOwnerVerified: true,
|
|
1247
|
+
error: backupError,
|
|
1248
|
+
...status
|
|
1249
|
+
};
|
|
1250
|
+
}
|
|
1251
|
+
this.recoveryKeyStore.commitStagedRecoveryKey({ keyId: stagedKeyId });
|
|
1252
|
+
return {
|
|
1253
|
+
success: true,
|
|
1254
|
+
recoveryKeyAccepted: true,
|
|
1255
|
+
backupUsable,
|
|
1256
|
+
deviceOwnerVerified: true,
|
|
1257
|
+
verifiedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1258
|
+
...status
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
try {
|
|
1262
|
+
const cryptoBootstrapper = this.cryptoBootstrapper;
|
|
1263
|
+
if (!cryptoBootstrapper) return await fail("Matrix crypto bootstrapper is not available");
|
|
1264
|
+
await cryptoBootstrapper.bootstrap(crypto, { allowAutomaticCrossSigningReset: false });
|
|
1265
|
+
await this.enableTrustedRoomKeyBackupIfPossible(crypto);
|
|
1266
|
+
const status = await this.getOwnDeviceVerificationStatus();
|
|
1267
|
+
const backupError = resolveMatrixRoomKeyBackupReadinessError(status.backup, { requireServerBackup: false });
|
|
1268
|
+
const backupUsable = resolveMatrixRoomKeyBackupReadinessError(status.backup, { requireServerBackup: true }) === null;
|
|
1269
|
+
const stagedRecoveryKeyUsed = this.recoveryKeyStore.hasStagedRecoveryKeyBeenUsed();
|
|
1270
|
+
const secretStorageStatus = typeof crypto.getSecretStorageStatus === "function" ? await crypto.getSecretStorageStatus().catch(() => null) : null;
|
|
1271
|
+
const stagedRecoveryKeyConfirmedBySecretStorage = Boolean(stagedKeyId) && secretStorageStatus?.secretStorageKeyValidityMap?.[stagedKeyId ?? ""] === true;
|
|
1272
|
+
const stagedRecoveryKeyRejectedBySecretStorage = Boolean(stagedKeyId) && secretStorageStatus?.secretStorageKeyValidityMap?.[stagedKeyId ?? ""] === false;
|
|
1273
|
+
const stagedRecoveryKeyValidated = stagedRecoveryKeyUsed && (stagedRecoveryKeyConfirmedBySecretStorage || stagedRecoveryKeyUsed && !stagedRecoveryKeyRejectedBySecretStorage && !stagedRecoveryKeyConfirmedBySecretStorage && !backupUsableBeforeStagedRecovery && backupUsable) || storedRecoveryKeyMatches && backupUsable;
|
|
1274
|
+
const recoveryKeyAccepted = stagedRecoveryKeyValidated && (status.verified || backupUsable);
|
|
1275
|
+
if (!status.verified) {
|
|
1276
|
+
if (backupUsable && stagedRecoveryKeyValidated) this.recoveryKeyStore.commitStagedRecoveryKey({ keyId: stagedKeyId });
|
|
1277
|
+
else this.recoveryKeyStore.discardStagedRecoveryKey();
|
|
1278
|
+
return {
|
|
1279
|
+
success: false,
|
|
1280
|
+
recoveryKeyAccepted,
|
|
1281
|
+
backupUsable,
|
|
1282
|
+
deviceOwnerVerified: false,
|
|
1283
|
+
error: "Matrix recovery key was applied, but this device still lacks full Matrix identity trust. The recovery key can unlock usable backup material only when 'Backup usable' is yes; full identity trust still requires Matrix cross-signing verification.",
|
|
1284
|
+
...recoveryKeyAccepted ? await this.getOwnDeviceVerificationStatus() : status
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1287
|
+
if (backupError) {
|
|
1288
|
+
this.recoveryKeyStore.discardStagedRecoveryKey();
|
|
1289
|
+
return {
|
|
1290
|
+
success: false,
|
|
1291
|
+
recoveryKeyAccepted,
|
|
1292
|
+
backupUsable,
|
|
1293
|
+
deviceOwnerVerified: true,
|
|
1294
|
+
error: backupError,
|
|
1295
|
+
...status
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
if (!stagedRecoveryKeyValidated) {
|
|
1299
|
+
this.recoveryKeyStore.discardStagedRecoveryKey();
|
|
1300
|
+
return {
|
|
1301
|
+
success: false,
|
|
1302
|
+
recoveryKeyAccepted: false,
|
|
1303
|
+
backupUsable,
|
|
1304
|
+
deviceOwnerVerified: true,
|
|
1305
|
+
error: "Matrix recovery key could not be verified against active Matrix backup material; existing backup may be usable from previously loaded recovery material.",
|
|
1306
|
+
...status
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
this.recoveryKeyStore.commitStagedRecoveryKey({ keyId: stagedKeyId });
|
|
1310
|
+
const committedStatus = await this.getOwnDeviceVerificationStatus();
|
|
1311
|
+
return {
|
|
1312
|
+
success: true,
|
|
1313
|
+
recoveryKeyAccepted: true,
|
|
1314
|
+
backupUsable,
|
|
1315
|
+
deviceOwnerVerified: true,
|
|
1316
|
+
verifiedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1317
|
+
...committedStatus
|
|
1318
|
+
};
|
|
1319
|
+
} catch (err) {
|
|
1320
|
+
this.recoveryKeyStore.discardStagedRecoveryKey();
|
|
1321
|
+
return await fail(formatMatrixErrorMessage(err));
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
async restoreRoomKeyBackup(params = {}) {
|
|
1325
|
+
let loadedFromSecretStorage = false;
|
|
1326
|
+
const fail = async (error) => {
|
|
1327
|
+
const backup = await this.getRoomKeyBackupStatus();
|
|
1328
|
+
return {
|
|
1329
|
+
success: false,
|
|
1330
|
+
error,
|
|
1331
|
+
backupVersion: backup.serverVersion,
|
|
1332
|
+
imported: 0,
|
|
1333
|
+
total: 0,
|
|
1334
|
+
loadedFromSecretStorage,
|
|
1335
|
+
backup
|
|
1336
|
+
};
|
|
1337
|
+
};
|
|
1338
|
+
if (!this.encryptionEnabled) return await fail("Matrix encryption is disabled for this client");
|
|
1339
|
+
await this.ensureStartedForCryptoControlPlane();
|
|
1340
|
+
const crypto = this.client.getCrypto();
|
|
1341
|
+
if (!crypto) return await fail("Matrix crypto is not available (start client with encryption enabled)");
|
|
1342
|
+
try {
|
|
1343
|
+
const rawRecoveryKey = params.recoveryKey?.trim();
|
|
1344
|
+
if (rawRecoveryKey) this.recoveryKeyStore.stageEncodedRecoveryKey({
|
|
1345
|
+
encodedPrivateKey: rawRecoveryKey,
|
|
1346
|
+
keyId: await this.resolveDefaultSecretStorageKeyId(crypto)
|
|
1347
|
+
});
|
|
1348
|
+
const backup = await this.getRoomKeyBackupStatus();
|
|
1349
|
+
loadedFromSecretStorage = backup.keyLoadAttempted && !backup.keyLoadError;
|
|
1350
|
+
const backupError = resolveMatrixRoomKeyBackupReadinessError(backup, {
|
|
1351
|
+
allowUntrustedMatchingKey: true,
|
|
1352
|
+
requireServerBackup: true
|
|
1353
|
+
});
|
|
1354
|
+
if (backupError) {
|
|
1355
|
+
this.recoveryKeyStore.discardStagedRecoveryKey();
|
|
1356
|
+
return await fail(backupError);
|
|
1357
|
+
}
|
|
1358
|
+
if (typeof crypto.restoreKeyBackup !== "function") {
|
|
1359
|
+
this.recoveryKeyStore.discardStagedRecoveryKey();
|
|
1360
|
+
return await fail("Matrix crypto backend does not support full key backup restore");
|
|
1361
|
+
}
|
|
1362
|
+
const restore = await crypto.restoreKeyBackup();
|
|
1363
|
+
if (rawRecoveryKey) this.recoveryKeyStore.commitStagedRecoveryKey({ keyId: await this.resolveDefaultSecretStorageKeyId(crypto) });
|
|
1364
|
+
const finalBackup = await this.getRoomKeyBackupStatus();
|
|
1365
|
+
return {
|
|
1366
|
+
success: true,
|
|
1367
|
+
backupVersion: backup.serverVersion,
|
|
1368
|
+
imported: typeof restore.imported === "number" ? restore.imported : 0,
|
|
1369
|
+
total: typeof restore.total === "number" ? restore.total : 0,
|
|
1370
|
+
loadedFromSecretStorage,
|
|
1371
|
+
restoredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1372
|
+
backup: finalBackup
|
|
1373
|
+
};
|
|
1374
|
+
} catch (err) {
|
|
1375
|
+
this.recoveryKeyStore.discardStagedRecoveryKey();
|
|
1376
|
+
return await fail(formatMatrixErrorMessage(err));
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
async resetRoomKeyBackup(options = {}) {
|
|
1380
|
+
let previousVersion = null;
|
|
1381
|
+
let deletedVersion = null;
|
|
1382
|
+
const fail = async (error) => {
|
|
1383
|
+
const backup = await this.getRoomKeyBackupStatus();
|
|
1384
|
+
return {
|
|
1385
|
+
success: false,
|
|
1386
|
+
error,
|
|
1387
|
+
previousVersion,
|
|
1388
|
+
deletedVersion,
|
|
1389
|
+
createdVersion: backup.serverVersion,
|
|
1390
|
+
backup
|
|
1391
|
+
};
|
|
1392
|
+
};
|
|
1393
|
+
if (!this.encryptionEnabled) return await fail("Matrix encryption is disabled for this client");
|
|
1394
|
+
await this.ensureStartedForCryptoControlPlane();
|
|
1395
|
+
const crypto = this.client.getCrypto();
|
|
1396
|
+
if (!crypto) return await fail("Matrix crypto is not available (start client with encryption enabled)");
|
|
1397
|
+
previousVersion = await this.resolveRoomKeyBackupVersion();
|
|
1398
|
+
const forceNewSecretStorage = options.rotateRecoveryKey === true || await this.shouldForceSecretStorageRecreationForBackupReset(crypto);
|
|
1399
|
+
try {
|
|
1400
|
+
if (previousVersion) {
|
|
1401
|
+
try {
|
|
1402
|
+
await this.doRequest("DELETE", `/_matrix/client/v3/room_keys/version/${encodeURIComponent(previousVersion)}`);
|
|
1403
|
+
} catch (err) {
|
|
1404
|
+
if (!isMatrixNotFoundError(err)) throw err;
|
|
1405
|
+
}
|
|
1406
|
+
deletedVersion = previousVersion;
|
|
1407
|
+
}
|
|
1408
|
+
await this.recoveryKeyStore.bootstrapSecretStorageWithRecoveryKey(crypto, {
|
|
1409
|
+
setupNewKeyBackup: true,
|
|
1410
|
+
forceNewSecretStorage,
|
|
1411
|
+
forceNewRecoveryKey: options.rotateRecoveryKey === true,
|
|
1412
|
+
allowSecretStorageRecreateWithoutRecoveryKey: true
|
|
1413
|
+
});
|
|
1414
|
+
await this.enableTrustedRoomKeyBackupIfPossible(crypto);
|
|
1415
|
+
const backup = await this.getRoomKeyBackupStatus();
|
|
1416
|
+
const createdVersion = backup.serverVersion;
|
|
1417
|
+
if (!createdVersion) return await fail("Matrix room key backup is still missing after reset.");
|
|
1418
|
+
if (backup.activeVersion !== createdVersion) return await fail("Matrix room key backup was recreated on the server but is not active on this device.");
|
|
1419
|
+
if (backup.decryptionKeyCached === false) return await fail("Matrix room key backup was recreated but its decryption key is not cached on this device.");
|
|
1420
|
+
if (backup.matchesDecryptionKey === false) return await fail("Matrix room key backup was recreated but this device does not have the matching backup decryption key.");
|
|
1421
|
+
if (backup.trusted === false) return await fail("Matrix room key backup was recreated but is not trusted on this device.");
|
|
1422
|
+
return {
|
|
1423
|
+
success: true,
|
|
1424
|
+
previousVersion,
|
|
1425
|
+
deletedVersion,
|
|
1426
|
+
createdVersion,
|
|
1427
|
+
resetAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1428
|
+
backup
|
|
1429
|
+
};
|
|
1430
|
+
} catch (err) {
|
|
1431
|
+
return await fail(formatMatrixErrorMessage(err));
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
async getOwnCrossSigningPublicationStatus() {
|
|
1435
|
+
const userId = this.client.getUserId() ?? this.selfUserId ?? null;
|
|
1436
|
+
if (!userId) return {
|
|
1437
|
+
userId: null,
|
|
1438
|
+
masterKeyPublished: false,
|
|
1439
|
+
selfSigningKeyPublished: false,
|
|
1440
|
+
userSigningKeyPublished: false,
|
|
1441
|
+
published: false
|
|
1442
|
+
};
|
|
1443
|
+
try {
|
|
1444
|
+
const response = await this.doRequest("POST", "/_matrix/client/v3/keys/query", void 0, { device_keys: { [userId]: [] } });
|
|
1445
|
+
const masterKeyPublished = Boolean(response.master_keys?.[userId]);
|
|
1446
|
+
const selfSigningKeyPublished = Boolean(response.self_signing_keys?.[userId]);
|
|
1447
|
+
const userSigningKeyPublished = Boolean(response.user_signing_keys?.[userId]);
|
|
1448
|
+
return {
|
|
1449
|
+
userId,
|
|
1450
|
+
masterKeyPublished,
|
|
1451
|
+
selfSigningKeyPublished,
|
|
1452
|
+
userSigningKeyPublished,
|
|
1453
|
+
published: masterKeyPublished && selfSigningKeyPublished && userSigningKeyPublished
|
|
1454
|
+
};
|
|
1455
|
+
} catch {
|
|
1456
|
+
return {
|
|
1457
|
+
userId,
|
|
1458
|
+
masterKeyPublished: false,
|
|
1459
|
+
selfSigningKeyPublished: false,
|
|
1460
|
+
userSigningKeyPublished: false,
|
|
1461
|
+
published: false
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
async bootstrapOwnDeviceVerification(params) {
|
|
1466
|
+
const pendingVerifications = async () => this.crypto ? (await this.crypto.listVerifications()).length : 0;
|
|
1467
|
+
if (!this.encryptionEnabled) return {
|
|
1468
|
+
success: false,
|
|
1469
|
+
error: "Matrix encryption is disabled for this client",
|
|
1470
|
+
verification: await this.getOwnDeviceVerificationStatus(),
|
|
1471
|
+
crossSigning: await this.getOwnCrossSigningPublicationStatus(),
|
|
1472
|
+
pendingVerifications: await pendingVerifications(),
|
|
1473
|
+
cryptoBootstrap: null
|
|
1474
|
+
};
|
|
1475
|
+
let bootstrapError;
|
|
1476
|
+
let bootstrapSummary = null;
|
|
1477
|
+
let rawRecoveryKey;
|
|
1478
|
+
try {
|
|
1479
|
+
await this.ensureStartedForCryptoControlPlane();
|
|
1480
|
+
await this.ensureCryptoSupportInitialized();
|
|
1481
|
+
const crypto = this.client.getCrypto();
|
|
1482
|
+
if (!crypto) throw new Error("Matrix crypto is not available (start client with encryption enabled)");
|
|
1483
|
+
rawRecoveryKey = params?.recoveryKey?.trim();
|
|
1484
|
+
if (rawRecoveryKey) this.recoveryKeyStore.stageEncodedRecoveryKey({
|
|
1485
|
+
encodedPrivateKey: rawRecoveryKey,
|
|
1486
|
+
keyId: await this.resolveDefaultSecretStorageKeyId(crypto)
|
|
1487
|
+
});
|
|
1488
|
+
const cryptoBootstrapper = this.cryptoBootstrapper;
|
|
1489
|
+
if (!cryptoBootstrapper) throw new Error("Matrix crypto bootstrapper is not available");
|
|
1490
|
+
bootstrapSummary = await cryptoBootstrapper.bootstrap(crypto, createMatrixExplicitBootstrapOptions({
|
|
1491
|
+
...params,
|
|
1492
|
+
allowAutomaticCrossSigningReset: rawRecoveryKey ? false : params?.allowAutomaticCrossSigningReset
|
|
1493
|
+
}));
|
|
1494
|
+
await this.ensureRoomKeyBackupEnabled(crypto);
|
|
1495
|
+
} catch (err) {
|
|
1496
|
+
this.recoveryKeyStore.discardStagedRecoveryKey();
|
|
1497
|
+
bootstrapError = formatMatrixErrorMessage(err);
|
|
1498
|
+
}
|
|
1499
|
+
const verification = await this.getOwnDeviceVerificationStatus();
|
|
1500
|
+
const crossSigning = await this.getOwnCrossSigningPublicationStatus();
|
|
1501
|
+
const verificationError = verification.verified && crossSigning.published ? null : bootstrapError ?? "Matrix verification bootstrap did not produce a device verified by its owner with published cross-signing keys";
|
|
1502
|
+
const backupError = verificationError === null ? resolveMatrixRoomKeyBackupReadinessError(verification.backup, {
|
|
1503
|
+
allowUntrustedMatchingKey: Boolean(rawRecoveryKey),
|
|
1504
|
+
requireServerBackup: true
|
|
1505
|
+
}) : null;
|
|
1506
|
+
const success = verificationError === null && backupError === null;
|
|
1507
|
+
if (success) this.recoveryKeyStore.commitStagedRecoveryKey({ keyId: await this.resolveDefaultSecretStorageKeyId(this.client.getCrypto()) });
|
|
1508
|
+
else this.recoveryKeyStore.discardStagedRecoveryKey();
|
|
1509
|
+
return {
|
|
1510
|
+
success,
|
|
1511
|
+
error: success ? void 0 : backupError ?? verificationError ?? void 0,
|
|
1512
|
+
verification: success ? await this.getOwnDeviceVerificationStatus() : verification,
|
|
1513
|
+
crossSigning,
|
|
1514
|
+
pendingVerifications: await pendingVerifications(),
|
|
1515
|
+
cryptoBootstrap: bootstrapSummary
|
|
1516
|
+
};
|
|
1517
|
+
}
|
|
1518
|
+
async listOwnDevices() {
|
|
1519
|
+
const currentDeviceId = this.client.getDeviceId()?.trim() || null;
|
|
1520
|
+
const devices = await this.client.getDevices();
|
|
1521
|
+
return (Array.isArray(devices?.devices) ? devices.devices : []).map((device) => ({
|
|
1522
|
+
deviceId: device.device_id,
|
|
1523
|
+
displayName: device.display_name?.trim() || null,
|
|
1524
|
+
lastSeenIp: device.last_seen_ip?.trim() || null,
|
|
1525
|
+
lastSeenTs: typeof device.last_seen_ts === "number" && Number.isFinite(device.last_seen_ts) ? device.last_seen_ts : null,
|
|
1526
|
+
current: currentDeviceId !== null && device.device_id === currentDeviceId
|
|
1527
|
+
}));
|
|
1528
|
+
}
|
|
1529
|
+
async deleteOwnDevices(deviceIds) {
|
|
1530
|
+
const uniqueDeviceIds = [...new Set(deviceIds.map((value) => value.trim()).filter(Boolean))];
|
|
1531
|
+
const currentDeviceId = this.client.getDeviceId()?.trim() || null;
|
|
1532
|
+
const protectedDeviceIds = uniqueDeviceIds.filter((deviceId) => deviceId === currentDeviceId);
|
|
1533
|
+
if (protectedDeviceIds.length > 0) throw new Error(`Refusing to delete the current Matrix device: ${protectedDeviceIds[0]}`);
|
|
1534
|
+
const deleteWithAuth = async (authData) => {
|
|
1535
|
+
await this.client.deleteMultipleDevices(uniqueDeviceIds, authData);
|
|
1536
|
+
};
|
|
1537
|
+
if (uniqueDeviceIds.length > 0) try {
|
|
1538
|
+
await deleteWithAuth();
|
|
1539
|
+
} catch (err) {
|
|
1540
|
+
const session = err && typeof err === "object" && "data" in err && err.data && typeof err.data === "object" && "session" in err.data && typeof err.data.session === "string" ? err.data.session : null;
|
|
1541
|
+
const userId = await this.getUserId().catch(() => this.selfUserId);
|
|
1542
|
+
if (!session || !userId || !this.password?.trim()) throw err;
|
|
1543
|
+
await deleteWithAuth({
|
|
1544
|
+
type: "m.login.password",
|
|
1545
|
+
session,
|
|
1546
|
+
identifier: {
|
|
1547
|
+
type: "m.id.user",
|
|
1548
|
+
user: userId
|
|
1549
|
+
},
|
|
1550
|
+
password: this.password
|
|
1551
|
+
});
|
|
1552
|
+
}
|
|
1553
|
+
return {
|
|
1554
|
+
currentDeviceId,
|
|
1555
|
+
deletedDeviceIds: uniqueDeviceIds,
|
|
1556
|
+
remainingDevices: await this.listOwnDevices()
|
|
1557
|
+
};
|
|
1558
|
+
}
|
|
1559
|
+
async resolveActiveRoomKeyBackupVersion(crypto) {
|
|
1560
|
+
if (typeof crypto.getActiveSessionBackupVersion !== "function") return null;
|
|
1561
|
+
return normalizeOptionalString$1(await crypto.getActiveSessionBackupVersion().catch(() => null));
|
|
1562
|
+
}
|
|
1563
|
+
async resolveCachedRoomKeyBackupDecryptionKey(crypto) {
|
|
1564
|
+
const getSessionBackupPrivateKey = crypto.getSessionBackupPrivateKey;
|
|
1565
|
+
if (typeof getSessionBackupPrivateKey !== "function") return null;
|
|
1566
|
+
const key = await getSessionBackupPrivateKey.call(crypto).catch(() => null);
|
|
1567
|
+
return key ? key.length > 0 : false;
|
|
1568
|
+
}
|
|
1569
|
+
async resolveRoomKeyBackupLocalState(crypto) {
|
|
1570
|
+
const [activeVersion, decryptionKeyCached] = await Promise.all([this.resolveActiveRoomKeyBackupVersion(crypto), this.resolveCachedRoomKeyBackupDecryptionKey(crypto)]);
|
|
1571
|
+
return {
|
|
1572
|
+
activeVersion,
|
|
1573
|
+
decryptionKeyCached
|
|
1574
|
+
};
|
|
1575
|
+
}
|
|
1576
|
+
async shouldForceSecretStorageRecreationForBackupReset(crypto) {
|
|
1577
|
+
if (await this.resolveCachedRoomKeyBackupDecryptionKey(crypto) !== false) return false;
|
|
1578
|
+
const loadSessionBackupPrivateKeyFromSecretStorage = crypto.loadSessionBackupPrivateKeyFromSecretStorage;
|
|
1579
|
+
if (typeof loadSessionBackupPrivateKeyFromSecretStorage !== "function") return false;
|
|
1580
|
+
try {
|
|
1581
|
+
await loadSessionBackupPrivateKeyFromSecretStorage.call(crypto);
|
|
1582
|
+
return false;
|
|
1583
|
+
} catch (err) {
|
|
1584
|
+
return isRepairableSecretStorageAccessError(err);
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
async resolveRoomKeyBackupTrustState(crypto, fallbackVersion) {
|
|
1588
|
+
let serverVersion = fallbackVersion;
|
|
1589
|
+
let trusted = null;
|
|
1590
|
+
let matchesDecryptionKey = null;
|
|
1591
|
+
if (typeof crypto.getKeyBackupInfo === "function") {
|
|
1592
|
+
const info = await crypto.getKeyBackupInfo().catch(() => null);
|
|
1593
|
+
serverVersion = normalizeOptionalString$1(info?.version) ?? serverVersion;
|
|
1594
|
+
if (info && typeof crypto.isKeyBackupTrusted === "function") {
|
|
1595
|
+
const trustInfo = await crypto.isKeyBackupTrusted(info).catch(() => null);
|
|
1596
|
+
trusted = typeof trustInfo?.trusted === "boolean" ? trustInfo.trusted : null;
|
|
1597
|
+
matchesDecryptionKey = typeof trustInfo?.matchesDecryptionKey === "boolean" ? trustInfo.matchesDecryptionKey : null;
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
return {
|
|
1601
|
+
serverVersion,
|
|
1602
|
+
trusted,
|
|
1603
|
+
matchesDecryptionKey
|
|
1604
|
+
};
|
|
1605
|
+
}
|
|
1606
|
+
async resolveDefaultSecretStorageKeyId(crypto) {
|
|
1607
|
+
const getSecretStorageStatus = crypto?.getSecretStorageStatus;
|
|
1608
|
+
if (typeof getSecretStorageStatus !== "function") return;
|
|
1609
|
+
return (await getSecretStorageStatus.call(crypto).catch(() => null))?.defaultKeyId;
|
|
1610
|
+
}
|
|
1611
|
+
async resolveRoomKeyBackupVersion() {
|
|
1612
|
+
try {
|
|
1613
|
+
return normalizeOptionalString$1((await this.doRequest("GET", "/_matrix/client/v3/room_keys/version")).version);
|
|
1614
|
+
} catch {
|
|
1615
|
+
return null;
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
async enableTrustedRoomKeyBackupIfPossible(crypto) {
|
|
1619
|
+
if (typeof crypto.checkKeyBackupAndEnable !== "function") return;
|
|
1620
|
+
await crypto.checkKeyBackupAndEnable();
|
|
1621
|
+
}
|
|
1622
|
+
async ensureRoomKeyBackupEnabled(crypto) {
|
|
1623
|
+
if (await this.resolveRoomKeyBackupVersion()) return;
|
|
1624
|
+
LogService.info("MatrixClientLite", "No room key backup version found on server, creating one via secret storage bootstrap");
|
|
1625
|
+
await this.recoveryKeyStore.bootstrapSecretStorageWithRecoveryKey(crypto, { setupNewKeyBackup: true });
|
|
1626
|
+
const createdVersion = await this.resolveRoomKeyBackupVersion();
|
|
1627
|
+
if (!createdVersion) throw new Error("Matrix room key backup is still missing after bootstrap");
|
|
1628
|
+
LogService.info("MatrixClientLite", `Room key backup enabled (version ${createdVersion})`);
|
|
1629
|
+
}
|
|
1630
|
+
registerBridge() {
|
|
1631
|
+
if (this.bridgeRegistered || !this.decryptBridge) return;
|
|
1632
|
+
this.bridgeRegistered = true;
|
|
1633
|
+
const decryptBridge = this.decryptBridge;
|
|
1634
|
+
this.client.on(ClientEvent.Event, (event) => {
|
|
1635
|
+
const roomId = event.getRoomId();
|
|
1636
|
+
if (!roomId) return;
|
|
1637
|
+
const raw = matrixEventToRaw(event, { contentMode: "original" });
|
|
1638
|
+
const isEncryptedEvent = raw.type === "m.room.encrypted";
|
|
1639
|
+
this.emitter.emit("room.event", roomId, raw);
|
|
1640
|
+
if (isEncryptedEvent) this.emitter.emit("room.encrypted_event", roomId, raw);
|
|
1641
|
+
else if (decryptBridge.shouldEmitUnencryptedMessage(roomId, raw.event_id)) this.emitter.emit("room.message", roomId, raw);
|
|
1642
|
+
const stateKey = raw.state_key ?? "";
|
|
1643
|
+
const selfUserId = this.client.getUserId() ?? this.selfUserId ?? "";
|
|
1644
|
+
const membership = raw.type === "m.room.member" ? raw.content.membership : void 0;
|
|
1645
|
+
if (stateKey && selfUserId && stateKey === selfUserId) {
|
|
1646
|
+
if (membership === "invite") this.emitter.emit("room.invite", roomId, raw);
|
|
1647
|
+
else if (membership === "join") this.emitter.emit("room.join", roomId, raw);
|
|
1648
|
+
}
|
|
1649
|
+
if (isEncryptedEvent) decryptBridge.attachEncryptedEvent(event, roomId);
|
|
1650
|
+
});
|
|
1651
|
+
this.client.on(ClientEvent.Room, (room) => {
|
|
1652
|
+
this.emitMembershipForRoom(room);
|
|
1653
|
+
});
|
|
1654
|
+
this.client.on(ClientEvent.Sync, (state, prevState, data) => {
|
|
1655
|
+
this.currentSyncState = state;
|
|
1656
|
+
const error = data && typeof data === "object" && "error" in data ? data.error : void 0;
|
|
1657
|
+
this.emitter.emit("sync.state", state, prevState, error);
|
|
1658
|
+
});
|
|
1659
|
+
this.client.on(ClientEvent.SyncUnexpectedError, (error) => {
|
|
1660
|
+
this.emitter.emit("sync.unexpected_error", error);
|
|
1661
|
+
});
|
|
1662
|
+
}
|
|
1663
|
+
emitMembershipForRoom(room) {
|
|
1664
|
+
const roomObj = room;
|
|
1665
|
+
const roomId = roomObj.roomId?.trim();
|
|
1666
|
+
if (!roomId) return;
|
|
1667
|
+
const membership = roomObj.getMyMembership?.() ?? roomObj.selfMembership ?? void 0;
|
|
1668
|
+
const selfUserId = this.client.getUserId() ?? this.selfUserId ?? "";
|
|
1669
|
+
if (!selfUserId) return;
|
|
1670
|
+
const raw = {
|
|
1671
|
+
event_id: `$membership-${roomId}-${Date.now()}`,
|
|
1672
|
+
type: "m.room.member",
|
|
1673
|
+
sender: selfUserId,
|
|
1674
|
+
state_key: selfUserId,
|
|
1675
|
+
content: { membership },
|
|
1676
|
+
origin_server_ts: Date.now(),
|
|
1677
|
+
unsigned: { age: 0 }
|
|
1678
|
+
};
|
|
1679
|
+
if (membership === "invite") {
|
|
1680
|
+
this.emitter.emit("room.invite", roomId, raw);
|
|
1681
|
+
return;
|
|
1682
|
+
}
|
|
1683
|
+
if (membership === "join") this.emitter.emit("room.join", roomId, raw);
|
|
1684
|
+
}
|
|
1685
|
+
emitOutstandingInviteEvents() {
|
|
1686
|
+
const listRooms = this.client.getRooms;
|
|
1687
|
+
if (typeof listRooms !== "function") return;
|
|
1688
|
+
const rooms = listRooms.call(this.client);
|
|
1689
|
+
if (!Array.isArray(rooms)) return;
|
|
1690
|
+
for (const room of rooms) this.emitMembershipForRoom(room);
|
|
1691
|
+
}
|
|
1692
|
+
async refreshDmCache() {
|
|
1693
|
+
const direct = await this.getAccountData("m.direct");
|
|
1694
|
+
this.dmRoomIds.clear();
|
|
1695
|
+
if (!direct || typeof direct !== "object") return false;
|
|
1696
|
+
for (const value of Object.values(direct)) {
|
|
1697
|
+
if (!Array.isArray(value)) continue;
|
|
1698
|
+
for (const roomId of value) if (typeof roomId === "string" && roomId.trim()) this.dmRoomIds.add(roomId);
|
|
1699
|
+
}
|
|
1700
|
+
return true;
|
|
1701
|
+
}
|
|
1702
|
+
};
|
|
1703
|
+
//#endregion
|
|
1704
|
+
export { MATRIX_IDB_SNAPSHOT_LOCK_OPTIONS as i, sdk_exports as n, isRepairableSecretStorageAccessError as r, MatrixClient as t };
|