@kodelyth/matrix 2026.5.42 → 2026.6.2

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.
Files changed (205) hide show
  1. package/klaw.plugin.json +891 -3
  2. package/package.json +18 -6
  3. package/CHANGELOG.md +0 -321
  4. package/SPEC-SUPPORT.md +0 -116
  5. package/api.ts +0 -38
  6. package/auth-presence.ts +0 -56
  7. package/channel-plugin-api.ts +0 -3
  8. package/cli-metadata.ts +0 -11
  9. package/contract-api.ts +0 -17
  10. package/doctor-contract-api.ts +0 -1
  11. package/helper-api.ts +0 -3
  12. package/index.ts +0 -55
  13. package/plugin-entry.handlers.runtime.ts +0 -1
  14. package/runtime-api.ts +0 -72
  15. package/runtime-heavy-api.ts +0 -1
  16. package/runtime-setter-api.ts +0 -3
  17. package/secret-contract-api.ts +0 -5
  18. package/setup-entry.ts +0 -17
  19. package/setup-plugin-api.ts +0 -3
  20. package/src/account-selection.ts +0 -223
  21. package/src/actions.ts +0 -346
  22. package/src/approval-auth.ts +0 -25
  23. package/src/approval-handler.runtime.ts +0 -592
  24. package/src/approval-ids.ts +0 -6
  25. package/src/approval-native.ts +0 -345
  26. package/src/approval-reaction-auth.ts +0 -45
  27. package/src/approval-reactions.ts +0 -313
  28. package/src/auth-precedence.ts +0 -61
  29. package/src/channel-account-paths.ts +0 -97
  30. package/src/channel.runtime.ts +0 -17
  31. package/src/channel.setup.ts +0 -48
  32. package/src/channel.ts +0 -667
  33. package/src/cli-metadata.ts +0 -19
  34. package/src/cli.ts +0 -2298
  35. package/src/config-adapter.ts +0 -41
  36. package/src/config-schema.ts +0 -159
  37. package/src/config-ui-hints.ts +0 -56
  38. package/src/directory-live.ts +0 -238
  39. package/src/doctor-contract.ts +0 -287
  40. package/src/doctor.ts +0 -262
  41. package/src/env-vars.ts +0 -92
  42. package/src/exec-approval-resolver.ts +0 -23
  43. package/src/exec-approvals.ts +0 -287
  44. package/src/group-mentions.ts +0 -41
  45. package/src/legacy-crypto-inspector-availability.ts +0 -60
  46. package/src/legacy-crypto.ts +0 -531
  47. package/src/legacy-state.ts +0 -156
  48. package/src/matrix/account-config.ts +0 -175
  49. package/src/matrix/accounts.ts +0 -194
  50. package/src/matrix/actions/client.ts +0 -31
  51. package/src/matrix/actions/devices.ts +0 -34
  52. package/src/matrix/actions/limits.ts +0 -6
  53. package/src/matrix/actions/messages.ts +0 -129
  54. package/src/matrix/actions/pins.ts +0 -63
  55. package/src/matrix/actions/polls.ts +0 -109
  56. package/src/matrix/actions/profile.ts +0 -37
  57. package/src/matrix/actions/reactions.ts +0 -59
  58. package/src/matrix/actions/room.ts +0 -71
  59. package/src/matrix/actions/summary.ts +0 -88
  60. package/src/matrix/actions/types.ts +0 -63
  61. package/src/matrix/actions/verification.ts +0 -589
  62. package/src/matrix/actions.ts +0 -37
  63. package/src/matrix/active-client.ts +0 -26
  64. package/src/matrix/async-lock.ts +0 -18
  65. package/src/matrix/backup-health.ts +0 -124
  66. package/src/matrix/client/config-runtime-api.ts +0 -9
  67. package/src/matrix/client/config-secret-input.runtime.ts +0 -1
  68. package/src/matrix/client/config.ts +0 -853
  69. package/src/matrix/client/create-client.ts +0 -105
  70. package/src/matrix/client/env-auth.ts +0 -95
  71. package/src/matrix/client/file-sync-store.ts +0 -289
  72. package/src/matrix/client/logging.ts +0 -140
  73. package/src/matrix/client/migration-snapshot.runtime.ts +0 -1
  74. package/src/matrix/client/private-network-host.ts +0 -1
  75. package/src/matrix/client/runtime.ts +0 -4
  76. package/src/matrix/client/shared.ts +0 -316
  77. package/src/matrix/client/storage.ts +0 -543
  78. package/src/matrix/client/types.ts +0 -50
  79. package/src/matrix/client/url-validation.ts +0 -73
  80. package/src/matrix/client-bootstrap.ts +0 -173
  81. package/src/matrix/client.ts +0 -23
  82. package/src/matrix/config-paths.ts +0 -31
  83. package/src/matrix/config-update.ts +0 -292
  84. package/src/matrix/credentials-read.ts +0 -208
  85. package/src/matrix/credentials-write.runtime.ts +0 -35
  86. package/src/matrix/credentials.ts +0 -95
  87. package/src/matrix/deps.ts +0 -309
  88. package/src/matrix/device-health.ts +0 -29
  89. package/src/matrix/direct-management.ts +0 -349
  90. package/src/matrix/direct-room.ts +0 -128
  91. package/src/matrix/draft-stream.ts +0 -225
  92. package/src/matrix/encryption-guidance.ts +0 -24
  93. package/src/matrix/errors.ts +0 -21
  94. package/src/matrix/format.ts +0 -426
  95. package/src/matrix/legacy-crypto-inspector.ts +0 -95
  96. package/src/matrix/media-errors.ts +0 -20
  97. package/src/matrix/media-text.ts +0 -162
  98. package/src/matrix/monitor/access-state.ts +0 -145
  99. package/src/matrix/monitor/ack-config.ts +0 -27
  100. package/src/matrix/monitor/allowlist.ts +0 -89
  101. package/src/matrix/monitor/auto-join.ts +0 -86
  102. package/src/matrix/monitor/config.ts +0 -569
  103. package/src/matrix/monitor/context-summary.ts +0 -43
  104. package/src/matrix/monitor/direct.ts +0 -296
  105. package/src/matrix/monitor/events.ts +0 -397
  106. package/src/matrix/monitor/handler.ts +0 -2266
  107. package/src/matrix/monitor/inbound-dedupe.ts +0 -267
  108. package/src/matrix/monitor/index.ts +0 -540
  109. package/src/matrix/monitor/legacy-crypto-restore.ts +0 -139
  110. package/src/matrix/monitor/location.ts +0 -108
  111. package/src/matrix/monitor/media.ts +0 -119
  112. package/src/matrix/monitor/mentions.ts +0 -256
  113. package/src/matrix/monitor/reaction-events.ts +0 -197
  114. package/src/matrix/monitor/recent-invite.ts +0 -30
  115. package/src/matrix/monitor/replies.ts +0 -136
  116. package/src/matrix/monitor/reply-context.ts +0 -92
  117. package/src/matrix/monitor/room-history.ts +0 -301
  118. package/src/matrix/monitor/room-info.ts +0 -126
  119. package/src/matrix/monitor/rooms.ts +0 -52
  120. package/src/matrix/monitor/route.ts +0 -179
  121. package/src/matrix/monitor/runtime-api.ts +0 -28
  122. package/src/matrix/monitor/startup-verification.ts +0 -237
  123. package/src/matrix/monitor/startup.ts +0 -218
  124. package/src/matrix/monitor/status.ts +0 -120
  125. package/src/matrix/monitor/sync-lifecycle.ts +0 -91
  126. package/src/matrix/monitor/task-runner.ts +0 -38
  127. package/src/matrix/monitor/test-events.ts +0 -21
  128. package/src/matrix/monitor/thread-context.ts +0 -108
  129. package/src/matrix/monitor/threads.ts +0 -85
  130. package/src/matrix/monitor/types.ts +0 -30
  131. package/src/matrix/monitor/verification-events.ts +0 -643
  132. package/src/matrix/monitor/verification-utils.ts +0 -46
  133. package/src/matrix/outbound-media-runtime.ts +0 -1
  134. package/src/matrix/poll-summary.ts +0 -110
  135. package/src/matrix/poll-types.ts +0 -429
  136. package/src/matrix/probe.runtime.ts +0 -4
  137. package/src/matrix/probe.ts +0 -97
  138. package/src/matrix/profile.ts +0 -184
  139. package/src/matrix/reaction-common.ts +0 -147
  140. package/src/matrix/sdk/crypto-bootstrap.ts +0 -438
  141. package/src/matrix/sdk/crypto-facade.ts +0 -242
  142. package/src/matrix/sdk/crypto-node.runtime.ts +0 -17
  143. package/src/matrix/sdk/crypto-runtime.ts +0 -14
  144. package/src/matrix/sdk/decrypt-bridge.ts +0 -410
  145. package/src/matrix/sdk/event-helpers.ts +0 -83
  146. package/src/matrix/sdk/http-client.ts +0 -87
  147. package/src/matrix/sdk/idb-persistence-lock.ts +0 -51
  148. package/src/matrix/sdk/idb-persistence.ts +0 -288
  149. package/src/matrix/sdk/logger.ts +0 -108
  150. package/src/matrix/sdk/read-response-with-limit.ts +0 -19
  151. package/src/matrix/sdk/recovery-key-store.ts +0 -453
  152. package/src/matrix/sdk/timeout-abort-signal.ts +0 -1
  153. package/src/matrix/sdk/transport-runtime-api.ts +0 -18
  154. package/src/matrix/sdk/transport.ts +0 -352
  155. package/src/matrix/sdk/types.ts +0 -245
  156. package/src/matrix/sdk/verification-manager.ts +0 -795
  157. package/src/matrix/sdk/verification-status.ts +0 -23
  158. package/src/matrix/sdk.ts +0 -2152
  159. package/src/matrix/send/client.ts +0 -93
  160. package/src/matrix/send/formatting.ts +0 -189
  161. package/src/matrix/send/media.ts +0 -244
  162. package/src/matrix/send/targets.ts +0 -104
  163. package/src/matrix/send/types.ts +0 -131
  164. package/src/matrix/send.ts +0 -660
  165. package/src/matrix/session-store-metadata.ts +0 -108
  166. package/src/matrix/startup-abort.ts +0 -44
  167. package/src/matrix/subagent-hooks.ts +0 -308
  168. package/src/matrix/sync-state.ts +0 -27
  169. package/src/matrix/target-ids.ts +0 -79
  170. package/src/matrix/thread-bindings-shared.ts +0 -206
  171. package/src/matrix/thread-bindings.ts +0 -580
  172. package/src/matrix-migration.runtime.ts +0 -9
  173. package/src/migration-config.ts +0 -243
  174. package/src/migration-snapshot-backup.ts +0 -116
  175. package/src/migration-snapshot.ts +0 -53
  176. package/src/onboarding.ts +0 -775
  177. package/src/outbound.ts +0 -248
  178. package/src/plugin-entry.runtime.js +0 -115
  179. package/src/plugin-entry.runtime.ts +0 -70
  180. package/src/profile-update.ts +0 -71
  181. package/src/record-shared.ts +0 -3
  182. package/src/resolve-targets.ts +0 -175
  183. package/src/resolver.runtime.ts +0 -5
  184. package/src/resolver.ts +0 -21
  185. package/src/runtime-api.ts +0 -106
  186. package/src/runtime.ts +0 -13
  187. package/src/secret-contract.ts +0 -174
  188. package/src/session-route.ts +0 -126
  189. package/src/setup-bootstrap.ts +0 -102
  190. package/src/setup-config.ts +0 -222
  191. package/src/setup-contract.ts +0 -90
  192. package/src/setup-core.ts +0 -146
  193. package/src/setup-dm-policy.ts +0 -15
  194. package/src/setup-surface.ts +0 -4
  195. package/src/startup-maintenance.ts +0 -114
  196. package/src/storage-paths.ts +0 -92
  197. package/src/thread-binding-api.ts +0 -23
  198. package/src/tool-actions.runtime.ts +0 -1
  199. package/src/tool-actions.ts +0 -498
  200. package/src/types.ts +0 -257
  201. package/subagent-hooks-api.ts +0 -31
  202. package/test-api.ts +0 -21
  203. package/thread-binding-api.ts +0 -4
  204. package/thread-bindings-runtime.ts +0 -4
  205. package/tsconfig.json +0 -16
@@ -1,643 +0,0 @@
1
- import type { MatrixClient } from "../sdk.js";
2
- import { resolveMatrixMonitorAccessState } from "./access-state.js";
3
- import type { MatrixRawEvent } from "./types.js";
4
- import { EventType } from "./types.js";
5
- import {
6
- isMatrixVerificationEventType,
7
- isMatrixVerificationRequestMsgType,
8
- matrixVerificationConstants,
9
- } from "./verification-utils.js";
10
-
11
- const MAX_TRACKED_VERIFICATION_EVENTS = 1024;
12
- const SAS_NOTICE_RETRY_DELAY_MS = 750;
13
- const VERIFICATION_EVENT_STARTUP_GRACE_MS = 30_000;
14
-
15
- type MatrixVerificationStage = "request" | "ready" | "start" | "cancel" | "done" | "other";
16
-
17
- type MatrixVerificationSummaryLike = {
18
- id: string;
19
- transactionId?: string;
20
- roomId?: string;
21
- otherUserId: string;
22
- updatedAt?: string;
23
- completed?: boolean;
24
- pending?: boolean;
25
- phase?: number;
26
- phaseName?: string;
27
- sas?: {
28
- decimal?: [number, number, number];
29
- emoji?: Array<[string, string]>;
30
- };
31
- };
32
-
33
- type MatrixDirectRoomDeps = {
34
- inspectMatrixDirectRooms: typeof import("../direct-management.js").inspectMatrixDirectRooms;
35
- isStrictDirectRoom: typeof import("../direct-room.js").isStrictDirectRoom;
36
- };
37
-
38
- let matrixDirectRoomDepsPromise: Promise<MatrixDirectRoomDeps> | undefined;
39
-
40
- async function loadMatrixDirectRoomDeps(): Promise<MatrixDirectRoomDeps> {
41
- matrixDirectRoomDepsPromise ??= Promise.all([
42
- import("../direct-management.js"),
43
- import("../direct-room.js"),
44
- ]).then(([directManagementModule, directRoomModule]) => ({
45
- inspectMatrixDirectRooms: directManagementModule.inspectMatrixDirectRooms,
46
- isStrictDirectRoom: directRoomModule.isStrictDirectRoom,
47
- }));
48
- return await matrixDirectRoomDepsPromise;
49
- }
50
-
51
- function trimMaybeString(input: unknown): string | null {
52
- if (typeof input !== "string") {
53
- return null;
54
- }
55
- const trimmed = input.trim();
56
- return trimmed.length > 0 ? trimmed : null;
57
- }
58
-
59
- function readVerificationSignal(event: MatrixRawEvent): {
60
- stage: MatrixVerificationStage;
61
- flowId: string | null;
62
- } | null {
63
- const type = trimMaybeString(event?.type) ?? "";
64
- const content = event?.content ?? {};
65
- const msgtype = trimMaybeString((content as { msgtype?: unknown }).msgtype) ?? "";
66
- const relatedEventId = trimMaybeString(
67
- (content as { "m.relates_to"?: { event_id?: unknown } })["m.relates_to"]?.event_id,
68
- );
69
- const transactionId = trimMaybeString((content as { transaction_id?: unknown }).transaction_id);
70
- if (type === EventType.RoomMessage && isMatrixVerificationRequestMsgType(msgtype)) {
71
- return {
72
- stage: "request",
73
- flowId: trimMaybeString(event.event_id) ?? transactionId ?? relatedEventId,
74
- };
75
- }
76
- if (!isMatrixVerificationEventType(type)) {
77
- return null;
78
- }
79
- const flowId = transactionId ?? relatedEventId ?? trimMaybeString(event.event_id);
80
- if (type === `${matrixVerificationConstants.eventPrefix}request`) {
81
- return { stage: "request", flowId };
82
- }
83
- if (type === `${matrixVerificationConstants.eventPrefix}ready`) {
84
- return { stage: "ready", flowId };
85
- }
86
- if (type === "m.key.verification.start") {
87
- return { stage: "start", flowId };
88
- }
89
- if (type === "m.key.verification.cancel") {
90
- return { stage: "cancel", flowId };
91
- }
92
- if (type === "m.key.verification.done") {
93
- return { stage: "done", flowId };
94
- }
95
- return { stage: "other", flowId };
96
- }
97
-
98
- function formatVerificationStageNotice(params: {
99
- stage: MatrixVerificationStage;
100
- senderId: string;
101
- event: MatrixRawEvent;
102
- }): string | null {
103
- const { stage, senderId, event } = params;
104
- const content = event.content as { code?: unknown; reason?: unknown };
105
- switch (stage) {
106
- case "request":
107
- return `Matrix verification request received from ${senderId}. Open "Verify by emoji" in your Matrix client to continue.`;
108
- case "ready":
109
- return `Matrix verification is ready with ${senderId}. Choose "Verify by emoji" to reveal the emoji sequence.`;
110
- case "start":
111
- return `Matrix verification started with ${senderId}.`;
112
- case "done":
113
- return `Matrix verification completed with ${senderId}.`;
114
- case "cancel": {
115
- const code = trimMaybeString(content.code);
116
- const reason = trimMaybeString(content.reason);
117
- if (code && reason) {
118
- return `Matrix verification cancelled by ${senderId} (${code}: ${reason}).`;
119
- }
120
- if (reason) {
121
- return `Matrix verification cancelled by ${senderId} (${reason}).`;
122
- }
123
- return `Matrix verification cancelled by ${senderId}.`;
124
- }
125
- default:
126
- return null;
127
- }
128
- }
129
-
130
- function formatVerificationSasNotice(summary: MatrixVerificationSummaryLike): string | null {
131
- const sas = summary.sas;
132
- if (!sas) {
133
- return null;
134
- }
135
- const emojiLine =
136
- Array.isArray(sas.emoji) && sas.emoji.length > 0
137
- ? `SAS emoji: ${sas.emoji
138
- .map(
139
- ([emoji, name]) => `${trimMaybeString(emoji) ?? "?"} ${trimMaybeString(name) ?? "?"}`,
140
- )
141
- .join(" | ")}`
142
- : null;
143
- const decimalLine =
144
- Array.isArray(sas.decimal) && sas.decimal.length === 3
145
- ? `SAS decimal: ${sas.decimal.join(" ")}`
146
- : null;
147
- if (!emojiLine && !decimalLine) {
148
- return null;
149
- }
150
- const lines = [`Matrix verification SAS with ${summary.otherUserId}:`];
151
- if (emojiLine) {
152
- lines.push(emojiLine);
153
- }
154
- if (decimalLine) {
155
- lines.push(decimalLine);
156
- }
157
- lines.push("If both sides match, choose 'They match' in your Matrix app.");
158
- return lines.join("\n");
159
- }
160
-
161
- function resolveVerificationFlowCandidates(params: {
162
- event: MatrixRawEvent;
163
- flowId: string | null;
164
- }): string[] {
165
- const { event, flowId } = params;
166
- const content = event.content as {
167
- transaction_id?: unknown;
168
- "m.relates_to"?: { event_id?: unknown };
169
- };
170
- const candidates = new Set<string>();
171
- const add = (value: unknown) => {
172
- const normalized = trimMaybeString(value);
173
- if (normalized) {
174
- candidates.add(normalized);
175
- }
176
- };
177
- add(flowId);
178
- add(event.event_id);
179
- add(content.transaction_id);
180
- add(content["m.relates_to"]?.event_id);
181
- return Array.from(candidates);
182
- }
183
-
184
- function resolveSummaryRecency(summary: MatrixVerificationSummaryLike): number {
185
- const ts = Date.parse(summary.updatedAt ?? "");
186
- return Number.isFinite(ts) ? ts : 0;
187
- }
188
-
189
- function isActiveVerificationSummary(summary: MatrixVerificationSummaryLike): boolean {
190
- if (summary.completed === true) {
191
- return false;
192
- }
193
- if (summary.phaseName === "cancelled" || summary.phaseName === "done") {
194
- return false;
195
- }
196
- if (typeof summary.phase === "number" && summary.phase >= 4) {
197
- return false;
198
- }
199
- return true;
200
- }
201
-
202
- async function resolveVerificationSummaryForSignal(
203
- client: MatrixClient,
204
- params: {
205
- roomId: string;
206
- event: MatrixRawEvent;
207
- senderId: string;
208
- flowId: string | null;
209
- },
210
- ): Promise<MatrixVerificationSummaryLike | null> {
211
- if (!client.crypto) {
212
- return null;
213
- }
214
- await client.crypto
215
- .ensureVerificationDmTracked({
216
- roomId: params.roomId,
217
- userId: params.senderId,
218
- })
219
- .catch(() => null);
220
- const list = await client.crypto.listVerifications();
221
- if (list.length === 0) {
222
- return null;
223
- }
224
- const candidates = resolveVerificationFlowCandidates({
225
- event: params.event,
226
- flowId: params.flowId,
227
- });
228
- const byTransactionId = list.find((entry) =>
229
- candidates.some((candidate) => entry.transactionId === candidate),
230
- );
231
- if (byTransactionId) {
232
- return byTransactionId;
233
- }
234
-
235
- // Only fall back by user inside the active DM with that user. Otherwise a
236
- // spoofed verification event in an unrelated room can leak the current SAS
237
- // prompt into that room.
238
- const { inspectMatrixDirectRooms, isStrictDirectRoom } = await loadMatrixDirectRoomDeps();
239
- const inspection = await inspectMatrixDirectRooms({
240
- client,
241
- remoteUserId: params.senderId,
242
- }).catch(() => null);
243
- const activeRoomId = trimMaybeString(inspection?.activeRoomId);
244
- if (activeRoomId) {
245
- if (activeRoomId !== params.roomId) {
246
- return null;
247
- }
248
- } else if (
249
- !(await isStrictDirectRoom({
250
- client,
251
- roomId: params.roomId,
252
- remoteUserId: params.senderId,
253
- }))
254
- ) {
255
- // If we cannot determine a canonical active DM, preserve the older
256
- // strict-room fallback so transient m.direct or joined-room read failures
257
- // do not suppress SAS notices for the current DM.
258
- return null;
259
- }
260
-
261
- // Fallback for DM flows where transaction IDs do not match room event IDs consistently.
262
- const activeByUser = list
263
- .filter((entry) => entry.otherUserId === params.senderId && isActiveVerificationSummary(entry))
264
- .toSorted((a, b) => resolveSummaryRecency(b) - resolveSummaryRecency(a));
265
- const activeInRoom = activeByUser.filter((entry) => {
266
- const roomId = trimMaybeString(entry.roomId);
267
- return roomId === params.roomId;
268
- });
269
- if (activeInRoom.length > 0) {
270
- return activeInRoom[0] ?? null;
271
- }
272
- return activeByUser[0] ?? null;
273
- }
274
-
275
- async function resolveVerificationSasNoticeForSignal(
276
- client: MatrixClient,
277
- params: {
278
- roomId: string;
279
- event: MatrixRawEvent;
280
- senderId: string;
281
- flowId: string | null;
282
- stage: MatrixVerificationStage;
283
- sasNoticeRetryDelayMs?: number;
284
- },
285
- ): Promise<{ summary: MatrixVerificationSummaryLike | null; sasNotice: string | null }> {
286
- const summary = await resolveVerificationSummaryForSignal(client, params);
287
- const immediateNotice =
288
- summary && isActiveVerificationSummary(summary) ? formatVerificationSasNotice(summary) : null;
289
- if (immediateNotice || (params.stage !== "ready" && params.stage !== "start")) {
290
- return {
291
- summary,
292
- sasNotice: immediateNotice,
293
- };
294
- }
295
-
296
- await new Promise((resolve) =>
297
- setTimeout(resolve, params.sasNoticeRetryDelayMs ?? SAS_NOTICE_RETRY_DELAY_MS),
298
- );
299
- const retriedSummary = await resolveVerificationSummaryForSignal(client, params);
300
- return {
301
- summary: retriedSummary,
302
- sasNotice:
303
- retriedSummary && isActiveVerificationSummary(retriedSummary)
304
- ? formatVerificationSasNotice(retriedSummary)
305
- : null,
306
- };
307
- }
308
-
309
- function trackBounded(set: Set<string>, value: string): boolean {
310
- if (!value || set.has(value)) {
311
- return false;
312
- }
313
- set.add(value);
314
- if (set.size > MAX_TRACKED_VERIFICATION_EVENTS) {
315
- const oldest = set.values().next().value;
316
- if (typeof oldest === "string") {
317
- set.delete(oldest);
318
- }
319
- }
320
- return true;
321
- }
322
-
323
- async function sendVerificationNotice(params: {
324
- client: MatrixClient;
325
- roomId: string;
326
- body: string;
327
- logVerboseMessage: (message: string) => void;
328
- }): Promise<void> {
329
- const roomId = trimMaybeString(params.roomId);
330
- if (!roomId) {
331
- return;
332
- }
333
- try {
334
- await params.client.sendMessage(roomId, {
335
- msgtype: "m.notice",
336
- body: params.body,
337
- });
338
- } catch (err) {
339
- params.logVerboseMessage(
340
- `matrix: failed sending verification notice room=${roomId}: ${String(err)}`,
341
- );
342
- }
343
- }
344
-
345
- async function isVerificationNoticeAuthorized(params: {
346
- senderId: string;
347
- allowFrom: string[];
348
- dmEnabled: boolean;
349
- dmPolicy: "open" | "pairing" | "allowlist" | "disabled";
350
- readStoreAllowFrom: () => Promise<string[]>;
351
- logVerboseMessage: (message: string) => void;
352
- }): Promise<boolean> {
353
- // Verification notices are DM-only. If DM ingress is disabled, there is no
354
- // policy-compatible path for posting these notices back into the room.
355
- if (!params.dmEnabled || params.dmPolicy === "disabled") {
356
- params.logVerboseMessage(
357
- `matrix: blocked verification sender ${params.senderId} (dmPolicy=${params.dmPolicy}, dmEnabled=${String(params.dmEnabled)})`,
358
- );
359
- return false;
360
- }
361
- const storeAllowFrom =
362
- params.dmPolicy !== "allowlist" && params.dmPolicy !== "open"
363
- ? await params.readStoreAllowFrom()
364
- : [];
365
- const accessState = await resolveMatrixMonitorAccessState({
366
- allowFrom: params.allowFrom,
367
- storeAllowFrom,
368
- dmPolicy: params.dmPolicy,
369
- // Verification flows only exist in strict DMs, so room/group allowlists do
370
- // not participate in the authorization decision here.
371
- groupPolicy: "open",
372
- groupAllowFrom: [],
373
- roomUsers: [],
374
- senderId: params.senderId,
375
- isRoom: false,
376
- });
377
- if (accessState.messageIngress.senderAccess.decision === "allow") {
378
- return true;
379
- }
380
- params.logVerboseMessage(
381
- `matrix: blocked verification sender ${params.senderId} (dmPolicy=${params.dmPolicy})`,
382
- );
383
- return false;
384
- }
385
-
386
- export function createMatrixVerificationEventRouter(params: {
387
- client: MatrixClient;
388
- allowFrom: string[];
389
- dmEnabled: boolean;
390
- dmPolicy: "open" | "pairing" | "allowlist" | "disabled";
391
- readStoreAllowFrom: () => Promise<string[]>;
392
- logVerboseMessage: (message: string) => void;
393
- sasNoticeRetryDelayMs?: number;
394
- runDetachedTask?: (label: string, task: () => Promise<void>) => Promise<void>;
395
- }) {
396
- const routerStartedAtMs = Date.now();
397
- const routedVerificationEvents = new Set<string>();
398
- const routedVerificationSasFingerprints = new Set<string>();
399
- const routedVerificationStageNotices = new Set<string>();
400
- const verificationFlowRooms = new Map<string, string>();
401
- const verificationUserRooms = new Map<string, string>();
402
-
403
- async function resolveActiveDirectRoomId(remoteUserId: string): Promise<string | null> {
404
- const { inspectMatrixDirectRooms } = await loadMatrixDirectRoomDeps();
405
- const inspection = await inspectMatrixDirectRooms({
406
- client: params.client,
407
- remoteUserId,
408
- }).catch(() => null);
409
- return trimMaybeString(inspection?.activeRoomId);
410
- }
411
-
412
- function shouldEmitVerificationEventNotice(event: MatrixRawEvent): boolean {
413
- const eventTs =
414
- typeof event.origin_server_ts === "number" && Number.isFinite(event.origin_server_ts)
415
- ? event.origin_server_ts
416
- : null;
417
- if (eventTs === null) {
418
- return true;
419
- }
420
- return eventTs >= routerStartedAtMs - VERIFICATION_EVENT_STARTUP_GRACE_MS;
421
- }
422
-
423
- function rememberVerificationRoom(roomId: string, event: MatrixRawEvent, flowId: string | null) {
424
- for (const candidate of resolveVerificationFlowCandidates({ event, flowId })) {
425
- verificationFlowRooms.set(candidate, roomId);
426
- if (verificationFlowRooms.size > MAX_TRACKED_VERIFICATION_EVENTS) {
427
- const oldest = verificationFlowRooms.keys().next().value;
428
- if (typeof oldest === "string") {
429
- verificationFlowRooms.delete(oldest);
430
- }
431
- }
432
- }
433
- }
434
-
435
- function rememberVerificationUserRoom(remoteUserId: string, roomId: string): void {
436
- const normalizedUserId = trimMaybeString(remoteUserId);
437
- const normalizedRoomId = trimMaybeString(roomId);
438
- if (!normalizedUserId || !normalizedRoomId) {
439
- return;
440
- }
441
- verificationUserRooms.delete(normalizedUserId);
442
- verificationUserRooms.set(normalizedUserId, normalizedRoomId);
443
- if (verificationUserRooms.size > MAX_TRACKED_VERIFICATION_EVENTS) {
444
- const oldest = verificationUserRooms.keys().next().value;
445
- if (typeof oldest === "string") {
446
- verificationUserRooms.delete(oldest);
447
- }
448
- }
449
- }
450
-
451
- async function resolveSummaryRoomId(
452
- summary: MatrixVerificationSummaryLike,
453
- ): Promise<string | null> {
454
- const mappedRoomId =
455
- trimMaybeString(summary.roomId) ??
456
- trimMaybeString(
457
- summary.transactionId ? verificationFlowRooms.get(summary.transactionId) : null,
458
- ) ??
459
- trimMaybeString(verificationFlowRooms.get(summary.id));
460
- if (mappedRoomId) {
461
- return mappedRoomId;
462
- }
463
-
464
- const remoteUserId = trimMaybeString(summary.otherUserId);
465
- if (!remoteUserId) {
466
- return null;
467
- }
468
- const recentRoomId = trimMaybeString(verificationUserRooms.get(remoteUserId));
469
- const activeRoomId = await resolveActiveDirectRoomId(remoteUserId);
470
- if (recentRoomId && activeRoomId && recentRoomId === activeRoomId) {
471
- return recentRoomId;
472
- }
473
- if (activeRoomId) {
474
- return activeRoomId;
475
- }
476
- if (
477
- recentRoomId &&
478
- (await (
479
- await loadMatrixDirectRoomDeps()
480
- ).isStrictDirectRoom({
481
- client: params.client,
482
- roomId: recentRoomId,
483
- remoteUserId,
484
- }))
485
- ) {
486
- return recentRoomId;
487
- }
488
- return null;
489
- }
490
-
491
- async function routeVerificationSummary(summary: MatrixVerificationSummaryLike): Promise<void> {
492
- const roomId = await resolveSummaryRoomId(summary);
493
- if (!roomId || !isActiveVerificationSummary(summary)) {
494
- return;
495
- }
496
- if (
497
- !(await (
498
- await loadMatrixDirectRoomDeps()
499
- ).isStrictDirectRoom({
500
- client: params.client,
501
- roomId,
502
- remoteUserId: summary.otherUserId,
503
- }))
504
- ) {
505
- params.logVerboseMessage(
506
- `matrix: ignoring verification summary outside strict DM room=${roomId} sender=${summary.otherUserId}`,
507
- );
508
- return;
509
- }
510
- if (
511
- !(await isVerificationNoticeAuthorized({
512
- senderId: summary.otherUserId,
513
- allowFrom: params.allowFrom,
514
- dmEnabled: params.dmEnabled,
515
- dmPolicy: params.dmPolicy,
516
- readStoreAllowFrom: params.readStoreAllowFrom,
517
- logVerboseMessage: params.logVerboseMessage,
518
- }))
519
- ) {
520
- return;
521
- }
522
- const sasNotice = formatVerificationSasNotice(summary);
523
- if (!sasNotice) {
524
- return;
525
- }
526
- const sasFingerprint = `${summary.id}:${JSON.stringify(summary.sas)}`;
527
- if (!trackBounded(routedVerificationSasFingerprints, sasFingerprint)) {
528
- return;
529
- }
530
- await sendVerificationNotice({
531
- client: params.client,
532
- roomId,
533
- body: sasNotice,
534
- logVerboseMessage: params.logVerboseMessage,
535
- });
536
- }
537
-
538
- function routeVerificationEvent(roomId: string, event: MatrixRawEvent): boolean {
539
- const senderId = trimMaybeString(event?.sender);
540
- if (!senderId) {
541
- return false;
542
- }
543
- const signal = readVerificationSignal(event);
544
- if (!signal) {
545
- return false;
546
- }
547
- rememberVerificationRoom(roomId, event, signal.flowId);
548
-
549
- const routeTask = async () => {
550
- if (!shouldEmitVerificationEventNotice(event)) {
551
- params.logVerboseMessage(
552
- `matrix: ignoring historical verification event room=${roomId} id=${event.event_id ?? "unknown"} type=${event.type ?? "unknown"}`,
553
- );
554
- return;
555
- }
556
- const flowId = signal.flowId;
557
- const sourceEventId = trimMaybeString(event?.event_id);
558
- const sourceFingerprint = sourceEventId ?? `${senderId}:${event.type}:${flowId ?? "none"}`;
559
- const shouldRouteInRoom = await (
560
- await loadMatrixDirectRoomDeps()
561
- ).isStrictDirectRoom({
562
- client: params.client,
563
- roomId,
564
- remoteUserId: senderId,
565
- });
566
- if (!shouldRouteInRoom) {
567
- params.logVerboseMessage(
568
- `matrix: ignoring verification event outside strict DM room=${roomId} sender=${senderId}`,
569
- );
570
- return;
571
- }
572
- if (
573
- !(await isVerificationNoticeAuthorized({
574
- senderId,
575
- allowFrom: params.allowFrom,
576
- dmEnabled: params.dmEnabled,
577
- dmPolicy: params.dmPolicy,
578
- readStoreAllowFrom: params.readStoreAllowFrom,
579
- logVerboseMessage: params.logVerboseMessage,
580
- }))
581
- ) {
582
- return;
583
- }
584
- rememberVerificationUserRoom(senderId, roomId);
585
- if (!trackBounded(routedVerificationEvents, sourceFingerprint)) {
586
- return;
587
- }
588
-
589
- const stageNotice = formatVerificationStageNotice({ stage: signal.stage, senderId, event });
590
- const { summary, sasNotice } = await resolveVerificationSasNoticeForSignal(params.client, {
591
- roomId,
592
- event,
593
- senderId,
594
- flowId,
595
- stage: signal.stage,
596
- sasNoticeRetryDelayMs: params.sasNoticeRetryDelayMs,
597
- }).catch(() => ({ summary: null, sasNotice: null }));
598
-
599
- const notices: string[] = [];
600
- if (stageNotice) {
601
- const stageKey = `${roomId}:${senderId}:${flowId ?? sourceFingerprint}:${signal.stage}`;
602
- if (trackBounded(routedVerificationStageNotices, stageKey)) {
603
- notices.push(stageNotice);
604
- }
605
- }
606
- if (summary && sasNotice) {
607
- const sasFingerprint = `${summary.id}:${JSON.stringify(summary.sas)}`;
608
- if (trackBounded(routedVerificationSasFingerprints, sasFingerprint)) {
609
- notices.push(sasNotice);
610
- }
611
- }
612
- if (notices.length === 0) {
613
- return;
614
- }
615
-
616
- for (const body of notices) {
617
- await sendVerificationNotice({
618
- client: params.client,
619
- roomId,
620
- body,
621
- logVerboseMessage: params.logVerboseMessage,
622
- });
623
- }
624
- };
625
- if (params.runDetachedTask) {
626
- void params.runDetachedTask(
627
- `verification event handler room=${roomId} id=${event.event_id ?? "unknown"}`,
628
- routeTask,
629
- );
630
- } else {
631
- void routeTask().catch((err) => {
632
- params.logVerboseMessage(`matrix: failed routing verification event: ${String(err)}`);
633
- });
634
- }
635
-
636
- return true;
637
- }
638
-
639
- return {
640
- routeVerificationEvent,
641
- routeVerificationSummary,
642
- };
643
- }
@@ -1,46 +0,0 @@
1
- import { normalizeOptionalString } from "klaw/plugin-sdk/string-coerce-runtime";
2
-
3
- const VERIFICATION_EVENT_PREFIX = "m.key.verification.";
4
- const VERIFICATION_REQUEST_MSGTYPE = "m.key.verification.request";
5
-
6
- const VERIFICATION_NOTICE_PREFIXES = [
7
- "Matrix verification request received from ",
8
- "Matrix verification is ready with ",
9
- "Matrix verification started with ",
10
- "Matrix verification completed with ",
11
- "Matrix verification cancelled by ",
12
- "Matrix verification SAS with ",
13
- ];
14
-
15
- function trimMaybeString(input: unknown): string {
16
- return normalizeOptionalString(input) ?? "";
17
- }
18
-
19
- export function isMatrixVerificationEventType(type: unknown): boolean {
20
- return trimMaybeString(type).startsWith(VERIFICATION_EVENT_PREFIX);
21
- }
22
-
23
- export function isMatrixVerificationRequestMsgType(msgtype: unknown): boolean {
24
- return trimMaybeString(msgtype) === VERIFICATION_REQUEST_MSGTYPE;
25
- }
26
-
27
- export function isMatrixVerificationNoticeBody(body: unknown): boolean {
28
- const text = trimMaybeString(body);
29
- return VERIFICATION_NOTICE_PREFIXES.some((prefix) => text.startsWith(prefix));
30
- }
31
-
32
- export function isMatrixVerificationRoomMessage(content: {
33
- msgtype?: unknown;
34
- body?: unknown;
35
- }): boolean {
36
- return (
37
- isMatrixVerificationRequestMsgType(content.msgtype) ||
38
- (trimMaybeString(content.msgtype) === "m.notice" &&
39
- isMatrixVerificationNoticeBody(content.body))
40
- );
41
- }
42
-
43
- export const matrixVerificationConstants = {
44
- eventPrefix: VERIFICATION_EVENT_PREFIX,
45
- requestMsgtype: VERIFICATION_REQUEST_MSGTYPE,
46
- } as const;
@@ -1 +0,0 @@
1
- export { loadOutboundMediaFromUrl } from "klaw/plugin-sdk/outbound-media";