@gakr-gakr/matrix 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +285 -0
- package/SPEC-SUPPORT.md +116 -0
- package/api.ts +38 -0
- package/auth-presence.ts +56 -0
- package/autobot.plugin.json +28 -0
- package/channel-plugin-api.ts +3 -0
- package/cli-metadata.ts +11 -0
- package/contract-api.ts +17 -0
- package/doctor-contract-api.ts +1 -0
- package/helper-api.ts +3 -0
- package/index.ts +55 -0
- package/package.json +101 -0
- package/plugin-entry.handlers.runtime.ts +1 -0
- package/runtime-api.ts +72 -0
- package/runtime-heavy-api.ts +1 -0
- package/runtime-setter-api.ts +3 -0
- package/secret-contract-api.ts +5 -0
- package/setup-entry.ts +17 -0
- package/setup-plugin-api.ts +3 -0
- package/src/account-selection.ts +223 -0
- package/src/actions.ts +346 -0
- package/src/approval-auth.ts +25 -0
- package/src/approval-handler.runtime.ts +595 -0
- package/src/approval-ids.ts +6 -0
- package/src/approval-native.ts +348 -0
- package/src/approval-reaction-auth.ts +45 -0
- package/src/approval-reactions.ts +313 -0
- package/src/auth-precedence.ts +61 -0
- package/src/channel-account-paths.ts +97 -0
- package/src/channel.runtime.ts +17 -0
- package/src/channel.setup.ts +48 -0
- package/src/channel.ts +667 -0
- package/src/cli-metadata.ts +19 -0
- package/src/cli.ts +2298 -0
- package/src/config-adapter.ts +41 -0
- package/src/config-schema.ts +159 -0
- package/src/config-ui-hints.ts +56 -0
- package/src/directory-live.ts +238 -0
- package/src/doctor-contract.ts +287 -0
- package/src/doctor.ts +262 -0
- package/src/env-vars.ts +92 -0
- package/src/exec-approval-resolver.ts +23 -0
- package/src/exec-approvals.ts +293 -0
- package/src/group-mentions.ts +41 -0
- package/src/legacy-crypto-inspector-availability.ts +60 -0
- package/src/legacy-crypto.ts +531 -0
- package/src/legacy-state.ts +156 -0
- package/src/matrix/account-config.ts +175 -0
- package/src/matrix/accounts.ts +194 -0
- package/src/matrix/actions/client.ts +31 -0
- package/src/matrix/actions/devices.ts +34 -0
- package/src/matrix/actions/limits.ts +6 -0
- package/src/matrix/actions/messages.ts +129 -0
- package/src/matrix/actions/pins.ts +63 -0
- package/src/matrix/actions/polls.ts +109 -0
- package/src/matrix/actions/profile.ts +37 -0
- package/src/matrix/actions/reactions.ts +59 -0
- package/src/matrix/actions/room.ts +71 -0
- package/src/matrix/actions/summary.ts +88 -0
- package/src/matrix/actions/types.ts +63 -0
- package/src/matrix/actions/verification.ts +589 -0
- package/src/matrix/actions.ts +37 -0
- package/src/matrix/active-client.ts +26 -0
- package/src/matrix/async-lock.ts +18 -0
- package/src/matrix/backup-health.ts +124 -0
- package/src/matrix/client/config-runtime-api.ts +9 -0
- package/src/matrix/client/config-secret-input.runtime.ts +1 -0
- package/src/matrix/client/config.ts +853 -0
- package/src/matrix/client/create-client.ts +105 -0
- package/src/matrix/client/env-auth.ts +95 -0
- package/src/matrix/client/file-sync-store.ts +289 -0
- package/src/matrix/client/logging.ts +140 -0
- package/src/matrix/client/migration-snapshot.runtime.ts +1 -0
- package/src/matrix/client/private-network-host.ts +1 -0
- package/src/matrix/client/runtime.ts +4 -0
- package/src/matrix/client/shared.ts +316 -0
- package/src/matrix/client/storage.ts +543 -0
- package/src/matrix/client/types.ts +50 -0
- package/src/matrix/client/url-validation.ts +76 -0
- package/src/matrix/client-bootstrap.ts +173 -0
- package/src/matrix/client.ts +23 -0
- package/src/matrix/config-paths.ts +31 -0
- package/src/matrix/config-update.ts +292 -0
- package/src/matrix/credentials-read.ts +207 -0
- package/src/matrix/credentials-write.runtime.ts +35 -0
- package/src/matrix/credentials.ts +95 -0
- package/src/matrix/deps.ts +309 -0
- package/src/matrix/device-health.ts +31 -0
- package/src/matrix/direct-management.ts +349 -0
- package/src/matrix/direct-room.ts +128 -0
- package/src/matrix/draft-stream.ts +225 -0
- package/src/matrix/encryption-guidance.ts +24 -0
- package/src/matrix/errors.ts +21 -0
- package/src/matrix/format.ts +426 -0
- package/src/matrix/legacy-crypto-inspector.ts +95 -0
- package/src/matrix/media-errors.ts +20 -0
- package/src/matrix/media-text.ts +162 -0
- package/src/matrix/monitor/access-state.ts +145 -0
- package/src/matrix/monitor/ack-config.ts +27 -0
- package/src/matrix/monitor/allowlist.ts +92 -0
- package/src/matrix/monitor/auto-join.ts +86 -0
- package/src/matrix/monitor/config.ts +569 -0
- package/src/matrix/monitor/context-summary.ts +43 -0
- package/src/matrix/monitor/direct.ts +296 -0
- package/src/matrix/monitor/events.ts +397 -0
- package/src/matrix/monitor/handler.ts +2271 -0
- package/src/matrix/monitor/inbound-dedupe.ts +267 -0
- package/src/matrix/monitor/index.ts +540 -0
- package/src/matrix/monitor/legacy-crypto-restore.ts +139 -0
- package/src/matrix/monitor/location.ts +108 -0
- package/src/matrix/monitor/media.ts +119 -0
- package/src/matrix/monitor/mentions.ts +256 -0
- package/src/matrix/monitor/reaction-events.ts +197 -0
- package/src/matrix/monitor/recent-invite.ts +30 -0
- package/src/matrix/monitor/replies.ts +136 -0
- package/src/matrix/monitor/reply-context.ts +92 -0
- package/src/matrix/monitor/room-history.ts +301 -0
- package/src/matrix/monitor/room-info.ts +126 -0
- package/src/matrix/monitor/rooms.ts +52 -0
- package/src/matrix/monitor/route.ts +179 -0
- package/src/matrix/monitor/runtime-api.ts +28 -0
- package/src/matrix/monitor/startup-verification.ts +237 -0
- package/src/matrix/monitor/startup.ts +218 -0
- package/src/matrix/monitor/status.ts +120 -0
- package/src/matrix/monitor/sync-lifecycle.ts +91 -0
- package/src/matrix/monitor/task-runner.ts +38 -0
- package/src/matrix/monitor/test-events.ts +21 -0
- package/src/matrix/monitor/thread-context.ts +108 -0
- package/src/matrix/monitor/threads.ts +85 -0
- package/src/matrix/monitor/types.ts +30 -0
- package/src/matrix/monitor/verification-events.ts +643 -0
- package/src/matrix/monitor/verification-utils.ts +46 -0
- package/src/matrix/outbound-media-runtime.ts +1 -0
- package/src/matrix/poll-summary.ts +110 -0
- package/src/matrix/poll-types.ts +429 -0
- package/src/matrix/probe.runtime.ts +4 -0
- package/src/matrix/probe.ts +97 -0
- package/src/matrix/profile.ts +184 -0
- package/src/matrix/reaction-common.ts +147 -0
- package/src/matrix/sdk/crypto-bootstrap.ts +438 -0
- package/src/matrix/sdk/crypto-facade.ts +242 -0
- package/src/matrix/sdk/crypto-node.runtime.ts +17 -0
- package/src/matrix/sdk/crypto-runtime.ts +14 -0
- package/src/matrix/sdk/decrypt-bridge.ts +410 -0
- package/src/matrix/sdk/event-helpers.ts +83 -0
- package/src/matrix/sdk/http-client.ts +87 -0
- package/src/matrix/sdk/idb-persistence-lock.ts +51 -0
- package/src/matrix/sdk/idb-persistence.ts +286 -0
- package/src/matrix/sdk/logger.ts +108 -0
- package/src/matrix/sdk/read-response-with-limit.ts +19 -0
- package/src/matrix/sdk/recovery-key-store.ts +453 -0
- package/src/matrix/sdk/timeout-abort-signal.ts +1 -0
- package/src/matrix/sdk/transport-runtime-api.ts +18 -0
- package/src/matrix/sdk/transport.ts +352 -0
- package/src/matrix/sdk/types.ts +245 -0
- package/src/matrix/sdk/verification-manager.ts +795 -0
- package/src/matrix/sdk/verification-status.ts +23 -0
- package/src/matrix/sdk.ts +2152 -0
- package/src/matrix/send/client.ts +93 -0
- package/src/matrix/send/formatting.ts +189 -0
- package/src/matrix/send/media.ts +244 -0
- package/src/matrix/send/targets.ts +104 -0
- package/src/matrix/send/types.ts +131 -0
- package/src/matrix/send.ts +660 -0
- package/src/matrix/session-store-metadata.ts +108 -0
- package/src/matrix/startup-abort.ts +44 -0
- package/src/matrix/subagent-hooks.ts +308 -0
- package/src/matrix/sync-state.ts +27 -0
- package/src/matrix/target-ids.ts +79 -0
- package/src/matrix/thread-bindings-shared.ts +206 -0
- package/src/matrix/thread-bindings.ts +580 -0
- package/src/matrix-migration.runtime.ts +9 -0
- package/src/migration-config.ts +243 -0
- package/src/migration-snapshot-backup.ts +116 -0
- package/src/migration-snapshot.ts +53 -0
- package/src/onboarding.ts +775 -0
- package/src/outbound.ts +248 -0
- package/src/plugin-entry.runtime.js +115 -0
- package/src/plugin-entry.runtime.ts +70 -0
- package/src/profile-update.ts +71 -0
- package/src/record-shared.ts +3 -0
- package/src/resolve-targets.ts +175 -0
- package/src/resolver.runtime.ts +5 -0
- package/src/resolver.ts +21 -0
- package/src/runtime-api.ts +106 -0
- package/src/runtime.ts +13 -0
- package/src/secret-contract.ts +174 -0
- package/src/session-route.ts +126 -0
- package/src/setup-bootstrap.ts +102 -0
- package/src/setup-config.ts +222 -0
- package/src/setup-contract.ts +90 -0
- package/src/setup-core.ts +146 -0
- package/src/setup-dm-policy.ts +15 -0
- package/src/setup-surface.ts +4 -0
- package/src/startup-maintenance.ts +114 -0
- package/src/storage-paths.ts +92 -0
- package/src/thread-binding-api.ts +23 -0
- package/src/tool-actions.runtime.ts +1 -0
- package/src/tool-actions.ts +498 -0
- package/src/types.ts +257 -0
- package/subagent-hooks-api.ts +31 -0
- package/test-api.ts +21 -0
- package/thread-binding-api.ts +4 -0
- package/thread-bindings-runtime.ts +4 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { promoteMatrixDirectRoomCandidate } from "../direct-management.js";
|
|
2
|
+
import {
|
|
3
|
+
hasDirectMatrixMemberFlag,
|
|
4
|
+
isStrictDirectMembership,
|
|
5
|
+
readJoinedMatrixMembers,
|
|
6
|
+
} from "../direct-room.js";
|
|
7
|
+
import type { MatrixClient } from "../sdk.js";
|
|
8
|
+
|
|
9
|
+
type DirectMessageCheck = {
|
|
10
|
+
roomId: string;
|
|
11
|
+
senderId?: string;
|
|
12
|
+
selfUserId?: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type DirectRoomTrackerOptions = {
|
|
16
|
+
log?: (message: string) => void;
|
|
17
|
+
canPromoteRecentInvite?: (roomId: string) => boolean | Promise<boolean>;
|
|
18
|
+
canPromoteUnmappedStrictRoom?: (roomId: string) => boolean | Promise<boolean>;
|
|
19
|
+
shouldKeepLocallyPromotedDirectRoom?:
|
|
20
|
+
| ((roomId: string) => boolean | undefined | Promise<boolean | undefined>)
|
|
21
|
+
| undefined;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const DM_CACHE_TTL_MS = 30_000;
|
|
25
|
+
const RECENT_INVITE_TTL_MS = 30_000;
|
|
26
|
+
const MAX_TRACKED_DM_ROOMS = 1024;
|
|
27
|
+
const MAX_TRACKED_DM_MEMBER_FLAGS = 2048;
|
|
28
|
+
|
|
29
|
+
function rememberBounded<T>(
|
|
30
|
+
map: Map<string, T>,
|
|
31
|
+
key: string,
|
|
32
|
+
value: T,
|
|
33
|
+
maxSize = MAX_TRACKED_DM_ROOMS,
|
|
34
|
+
): void {
|
|
35
|
+
map.set(key, value);
|
|
36
|
+
if (map.size > maxSize) {
|
|
37
|
+
const oldest = map.keys().next().value;
|
|
38
|
+
if (typeof oldest === "string") {
|
|
39
|
+
map.delete(oldest);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function createDirectRoomTracker(client: MatrixClient, opts: DirectRoomTrackerOptions = {}) {
|
|
45
|
+
const log = opts.log ?? (() => {});
|
|
46
|
+
let lastDmUpdateMs = 0;
|
|
47
|
+
// Once m.direct has seeded successfully, prefer the explicit cache over
|
|
48
|
+
// re-enabling the broad 2-person fallback after a later transient failure.
|
|
49
|
+
let hasSeededDmCache = false;
|
|
50
|
+
let cachedSelfUserId: string | null = null;
|
|
51
|
+
const joinedMembersCache = new Map<string, { members: string[]; ts: number }>();
|
|
52
|
+
const directMemberFlagCache = new Map<string, { isDirect: boolean | null; ts: number }>();
|
|
53
|
+
const recentInviteCandidates = new Map<string, { remoteUserId: string; ts: number }>();
|
|
54
|
+
const locallyPromotedDirectRooms = new Map<string, { remoteUserId: string }>();
|
|
55
|
+
|
|
56
|
+
const ensureSelfUserId = async (): Promise<string | null> => {
|
|
57
|
+
if (cachedSelfUserId) {
|
|
58
|
+
return cachedSelfUserId;
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
cachedSelfUserId = await client.getUserId();
|
|
62
|
+
} catch {
|
|
63
|
+
cachedSelfUserId = null;
|
|
64
|
+
}
|
|
65
|
+
return cachedSelfUserId;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const refreshDmCache = async (): Promise<void> => {
|
|
69
|
+
const now = Date.now();
|
|
70
|
+
if (now - lastDmUpdateMs < DM_CACHE_TTL_MS) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
lastDmUpdateMs = now;
|
|
74
|
+
hasSeededDmCache = (await client.dms.update()) || hasSeededDmCache;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const resolveJoinedMembers = async (roomId: string): Promise<string[] | null> => {
|
|
78
|
+
const cached = joinedMembersCache.get(roomId);
|
|
79
|
+
const now = Date.now();
|
|
80
|
+
if (cached && now - cached.ts < DM_CACHE_TTL_MS) {
|
|
81
|
+
return cached.members;
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
const normalized = await readJoinedMatrixMembers(client, roomId);
|
|
85
|
+
if (!normalized) {
|
|
86
|
+
throw new Error("membership unavailable");
|
|
87
|
+
}
|
|
88
|
+
rememberBounded(joinedMembersCache, roomId, { members: normalized, ts: now });
|
|
89
|
+
return normalized;
|
|
90
|
+
} catch (err) {
|
|
91
|
+
log(`matrix: dm member lookup failed room=${roomId} (${String(err)})`);
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const resolveDirectMemberFlag = async (
|
|
97
|
+
roomId: string,
|
|
98
|
+
userId?: string | null,
|
|
99
|
+
): Promise<boolean | null> => {
|
|
100
|
+
const normalizedUserId = userId?.trim();
|
|
101
|
+
if (!normalizedUserId) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
const cacheKey = `${roomId}\n${normalizedUserId}`;
|
|
105
|
+
const cached = directMemberFlagCache.get(cacheKey);
|
|
106
|
+
const now = Date.now();
|
|
107
|
+
if (cached && now - cached.ts < DM_CACHE_TTL_MS) {
|
|
108
|
+
return cached.isDirect;
|
|
109
|
+
}
|
|
110
|
+
const isDirect = await hasDirectMatrixMemberFlag(client, roomId, normalizedUserId);
|
|
111
|
+
rememberBounded(
|
|
112
|
+
directMemberFlagCache,
|
|
113
|
+
cacheKey,
|
|
114
|
+
{ isDirect, ts: now },
|
|
115
|
+
MAX_TRACKED_DM_MEMBER_FLAGS,
|
|
116
|
+
);
|
|
117
|
+
return isDirect;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const hasRecentInviteCandidate = (roomId: string, remoteUserId?: string | null): boolean => {
|
|
121
|
+
const normalizedRemoteUserId = remoteUserId?.trim();
|
|
122
|
+
if (!normalizedRemoteUserId) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
const cached = recentInviteCandidates.get(roomId);
|
|
126
|
+
if (!cached) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
if (Date.now() - cached.ts >= RECENT_INVITE_TTL_MS) {
|
|
130
|
+
recentInviteCandidates.delete(roomId);
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
return cached.remoteUserId === normalizedRemoteUserId;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const canPromoteRecentInvite = async (roomId: string): Promise<boolean> => {
|
|
137
|
+
try {
|
|
138
|
+
return (await opts.canPromoteRecentInvite?.(roomId)) ?? true;
|
|
139
|
+
} catch (err) {
|
|
140
|
+
log(`matrix: recent invite promotion veto failed room=${roomId} (${String(err)})`);
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const canPromoteUnmappedStrictRoom = async (roomId: string): Promise<boolean> => {
|
|
146
|
+
try {
|
|
147
|
+
return (await opts.canPromoteUnmappedStrictRoom?.(roomId)) ?? false;
|
|
148
|
+
} catch (err) {
|
|
149
|
+
log(`matrix: unmapped strict room promotion veto failed room=${roomId} (${String(err)})`);
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const shouldKeepLocallyPromotedDirectRoom = async (
|
|
155
|
+
roomId: string,
|
|
156
|
+
): Promise<boolean | undefined> => {
|
|
157
|
+
try {
|
|
158
|
+
return await opts.shouldKeepLocallyPromotedDirectRoom?.(roomId);
|
|
159
|
+
} catch (err) {
|
|
160
|
+
log(`matrix: local promotion keep-check failed room=${roomId} (${String(err)})`);
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const hasLocallyPromotedDirectRoom = (roomId: string, remoteUserId?: string | null): boolean => {
|
|
166
|
+
const normalizedRemoteUserId = remoteUserId?.trim();
|
|
167
|
+
if (!normalizedRemoteUserId) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
return locallyPromotedDirectRooms.get(roomId)?.remoteUserId === normalizedRemoteUserId;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const rememberLocallyPromotedDirectRoom = (roomId: string, remoteUserId: string): void => {
|
|
174
|
+
const normalizedRemoteUserId = remoteUserId.trim();
|
|
175
|
+
if (!normalizedRemoteUserId) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
rememberBounded(locallyPromotedDirectRooms, roomId, {
|
|
179
|
+
remoteUserId: normalizedRemoteUserId,
|
|
180
|
+
});
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
invalidateRoom: (roomId: string): void => {
|
|
185
|
+
joinedMembersCache.delete(roomId);
|
|
186
|
+
for (const key of directMemberFlagCache.keys()) {
|
|
187
|
+
if (key.startsWith(`${roomId}\n`)) {
|
|
188
|
+
directMemberFlagCache.delete(key);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
lastDmUpdateMs = 0;
|
|
192
|
+
log(`matrix: invalidated dm cache room=${roomId}`);
|
|
193
|
+
},
|
|
194
|
+
rememberInvite: (roomId: string, remoteUserId: string): void => {
|
|
195
|
+
const normalizedRemoteUserId = remoteUserId.trim();
|
|
196
|
+
if (!normalizedRemoteUserId) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
rememberBounded(recentInviteCandidates, roomId, {
|
|
200
|
+
remoteUserId: normalizedRemoteUserId,
|
|
201
|
+
ts: Date.now(),
|
|
202
|
+
});
|
|
203
|
+
log(`matrix: remembered invite candidate room=${roomId} sender=${normalizedRemoteUserId}`);
|
|
204
|
+
},
|
|
205
|
+
isDirectMessage: async (params: DirectMessageCheck): Promise<boolean> => {
|
|
206
|
+
const { roomId, senderId } = params;
|
|
207
|
+
const selfUserId = params.selfUserId ?? (await ensureSelfUserId());
|
|
208
|
+
const joinedMembers = await resolveJoinedMembers(roomId);
|
|
209
|
+
const strictDirectMembership = isStrictDirectMembership({
|
|
210
|
+
selfUserId,
|
|
211
|
+
remoteUserId: senderId,
|
|
212
|
+
joinedMembers,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
await refreshDmCache();
|
|
217
|
+
} catch (err) {
|
|
218
|
+
log(`matrix: dm cache refresh failed (${String(err)})`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (client.dms.isDm(roomId)) {
|
|
222
|
+
if (strictDirectMembership) {
|
|
223
|
+
log(`matrix: dm detected via m.direct room=${roomId}`);
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
log(`matrix: ignoring stale m.direct classification room=${roomId}`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (strictDirectMembership) {
|
|
230
|
+
const directViaSelf = await resolveDirectMemberFlag(roomId, selfUserId);
|
|
231
|
+
if (directViaSelf === true) {
|
|
232
|
+
log(`matrix: dm detected via member state room=${roomId}`);
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
if (directViaSelf === false) {
|
|
236
|
+
log(`matrix: dm rejected via member state room=${roomId}`);
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (!hasSeededDmCache) {
|
|
241
|
+
log(
|
|
242
|
+
`matrix: dm detected via exact 2-member fallback before dm cache seed room=${roomId}`,
|
|
243
|
+
);
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (hasLocallyPromotedDirectRoom(roomId, senderId)) {
|
|
248
|
+
const shouldKeep = await shouldKeepLocallyPromotedDirectRoom(roomId);
|
|
249
|
+
if (shouldKeep !== false) {
|
|
250
|
+
log(`matrix: dm detected via local promotion room=${roomId}`);
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
locallyPromotedDirectRooms.delete(roomId);
|
|
254
|
+
log(`matrix: local promotion cleared room=${roomId}`);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (hasRecentInviteCandidate(roomId, senderId) && (await canPromoteRecentInvite(roomId))) {
|
|
258
|
+
const promotion = await promoteMatrixDirectRoomCandidate({
|
|
259
|
+
client,
|
|
260
|
+
remoteUserId: senderId ?? "",
|
|
261
|
+
roomId,
|
|
262
|
+
selfUserId,
|
|
263
|
+
});
|
|
264
|
+
if (promotion.classifyAsDirect) {
|
|
265
|
+
rememberLocallyPromotedDirectRoom(roomId, senderId ?? "");
|
|
266
|
+
log(
|
|
267
|
+
`matrix: dm detected via recent invite room=${roomId} reason=${promotion.reason} repaired=${String(promotion.repaired)}`,
|
|
268
|
+
);
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (await canPromoteUnmappedStrictRoom(roomId)) {
|
|
274
|
+
const promotion = await promoteMatrixDirectRoomCandidate({
|
|
275
|
+
client,
|
|
276
|
+
remoteUserId: senderId ?? "",
|
|
277
|
+
roomId,
|
|
278
|
+
selfUserId,
|
|
279
|
+
});
|
|
280
|
+
if (promotion.classifyAsDirect) {
|
|
281
|
+
rememberLocallyPromotedDirectRoom(roomId, senderId ?? "");
|
|
282
|
+
log(
|
|
283
|
+
`matrix: dm detected via per-room strict fallback room=${roomId} reason=${promotion.reason} repaired=${String(promotion.repaired)}`,
|
|
284
|
+
);
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
log(
|
|
291
|
+
`matrix: dm check room=${roomId} result=group members=${joinedMembers?.length ?? "unknown"}`,
|
|
292
|
+
);
|
|
293
|
+
return false;
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
}
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
import { normalizeOptionalString } from "autobot/plugin-sdk/string-coerce-runtime";
|
|
2
|
+
import type { PluginRuntime, RuntimeLogger } from "../../runtime-api.js";
|
|
3
|
+
import type { CoreConfig } from "../../types.js";
|
|
4
|
+
import type { MatrixAuth } from "../client.js";
|
|
5
|
+
import { formatMatrixEncryptedEventDisabledWarning } from "../encryption-guidance.js";
|
|
6
|
+
import type { MatrixClient } from "../sdk.js";
|
|
7
|
+
import type { MatrixRawEvent } from "./types.js";
|
|
8
|
+
import { EventType } from "./types.js";
|
|
9
|
+
import { createMatrixVerificationEventRouter } from "./verification-events.js";
|
|
10
|
+
|
|
11
|
+
const MATRIX_POST_HEALTHY_SYNC_DECRYPT_FAILURE_WINDOW_MS = 2 * 60_000;
|
|
12
|
+
const MATRIX_POST_HEALTHY_SYNC_DECRYPT_FAILURE_THRESHOLD = 3;
|
|
13
|
+
const MATRIX_POST_HEALTHY_SYNC_DECRYPT_FAILURE_SAMPLE_LIMIT = 3;
|
|
14
|
+
|
|
15
|
+
type MatrixPostHealthySyncDecryptFailureObservation = {
|
|
16
|
+
key: string;
|
|
17
|
+
roomId: string;
|
|
18
|
+
eventId: string;
|
|
19
|
+
sender: string | null;
|
|
20
|
+
eventTs: number;
|
|
21
|
+
error: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function formatMatrixPostHealthySyncDecryptionHint(accountId: string): string {
|
|
25
|
+
return (
|
|
26
|
+
"matrix: repeated fresh encrypted messages are still failing to decrypt after Matrix resumed healthy sync. " +
|
|
27
|
+
"This device may still be missing new room keys. " +
|
|
28
|
+
`Check 'autobot matrix verify status --verbose --account ${accountId}' and 'autobot matrix devices list --account ${accountId}'.`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isFreshPostHealthySyncDecryptFailure(params: {
|
|
33
|
+
event: MatrixRawEvent;
|
|
34
|
+
healthySyncSinceMs?: number;
|
|
35
|
+
graceMs?: number;
|
|
36
|
+
nowMs: number;
|
|
37
|
+
}): boolean {
|
|
38
|
+
const { event, healthySyncSinceMs, graceMs = 0, nowMs } = params;
|
|
39
|
+
if (typeof healthySyncSinceMs !== "number" || !Number.isFinite(healthySyncSinceMs)) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
const eventTs = event.origin_server_ts;
|
|
43
|
+
if (!Number.isFinite(eventTs) || eventTs <= 0) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
if (eventTs < healthySyncSinceMs + graceMs) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
if (eventTs > nowMs + 60_000) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function createMatrixPostHealthySyncDecryptFailureTracker(params: {
|
|
56
|
+
getHealthySyncSinceMs?: () => number | undefined;
|
|
57
|
+
startupGraceMs?: number;
|
|
58
|
+
}) {
|
|
59
|
+
let observations: MatrixPostHealthySyncDecryptFailureObservation[] = [];
|
|
60
|
+
let warningEmitted = false;
|
|
61
|
+
let trackedHealthySyncSinceMs: number | undefined;
|
|
62
|
+
|
|
63
|
+
const resetObservations = () => {
|
|
64
|
+
observations = [];
|
|
65
|
+
warningEmitted = false;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const pruneObservations = (nowMs: number) => {
|
|
69
|
+
observations = observations.filter(
|
|
70
|
+
(entry) => nowMs - entry.eventTs <= MATRIX_POST_HEALTHY_SYNC_DECRYPT_FAILURE_WINDOW_MS,
|
|
71
|
+
);
|
|
72
|
+
if (observations.length === 0) {
|
|
73
|
+
warningEmitted = false;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
recordFailure(roomId: string, event: MatrixRawEvent, error: Error) {
|
|
79
|
+
const nowMs = Date.now();
|
|
80
|
+
const healthySyncSinceMs = params.getHealthySyncSinceMs?.();
|
|
81
|
+
if (healthySyncSinceMs !== trackedHealthySyncSinceMs) {
|
|
82
|
+
trackedHealthySyncSinceMs = healthySyncSinceMs;
|
|
83
|
+
resetObservations();
|
|
84
|
+
}
|
|
85
|
+
if (
|
|
86
|
+
!isFreshPostHealthySyncDecryptFailure({
|
|
87
|
+
event,
|
|
88
|
+
healthySyncSinceMs,
|
|
89
|
+
graceMs: params.startupGraceMs,
|
|
90
|
+
nowMs,
|
|
91
|
+
})
|
|
92
|
+
) {
|
|
93
|
+
return { freshAfterHealthySync: false, failureCount: 0 } as const;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
pruneObservations(nowMs);
|
|
97
|
+
|
|
98
|
+
const key = `${roomId}|${event.event_id}`;
|
|
99
|
+
if (!observations.some((entry) => entry.key === key)) {
|
|
100
|
+
observations.push({
|
|
101
|
+
key,
|
|
102
|
+
roomId,
|
|
103
|
+
eventId: event.event_id,
|
|
104
|
+
sender: typeof event.sender === "string" ? event.sender : null,
|
|
105
|
+
eventTs: event.origin_server_ts,
|
|
106
|
+
error: error.message,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const failureCount = observations.length;
|
|
111
|
+
if (warningEmitted || failureCount < MATRIX_POST_HEALTHY_SYNC_DECRYPT_FAILURE_THRESHOLD) {
|
|
112
|
+
return { freshAfterHealthySync: true, failureCount } as const;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
warningEmitted = true;
|
|
116
|
+
const rooms = [...new Set(observations.map((entry) => entry.roomId))].slice(
|
|
117
|
+
0,
|
|
118
|
+
MATRIX_POST_HEALTHY_SYNC_DECRYPT_FAILURE_SAMPLE_LIMIT,
|
|
119
|
+
);
|
|
120
|
+
const senders = [...new Set(observations.map((entry) => entry.sender).filter(Boolean))].slice(
|
|
121
|
+
0,
|
|
122
|
+
MATRIX_POST_HEALTHY_SYNC_DECRYPT_FAILURE_SAMPLE_LIMIT,
|
|
123
|
+
);
|
|
124
|
+
const eventIds = observations
|
|
125
|
+
.slice(-MATRIX_POST_HEALTHY_SYNC_DECRYPT_FAILURE_SAMPLE_LIMIT)
|
|
126
|
+
.map((entry) => entry.eventId);
|
|
127
|
+
const latestError = observations.at(-1)?.error ?? error.message;
|
|
128
|
+
return {
|
|
129
|
+
freshAfterHealthySync: true,
|
|
130
|
+
failureCount,
|
|
131
|
+
warning: {
|
|
132
|
+
rooms,
|
|
133
|
+
roomCount: new Set(observations.map((entry) => entry.roomId)).size,
|
|
134
|
+
senders,
|
|
135
|
+
senderCount: new Set(observations.map((entry) => entry.sender).filter(Boolean)).size,
|
|
136
|
+
eventIds,
|
|
137
|
+
latestError,
|
|
138
|
+
windowMs: MATRIX_POST_HEALTHY_SYNC_DECRYPT_FAILURE_WINDOW_MS,
|
|
139
|
+
},
|
|
140
|
+
} as const;
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function formatMatrixSelfDecryptionHint(accountId: string): string {
|
|
146
|
+
return (
|
|
147
|
+
"matrix: failed to decrypt a message from this same Matrix user. " +
|
|
148
|
+
"This usually means another Matrix device did not share the room key, or another AutoBot runtime is using the same account. " +
|
|
149
|
+
`Check 'autobot matrix verify status --verbose --account ${accountId}' and 'autobot matrix devices list --account ${accountId}'.`
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function resolveMatrixSelfUserId(
|
|
154
|
+
client: MatrixClient,
|
|
155
|
+
logVerboseMessage: (message: string) => void,
|
|
156
|
+
): Promise<string | null> {
|
|
157
|
+
if (typeof client.getUserId !== "function") {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
return (await client.getUserId()) ?? null;
|
|
162
|
+
} catch (err) {
|
|
163
|
+
logVerboseMessage(`matrix: failed resolving self user id for decrypt warning: ${String(err)}`);
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function registerMatrixMonitorEvents(params: {
|
|
169
|
+
cfg: CoreConfig;
|
|
170
|
+
client: MatrixClient;
|
|
171
|
+
auth: MatrixAuth;
|
|
172
|
+
allowFrom: string[];
|
|
173
|
+
dmEnabled: boolean;
|
|
174
|
+
dmPolicy: "open" | "pairing" | "allowlist" | "disabled";
|
|
175
|
+
readStoreAllowFrom: () => Promise<string[]>;
|
|
176
|
+
directTracker?: {
|
|
177
|
+
invalidateRoom: (roomId: string) => void;
|
|
178
|
+
rememberInvite?: (roomId: string, remoteUserId: string) => void;
|
|
179
|
+
};
|
|
180
|
+
logVerboseMessage: (message: string) => void;
|
|
181
|
+
warnedEncryptedRooms: Set<string>;
|
|
182
|
+
warnedCryptoMissingRooms: Set<string>;
|
|
183
|
+
logger: RuntimeLogger;
|
|
184
|
+
startupGraceMs?: number;
|
|
185
|
+
getHealthySyncSinceMs?: () => number | undefined;
|
|
186
|
+
formatNativeDependencyHint: PluginRuntime["system"]["formatNativeDependencyHint"];
|
|
187
|
+
onRoomMessage: (roomId: string, event: MatrixRawEvent) => void | Promise<void>;
|
|
188
|
+
runDetachedTask?: (label: string, task: () => Promise<void>) => Promise<void>;
|
|
189
|
+
sasNoticeRetryDelayMs?: number;
|
|
190
|
+
}): void {
|
|
191
|
+
const {
|
|
192
|
+
cfg,
|
|
193
|
+
client,
|
|
194
|
+
auth,
|
|
195
|
+
allowFrom,
|
|
196
|
+
dmEnabled,
|
|
197
|
+
dmPolicy,
|
|
198
|
+
readStoreAllowFrom,
|
|
199
|
+
directTracker,
|
|
200
|
+
logVerboseMessage,
|
|
201
|
+
warnedEncryptedRooms,
|
|
202
|
+
warnedCryptoMissingRooms,
|
|
203
|
+
logger,
|
|
204
|
+
startupGraceMs,
|
|
205
|
+
getHealthySyncSinceMs,
|
|
206
|
+
formatNativeDependencyHint,
|
|
207
|
+
onRoomMessage,
|
|
208
|
+
runDetachedTask,
|
|
209
|
+
sasNoticeRetryDelayMs,
|
|
210
|
+
} = params;
|
|
211
|
+
const postHealthySyncDecryptFailureTracker = createMatrixPostHealthySyncDecryptFailureTracker({
|
|
212
|
+
getHealthySyncSinceMs,
|
|
213
|
+
startupGraceMs,
|
|
214
|
+
});
|
|
215
|
+
const { routeVerificationEvent, routeVerificationSummary } = createMatrixVerificationEventRouter({
|
|
216
|
+
client,
|
|
217
|
+
allowFrom,
|
|
218
|
+
dmEnabled,
|
|
219
|
+
dmPolicy,
|
|
220
|
+
readStoreAllowFrom,
|
|
221
|
+
logVerboseMessage,
|
|
222
|
+
runDetachedTask,
|
|
223
|
+
sasNoticeRetryDelayMs,
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const runMonitorTask = (label: string, task: () => Promise<void>) => {
|
|
227
|
+
if (runDetachedTask) {
|
|
228
|
+
return runDetachedTask(label, task);
|
|
229
|
+
}
|
|
230
|
+
return Promise.resolve()
|
|
231
|
+
.then(task)
|
|
232
|
+
.catch((error) => {
|
|
233
|
+
logVerboseMessage(`matrix: ${label} failed (${String(error)})`);
|
|
234
|
+
});
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
client.on("room.message", (roomId: string, event: MatrixRawEvent) => {
|
|
238
|
+
if (routeVerificationEvent(roomId, event)) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
void runMonitorTask(
|
|
242
|
+
`room message handler room=${roomId} id=${event.event_id ?? "unknown"}`,
|
|
243
|
+
async () => {
|
|
244
|
+
await onRoomMessage(roomId, event);
|
|
245
|
+
},
|
|
246
|
+
);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
client.on("room.encrypted_event", (roomId: string, event: MatrixRawEvent) => {
|
|
250
|
+
const eventId = event?.event_id ?? "unknown";
|
|
251
|
+
const eventType = event?.type ?? "unknown";
|
|
252
|
+
logVerboseMessage(`matrix: encrypted event room=${roomId} type=${eventType} id=${eventId}`);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
client.on("room.decrypted_event", (roomId: string, event: MatrixRawEvent) => {
|
|
256
|
+
const eventId = event?.event_id ?? "unknown";
|
|
257
|
+
const eventType = event?.type ?? "unknown";
|
|
258
|
+
logVerboseMessage(`matrix: decrypted event room=${roomId} type=${eventType} id=${eventId}`);
|
|
259
|
+
if (routeVerificationEvent(roomId, event)) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (eventType !== EventType.RoomMessage) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
void runMonitorTask(
|
|
266
|
+
`decrypted room message handler room=${roomId} id=${event.event_id ?? "unknown"}`,
|
|
267
|
+
async () => {
|
|
268
|
+
await onRoomMessage(roomId, event);
|
|
269
|
+
},
|
|
270
|
+
);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
client.on(
|
|
274
|
+
"room.failed_decryption",
|
|
275
|
+
async (roomId: string, event: MatrixRawEvent, error: Error) => {
|
|
276
|
+
const failureState = postHealthySyncDecryptFailureTracker.recordFailure(roomId, event, error);
|
|
277
|
+
const selfUserId = await resolveMatrixSelfUserId(client, logVerboseMessage);
|
|
278
|
+
const sender = typeof event.sender === "string" ? event.sender : null;
|
|
279
|
+
const senderMatchesOwnUser = Boolean(selfUserId && sender && selfUserId === sender);
|
|
280
|
+
logger.warn(
|
|
281
|
+
failureState.freshAfterHealthySync
|
|
282
|
+
? "Failed to decrypt fresh post-healthy-sync message"
|
|
283
|
+
: "Failed to decrypt message",
|
|
284
|
+
{
|
|
285
|
+
roomId,
|
|
286
|
+
eventId: event.event_id,
|
|
287
|
+
sender,
|
|
288
|
+
senderMatchesOwnUser,
|
|
289
|
+
error: error.message,
|
|
290
|
+
freshAfterHealthySync: failureState.freshAfterHealthySync,
|
|
291
|
+
...(failureState.freshAfterHealthySync
|
|
292
|
+
? {
|
|
293
|
+
postHealthySyncFailureCount: failureState.failureCount,
|
|
294
|
+
}
|
|
295
|
+
: {}),
|
|
296
|
+
},
|
|
297
|
+
);
|
|
298
|
+
if (failureState.warning) {
|
|
299
|
+
logger.warn(formatMatrixPostHealthySyncDecryptionHint(auth.accountId), {
|
|
300
|
+
roomId,
|
|
301
|
+
eventId: event.event_id,
|
|
302
|
+
failureCount: failureState.failureCount,
|
|
303
|
+
roomCount: failureState.warning.roomCount,
|
|
304
|
+
rooms: failureState.warning.rooms,
|
|
305
|
+
senderCount: failureState.warning.senderCount,
|
|
306
|
+
senders: failureState.warning.senders,
|
|
307
|
+
sampleEventIds: failureState.warning.eventIds,
|
|
308
|
+
latestError: failureState.warning.latestError,
|
|
309
|
+
windowMs: failureState.warning.windowMs,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
if (senderMatchesOwnUser) {
|
|
313
|
+
logger.warn(formatMatrixSelfDecryptionHint(auth.accountId), {
|
|
314
|
+
roomId,
|
|
315
|
+
eventId: event.event_id,
|
|
316
|
+
sender,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
logVerboseMessage(
|
|
320
|
+
`matrix: failed decrypt room=${roomId} id=${event.event_id ?? "unknown"} freshAfterHealthySync=${String(failureState.freshAfterHealthySync)} error=${error.message}`,
|
|
321
|
+
);
|
|
322
|
+
},
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
client.on("verification.summary", (summary) => {
|
|
326
|
+
void runMonitorTask("verification summary handler", async () => {
|
|
327
|
+
await routeVerificationSummary(summary);
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
client.on("room.invite", (roomId: string, event: MatrixRawEvent) => {
|
|
332
|
+
directTracker?.invalidateRoom(roomId);
|
|
333
|
+
const eventId = event?.event_id ?? "unknown";
|
|
334
|
+
const sender = event?.sender ?? "unknown";
|
|
335
|
+
const invitee = normalizeOptionalString(event?.state_key) ?? "";
|
|
336
|
+
const senderIsInvitee =
|
|
337
|
+
Boolean(invitee) && (normalizeOptionalString(event?.sender) ?? "") === invitee;
|
|
338
|
+
const isDirect = (event?.content as { is_direct?: boolean } | undefined)?.is_direct === true;
|
|
339
|
+
const rememberedSender = normalizeOptionalString(event?.sender);
|
|
340
|
+
if (rememberedSender && !senderIsInvitee) {
|
|
341
|
+
directTracker?.rememberInvite?.(roomId, rememberedSender);
|
|
342
|
+
}
|
|
343
|
+
logVerboseMessage(
|
|
344
|
+
`matrix: invite room=${roomId} sender=${sender} direct=${String(isDirect)} id=${eventId}`,
|
|
345
|
+
);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
client.on("room.join", (roomId: string, event: MatrixRawEvent) => {
|
|
349
|
+
directTracker?.invalidateRoom(roomId);
|
|
350
|
+
const eventId = event?.event_id ?? "unknown";
|
|
351
|
+
logVerboseMessage(`matrix: join room=${roomId} id=${eventId}`);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
client.on("room.event", (roomId: string, event: MatrixRawEvent) => {
|
|
355
|
+
const eventType = event?.type ?? "unknown";
|
|
356
|
+
if (eventType === EventType.RoomMessageEncrypted) {
|
|
357
|
+
logVerboseMessage(
|
|
358
|
+
`matrix: encrypted raw event room=${roomId} id=${event?.event_id ?? "unknown"}`,
|
|
359
|
+
);
|
|
360
|
+
if (auth.encryption !== true && !warnedEncryptedRooms.has(roomId)) {
|
|
361
|
+
warnedEncryptedRooms.add(roomId);
|
|
362
|
+
const warning = formatMatrixEncryptedEventDisabledWarning(cfg, auth.accountId);
|
|
363
|
+
logger.warn(warning, { roomId });
|
|
364
|
+
}
|
|
365
|
+
if (auth.encryption === true && !client.crypto && !warnedCryptoMissingRooms.has(roomId)) {
|
|
366
|
+
warnedCryptoMissingRooms.add(roomId);
|
|
367
|
+
const hint = formatNativeDependencyHint({
|
|
368
|
+
packageName: "@matrix-org/matrix-sdk-crypto-nodejs",
|
|
369
|
+
manager: "pnpm",
|
|
370
|
+
downloadCommand: "node node_modules/@matrix-org/matrix-sdk-crypto-nodejs/download-lib.js",
|
|
371
|
+
});
|
|
372
|
+
const warning = `matrix: encryption enabled but crypto is unavailable; ${hint}`;
|
|
373
|
+
logger.warn(warning, { roomId });
|
|
374
|
+
}
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
if (eventType === EventType.RoomMember) {
|
|
378
|
+
directTracker?.invalidateRoom(roomId);
|
|
379
|
+
const membership = (event?.content as { membership?: string } | undefined)?.membership;
|
|
380
|
+
const stateKey = (event as { state_key?: string }).state_key ?? "";
|
|
381
|
+
logVerboseMessage(
|
|
382
|
+
`matrix: member event room=${roomId} stateKey=${stateKey} membership=${membership ?? "unknown"}`,
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
if (eventType === EventType.Reaction) {
|
|
386
|
+
void runMonitorTask(
|
|
387
|
+
`reaction handler room=${roomId} id=${event.event_id ?? "unknown"}`,
|
|
388
|
+
async () => {
|
|
389
|
+
await onRoomMessage(roomId, event);
|
|
390
|
+
},
|
|
391
|
+
);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
routeVerificationEvent(roomId, event);
|
|
396
|
+
});
|
|
397
|
+
}
|