@openclaw/matrix 2026.3.13 → 2026.5.9-beta.1
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/dist/account-config-D2W-V1eQ.js +96 -0
- package/dist/account-selection-BWwIruri.js +158 -0
- package/dist/accounts-Bm90Rzvp.js +130 -0
- package/dist/active-client-uhlxdhEy.js +20 -0
- package/dist/allowlist-sTzpCn5d.js +68 -0
- package/dist/api.js +12 -0
- package/dist/approval-handler.runtime-DWTQfd4m.js +370 -0
- package/dist/approval-ids-DoC2z7tR.js +7 -0
- package/dist/approval-reaction-auth-DbcA1gGd.js +27 -0
- package/dist/approval-reactions-o2_tuH8D.js +162 -0
- package/dist/async-lock-uQfhfQIY.js +19 -0
- package/dist/auth-presence.js +26 -0
- package/dist/backup-health-Cabu_WQC.js +60 -0
- package/dist/channel-DJNir3Rb.js +1116 -0
- package/dist/channel-plugin-api.js +2 -0
- package/dist/channel.runtime-BQu0hTih.js +246 -0
- package/dist/cli-BmfTmg7x.js +1340 -0
- package/dist/cli-metadata-B-PCEzrA.js +22 -0
- package/dist/cli-metadata.js +2 -0
- package/dist/client-DkcXnm0X.js +25 -0
- package/dist/client-_hckQNGW.js +31 -0
- package/dist/client-bootstrap-Rb8oHvhH.js +114 -0
- package/dist/config--5-S2Akv.js +452 -0
- package/dist/config-paths-nsVaysCu.js +19 -0
- package/dist/config-schema-nPLpEgHl.js +200 -0
- package/dist/config-secret-input.runtime-DiKFehsE.js +2 -0
- package/dist/config-update-wZX-HLMn.js +143 -0
- package/dist/contract-api.js +9 -0
- package/dist/create-client-DCnqDaqd.js +64 -0
- package/dist/credentials-DV6fWXhC.js +56 -0
- package/dist/credentials-read-cmHgousK.js +112 -0
- package/dist/credentials-write.runtime-zniTq-Gr.js +17 -0
- package/dist/crypto-node.runtime-pihzdpY7.js +12 -0
- package/dist/crypto-runtime-ZI0zAtn3.js +1214 -0
- package/dist/deps-C6WqKY7m.js +235 -0
- package/dist/device-health-UVYpbA_W.js +16 -0
- package/dist/direct-management-DMMMgtTB.js +249 -0
- package/dist/direct-room-XkutHjES.js +76 -0
- package/dist/directory-live-DmOtMhyr.js +150 -0
- package/dist/doctor-C4__7c-U.js +153 -0
- package/dist/doctor-contract-D4-64QuJ.js +246 -0
- package/dist/doctor-contract-api.js +2 -0
- package/dist/draft-stream-BE2QevQQ.js +144 -0
- package/dist/encryption-guidance-BPi3A_m3.js +15 -0
- package/dist/env-auth-BJqGI8M6.js +63 -0
- package/dist/env-vars-C7uQCTKn.js +63 -0
- package/dist/errors-CTcpEDq-.js +17 -0
- package/dist/exec-approval-resolver-Bza9Dhlm.js +15 -0
- package/dist/exec-approvals-Crnh543m.js +196 -0
- package/dist/helper-api.js +4 -0
- package/dist/http-client-C7AeVJay.js +319 -0
- package/dist/index.js +46 -0
- package/dist/legacy-crypto-inspector-poDWldgy.js +41 -0
- package/dist/legacy-crypto-restore-Biw-w2ng.js +85 -0
- package/dist/logger-CnZRVrux.js +78 -0
- package/dist/logging-DZHSPP5N.js +99 -0
- package/dist/matrix-migration.runtime-WY6ffcrf.js +525 -0
- package/dist/media-text-DU6nWZuj.js +146 -0
- package/dist/messages-BpihMh82.js +140 -0
- package/dist/migration-snapshot-backup-DaCHTp8C.js +69 -0
- package/dist/migration-snapshot.runtime-CKHE3xF9.js +2 -0
- package/dist/monitor-C_81r_Ck.js +4125 -0
- package/dist/plugin-entry.handlers.runtime.js +51 -0
- package/dist/probe.runtime-BvAzYAIe.js +3 -0
- package/dist/profile-BlHu0wDX.js +111 -0
- package/dist/profile-update-DjeBNgIV.js +69 -0
- package/dist/reaction-common-ejrL19w-.js +71 -0
- package/dist/reaction-events-CiARZfjk.js +121 -0
- package/dist/record-shared-CHWJCTWf.js +2 -0
- package/dist/recovery-key-store-BTJ6jz5v.js +294 -0
- package/dist/resolve-targets-YtJnw1Tb.js +140 -0
- package/dist/resolver.runtime-D9piiGEl.js +5 -0
- package/dist/rolldown-runtime-DUslC3ob.js +14 -0
- package/dist/route-D6rg-iXN.js +161 -0
- package/dist/runtime-C6X4h_SJ.js +6 -0
- package/dist/runtime-Dog86njy.js +8 -0
- package/dist/runtime-api-BXWBFIqm.js +25 -0
- package/dist/runtime-api.js +25 -0
- package/dist/runtime-heavy-api.js +3 -0
- package/dist/runtime-setter-api.js +2 -0
- package/dist/sdk-B2vZA27-.js +1416 -0
- package/dist/secret-contract-DcrJWCQI.js +120 -0
- package/dist/secret-contract-api.js +2 -0
- package/dist/send-Bo0DU1ca.js +1200 -0
- package/dist/session-store-metadata-DI5SCofx.js +77 -0
- package/dist/setup-bootstrap-ImenBsMt.js +62 -0
- package/dist/setup-core-CfZy05oW.js +116 -0
- package/dist/setup-dm-policy-2-r1FrQh.js +194 -0
- package/dist/setup-entry.js +19 -0
- package/dist/setup-plugin-api.js +44 -0
- package/dist/setup-surface-CqT_o61M.js +540 -0
- package/dist/shared-CpMoYKm1.js +195 -0
- package/dist/startup-abort-56edvmbM.js +32 -0
- package/dist/startup-verification-Demyp0bP.js +132 -0
- package/dist/storage-paths-BJLdnCjV.js +52 -0
- package/dist/storage-tC3ujLiW.js +281 -0
- package/dist/subagent-hooks-DQbyqq9V.js +149 -0
- package/dist/subagent-hooks-api.js +23 -0
- package/dist/sync-state-C_beeevA.js +12 -0
- package/dist/target-ids-80nQ2gql.js +77 -0
- package/dist/test-api.js +4 -0
- package/dist/thread-binding-api-Cq_E-E1K.js +17 -0
- package/dist/thread-binding-api.js +2 -0
- package/dist/thread-bindings-B9mesxXk.js +352 -0
- package/dist/thread-bindings-runtime.js +2 -0
- package/dist/thread-bindings-shared-DK-d-oYX.js +97 -0
- package/dist/timeout-abort-signal-CtaIaP1v.js +2 -0
- package/dist/tool-actions.runtime-BIH49vRr.js +532 -0
- package/dist/url-validation-DiK9j7jz.js +36 -0
- package/dist/verification-CZ2rDeHL.js +345 -0
- package/openclaw.plugin.json +788 -1
- package/package.json +82 -16
- package/CHANGELOG.md +0 -104
- package/index.ts +0 -22
- package/src/actions.ts +0 -195
- package/src/channel.directory.test.ts +0 -135
- package/src/channel.ts +0 -461
- package/src/config-schema.test.ts +0 -26
- package/src/config-schema.ts +0 -62
- package/src/directory-live.test.ts +0 -85
- package/src/directory-live.ts +0 -209
- package/src/group-mentions.ts +0 -52
- package/src/matrix/accounts.test.ts +0 -131
- package/src/matrix/accounts.ts +0 -114
- package/src/matrix/actions/client.ts +0 -47
- package/src/matrix/actions/limits.test.ts +0 -15
- package/src/matrix/actions/limits.ts +0 -6
- package/src/matrix/actions/messages.ts +0 -126
- package/src/matrix/actions/pins.test.ts +0 -74
- package/src/matrix/actions/pins.ts +0 -84
- package/src/matrix/actions/reactions.test.ts +0 -109
- package/src/matrix/actions/reactions.ts +0 -102
- package/src/matrix/actions/room.ts +0 -85
- package/src/matrix/actions/summary.ts +0 -75
- package/src/matrix/actions/types.ts +0 -85
- package/src/matrix/actions.ts +0 -15
- package/src/matrix/active-client.ts +0 -32
- package/src/matrix/client/config.ts +0 -245
- package/src/matrix/client/create-client.ts +0 -125
- package/src/matrix/client/logging.ts +0 -46
- package/src/matrix/client/runtime.ts +0 -4
- package/src/matrix/client/shared.test.ts +0 -85
- package/src/matrix/client/shared.ts +0 -210
- package/src/matrix/client/startup.test.ts +0 -49
- package/src/matrix/client/startup.ts +0 -29
- package/src/matrix/client/storage.ts +0 -131
- package/src/matrix/client/types.ts +0 -34
- package/src/matrix/client-bootstrap.ts +0 -47
- package/src/matrix/client.test.ts +0 -56
- package/src/matrix/client.ts +0 -14
- package/src/matrix/credentials.ts +0 -125
- package/src/matrix/deps.test.ts +0 -74
- package/src/matrix/deps.ts +0 -126
- package/src/matrix/format.test.ts +0 -33
- package/src/matrix/format.ts +0 -22
- package/src/matrix/index.ts +0 -11
- package/src/matrix/monitor/access-policy.ts +0 -126
- package/src/matrix/monitor/allowlist.test.ts +0 -45
- package/src/matrix/monitor/allowlist.ts +0 -94
- package/src/matrix/monitor/auto-join.ts +0 -72
- package/src/matrix/monitor/direct.test.ts +0 -396
- package/src/matrix/monitor/direct.ts +0 -152
- package/src/matrix/monitor/events.test.ts +0 -186
- package/src/matrix/monitor/events.ts +0 -168
- package/src/matrix/monitor/handler.body-for-agent.test.ts +0 -196
- package/src/matrix/monitor/handler.ts +0 -768
- package/src/matrix/monitor/inbound-body.test.ts +0 -73
- package/src/matrix/monitor/inbound-body.ts +0 -28
- package/src/matrix/monitor/index.test.ts +0 -18
- package/src/matrix/monitor/index.ts +0 -414
- package/src/matrix/monitor/location.ts +0 -100
- package/src/matrix/monitor/media.test.ts +0 -86
- package/src/matrix/monitor/media.ts +0 -118
- package/src/matrix/monitor/mentions.test.ts +0 -154
- package/src/matrix/monitor/mentions.ts +0 -62
- package/src/matrix/monitor/replies.test.ts +0 -184
- package/src/matrix/monitor/replies.ts +0 -124
- package/src/matrix/monitor/room-info.ts +0 -55
- package/src/matrix/monitor/rooms.test.ts +0 -124
- package/src/matrix/monitor/rooms.ts +0 -47
- package/src/matrix/monitor/threads.ts +0 -68
- package/src/matrix/monitor/types.ts +0 -39
- package/src/matrix/poll-types.test.ts +0 -21
- package/src/matrix/poll-types.ts +0 -167
- package/src/matrix/probe.ts +0 -69
- package/src/matrix/sdk-runtime.ts +0 -18
- package/src/matrix/send/client.ts +0 -99
- package/src/matrix/send/formatting.ts +0 -93
- package/src/matrix/send/media.ts +0 -230
- package/src/matrix/send/targets.test.ts +0 -98
- package/src/matrix/send/targets.ts +0 -150
- package/src/matrix/send/types.ts +0 -110
- package/src/matrix/send-queue.test.ts +0 -145
- package/src/matrix/send-queue.ts +0 -28
- package/src/matrix/send.test.ts +0 -319
- package/src/matrix/send.ts +0 -267
- package/src/onboarding.ts +0 -462
- package/src/outbound.test.ts +0 -159
- package/src/outbound.ts +0 -58
- package/src/resolve-targets.test.ts +0 -68
- package/src/resolve-targets.ts +0 -125
- package/src/runtime.ts +0 -6
- package/src/secret-input.ts +0 -13
- package/src/test-mocks.ts +0 -53
- package/src/tool-actions.ts +0 -164
- package/src/types.ts +0 -118
|
@@ -0,0 +1,1416 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-DUslC3ob.js";
|
|
2
|
+
import { t as isRecord } from "./record-shared-CHWJCTWf.js";
|
|
3
|
+
import { n as formatMatrixErrorReason, r as isMatrixNotFoundError, t as formatMatrixErrorMessage } from "./errors-CTcpEDq-.js";
|
|
4
|
+
import { t as claimCurrentTokenStorageState } from "./storage-tC3ujLiW.js";
|
|
5
|
+
import { n as resolveMatrixRoomKeyBackupReadinessError } from "./backup-health-Cabu_WQC.js";
|
|
6
|
+
import { t as createAsyncLock } from "./async-lock-uQfhfQIY.js";
|
|
7
|
+
import { n as LogService, r as noop, t as ConsoleLogger } from "./logger-CnZRVrux.js";
|
|
8
|
+
import { t as createMatrixJsSdkClientLogger } from "./logging-DZHSPP5N.js";
|
|
9
|
+
import { a as matrixEventToRaw, n as createMatrixGuardedFetch, o as parseMxc, t as MatrixAuthedHttpClient } from "./http-client-C7AeVJay.js";
|
|
10
|
+
import { n as isRepairableSecretStorageAccessError, r as MATRIX_IDB_PERSIST_INTERVAL_MS, t as MatrixRecoveryKeyStore } from "./recovery-key-store-BTJ6jz5v.js";
|
|
11
|
+
import { i as throwIfMatrixStartupAborted, n as createMatrixStartupAbortError } from "./startup-abort-56edvmbM.js";
|
|
12
|
+
import { n as isMatrixReadySyncState, r as isMatrixTerminalSyncState } from "./sync-state-C_beeevA.js";
|
|
13
|
+
import { normalizeNullableString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
14
|
+
import { readFileSync } from "node:fs";
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
import { writeJsonFileAtomically } from "openclaw/plugin-sdk/json-store";
|
|
17
|
+
import { KeyedAsyncQueue } from "openclaw/plugin-sdk/keyed-async-queue";
|
|
18
|
+
import { EventEmitter } from "node:events";
|
|
19
|
+
import { Category, ClientEvent, Filter, MatrixEventEvent, MemoryStore, Preset, SyncAccumulator, createClient } from "matrix-js-sdk/lib/matrix.js";
|
|
20
|
+
import { VerificationMethod } from "matrix-js-sdk/lib/types.js";
|
|
21
|
+
import fs$1 from "node:fs/promises";
|
|
22
|
+
//#region extensions/matrix/src/matrix/client/file-sync-store.ts
|
|
23
|
+
const STORE_VERSION = 1;
|
|
24
|
+
const PERSIST_DEBOUNCE_MS = 250;
|
|
25
|
+
function normalizeRoomsData(value) {
|
|
26
|
+
if (!isRecord(value)) return null;
|
|
27
|
+
return {
|
|
28
|
+
[Category.Join]: isRecord(value[Category.Join]) ? value[Category.Join] : {},
|
|
29
|
+
[Category.Invite]: isRecord(value[Category.Invite]) ? value[Category.Invite] : {},
|
|
30
|
+
[Category.Leave]: isRecord(value[Category.Leave]) ? value[Category.Leave] : {},
|
|
31
|
+
[Category.Knock]: isRecord(value[Category.Knock]) ? value[Category.Knock] : {}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function toPersistedSyncData(value) {
|
|
35
|
+
if (!isRecord(value)) return null;
|
|
36
|
+
if (typeof value.nextBatch === "string" && value.nextBatch.trim()) {
|
|
37
|
+
const roomsData = normalizeRoomsData(value.roomsData);
|
|
38
|
+
if (!Array.isArray(value.accountData) || !roomsData) return null;
|
|
39
|
+
return {
|
|
40
|
+
nextBatch: value.nextBatch,
|
|
41
|
+
accountData: value.accountData,
|
|
42
|
+
roomsData
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (typeof value.next_batch === "string" && value.next_batch.trim()) {
|
|
46
|
+
const roomsData = normalizeRoomsData(value.rooms);
|
|
47
|
+
if (!roomsData) return null;
|
|
48
|
+
return {
|
|
49
|
+
nextBatch: value.next_batch,
|
|
50
|
+
accountData: isRecord(value.account_data) && Array.isArray(value.account_data.events) ? value.account_data.events : [],
|
|
51
|
+
roomsData
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
function readPersistedStore(raw) {
|
|
57
|
+
try {
|
|
58
|
+
const parsed = JSON.parse(raw);
|
|
59
|
+
const savedSync = toPersistedSyncData(parsed.savedSync);
|
|
60
|
+
if (parsed.version === STORE_VERSION) return {
|
|
61
|
+
version: STORE_VERSION,
|
|
62
|
+
savedSync,
|
|
63
|
+
clientOptions: isRecord(parsed.clientOptions) ? parsed.clientOptions : void 0,
|
|
64
|
+
cleanShutdown: parsed.cleanShutdown === true
|
|
65
|
+
};
|
|
66
|
+
return {
|
|
67
|
+
version: STORE_VERSION,
|
|
68
|
+
savedSync: toPersistedSyncData(parsed),
|
|
69
|
+
cleanShutdown: false
|
|
70
|
+
};
|
|
71
|
+
} catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function cloneJson(value) {
|
|
76
|
+
return structuredClone(value);
|
|
77
|
+
}
|
|
78
|
+
function syncDataToSyncResponse(syncData) {
|
|
79
|
+
return {
|
|
80
|
+
next_batch: syncData.nextBatch,
|
|
81
|
+
rooms: syncData.roomsData,
|
|
82
|
+
account_data: { events: syncData.accountData }
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
var FileBackedMatrixSyncStore = class extends MemoryStore {
|
|
86
|
+
constructor(storagePath) {
|
|
87
|
+
super();
|
|
88
|
+
this.storagePath = storagePath;
|
|
89
|
+
this.persistLock = createAsyncLock();
|
|
90
|
+
this.accumulator = new SyncAccumulator();
|
|
91
|
+
this.savedSync = null;
|
|
92
|
+
this.cleanShutdown = false;
|
|
93
|
+
this.dirty = false;
|
|
94
|
+
this.persistTimer = null;
|
|
95
|
+
this.persistPromise = null;
|
|
96
|
+
let restoredSavedSync = null;
|
|
97
|
+
let restoredClientOptions;
|
|
98
|
+
let restoredCleanShutdown = false;
|
|
99
|
+
try {
|
|
100
|
+
const persisted = readPersistedStore(readFileSync(this.storagePath, "utf8"));
|
|
101
|
+
restoredSavedSync = persisted?.savedSync ?? null;
|
|
102
|
+
restoredClientOptions = persisted?.clientOptions;
|
|
103
|
+
restoredCleanShutdown = persisted?.cleanShutdown === true;
|
|
104
|
+
} catch {}
|
|
105
|
+
this.savedSync = restoredSavedSync;
|
|
106
|
+
this.savedClientOptions = restoredClientOptions;
|
|
107
|
+
this.hadSavedSyncOnLoad = restoredSavedSync !== null;
|
|
108
|
+
this.hadCleanShutdownOnLoad = this.hadSavedSyncOnLoad && restoredCleanShutdown;
|
|
109
|
+
this.cleanShutdown = this.hadCleanShutdownOnLoad;
|
|
110
|
+
if (this.savedSync) {
|
|
111
|
+
this.accumulator.accumulate(syncDataToSyncResponse(this.savedSync), true);
|
|
112
|
+
super.setSyncToken(this.savedSync.nextBatch);
|
|
113
|
+
}
|
|
114
|
+
if (this.savedClientOptions) super.storeClientOptions(this.savedClientOptions);
|
|
115
|
+
}
|
|
116
|
+
hasSavedSync() {
|
|
117
|
+
return this.hadSavedSyncOnLoad;
|
|
118
|
+
}
|
|
119
|
+
hasSavedSyncFromCleanShutdown() {
|
|
120
|
+
return this.hadCleanShutdownOnLoad;
|
|
121
|
+
}
|
|
122
|
+
getSavedSync() {
|
|
123
|
+
return Promise.resolve(this.savedSync ? cloneJson(this.savedSync) : null);
|
|
124
|
+
}
|
|
125
|
+
getSavedSyncToken() {
|
|
126
|
+
return Promise.resolve(this.savedSync?.nextBatch ?? null);
|
|
127
|
+
}
|
|
128
|
+
setSyncData(syncData) {
|
|
129
|
+
this.accumulator.accumulate(syncData);
|
|
130
|
+
this.savedSync = this.accumulator.getJSON();
|
|
131
|
+
this.markDirtyAndSchedulePersist();
|
|
132
|
+
return Promise.resolve();
|
|
133
|
+
}
|
|
134
|
+
getClientOptions() {
|
|
135
|
+
return Promise.resolve(this.savedClientOptions ? cloneJson(this.savedClientOptions) : void 0);
|
|
136
|
+
}
|
|
137
|
+
storeClientOptions(options) {
|
|
138
|
+
this.savedClientOptions = cloneJson(options);
|
|
139
|
+
super.storeClientOptions(options);
|
|
140
|
+
this.markDirtyAndSchedulePersist();
|
|
141
|
+
return Promise.resolve();
|
|
142
|
+
}
|
|
143
|
+
save(force = false) {
|
|
144
|
+
if (force) return this.flush();
|
|
145
|
+
return Promise.resolve();
|
|
146
|
+
}
|
|
147
|
+
wantsSave() {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
async deleteAllData() {
|
|
151
|
+
if (this.persistTimer) {
|
|
152
|
+
clearTimeout(this.persistTimer);
|
|
153
|
+
this.persistTimer = null;
|
|
154
|
+
}
|
|
155
|
+
this.dirty = false;
|
|
156
|
+
await this.persistPromise?.catch(() => void 0);
|
|
157
|
+
await super.deleteAllData();
|
|
158
|
+
this.savedSync = null;
|
|
159
|
+
this.savedClientOptions = void 0;
|
|
160
|
+
this.cleanShutdown = false;
|
|
161
|
+
await fs$1.rm(this.storagePath, { force: true }).catch(() => void 0);
|
|
162
|
+
}
|
|
163
|
+
markCleanShutdown() {
|
|
164
|
+
this.cleanShutdown = true;
|
|
165
|
+
this.dirty = true;
|
|
166
|
+
}
|
|
167
|
+
async flush() {
|
|
168
|
+
if (this.persistTimer) {
|
|
169
|
+
clearTimeout(this.persistTimer);
|
|
170
|
+
this.persistTimer = null;
|
|
171
|
+
}
|
|
172
|
+
while (this.dirty || this.persistPromise) {
|
|
173
|
+
if (this.dirty && !this.persistPromise) this.persistPromise = this.persist().finally(() => {
|
|
174
|
+
this.persistPromise = null;
|
|
175
|
+
});
|
|
176
|
+
await this.persistPromise;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
markDirtyAndSchedulePersist() {
|
|
180
|
+
this.cleanShutdown = false;
|
|
181
|
+
this.dirty = true;
|
|
182
|
+
if (this.persistTimer) return;
|
|
183
|
+
this.persistTimer = setTimeout(() => {
|
|
184
|
+
this.persistTimer = null;
|
|
185
|
+
this.flush().catch((err) => {
|
|
186
|
+
LogService.warn("MatrixFileSyncStore", "Failed to persist Matrix sync store:", err);
|
|
187
|
+
});
|
|
188
|
+
}, PERSIST_DEBOUNCE_MS);
|
|
189
|
+
this.persistTimer.unref?.();
|
|
190
|
+
}
|
|
191
|
+
async persist() {
|
|
192
|
+
this.dirty = false;
|
|
193
|
+
const payload = {
|
|
194
|
+
version: STORE_VERSION,
|
|
195
|
+
savedSync: this.savedSync ? cloneJson(this.savedSync) : null,
|
|
196
|
+
cleanShutdown: this.cleanShutdown,
|
|
197
|
+
...this.savedClientOptions ? { clientOptions: cloneJson(this.savedClientOptions) } : {}
|
|
198
|
+
};
|
|
199
|
+
try {
|
|
200
|
+
await this.persistLock(async () => {
|
|
201
|
+
await writeJsonFileAtomically(this.storagePath, payload);
|
|
202
|
+
claimCurrentTokenStorageState({ rootDir: path.dirname(this.storagePath) });
|
|
203
|
+
});
|
|
204
|
+
} catch (err) {
|
|
205
|
+
this.dirty = true;
|
|
206
|
+
throw err;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
//#endregion
|
|
211
|
+
//#region extensions/matrix/src/matrix/sdk.ts
|
|
212
|
+
var sdk_exports = /* @__PURE__ */ __exportAll({
|
|
213
|
+
ConsoleLogger: () => ConsoleLogger,
|
|
214
|
+
LogService: () => LogService,
|
|
215
|
+
MatrixClient: () => MatrixClient
|
|
216
|
+
});
|
|
217
|
+
const MATRIX_STATUS_DIAGNOSTIC_TIMEOUT_MS = 1e4;
|
|
218
|
+
function unresolvedMatrixRoomKeyBackupStatus() {
|
|
219
|
+
return {
|
|
220
|
+
serverVersion: null,
|
|
221
|
+
activeVersion: null,
|
|
222
|
+
trusted: null,
|
|
223
|
+
matchesDecryptionKey: null,
|
|
224
|
+
decryptionKeyCached: null,
|
|
225
|
+
keyLoadAttempted: false,
|
|
226
|
+
keyLoadError: null
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function unresolvedMatrixDeviceVerificationStatus(params) {
|
|
230
|
+
return {
|
|
231
|
+
encryptionEnabled: true,
|
|
232
|
+
userId: params.userId,
|
|
233
|
+
deviceId: params.deviceId,
|
|
234
|
+
verified: false,
|
|
235
|
+
localVerified: false,
|
|
236
|
+
crossSigningVerified: false,
|
|
237
|
+
signedByOwner: false
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
async function resolveMatrixDiagnostic(promise, timeoutMs) {
|
|
241
|
+
return (await resolveMatrixDiagnosticResult(promise, timeoutMs)).value;
|
|
242
|
+
}
|
|
243
|
+
async function resolveMatrixDiagnosticResult(promise, timeoutMs) {
|
|
244
|
+
let timeoutId;
|
|
245
|
+
try {
|
|
246
|
+
const guarded = promise.then((value) => ({
|
|
247
|
+
error: null,
|
|
248
|
+
timedOut: false,
|
|
249
|
+
value
|
|
250
|
+
})).catch((error) => ({
|
|
251
|
+
error,
|
|
252
|
+
timedOut: false,
|
|
253
|
+
value: null
|
|
254
|
+
}));
|
|
255
|
+
const timeout = new Promise((resolve) => {
|
|
256
|
+
timeoutId = setTimeout(() => resolve({
|
|
257
|
+
error: null,
|
|
258
|
+
timedOut: true,
|
|
259
|
+
value: null
|
|
260
|
+
}), timeoutMs);
|
|
261
|
+
timeoutId.unref?.();
|
|
262
|
+
});
|
|
263
|
+
return await Promise.race([guarded, timeout]);
|
|
264
|
+
} finally {
|
|
265
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function isMatrixAccessTokenInvalidatedError(error) {
|
|
269
|
+
if (!error || typeof error !== "object") return false;
|
|
270
|
+
const err = error;
|
|
271
|
+
const errcode = err.body?.errcode ?? err.data?.errcode;
|
|
272
|
+
if (err.statusCode === 401 && errcode === "M_UNKNOWN_TOKEN") return true;
|
|
273
|
+
const reason = formatMatrixErrorReason(error);
|
|
274
|
+
return reason.includes("m_unknown_token") || reason.includes("unknown token") || reason.includes("access token") && (reason.includes("invalid") || reason.includes("unrecognized") || reason.includes("unknown"));
|
|
275
|
+
}
|
|
276
|
+
const MATRIX_INITIAL_CRYPTO_BOOTSTRAP_OPTIONS = { allowAutomaticCrossSigningReset: false };
|
|
277
|
+
const MATRIX_AUTOMATIC_REPAIR_BOOTSTRAP_OPTIONS = {
|
|
278
|
+
forceResetCrossSigning: true,
|
|
279
|
+
allowSecretStorageRecreateWithoutRecoveryKey: true,
|
|
280
|
+
strict: true
|
|
281
|
+
};
|
|
282
|
+
function createMatrixExplicitBootstrapOptions(params) {
|
|
283
|
+
return {
|
|
284
|
+
forceResetCrossSigning: params?.forceResetCrossSigning === true,
|
|
285
|
+
allowAutomaticCrossSigningReset: params?.allowAutomaticCrossSigningReset !== false,
|
|
286
|
+
allowSecretStorageRecreateWithoutRecoveryKey: true,
|
|
287
|
+
strict: params?.strict !== false
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
let loadedMatrixCryptoRuntime = null;
|
|
291
|
+
let matrixCryptoRuntimePromise = null;
|
|
292
|
+
async function loadMatrixCryptoRuntime() {
|
|
293
|
+
matrixCryptoRuntimePromise ??= import("./crypto-runtime-ZI0zAtn3.js").then((runtime) => {
|
|
294
|
+
loadedMatrixCryptoRuntime = runtime;
|
|
295
|
+
return runtime;
|
|
296
|
+
});
|
|
297
|
+
return await matrixCryptoRuntimePromise;
|
|
298
|
+
}
|
|
299
|
+
const normalizeOptionalString$1 = normalizeNullableString;
|
|
300
|
+
function isUnsupportedAuthenticatedMediaEndpointError(err) {
|
|
301
|
+
const statusCode = err?.statusCode;
|
|
302
|
+
if (statusCode === 404 || statusCode === 405 || statusCode === 501) return true;
|
|
303
|
+
const message = formatMatrixErrorReason(err);
|
|
304
|
+
return message.includes("m_unrecognized") || message.includes("unrecognized request") || message.includes("method not allowed") || message.includes("not implemented");
|
|
305
|
+
}
|
|
306
|
+
var MatrixClient = class {
|
|
307
|
+
constructor(homeserver, accessToken, opts = {}) {
|
|
308
|
+
this.emitter = new EventEmitter();
|
|
309
|
+
this.bridgeRegistered = false;
|
|
310
|
+
this.started = false;
|
|
311
|
+
this.cryptoBootstrapped = false;
|
|
312
|
+
this.dmRoomIds = /* @__PURE__ */ new Set();
|
|
313
|
+
this.cryptoInitialized = false;
|
|
314
|
+
this.sendQueue = new KeyedAsyncQueue();
|
|
315
|
+
this.stopPersistPromise = null;
|
|
316
|
+
this.verificationSummaryListenerBound = false;
|
|
317
|
+
this.currentSyncState = null;
|
|
318
|
+
this.dms = {
|
|
319
|
+
update: async () => {
|
|
320
|
+
return await this.refreshDmCache();
|
|
321
|
+
},
|
|
322
|
+
isDm: (roomId) => this.dmRoomIds.has(roomId)
|
|
323
|
+
};
|
|
324
|
+
this.idbPersistTimer = null;
|
|
325
|
+
this.httpClient = new MatrixAuthedHttpClient({
|
|
326
|
+
homeserver,
|
|
327
|
+
accessToken,
|
|
328
|
+
ssrfPolicy: opts.ssrfPolicy,
|
|
329
|
+
dispatcherPolicy: opts.dispatcherPolicy
|
|
330
|
+
});
|
|
331
|
+
this.localTimeoutMs = Math.max(1, opts.localTimeoutMs ?? 6e4);
|
|
332
|
+
this.initialSyncLimit = opts.initialSyncLimit;
|
|
333
|
+
this.syncFilter = opts.syncFilter;
|
|
334
|
+
this.encryptionEnabled = opts.encryption === true;
|
|
335
|
+
this.password = opts.password;
|
|
336
|
+
this.syncStore = opts.storagePath ? new FileBackedMatrixSyncStore(opts.storagePath) : void 0;
|
|
337
|
+
this.idbSnapshotPath = opts.idbSnapshotPath;
|
|
338
|
+
this.cryptoDatabasePrefix = opts.cryptoDatabasePrefix;
|
|
339
|
+
this.selfUserId = opts.userId?.trim() || null;
|
|
340
|
+
this.autoBootstrapCrypto = opts.autoBootstrapCrypto !== false;
|
|
341
|
+
this.recoveryKeyStore = new MatrixRecoveryKeyStore(opts.recoveryKeyPath);
|
|
342
|
+
const cryptoCallbacks = this.encryptionEnabled ? this.recoveryKeyStore.buildCryptoCallbacks() : void 0;
|
|
343
|
+
this.client = createClient({
|
|
344
|
+
baseUrl: homeserver,
|
|
345
|
+
accessToken,
|
|
346
|
+
userId: opts.userId,
|
|
347
|
+
deviceId: opts.deviceId,
|
|
348
|
+
logger: createMatrixJsSdkClientLogger("MatrixClient"),
|
|
349
|
+
localTimeoutMs: this.localTimeoutMs,
|
|
350
|
+
fetchFn: createMatrixGuardedFetch({
|
|
351
|
+
ssrfPolicy: opts.ssrfPolicy,
|
|
352
|
+
dispatcherPolicy: opts.dispatcherPolicy
|
|
353
|
+
}),
|
|
354
|
+
store: this.syncStore,
|
|
355
|
+
cryptoCallbacks,
|
|
356
|
+
verificationMethods: [
|
|
357
|
+
VerificationMethod.Sas,
|
|
358
|
+
VerificationMethod.ShowQrCode,
|
|
359
|
+
VerificationMethod.ScanQrCode,
|
|
360
|
+
VerificationMethod.Reciprocate
|
|
361
|
+
]
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
on(eventName, listener) {
|
|
365
|
+
this.emitter.on(eventName, listener);
|
|
366
|
+
return this;
|
|
367
|
+
}
|
|
368
|
+
off(eventName, listener) {
|
|
369
|
+
this.emitter.off(eventName, listener);
|
|
370
|
+
return this;
|
|
371
|
+
}
|
|
372
|
+
async ensureCryptoSupportInitialized() {
|
|
373
|
+
if (this.decryptBridge && (!this.encryptionEnabled || this.verificationManager && this.cryptoBootstrapper && this.crypto)) return;
|
|
374
|
+
const runtime = await loadMatrixCryptoRuntime();
|
|
375
|
+
this.decryptBridge ??= new runtime.MatrixDecryptBridge({
|
|
376
|
+
client: this.client,
|
|
377
|
+
toRaw: (event) => matrixEventToRaw(event, { contentMode: "original" }),
|
|
378
|
+
emitDecryptedEvent: (roomId, event) => {
|
|
379
|
+
this.emitter.emit("room.decrypted_event", roomId, event);
|
|
380
|
+
},
|
|
381
|
+
emitMessage: (roomId, event) => {
|
|
382
|
+
this.emitter.emit("room.message", roomId, event);
|
|
383
|
+
},
|
|
384
|
+
emitFailedDecryption: (roomId, event, error) => {
|
|
385
|
+
this.emitter.emit("room.failed_decryption", roomId, event, error);
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
if (!this.encryptionEnabled) return;
|
|
389
|
+
this.verificationManager ??= new runtime.MatrixVerificationManager({ trustOwnDeviceAfterSas: async (deviceId) => {
|
|
390
|
+
const crypto = this.client.getCrypto();
|
|
391
|
+
if (typeof crypto?.crossSignDevice !== "function") return;
|
|
392
|
+
await crypto.crossSignDevice(deviceId);
|
|
393
|
+
} });
|
|
394
|
+
this.cryptoBootstrapper ??= new runtime.MatrixCryptoBootstrapper({
|
|
395
|
+
getUserId: () => this.getUserId(),
|
|
396
|
+
getPassword: () => this.password,
|
|
397
|
+
getDeviceId: () => this.client.getDeviceId(),
|
|
398
|
+
verificationManager: this.verificationManager,
|
|
399
|
+
recoveryKeyStore: this.recoveryKeyStore,
|
|
400
|
+
decryptBridge: this.decryptBridge
|
|
401
|
+
});
|
|
402
|
+
if (!this.crypto) this.crypto = runtime.createMatrixCryptoFacade({
|
|
403
|
+
client: this.client,
|
|
404
|
+
verificationManager: this.verificationManager,
|
|
405
|
+
recoveryKeyStore: this.recoveryKeyStore,
|
|
406
|
+
getRoomStateEvent: (roomId, eventType, stateKey = "") => this.getRoomStateEvent(roomId, eventType, stateKey),
|
|
407
|
+
downloadContent: (mxcUrl) => this.downloadContent(mxcUrl)
|
|
408
|
+
});
|
|
409
|
+
if (!this.verificationSummaryListenerBound) {
|
|
410
|
+
this.verificationSummaryListenerBound = true;
|
|
411
|
+
this.verificationManager.onSummaryChanged((summary) => {
|
|
412
|
+
this.emitter.emit("verification.summary", summary);
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
async start(opts = {}) {
|
|
417
|
+
await this.startSyncSession({
|
|
418
|
+
bootstrapCrypto: true,
|
|
419
|
+
abortSignal: opts.abortSignal,
|
|
420
|
+
readyTimeoutMs: opts.readyTimeoutMs
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
async waitForInitialSyncReady(params = {}) {
|
|
424
|
+
const timeoutMs = params.timeoutMs ?? 3e4;
|
|
425
|
+
if (isMatrixReadySyncState(this.currentSyncState)) return;
|
|
426
|
+
if (isMatrixTerminalSyncState(this.currentSyncState)) throw new Error(`Matrix sync entered ${this.currentSyncState} during startup`);
|
|
427
|
+
await new Promise((resolve, reject) => {
|
|
428
|
+
let settled = false;
|
|
429
|
+
let timeoutId;
|
|
430
|
+
const abortSignal = params.abortSignal;
|
|
431
|
+
const cleanup = () => {
|
|
432
|
+
this.off("sync.state", onSyncState);
|
|
433
|
+
this.off("sync.unexpected_error", onUnexpectedError);
|
|
434
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
435
|
+
if (timeoutId) {
|
|
436
|
+
clearTimeout(timeoutId);
|
|
437
|
+
timeoutId = void 0;
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
const settleResolve = () => {
|
|
441
|
+
if (settled) return;
|
|
442
|
+
settled = true;
|
|
443
|
+
cleanup();
|
|
444
|
+
resolve();
|
|
445
|
+
};
|
|
446
|
+
const settleReject = (error) => {
|
|
447
|
+
if (settled) return;
|
|
448
|
+
settled = true;
|
|
449
|
+
cleanup();
|
|
450
|
+
reject(error);
|
|
451
|
+
};
|
|
452
|
+
const onSyncState = (state, _prevState, error) => {
|
|
453
|
+
if (isMatrixReadySyncState(state)) {
|
|
454
|
+
settleResolve();
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
if (isMatrixTerminalSyncState(state)) settleReject(new Error(error instanceof Error && error.message ? error.message : `Matrix sync entered ${state} during startup`));
|
|
458
|
+
};
|
|
459
|
+
const onUnexpectedError = (error) => {
|
|
460
|
+
settleReject(error);
|
|
461
|
+
};
|
|
462
|
+
const onAbort = () => {
|
|
463
|
+
settleReject(createMatrixStartupAbortError());
|
|
464
|
+
};
|
|
465
|
+
this.on("sync.state", onSyncState);
|
|
466
|
+
this.on("sync.unexpected_error", onUnexpectedError);
|
|
467
|
+
if (abortSignal?.aborted) {
|
|
468
|
+
onAbort();
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
472
|
+
timeoutId = setTimeout(() => {
|
|
473
|
+
settleReject(/* @__PURE__ */ new Error(`Matrix client did not reach a ready sync state within ${timeoutMs}ms`));
|
|
474
|
+
}, timeoutMs);
|
|
475
|
+
timeoutId.unref?.();
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
async startSyncSession(opts) {
|
|
479
|
+
if (this.started) return;
|
|
480
|
+
throwIfMatrixStartupAborted(opts.abortSignal);
|
|
481
|
+
await this.ensureCryptoSupportInitialized();
|
|
482
|
+
throwIfMatrixStartupAborted(opts.abortSignal);
|
|
483
|
+
this.registerBridge();
|
|
484
|
+
await this.initializeCryptoIfNeeded(opts.abortSignal);
|
|
485
|
+
await this.client.startClient({
|
|
486
|
+
initialSyncLimit: this.initialSyncLimit,
|
|
487
|
+
...this.syncFilter ? { filter: Filter.fromJson(this.selfUserId, "", this.syncFilter) } : {}
|
|
488
|
+
});
|
|
489
|
+
await this.waitForInitialSyncReady({
|
|
490
|
+
abortSignal: opts.abortSignal,
|
|
491
|
+
timeoutMs: opts.readyTimeoutMs
|
|
492
|
+
});
|
|
493
|
+
throwIfMatrixStartupAborted(opts.abortSignal);
|
|
494
|
+
if (opts.bootstrapCrypto && this.autoBootstrapCrypto) await this.bootstrapCryptoIfNeeded(opts.abortSignal);
|
|
495
|
+
throwIfMatrixStartupAborted(opts.abortSignal);
|
|
496
|
+
this.started = true;
|
|
497
|
+
this.emitOutstandingInviteEvents();
|
|
498
|
+
await this.refreshDmCache().catch(noop);
|
|
499
|
+
}
|
|
500
|
+
async prepareForOneOff() {
|
|
501
|
+
if (!this.encryptionEnabled) return;
|
|
502
|
+
await this.ensureCryptoSupportInitialized();
|
|
503
|
+
await this.initializeCryptoIfNeeded();
|
|
504
|
+
if (!this.crypto) return;
|
|
505
|
+
try {
|
|
506
|
+
const joinedRooms = await this.getJoinedRooms();
|
|
507
|
+
await this.crypto.prepare(joinedRooms);
|
|
508
|
+
} catch {}
|
|
509
|
+
}
|
|
510
|
+
hasPersistedSyncState() {
|
|
511
|
+
return this.syncStore?.hasSavedSyncFromCleanShutdown() === true;
|
|
512
|
+
}
|
|
513
|
+
async ensureStartedForCryptoControlPlane() {
|
|
514
|
+
if (this.started) return;
|
|
515
|
+
await this.startSyncSession({ bootstrapCrypto: false });
|
|
516
|
+
}
|
|
517
|
+
stopSyncWithoutPersist() {
|
|
518
|
+
if (this.idbPersistTimer) {
|
|
519
|
+
clearInterval(this.idbPersistTimer);
|
|
520
|
+
this.idbPersistTimer = null;
|
|
521
|
+
}
|
|
522
|
+
this.currentSyncState = null;
|
|
523
|
+
this.client.stopClient();
|
|
524
|
+
this.started = false;
|
|
525
|
+
}
|
|
526
|
+
async drainPendingDecryptions(reason = "matrix client shutdown") {
|
|
527
|
+
await this.decryptBridge?.drainPendingDecryptions(reason);
|
|
528
|
+
}
|
|
529
|
+
stop() {
|
|
530
|
+
this.stopSyncWithoutPersist();
|
|
531
|
+
this.decryptBridge?.stop();
|
|
532
|
+
this.syncStore?.markCleanShutdown();
|
|
533
|
+
if (loadedMatrixCryptoRuntime) {
|
|
534
|
+
const { persistIdbToDisk } = loadedMatrixCryptoRuntime;
|
|
535
|
+
this.stopPersistPromise = Promise.all([persistIdbToDisk({
|
|
536
|
+
snapshotPath: this.idbSnapshotPath,
|
|
537
|
+
databasePrefix: this.cryptoDatabasePrefix
|
|
538
|
+
}).catch(noop), this.syncStore?.flush().catch(noop)]).then(() => void 0);
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
this.stopPersistPromise = loadMatrixCryptoRuntime().then(async ({ persistIdbToDisk }) => {
|
|
542
|
+
await Promise.all([persistIdbToDisk({
|
|
543
|
+
snapshotPath: this.idbSnapshotPath,
|
|
544
|
+
databasePrefix: this.cryptoDatabasePrefix
|
|
545
|
+
}).catch(noop), this.syncStore?.flush().catch(noop)]);
|
|
546
|
+
}).catch(noop).then(() => void 0);
|
|
547
|
+
}
|
|
548
|
+
async stopAndPersist() {
|
|
549
|
+
this.stop();
|
|
550
|
+
await this.stopPersistPromise;
|
|
551
|
+
}
|
|
552
|
+
stopWithoutPersist() {
|
|
553
|
+
this.stopSyncWithoutPersist();
|
|
554
|
+
this.decryptBridge?.stop();
|
|
555
|
+
this.stopPersistPromise = Promise.resolve();
|
|
556
|
+
}
|
|
557
|
+
async bootstrapCryptoIfNeeded(abortSignal) {
|
|
558
|
+
if (!this.encryptionEnabled || !this.cryptoInitialized || this.cryptoBootstrapped) return;
|
|
559
|
+
throwIfMatrixStartupAborted(abortSignal);
|
|
560
|
+
await this.ensureCryptoSupportInitialized();
|
|
561
|
+
const crypto = this.client.getCrypto();
|
|
562
|
+
if (!crypto) return;
|
|
563
|
+
const cryptoBootstrapper = this.cryptoBootstrapper;
|
|
564
|
+
if (!cryptoBootstrapper) return;
|
|
565
|
+
const initial = await cryptoBootstrapper.bootstrap(crypto, MATRIX_INITIAL_CRYPTO_BOOTSTRAP_OPTIONS);
|
|
566
|
+
throwIfMatrixStartupAborted(abortSignal);
|
|
567
|
+
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.");
|
|
568
|
+
else try {
|
|
569
|
+
const repaired = await cryptoBootstrapper.bootstrap(crypto, MATRIX_AUTOMATIC_REPAIR_BOOTSTRAP_OPTIONS);
|
|
570
|
+
throwIfMatrixStartupAborted(abortSignal);
|
|
571
|
+
if (repaired.crossSigningPublished && repaired.ownDeviceVerified !== false) LogService.info("MatrixClientLite", "Cross-signing/bootstrap recovered after forced reset");
|
|
572
|
+
} catch (err) {
|
|
573
|
+
LogService.warn("MatrixClientLite", "Failed to recover cross-signing/bootstrap with forced reset:", err);
|
|
574
|
+
}
|
|
575
|
+
this.cryptoBootstrapped = true;
|
|
576
|
+
}
|
|
577
|
+
async initializeCryptoIfNeeded(abortSignal) {
|
|
578
|
+
if (!this.encryptionEnabled || this.cryptoInitialized) return;
|
|
579
|
+
throwIfMatrixStartupAborted(abortSignal);
|
|
580
|
+
const { persistIdbToDisk, restoreIdbFromDisk } = await loadMatrixCryptoRuntime();
|
|
581
|
+
await restoreIdbFromDisk(this.idbSnapshotPath);
|
|
582
|
+
throwIfMatrixStartupAborted(abortSignal);
|
|
583
|
+
try {
|
|
584
|
+
await this.client.initRustCrypto({ cryptoDatabasePrefix: this.cryptoDatabasePrefix });
|
|
585
|
+
this.cryptoInitialized = true;
|
|
586
|
+
throwIfMatrixStartupAborted(abortSignal);
|
|
587
|
+
await persistIdbToDisk({
|
|
588
|
+
snapshotPath: this.idbSnapshotPath,
|
|
589
|
+
databasePrefix: this.cryptoDatabasePrefix
|
|
590
|
+
});
|
|
591
|
+
throwIfMatrixStartupAborted(abortSignal);
|
|
592
|
+
this.idbPersistTimer = setInterval(() => {
|
|
593
|
+
persistIdbToDisk({
|
|
594
|
+
snapshotPath: this.idbSnapshotPath,
|
|
595
|
+
databasePrefix: this.cryptoDatabasePrefix
|
|
596
|
+
}).catch(noop);
|
|
597
|
+
}, MATRIX_IDB_PERSIST_INTERVAL_MS);
|
|
598
|
+
this.idbPersistTimer.unref?.();
|
|
599
|
+
} catch (err) {
|
|
600
|
+
LogService.warn("MatrixClientLite", "Failed to initialize rust crypto:", err);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
async getUserId() {
|
|
604
|
+
const fromClient = this.client.getUserId();
|
|
605
|
+
if (fromClient) {
|
|
606
|
+
this.selfUserId = fromClient;
|
|
607
|
+
return fromClient;
|
|
608
|
+
}
|
|
609
|
+
if (this.selfUserId) return this.selfUserId;
|
|
610
|
+
const resolved = (await this.doRequest("GET", "/_matrix/client/v3/account/whoami")).user_id?.trim();
|
|
611
|
+
if (!resolved) throw new Error("Matrix whoami did not return user_id");
|
|
612
|
+
this.selfUserId = resolved;
|
|
613
|
+
return resolved;
|
|
614
|
+
}
|
|
615
|
+
async getJoinedRooms() {
|
|
616
|
+
const joined = await this.doRequest("GET", "/_matrix/client/v3/joined_rooms");
|
|
617
|
+
return Array.isArray(joined.joined_rooms) ? joined.joined_rooms : [];
|
|
618
|
+
}
|
|
619
|
+
async getJoinedRoomMembers(roomId) {
|
|
620
|
+
const joined = (await this.client.getJoinedRoomMembers(roomId))?.joined;
|
|
621
|
+
if (!joined || typeof joined !== "object") return [];
|
|
622
|
+
return Object.keys(joined);
|
|
623
|
+
}
|
|
624
|
+
hasSyncedJoinedRoomMember(roomId, userId) {
|
|
625
|
+
return (this.client.getRoom?.(roomId))?.currentState?.getMember?.(userId)?.membership === "join";
|
|
626
|
+
}
|
|
627
|
+
async getRoomStateEvent(roomId, eventType, stateKey = "") {
|
|
628
|
+
return await this.client.getStateEvent(roomId, eventType, stateKey) ?? {};
|
|
629
|
+
}
|
|
630
|
+
async getAccountData(eventType) {
|
|
631
|
+
return this.client.getAccountData(eventType)?.getContent() ?? void 0;
|
|
632
|
+
}
|
|
633
|
+
async setAccountData(eventType, content) {
|
|
634
|
+
await this.client.setAccountData(eventType, content);
|
|
635
|
+
await this.refreshDmCache().catch(noop);
|
|
636
|
+
}
|
|
637
|
+
async resolveRoom(aliasOrRoomId) {
|
|
638
|
+
if (aliasOrRoomId.startsWith("!")) return aliasOrRoomId;
|
|
639
|
+
if (!aliasOrRoomId.startsWith("#")) return aliasOrRoomId;
|
|
640
|
+
try {
|
|
641
|
+
return (await this.client.getRoomIdForAlias(aliasOrRoomId)).room_id ?? null;
|
|
642
|
+
} catch {
|
|
643
|
+
return null;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
async createDirectRoom(remoteUserId, opts = {}) {
|
|
647
|
+
const initialState = opts.encrypted ? [{
|
|
648
|
+
type: "m.room.encryption",
|
|
649
|
+
state_key: "",
|
|
650
|
+
content: { algorithm: "m.megolm.v1.aes-sha2" }
|
|
651
|
+
}] : void 0;
|
|
652
|
+
return (await this.client.createRoom({
|
|
653
|
+
invite: [remoteUserId],
|
|
654
|
+
is_direct: true,
|
|
655
|
+
preset: Preset.TrustedPrivateChat,
|
|
656
|
+
initial_state: initialState
|
|
657
|
+
})).room_id;
|
|
658
|
+
}
|
|
659
|
+
async sendMessage(roomId, content) {
|
|
660
|
+
return await this.runSerializedRoomSend(roomId, async () => {
|
|
661
|
+
return (await this.client.sendMessage(roomId, content)).event_id;
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
async sendEvent(roomId, eventType, content) {
|
|
665
|
+
return await this.runSerializedRoomSend(roomId, async () => {
|
|
666
|
+
return (await this.client.sendEvent(roomId, eventType, content)).event_id;
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
async runSerializedRoomSend(roomId, task) {
|
|
670
|
+
return await this.sendQueue.enqueue(roomId, task);
|
|
671
|
+
}
|
|
672
|
+
async sendStateEvent(roomId, eventType, stateKey, content) {
|
|
673
|
+
return (await this.client.sendStateEvent(roomId, eventType, content, stateKey)).event_id;
|
|
674
|
+
}
|
|
675
|
+
async redactEvent(roomId, eventId, reason) {
|
|
676
|
+
return (await this.client.redactEvent(roomId, eventId, void 0, reason?.trim() ? { reason } : void 0)).event_id;
|
|
677
|
+
}
|
|
678
|
+
async doRequest(method, endpoint, qs, body, opts) {
|
|
679
|
+
return await this.httpClient.requestJson({
|
|
680
|
+
method,
|
|
681
|
+
endpoint,
|
|
682
|
+
qs,
|
|
683
|
+
body,
|
|
684
|
+
timeoutMs: this.localTimeoutMs,
|
|
685
|
+
allowAbsoluteEndpoint: opts?.allowAbsoluteEndpoint
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
async getUserProfile(userId) {
|
|
689
|
+
return await this.client.getProfileInfo(userId);
|
|
690
|
+
}
|
|
691
|
+
async setDisplayName(displayName) {
|
|
692
|
+
await this.client.setDisplayName(displayName);
|
|
693
|
+
}
|
|
694
|
+
async setAvatarUrl(avatarUrl) {
|
|
695
|
+
await this.client.setAvatarUrl(avatarUrl);
|
|
696
|
+
}
|
|
697
|
+
async joinRoom(roomId) {
|
|
698
|
+
await this.client.joinRoom(roomId);
|
|
699
|
+
}
|
|
700
|
+
mxcToHttp(mxcUrl) {
|
|
701
|
+
return this.client.mxcUrlToHttp(mxcUrl, void 0, void 0, void 0, true, false, true);
|
|
702
|
+
}
|
|
703
|
+
async downloadContent(mxcUrl, opts = {}) {
|
|
704
|
+
const parsed = parseMxc(mxcUrl);
|
|
705
|
+
if (!parsed) throw new Error(`Invalid Matrix content URI: ${mxcUrl}`);
|
|
706
|
+
const encodedServer = encodeURIComponent(parsed.server);
|
|
707
|
+
const encodedMediaId = encodeURIComponent(parsed.mediaId);
|
|
708
|
+
const request = async (endpoint) => await this.httpClient.requestRaw({
|
|
709
|
+
method: "GET",
|
|
710
|
+
endpoint,
|
|
711
|
+
qs: { allow_remote: opts.allowRemote ?? true },
|
|
712
|
+
timeoutMs: this.localTimeoutMs,
|
|
713
|
+
maxBytes: opts.maxBytes,
|
|
714
|
+
readIdleTimeoutMs: opts.readIdleTimeoutMs
|
|
715
|
+
});
|
|
716
|
+
const authenticatedEndpoint = `/_matrix/client/v1/media/download/${encodedServer}/${encodedMediaId}`;
|
|
717
|
+
try {
|
|
718
|
+
return await request(authenticatedEndpoint);
|
|
719
|
+
} catch (err) {
|
|
720
|
+
if (!isUnsupportedAuthenticatedMediaEndpointError(err)) throw err;
|
|
721
|
+
}
|
|
722
|
+
return await request(`/_matrix/media/v3/download/${encodedServer}/${encodedMediaId}`);
|
|
723
|
+
}
|
|
724
|
+
async uploadContent(file, contentType, filename) {
|
|
725
|
+
return (await this.client.uploadContent(new Uint8Array(file), {
|
|
726
|
+
type: contentType || "application/octet-stream",
|
|
727
|
+
name: filename,
|
|
728
|
+
includeFilename: Boolean(filename)
|
|
729
|
+
})).content_uri;
|
|
730
|
+
}
|
|
731
|
+
async getEvent(roomId, eventId) {
|
|
732
|
+
const rawEvent = await this.client.fetchRoomEvent(roomId, eventId);
|
|
733
|
+
if (rawEvent.type !== "m.room.encrypted") return rawEvent;
|
|
734
|
+
const event = this.client.getEventMapper()(rawEvent);
|
|
735
|
+
let decryptedEvent;
|
|
736
|
+
const onDecrypted = (candidate) => {
|
|
737
|
+
decryptedEvent = candidate;
|
|
738
|
+
};
|
|
739
|
+
event.once(MatrixEventEvent.Decrypted, onDecrypted);
|
|
740
|
+
try {
|
|
741
|
+
await this.client.decryptEventIfNeeded(event);
|
|
742
|
+
} finally {
|
|
743
|
+
event.off(MatrixEventEvent.Decrypted, onDecrypted);
|
|
744
|
+
}
|
|
745
|
+
return matrixEventToRaw(decryptedEvent ?? event);
|
|
746
|
+
}
|
|
747
|
+
async getRelations(roomId, eventId, relationType, eventType, opts = {}) {
|
|
748
|
+
const result = await this.client.relations(roomId, eventId, relationType, eventType, opts);
|
|
749
|
+
return {
|
|
750
|
+
originalEvent: result.originalEvent ? matrixEventToRaw(result.originalEvent) : null,
|
|
751
|
+
events: result.events.map((event) => matrixEventToRaw(event)),
|
|
752
|
+
nextBatch: result.nextBatch ?? null,
|
|
753
|
+
prevBatch: result.prevBatch ?? null
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
async hydrateEvents(roomId, events) {
|
|
757
|
+
if (events.length === 0) return [];
|
|
758
|
+
const mapper = this.client.getEventMapper();
|
|
759
|
+
const mappedEvents = events.map((event) => mapper({
|
|
760
|
+
room_id: roomId,
|
|
761
|
+
...event
|
|
762
|
+
}));
|
|
763
|
+
await Promise.all(mappedEvents.map((event) => this.client.decryptEventIfNeeded(event)));
|
|
764
|
+
return mappedEvents.map((event) => matrixEventToRaw(event));
|
|
765
|
+
}
|
|
766
|
+
async setTyping(roomId, typing, timeoutMs) {
|
|
767
|
+
await this.client.sendTyping(roomId, typing, timeoutMs);
|
|
768
|
+
}
|
|
769
|
+
async sendReadReceipt(roomId, eventId) {
|
|
770
|
+
await this.httpClient.requestJson({
|
|
771
|
+
method: "POST",
|
|
772
|
+
endpoint: `/_matrix/client/v3/rooms/${encodeURIComponent(roomId)}/receipt/m.read/${encodeURIComponent(eventId)}`,
|
|
773
|
+
body: {},
|
|
774
|
+
timeoutMs: this.localTimeoutMs
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
async getRoomKeyBackupStatus() {
|
|
778
|
+
if (!this.encryptionEnabled) return {
|
|
779
|
+
serverVersion: null,
|
|
780
|
+
activeVersion: null,
|
|
781
|
+
trusted: null,
|
|
782
|
+
matchesDecryptionKey: null,
|
|
783
|
+
decryptionKeyCached: null,
|
|
784
|
+
keyLoadAttempted: false,
|
|
785
|
+
keyLoadError: null
|
|
786
|
+
};
|
|
787
|
+
const crypto = this.client.getCrypto();
|
|
788
|
+
const serverVersionFallback = await this.resolveRoomKeyBackupVersion();
|
|
789
|
+
if (!crypto) return {
|
|
790
|
+
serverVersion: serverVersionFallback,
|
|
791
|
+
activeVersion: null,
|
|
792
|
+
trusted: null,
|
|
793
|
+
matchesDecryptionKey: null,
|
|
794
|
+
decryptionKeyCached: null,
|
|
795
|
+
keyLoadAttempted: false,
|
|
796
|
+
keyLoadError: null
|
|
797
|
+
};
|
|
798
|
+
let { activeVersion, decryptionKeyCached } = await this.resolveRoomKeyBackupLocalState(crypto);
|
|
799
|
+
let { serverVersion, trusted, matchesDecryptionKey } = await this.resolveRoomKeyBackupTrustState(crypto, serverVersionFallback);
|
|
800
|
+
const shouldLoadBackupKey = Boolean(serverVersion) && (decryptionKeyCached === false || matchesDecryptionKey === false);
|
|
801
|
+
const shouldActivateBackup = Boolean(serverVersion) && !activeVersion;
|
|
802
|
+
let keyLoadAttempted = false;
|
|
803
|
+
let keyLoadError = null;
|
|
804
|
+
if (serverVersion && (shouldLoadBackupKey || shouldActivateBackup)) {
|
|
805
|
+
if (shouldLoadBackupKey) if (typeof crypto.loadSessionBackupPrivateKeyFromSecretStorage === "function") {
|
|
806
|
+
keyLoadAttempted = true;
|
|
807
|
+
try {
|
|
808
|
+
await crypto.loadSessionBackupPrivateKeyFromSecretStorage();
|
|
809
|
+
} catch (err) {
|
|
810
|
+
keyLoadError = formatMatrixErrorMessage(err);
|
|
811
|
+
}
|
|
812
|
+
} else keyLoadError = "Matrix crypto backend does not support loading backup keys from secret storage";
|
|
813
|
+
if (!keyLoadError) await this.enableTrustedRoomKeyBackupIfPossible(crypto);
|
|
814
|
+
({activeVersion, decryptionKeyCached} = await this.resolveRoomKeyBackupLocalState(crypto));
|
|
815
|
+
({serverVersion, trusted, matchesDecryptionKey} = await this.resolveRoomKeyBackupTrustState(crypto, serverVersion));
|
|
816
|
+
}
|
|
817
|
+
return {
|
|
818
|
+
serverVersion,
|
|
819
|
+
activeVersion,
|
|
820
|
+
trusted,
|
|
821
|
+
matchesDecryptionKey,
|
|
822
|
+
decryptionKeyCached,
|
|
823
|
+
keyLoadAttempted,
|
|
824
|
+
keyLoadError
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
async getDeviceVerificationStatus(userId, deviceId) {
|
|
828
|
+
const normalizedUserId = userId?.trim() || null;
|
|
829
|
+
const normalizedDeviceId = deviceId?.trim() || null;
|
|
830
|
+
if (!this.encryptionEnabled) return {
|
|
831
|
+
encryptionEnabled: false,
|
|
832
|
+
userId: normalizedUserId,
|
|
833
|
+
deviceId: normalizedDeviceId,
|
|
834
|
+
verified: false,
|
|
835
|
+
localVerified: false,
|
|
836
|
+
crossSigningVerified: false,
|
|
837
|
+
signedByOwner: false
|
|
838
|
+
};
|
|
839
|
+
const crypto = this.client.getCrypto();
|
|
840
|
+
let deviceStatus = null;
|
|
841
|
+
if (crypto && normalizedUserId && normalizedDeviceId && typeof crypto.getDeviceVerificationStatus === "function") deviceStatus = await crypto.getDeviceVerificationStatus(normalizedUserId, normalizedDeviceId).catch(() => null);
|
|
842
|
+
const { isMatrixDeviceVerifiedInCurrentClient } = await loadMatrixCryptoRuntime();
|
|
843
|
+
return {
|
|
844
|
+
encryptionEnabled: true,
|
|
845
|
+
userId: normalizedUserId,
|
|
846
|
+
deviceId: normalizedDeviceId,
|
|
847
|
+
verified: isMatrixDeviceVerifiedInCurrentClient(deviceStatus),
|
|
848
|
+
localVerified: deviceStatus?.localVerified === true,
|
|
849
|
+
crossSigningVerified: deviceStatus?.crossSigningVerified === true,
|
|
850
|
+
signedByOwner: deviceStatus?.signedByOwner === true
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
async getOwnDeviceVerificationStatus() {
|
|
854
|
+
const recoveryKey = this.recoveryKeyStore.getRecoveryKeySummary();
|
|
855
|
+
const userId = this.client.getUserId() ?? this.selfUserId ?? null;
|
|
856
|
+
const deviceId = this.client.getDeviceId()?.trim() || null;
|
|
857
|
+
const diagnosticTimeoutMs = Math.min(this.localTimeoutMs, MATRIX_STATUS_DIAGNOSTIC_TIMEOUT_MS);
|
|
858
|
+
const [backup, deviceVerification, ownDevices] = await Promise.all([
|
|
859
|
+
resolveMatrixDiagnostic(this.getRoomKeyBackupStatus(), diagnosticTimeoutMs),
|
|
860
|
+
resolveMatrixDiagnostic(this.getDeviceVerificationStatus(userId, deviceId), diagnosticTimeoutMs),
|
|
861
|
+
resolveMatrixDiagnosticResult(this.listOwnDevices(), diagnosticTimeoutMs)
|
|
862
|
+
]);
|
|
863
|
+
const resolvedBackup = backup ?? unresolvedMatrixRoomKeyBackupStatus();
|
|
864
|
+
const resolvedDeviceVerification = deviceVerification ?? unresolvedMatrixDeviceVerificationStatus({
|
|
865
|
+
userId,
|
|
866
|
+
deviceId
|
|
867
|
+
});
|
|
868
|
+
const serverDeviceKnown = deviceId ? ownDevices.value ? ownDevices.value.some((device) => device.deviceId === deviceId) : isMatrixAccessTokenInvalidatedError(ownDevices.error) ? false : null : null;
|
|
869
|
+
return {
|
|
870
|
+
...resolvedDeviceVerification,
|
|
871
|
+
verified: resolvedDeviceVerification.crossSigningVerified,
|
|
872
|
+
recoveryKeyStored: Boolean(recoveryKey),
|
|
873
|
+
recoveryKeyCreatedAt: recoveryKey?.createdAt ?? null,
|
|
874
|
+
recoveryKeyId: recoveryKey?.keyId ?? null,
|
|
875
|
+
backupVersion: resolvedBackup.serverVersion,
|
|
876
|
+
backup: resolvedBackup,
|
|
877
|
+
serverDeviceKnown
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
async getOwnDeviceIdentityVerificationStatus() {
|
|
881
|
+
const userId = this.client.getUserId() ?? this.selfUserId ?? null;
|
|
882
|
+
const deviceId = this.client.getDeviceId()?.trim() || null;
|
|
883
|
+
const deviceVerification = await this.getDeviceVerificationStatus(userId, deviceId);
|
|
884
|
+
return {
|
|
885
|
+
...deviceVerification,
|
|
886
|
+
verified: deviceVerification.crossSigningVerified
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
async trustOwnIdentityAfterSelfVerification() {
|
|
890
|
+
if (!this.encryptionEnabled) return;
|
|
891
|
+
await this.ensureStartedForCryptoControlPlane();
|
|
892
|
+
await this.ensureCryptoSupportInitialized();
|
|
893
|
+
const crypto = this.client.getCrypto();
|
|
894
|
+
const ownIdentity = crypto && typeof crypto.getOwnIdentity === "function" ? await crypto.getOwnIdentity().catch(() => void 0) : void 0;
|
|
895
|
+
if (!ownIdentity) return;
|
|
896
|
+
try {
|
|
897
|
+
if (typeof ownIdentity.isVerified === "function" && ownIdentity.isVerified()) return;
|
|
898
|
+
if (typeof ownIdentity.verify !== "function") return;
|
|
899
|
+
await ownIdentity.verify();
|
|
900
|
+
} finally {
|
|
901
|
+
ownIdentity.free?.();
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
async verifyWithRecoveryKey(rawRecoveryKey) {
|
|
905
|
+
const fail = async (error, fields = {}) => {
|
|
906
|
+
const status = await this.getOwnDeviceVerificationStatus();
|
|
907
|
+
return {
|
|
908
|
+
success: false,
|
|
909
|
+
recoveryKeyAccepted: fields.recoveryKeyAccepted ?? false,
|
|
910
|
+
backupUsable: fields.backupUsable ?? false,
|
|
911
|
+
deviceOwnerVerified: fields.deviceOwnerVerified ?? status.verified,
|
|
912
|
+
error,
|
|
913
|
+
...status
|
|
914
|
+
};
|
|
915
|
+
};
|
|
916
|
+
if (!this.encryptionEnabled) return await fail("Matrix encryption is disabled for this client");
|
|
917
|
+
await this.ensureStartedForCryptoControlPlane();
|
|
918
|
+
await this.ensureCryptoSupportInitialized();
|
|
919
|
+
const crypto = this.client.getCrypto();
|
|
920
|
+
if (!crypto) return await fail("Matrix crypto is not available (start client with encryption enabled)");
|
|
921
|
+
const backupUsableBeforeStagedRecovery = resolveMatrixRoomKeyBackupReadinessError(await this.getRoomKeyBackupStatus(), { requireServerBackup: true }) === null;
|
|
922
|
+
const trimmedRecoveryKey = rawRecoveryKey.trim();
|
|
923
|
+
if (!trimmedRecoveryKey) return await fail("Matrix recovery key is required");
|
|
924
|
+
let stagedKeyId = null;
|
|
925
|
+
try {
|
|
926
|
+
stagedKeyId = await this.resolveDefaultSecretStorageKeyId(crypto) ?? null;
|
|
927
|
+
this.recoveryKeyStore.stageEncodedRecoveryKey({
|
|
928
|
+
encodedPrivateKey: trimmedRecoveryKey,
|
|
929
|
+
keyId: stagedKeyId
|
|
930
|
+
});
|
|
931
|
+
} catch (err) {
|
|
932
|
+
return await fail(formatMatrixErrorMessage(err));
|
|
933
|
+
}
|
|
934
|
+
const storedRecoveryKeyMatches = this.recoveryKeyStore.getRecoveryKeySummary()?.encodedPrivateKey?.trim() === trimmedRecoveryKey;
|
|
935
|
+
if (backupUsableBeforeStagedRecovery && storedRecoveryKeyMatches) {
|
|
936
|
+
const status = await this.getOwnDeviceVerificationStatus();
|
|
937
|
+
const backupUsable = resolveMatrixRoomKeyBackupReadinessError(status.backup, { requireServerBackup: true }) === null;
|
|
938
|
+
const backupError = resolveMatrixRoomKeyBackupReadinessError(status.backup, { requireServerBackup: false });
|
|
939
|
+
const recoveryKeyAccepted = backupUsable;
|
|
940
|
+
if (!status.verified) {
|
|
941
|
+
if (recoveryKeyAccepted) this.recoveryKeyStore.commitStagedRecoveryKey({ keyId: stagedKeyId });
|
|
942
|
+
else this.recoveryKeyStore.discardStagedRecoveryKey();
|
|
943
|
+
return {
|
|
944
|
+
success: false,
|
|
945
|
+
recoveryKeyAccepted,
|
|
946
|
+
backupUsable,
|
|
947
|
+
deviceOwnerVerified: false,
|
|
948
|
+
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.",
|
|
949
|
+
...status
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
if (backupError) {
|
|
953
|
+
this.recoveryKeyStore.discardStagedRecoveryKey();
|
|
954
|
+
return {
|
|
955
|
+
success: false,
|
|
956
|
+
recoveryKeyAccepted,
|
|
957
|
+
backupUsable,
|
|
958
|
+
deviceOwnerVerified: true,
|
|
959
|
+
error: backupError,
|
|
960
|
+
...status
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
this.recoveryKeyStore.commitStagedRecoveryKey({ keyId: stagedKeyId });
|
|
964
|
+
return {
|
|
965
|
+
success: true,
|
|
966
|
+
recoveryKeyAccepted: true,
|
|
967
|
+
backupUsable,
|
|
968
|
+
deviceOwnerVerified: true,
|
|
969
|
+
verifiedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
970
|
+
...status
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
try {
|
|
974
|
+
const cryptoBootstrapper = this.cryptoBootstrapper;
|
|
975
|
+
if (!cryptoBootstrapper) return await fail("Matrix crypto bootstrapper is not available");
|
|
976
|
+
await cryptoBootstrapper.bootstrap(crypto, { allowAutomaticCrossSigningReset: false });
|
|
977
|
+
await this.enableTrustedRoomKeyBackupIfPossible(crypto);
|
|
978
|
+
const status = await this.getOwnDeviceVerificationStatus();
|
|
979
|
+
const backupError = resolveMatrixRoomKeyBackupReadinessError(status.backup, { requireServerBackup: false });
|
|
980
|
+
const backupUsable = resolveMatrixRoomKeyBackupReadinessError(status.backup, { requireServerBackup: true }) === null;
|
|
981
|
+
const stagedRecoveryKeyUsed = this.recoveryKeyStore.hasStagedRecoveryKeyBeenUsed();
|
|
982
|
+
const secretStorageStatus = typeof crypto.getSecretStorageStatus === "function" ? await crypto.getSecretStorageStatus().catch(() => null) : null;
|
|
983
|
+
const stagedRecoveryKeyConfirmedBySecretStorage = Boolean(stagedKeyId) && secretStorageStatus?.secretStorageKeyValidityMap?.[stagedKeyId ?? ""] === true;
|
|
984
|
+
const stagedRecoveryKeyRejectedBySecretStorage = Boolean(stagedKeyId) && secretStorageStatus?.secretStorageKeyValidityMap?.[stagedKeyId ?? ""] === false;
|
|
985
|
+
const stagedRecoveryKeyValidated = stagedRecoveryKeyUsed && (stagedRecoveryKeyConfirmedBySecretStorage || stagedRecoveryKeyUsed && !stagedRecoveryKeyRejectedBySecretStorage && !stagedRecoveryKeyConfirmedBySecretStorage && !backupUsableBeforeStagedRecovery && backupUsable) || storedRecoveryKeyMatches && backupUsable;
|
|
986
|
+
const recoveryKeyAccepted = stagedRecoveryKeyValidated && (status.verified || backupUsable);
|
|
987
|
+
if (!status.verified) {
|
|
988
|
+
if (backupUsable && stagedRecoveryKeyValidated) this.recoveryKeyStore.commitStagedRecoveryKey({ keyId: stagedKeyId });
|
|
989
|
+
else this.recoveryKeyStore.discardStagedRecoveryKey();
|
|
990
|
+
return {
|
|
991
|
+
success: false,
|
|
992
|
+
recoveryKeyAccepted,
|
|
993
|
+
backupUsable,
|
|
994
|
+
deviceOwnerVerified: false,
|
|
995
|
+
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.",
|
|
996
|
+
...recoveryKeyAccepted ? await this.getOwnDeviceVerificationStatus() : status
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
if (backupError) {
|
|
1000
|
+
this.recoveryKeyStore.discardStagedRecoveryKey();
|
|
1001
|
+
return {
|
|
1002
|
+
success: false,
|
|
1003
|
+
recoveryKeyAccepted,
|
|
1004
|
+
backupUsable,
|
|
1005
|
+
deviceOwnerVerified: true,
|
|
1006
|
+
error: backupError,
|
|
1007
|
+
...status
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
if (!stagedRecoveryKeyValidated) {
|
|
1011
|
+
this.recoveryKeyStore.discardStagedRecoveryKey();
|
|
1012
|
+
return {
|
|
1013
|
+
success: false,
|
|
1014
|
+
recoveryKeyAccepted: false,
|
|
1015
|
+
backupUsable,
|
|
1016
|
+
deviceOwnerVerified: true,
|
|
1017
|
+
error: "Matrix recovery key could not be verified against active Matrix backup material; existing backup may be usable from previously loaded recovery material.",
|
|
1018
|
+
...status
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
this.recoveryKeyStore.commitStagedRecoveryKey({ keyId: stagedKeyId });
|
|
1022
|
+
const committedStatus = await this.getOwnDeviceVerificationStatus();
|
|
1023
|
+
return {
|
|
1024
|
+
success: true,
|
|
1025
|
+
recoveryKeyAccepted: true,
|
|
1026
|
+
backupUsable,
|
|
1027
|
+
deviceOwnerVerified: true,
|
|
1028
|
+
verifiedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1029
|
+
...committedStatus
|
|
1030
|
+
};
|
|
1031
|
+
} catch (err) {
|
|
1032
|
+
this.recoveryKeyStore.discardStagedRecoveryKey();
|
|
1033
|
+
return await fail(formatMatrixErrorMessage(err));
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
async restoreRoomKeyBackup(params = {}) {
|
|
1037
|
+
let loadedFromSecretStorage = false;
|
|
1038
|
+
const fail = async (error) => {
|
|
1039
|
+
const backup = await this.getRoomKeyBackupStatus();
|
|
1040
|
+
return {
|
|
1041
|
+
success: false,
|
|
1042
|
+
error,
|
|
1043
|
+
backupVersion: backup.serverVersion,
|
|
1044
|
+
imported: 0,
|
|
1045
|
+
total: 0,
|
|
1046
|
+
loadedFromSecretStorage,
|
|
1047
|
+
backup
|
|
1048
|
+
};
|
|
1049
|
+
};
|
|
1050
|
+
if (!this.encryptionEnabled) return await fail("Matrix encryption is disabled for this client");
|
|
1051
|
+
await this.ensureStartedForCryptoControlPlane();
|
|
1052
|
+
const crypto = this.client.getCrypto();
|
|
1053
|
+
if (!crypto) return await fail("Matrix crypto is not available (start client with encryption enabled)");
|
|
1054
|
+
try {
|
|
1055
|
+
const rawRecoveryKey = params.recoveryKey?.trim();
|
|
1056
|
+
if (rawRecoveryKey) this.recoveryKeyStore.stageEncodedRecoveryKey({
|
|
1057
|
+
encodedPrivateKey: rawRecoveryKey,
|
|
1058
|
+
keyId: await this.resolveDefaultSecretStorageKeyId(crypto)
|
|
1059
|
+
});
|
|
1060
|
+
const backup = await this.getRoomKeyBackupStatus();
|
|
1061
|
+
loadedFromSecretStorage = backup.keyLoadAttempted && !backup.keyLoadError;
|
|
1062
|
+
const backupError = resolveMatrixRoomKeyBackupReadinessError(backup, {
|
|
1063
|
+
allowUntrustedMatchingKey: true,
|
|
1064
|
+
requireServerBackup: true
|
|
1065
|
+
});
|
|
1066
|
+
if (backupError) {
|
|
1067
|
+
this.recoveryKeyStore.discardStagedRecoveryKey();
|
|
1068
|
+
return await fail(backupError);
|
|
1069
|
+
}
|
|
1070
|
+
if (typeof crypto.restoreKeyBackup !== "function") {
|
|
1071
|
+
this.recoveryKeyStore.discardStagedRecoveryKey();
|
|
1072
|
+
return await fail("Matrix crypto backend does not support full key backup restore");
|
|
1073
|
+
}
|
|
1074
|
+
const restore = await crypto.restoreKeyBackup();
|
|
1075
|
+
if (rawRecoveryKey) this.recoveryKeyStore.commitStagedRecoveryKey({ keyId: await this.resolveDefaultSecretStorageKeyId(crypto) });
|
|
1076
|
+
const finalBackup = await this.getRoomKeyBackupStatus();
|
|
1077
|
+
return {
|
|
1078
|
+
success: true,
|
|
1079
|
+
backupVersion: backup.serverVersion,
|
|
1080
|
+
imported: typeof restore.imported === "number" ? restore.imported : 0,
|
|
1081
|
+
total: typeof restore.total === "number" ? restore.total : 0,
|
|
1082
|
+
loadedFromSecretStorage,
|
|
1083
|
+
restoredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1084
|
+
backup: finalBackup
|
|
1085
|
+
};
|
|
1086
|
+
} catch (err) {
|
|
1087
|
+
this.recoveryKeyStore.discardStagedRecoveryKey();
|
|
1088
|
+
return await fail(formatMatrixErrorMessage(err));
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
async resetRoomKeyBackup(options = {}) {
|
|
1092
|
+
let previousVersion = null;
|
|
1093
|
+
let deletedVersion = null;
|
|
1094
|
+
const fail = async (error) => {
|
|
1095
|
+
const backup = await this.getRoomKeyBackupStatus();
|
|
1096
|
+
return {
|
|
1097
|
+
success: false,
|
|
1098
|
+
error,
|
|
1099
|
+
previousVersion,
|
|
1100
|
+
deletedVersion,
|
|
1101
|
+
createdVersion: backup.serverVersion,
|
|
1102
|
+
backup
|
|
1103
|
+
};
|
|
1104
|
+
};
|
|
1105
|
+
if (!this.encryptionEnabled) return await fail("Matrix encryption is disabled for this client");
|
|
1106
|
+
await this.ensureStartedForCryptoControlPlane();
|
|
1107
|
+
const crypto = this.client.getCrypto();
|
|
1108
|
+
if (!crypto) return await fail("Matrix crypto is not available (start client with encryption enabled)");
|
|
1109
|
+
previousVersion = await this.resolveRoomKeyBackupVersion();
|
|
1110
|
+
const forceNewSecretStorage = options.rotateRecoveryKey === true || await this.shouldForceSecretStorageRecreationForBackupReset(crypto);
|
|
1111
|
+
try {
|
|
1112
|
+
if (previousVersion) {
|
|
1113
|
+
try {
|
|
1114
|
+
await this.doRequest("DELETE", `/_matrix/client/v3/room_keys/version/${encodeURIComponent(previousVersion)}`);
|
|
1115
|
+
} catch (err) {
|
|
1116
|
+
if (!isMatrixNotFoundError(err)) throw err;
|
|
1117
|
+
}
|
|
1118
|
+
deletedVersion = previousVersion;
|
|
1119
|
+
}
|
|
1120
|
+
await this.recoveryKeyStore.bootstrapSecretStorageWithRecoveryKey(crypto, {
|
|
1121
|
+
setupNewKeyBackup: true,
|
|
1122
|
+
forceNewSecretStorage,
|
|
1123
|
+
forceNewRecoveryKey: options.rotateRecoveryKey === true,
|
|
1124
|
+
allowSecretStorageRecreateWithoutRecoveryKey: true
|
|
1125
|
+
});
|
|
1126
|
+
await this.enableTrustedRoomKeyBackupIfPossible(crypto);
|
|
1127
|
+
const backup = await this.getRoomKeyBackupStatus();
|
|
1128
|
+
const createdVersion = backup.serverVersion;
|
|
1129
|
+
if (!createdVersion) return await fail("Matrix room key backup is still missing after reset.");
|
|
1130
|
+
if (backup.activeVersion !== createdVersion) return await fail("Matrix room key backup was recreated on the server but is not active on this device.");
|
|
1131
|
+
if (backup.decryptionKeyCached === false) return await fail("Matrix room key backup was recreated but its decryption key is not cached on this device.");
|
|
1132
|
+
if (backup.matchesDecryptionKey === false) return await fail("Matrix room key backup was recreated but this device does not have the matching backup decryption key.");
|
|
1133
|
+
if (backup.trusted === false) return await fail("Matrix room key backup was recreated but is not trusted on this device.");
|
|
1134
|
+
return {
|
|
1135
|
+
success: true,
|
|
1136
|
+
previousVersion,
|
|
1137
|
+
deletedVersion,
|
|
1138
|
+
createdVersion,
|
|
1139
|
+
resetAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1140
|
+
backup
|
|
1141
|
+
};
|
|
1142
|
+
} catch (err) {
|
|
1143
|
+
return await fail(formatMatrixErrorMessage(err));
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
async getOwnCrossSigningPublicationStatus() {
|
|
1147
|
+
const userId = this.client.getUserId() ?? this.selfUserId ?? null;
|
|
1148
|
+
if (!userId) return {
|
|
1149
|
+
userId: null,
|
|
1150
|
+
masterKeyPublished: false,
|
|
1151
|
+
selfSigningKeyPublished: false,
|
|
1152
|
+
userSigningKeyPublished: false,
|
|
1153
|
+
published: false
|
|
1154
|
+
};
|
|
1155
|
+
try {
|
|
1156
|
+
const response = await this.doRequest("POST", "/_matrix/client/v3/keys/query", void 0, { device_keys: { [userId]: [] } });
|
|
1157
|
+
const masterKeyPublished = Boolean(response.master_keys?.[userId]);
|
|
1158
|
+
const selfSigningKeyPublished = Boolean(response.self_signing_keys?.[userId]);
|
|
1159
|
+
const userSigningKeyPublished = Boolean(response.user_signing_keys?.[userId]);
|
|
1160
|
+
return {
|
|
1161
|
+
userId,
|
|
1162
|
+
masterKeyPublished,
|
|
1163
|
+
selfSigningKeyPublished,
|
|
1164
|
+
userSigningKeyPublished,
|
|
1165
|
+
published: masterKeyPublished && selfSigningKeyPublished && userSigningKeyPublished
|
|
1166
|
+
};
|
|
1167
|
+
} catch {
|
|
1168
|
+
return {
|
|
1169
|
+
userId,
|
|
1170
|
+
masterKeyPublished: false,
|
|
1171
|
+
selfSigningKeyPublished: false,
|
|
1172
|
+
userSigningKeyPublished: false,
|
|
1173
|
+
published: false
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
async bootstrapOwnDeviceVerification(params) {
|
|
1178
|
+
const pendingVerifications = async () => this.crypto ? (await this.crypto.listVerifications()).length : 0;
|
|
1179
|
+
if (!this.encryptionEnabled) return {
|
|
1180
|
+
success: false,
|
|
1181
|
+
error: "Matrix encryption is disabled for this client",
|
|
1182
|
+
verification: await this.getOwnDeviceVerificationStatus(),
|
|
1183
|
+
crossSigning: await this.getOwnCrossSigningPublicationStatus(),
|
|
1184
|
+
pendingVerifications: await pendingVerifications(),
|
|
1185
|
+
cryptoBootstrap: null
|
|
1186
|
+
};
|
|
1187
|
+
let bootstrapError;
|
|
1188
|
+
let bootstrapSummary = null;
|
|
1189
|
+
let rawRecoveryKey;
|
|
1190
|
+
try {
|
|
1191
|
+
await this.ensureStartedForCryptoControlPlane();
|
|
1192
|
+
await this.ensureCryptoSupportInitialized();
|
|
1193
|
+
const crypto = this.client.getCrypto();
|
|
1194
|
+
if (!crypto) throw new Error("Matrix crypto is not available (start client with encryption enabled)");
|
|
1195
|
+
rawRecoveryKey = params?.recoveryKey?.trim();
|
|
1196
|
+
if (rawRecoveryKey) this.recoveryKeyStore.stageEncodedRecoveryKey({
|
|
1197
|
+
encodedPrivateKey: rawRecoveryKey,
|
|
1198
|
+
keyId: await this.resolveDefaultSecretStorageKeyId(crypto)
|
|
1199
|
+
});
|
|
1200
|
+
const cryptoBootstrapper = this.cryptoBootstrapper;
|
|
1201
|
+
if (!cryptoBootstrapper) throw new Error("Matrix crypto bootstrapper is not available");
|
|
1202
|
+
bootstrapSummary = await cryptoBootstrapper.bootstrap(crypto, createMatrixExplicitBootstrapOptions({
|
|
1203
|
+
...params,
|
|
1204
|
+
allowAutomaticCrossSigningReset: rawRecoveryKey ? false : params?.allowAutomaticCrossSigningReset
|
|
1205
|
+
}));
|
|
1206
|
+
await this.ensureRoomKeyBackupEnabled(crypto);
|
|
1207
|
+
} catch (err) {
|
|
1208
|
+
this.recoveryKeyStore.discardStagedRecoveryKey();
|
|
1209
|
+
bootstrapError = formatMatrixErrorMessage(err);
|
|
1210
|
+
}
|
|
1211
|
+
const verification = await this.getOwnDeviceVerificationStatus();
|
|
1212
|
+
const crossSigning = await this.getOwnCrossSigningPublicationStatus();
|
|
1213
|
+
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";
|
|
1214
|
+
const backupError = verificationError === null ? resolveMatrixRoomKeyBackupReadinessError(verification.backup, {
|
|
1215
|
+
allowUntrustedMatchingKey: Boolean(rawRecoveryKey),
|
|
1216
|
+
requireServerBackup: true
|
|
1217
|
+
}) : null;
|
|
1218
|
+
const success = verificationError === null && backupError === null;
|
|
1219
|
+
if (success) this.recoveryKeyStore.commitStagedRecoveryKey({ keyId: await this.resolveDefaultSecretStorageKeyId(this.client.getCrypto()) });
|
|
1220
|
+
else this.recoveryKeyStore.discardStagedRecoveryKey();
|
|
1221
|
+
return {
|
|
1222
|
+
success,
|
|
1223
|
+
error: success ? void 0 : backupError ?? verificationError ?? void 0,
|
|
1224
|
+
verification: success ? await this.getOwnDeviceVerificationStatus() : verification,
|
|
1225
|
+
crossSigning,
|
|
1226
|
+
pendingVerifications: await pendingVerifications(),
|
|
1227
|
+
cryptoBootstrap: bootstrapSummary
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
async listOwnDevices() {
|
|
1231
|
+
const currentDeviceId = this.client.getDeviceId()?.trim() || null;
|
|
1232
|
+
const devices = await this.client.getDevices();
|
|
1233
|
+
return (Array.isArray(devices?.devices) ? devices.devices : []).map((device) => ({
|
|
1234
|
+
deviceId: device.device_id,
|
|
1235
|
+
displayName: device.display_name?.trim() || null,
|
|
1236
|
+
lastSeenIp: device.last_seen_ip?.trim() || null,
|
|
1237
|
+
lastSeenTs: typeof device.last_seen_ts === "number" && Number.isFinite(device.last_seen_ts) ? device.last_seen_ts : null,
|
|
1238
|
+
current: currentDeviceId !== null && device.device_id === currentDeviceId
|
|
1239
|
+
}));
|
|
1240
|
+
}
|
|
1241
|
+
async deleteOwnDevices(deviceIds) {
|
|
1242
|
+
const uniqueDeviceIds = [...new Set(deviceIds.map((value) => value.trim()).filter(Boolean))];
|
|
1243
|
+
const currentDeviceId = this.client.getDeviceId()?.trim() || null;
|
|
1244
|
+
const protectedDeviceIds = uniqueDeviceIds.filter((deviceId) => deviceId === currentDeviceId);
|
|
1245
|
+
if (protectedDeviceIds.length > 0) throw new Error(`Refusing to delete the current Matrix device: ${protectedDeviceIds[0]}`);
|
|
1246
|
+
const deleteWithAuth = async (authData) => {
|
|
1247
|
+
await this.client.deleteMultipleDevices(uniqueDeviceIds, authData);
|
|
1248
|
+
};
|
|
1249
|
+
if (uniqueDeviceIds.length > 0) try {
|
|
1250
|
+
await deleteWithAuth();
|
|
1251
|
+
} catch (err) {
|
|
1252
|
+
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;
|
|
1253
|
+
const userId = await this.getUserId().catch(() => this.selfUserId);
|
|
1254
|
+
if (!session || !userId || !this.password?.trim()) throw err;
|
|
1255
|
+
await deleteWithAuth({
|
|
1256
|
+
type: "m.login.password",
|
|
1257
|
+
session,
|
|
1258
|
+
identifier: {
|
|
1259
|
+
type: "m.id.user",
|
|
1260
|
+
user: userId
|
|
1261
|
+
},
|
|
1262
|
+
password: this.password
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
return {
|
|
1266
|
+
currentDeviceId,
|
|
1267
|
+
deletedDeviceIds: uniqueDeviceIds,
|
|
1268
|
+
remainingDevices: await this.listOwnDevices()
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
async resolveActiveRoomKeyBackupVersion(crypto) {
|
|
1272
|
+
if (typeof crypto.getActiveSessionBackupVersion !== "function") return null;
|
|
1273
|
+
return normalizeOptionalString$1(await crypto.getActiveSessionBackupVersion().catch(() => null));
|
|
1274
|
+
}
|
|
1275
|
+
async resolveCachedRoomKeyBackupDecryptionKey(crypto) {
|
|
1276
|
+
const getSessionBackupPrivateKey = crypto.getSessionBackupPrivateKey;
|
|
1277
|
+
if (typeof getSessionBackupPrivateKey !== "function") return null;
|
|
1278
|
+
const key = await getSessionBackupPrivateKey.call(crypto).catch(() => null);
|
|
1279
|
+
return key ? key.length > 0 : false;
|
|
1280
|
+
}
|
|
1281
|
+
async resolveRoomKeyBackupLocalState(crypto) {
|
|
1282
|
+
const [activeVersion, decryptionKeyCached] = await Promise.all([this.resolveActiveRoomKeyBackupVersion(crypto), this.resolveCachedRoomKeyBackupDecryptionKey(crypto)]);
|
|
1283
|
+
return {
|
|
1284
|
+
activeVersion,
|
|
1285
|
+
decryptionKeyCached
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1288
|
+
async shouldForceSecretStorageRecreationForBackupReset(crypto) {
|
|
1289
|
+
if (await this.resolveCachedRoomKeyBackupDecryptionKey(crypto) !== false) return false;
|
|
1290
|
+
const loadSessionBackupPrivateKeyFromSecretStorage = crypto.loadSessionBackupPrivateKeyFromSecretStorage;
|
|
1291
|
+
if (typeof loadSessionBackupPrivateKeyFromSecretStorage !== "function") return false;
|
|
1292
|
+
try {
|
|
1293
|
+
await loadSessionBackupPrivateKeyFromSecretStorage.call(crypto);
|
|
1294
|
+
return false;
|
|
1295
|
+
} catch (err) {
|
|
1296
|
+
return isRepairableSecretStorageAccessError(err);
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
async resolveRoomKeyBackupTrustState(crypto, fallbackVersion) {
|
|
1300
|
+
let serverVersion = fallbackVersion;
|
|
1301
|
+
let trusted = null;
|
|
1302
|
+
let matchesDecryptionKey = null;
|
|
1303
|
+
if (typeof crypto.getKeyBackupInfo === "function") {
|
|
1304
|
+
const info = await crypto.getKeyBackupInfo().catch(() => null);
|
|
1305
|
+
serverVersion = normalizeOptionalString$1(info?.version) ?? serverVersion;
|
|
1306
|
+
if (info && typeof crypto.isKeyBackupTrusted === "function") {
|
|
1307
|
+
const trustInfo = await crypto.isKeyBackupTrusted(info).catch(() => null);
|
|
1308
|
+
trusted = typeof trustInfo?.trusted === "boolean" ? trustInfo.trusted : null;
|
|
1309
|
+
matchesDecryptionKey = typeof trustInfo?.matchesDecryptionKey === "boolean" ? trustInfo.matchesDecryptionKey : null;
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
return {
|
|
1313
|
+
serverVersion,
|
|
1314
|
+
trusted,
|
|
1315
|
+
matchesDecryptionKey
|
|
1316
|
+
};
|
|
1317
|
+
}
|
|
1318
|
+
async resolveDefaultSecretStorageKeyId(crypto) {
|
|
1319
|
+
const getSecretStorageStatus = crypto?.getSecretStorageStatus;
|
|
1320
|
+
if (typeof getSecretStorageStatus !== "function") return;
|
|
1321
|
+
return (await getSecretStorageStatus.call(crypto).catch(() => null))?.defaultKeyId;
|
|
1322
|
+
}
|
|
1323
|
+
async resolveRoomKeyBackupVersion() {
|
|
1324
|
+
try {
|
|
1325
|
+
return normalizeOptionalString$1((await this.doRequest("GET", "/_matrix/client/v3/room_keys/version")).version);
|
|
1326
|
+
} catch {
|
|
1327
|
+
return null;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
async enableTrustedRoomKeyBackupIfPossible(crypto) {
|
|
1331
|
+
if (typeof crypto.checkKeyBackupAndEnable !== "function") return;
|
|
1332
|
+
await crypto.checkKeyBackupAndEnable();
|
|
1333
|
+
}
|
|
1334
|
+
async ensureRoomKeyBackupEnabled(crypto) {
|
|
1335
|
+
if (await this.resolveRoomKeyBackupVersion()) return;
|
|
1336
|
+
LogService.info("MatrixClientLite", "No room key backup version found on server, creating one via secret storage bootstrap");
|
|
1337
|
+
await this.recoveryKeyStore.bootstrapSecretStorageWithRecoveryKey(crypto, { setupNewKeyBackup: true });
|
|
1338
|
+
const createdVersion = await this.resolveRoomKeyBackupVersion();
|
|
1339
|
+
if (!createdVersion) throw new Error("Matrix room key backup is still missing after bootstrap");
|
|
1340
|
+
LogService.info("MatrixClientLite", `Room key backup enabled (version ${createdVersion})`);
|
|
1341
|
+
}
|
|
1342
|
+
registerBridge() {
|
|
1343
|
+
if (this.bridgeRegistered || !this.decryptBridge) return;
|
|
1344
|
+
this.bridgeRegistered = true;
|
|
1345
|
+
const decryptBridge = this.decryptBridge;
|
|
1346
|
+
this.client.on(ClientEvent.Event, (event) => {
|
|
1347
|
+
const roomId = event.getRoomId();
|
|
1348
|
+
if (!roomId) return;
|
|
1349
|
+
const raw = matrixEventToRaw(event, { contentMode: "original" });
|
|
1350
|
+
const isEncryptedEvent = raw.type === "m.room.encrypted";
|
|
1351
|
+
this.emitter.emit("room.event", roomId, raw);
|
|
1352
|
+
if (isEncryptedEvent) this.emitter.emit("room.encrypted_event", roomId, raw);
|
|
1353
|
+
else if (decryptBridge.shouldEmitUnencryptedMessage(roomId, raw.event_id)) this.emitter.emit("room.message", roomId, raw);
|
|
1354
|
+
const stateKey = raw.state_key ?? "";
|
|
1355
|
+
const selfUserId = this.client.getUserId() ?? this.selfUserId ?? "";
|
|
1356
|
+
const membership = raw.type === "m.room.member" ? raw.content.membership : void 0;
|
|
1357
|
+
if (stateKey && selfUserId && stateKey === selfUserId) {
|
|
1358
|
+
if (membership === "invite") this.emitter.emit("room.invite", roomId, raw);
|
|
1359
|
+
else if (membership === "join") this.emitter.emit("room.join", roomId, raw);
|
|
1360
|
+
}
|
|
1361
|
+
if (isEncryptedEvent) decryptBridge.attachEncryptedEvent(event, roomId);
|
|
1362
|
+
});
|
|
1363
|
+
this.client.on(ClientEvent.Room, (room) => {
|
|
1364
|
+
this.emitMembershipForRoom(room);
|
|
1365
|
+
});
|
|
1366
|
+
this.client.on(ClientEvent.Sync, (state, prevState, data) => {
|
|
1367
|
+
this.currentSyncState = state;
|
|
1368
|
+
const error = data && typeof data === "object" && "error" in data ? data.error : void 0;
|
|
1369
|
+
this.emitter.emit("sync.state", state, prevState, error);
|
|
1370
|
+
});
|
|
1371
|
+
this.client.on(ClientEvent.SyncUnexpectedError, (error) => {
|
|
1372
|
+
this.emitter.emit("sync.unexpected_error", error);
|
|
1373
|
+
});
|
|
1374
|
+
}
|
|
1375
|
+
emitMembershipForRoom(room) {
|
|
1376
|
+
const roomObj = room;
|
|
1377
|
+
const roomId = roomObj.roomId?.trim();
|
|
1378
|
+
if (!roomId) return;
|
|
1379
|
+
const membership = roomObj.getMyMembership?.() ?? roomObj.selfMembership ?? void 0;
|
|
1380
|
+
const selfUserId = this.client.getUserId() ?? this.selfUserId ?? "";
|
|
1381
|
+
if (!selfUserId) return;
|
|
1382
|
+
const raw = {
|
|
1383
|
+
event_id: `$membership-${roomId}-${Date.now()}`,
|
|
1384
|
+
type: "m.room.member",
|
|
1385
|
+
sender: selfUserId,
|
|
1386
|
+
state_key: selfUserId,
|
|
1387
|
+
content: { membership },
|
|
1388
|
+
origin_server_ts: Date.now(),
|
|
1389
|
+
unsigned: { age: 0 }
|
|
1390
|
+
};
|
|
1391
|
+
if (membership === "invite") {
|
|
1392
|
+
this.emitter.emit("room.invite", roomId, raw);
|
|
1393
|
+
return;
|
|
1394
|
+
}
|
|
1395
|
+
if (membership === "join") this.emitter.emit("room.join", roomId, raw);
|
|
1396
|
+
}
|
|
1397
|
+
emitOutstandingInviteEvents() {
|
|
1398
|
+
const listRooms = this.client.getRooms;
|
|
1399
|
+
if (typeof listRooms !== "function") return;
|
|
1400
|
+
const rooms = listRooms.call(this.client);
|
|
1401
|
+
if (!Array.isArray(rooms)) return;
|
|
1402
|
+
for (const room of rooms) this.emitMembershipForRoom(room);
|
|
1403
|
+
}
|
|
1404
|
+
async refreshDmCache() {
|
|
1405
|
+
const direct = await this.getAccountData("m.direct");
|
|
1406
|
+
this.dmRoomIds.clear();
|
|
1407
|
+
if (!direct || typeof direct !== "object") return false;
|
|
1408
|
+
for (const value of Object.values(direct)) {
|
|
1409
|
+
if (!Array.isArray(value)) continue;
|
|
1410
|
+
for (const roomId of value) if (typeof roomId === "string" && roomId.trim()) this.dmRoomIds.add(roomId);
|
|
1411
|
+
}
|
|
1412
|
+
return true;
|
|
1413
|
+
}
|
|
1414
|
+
};
|
|
1415
|
+
//#endregion
|
|
1416
|
+
export { sdk_exports as n, MatrixClient as t };
|