@archipelagolab/lobi 1.0.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.
Files changed (315) hide show
  1. package/CHANGELOG.md +164 -0
  2. package/ENDOFFILE +0 -0
  3. package/EOF +0 -0
  4. package/LICENSE +21 -0
  5. package/SPEC-SUPPORT.md +116 -0
  6. package/YAMLEND +0 -0
  7. package/api.ts +18 -0
  8. package/archipelagolab-lobi-1.0.0.tgz +0 -0
  9. package/auth-presence.ts +56 -0
  10. package/channel-plugin-api.ts +3 -0
  11. package/cli-metadata.ts +11 -0
  12. package/contract-api.ts +17 -0
  13. package/docs/CHECKLIST.md +83 -0
  14. package/docs/FORK_SDK_GUIDE.md +279 -0
  15. package/helper-api.ts +3 -0
  16. package/index.test.ts +61 -0
  17. package/index.ts +65 -0
  18. package/openclaw.plugin.json +23 -0
  19. package/package.json +52 -0
  20. package/plugin-entry.handlers.runtime.ts +1 -0
  21. package/runtime-api.ts +54 -0
  22. package/runtime-heavy-api.ts +1 -0
  23. package/scripts/migrate-to-lobi.sh +72 -0
  24. package/secret-contract-api.ts +5 -0
  25. package/setup-entry.ts +13 -0
  26. package/src/account-selection.test.ts +124 -0
  27. package/src/account-selection.ts +226 -0
  28. package/src/actions.account-propagation.test.ts +251 -0
  29. package/src/actions.test.ts +251 -0
  30. package/src/actions.ts +336 -0
  31. package/src/approval-auth.test.ts +23 -0
  32. package/src/approval-auth.ts +25 -0
  33. package/src/approval-handler.runtime.test.ts +46 -0
  34. package/src/approval-handler.runtime.ts +400 -0
  35. package/src/approval-ids.ts +6 -0
  36. package/src/approval-native.test.ts +329 -0
  37. package/src/approval-native.ts +336 -0
  38. package/src/approval-reactions.test.ts +107 -0
  39. package/src/approval-reactions.ts +158 -0
  40. package/src/auth-precedence.ts +61 -0
  41. package/src/channel-account-paths.ts +92 -0
  42. package/src/channel.account-paths.test.ts +102 -0
  43. package/src/channel.directory.test.ts +601 -0
  44. package/src/channel.resolve.test.ts +38 -0
  45. package/src/channel.runtime.ts +16 -0
  46. package/src/channel.setup.test.ts +269 -0
  47. package/src/channel.ts +570 -0
  48. package/src/cli-metadata.ts +19 -0
  49. package/src/cli.test.ts +1015 -0
  50. package/src/cli.ts +1198 -0
  51. package/src/config-adapter.ts +41 -0
  52. package/src/config-schema.test.ts +90 -0
  53. package/src/config-schema.ts +114 -0
  54. package/src/directory-live.test.ts +200 -0
  55. package/src/directory-live.ts +238 -0
  56. package/src/doctor-contract.ts +287 -0
  57. package/src/doctor.test.ts +440 -0
  58. package/src/doctor.ts +262 -0
  59. package/src/env-vars.ts +92 -0
  60. package/src/exec-approval-resolver.test.ts +68 -0
  61. package/src/exec-approval-resolver.ts +23 -0
  62. package/src/exec-approvals.test.ts +483 -0
  63. package/src/exec-approvals.ts +290 -0
  64. package/src/group-mentions.ts +41 -0
  65. package/src/legacy-crypto-inspector-availability.test.ts +81 -0
  66. package/src/legacy-crypto-inspector-availability.ts +60 -0
  67. package/src/legacy-crypto.test.ts +234 -0
  68. package/src/legacy-crypto.ts +549 -0
  69. package/src/legacy-state.test.ts +86 -0
  70. package/src/legacy-state.ts +156 -0
  71. package/src/matrix/account-config.ts +150 -0
  72. package/src/matrix/accounts.readiness.test.ts +27 -0
  73. package/src/matrix/accounts.test.ts +757 -0
  74. package/src/matrix/accounts.ts +194 -0
  75. package/src/matrix/actions/client.test.ts +215 -0
  76. package/src/matrix/actions/client.ts +31 -0
  77. package/src/matrix/actions/devices.test.ts +114 -0
  78. package/src/matrix/actions/devices.ts +34 -0
  79. package/src/matrix/actions/limits.test.ts +15 -0
  80. package/src/matrix/actions/limits.ts +6 -0
  81. package/src/matrix/actions/messages.test.ts +289 -0
  82. package/src/matrix/actions/messages.ts +123 -0
  83. package/src/matrix/actions/pins.test.ts +74 -0
  84. package/src/matrix/actions/pins.ts +64 -0
  85. package/src/matrix/actions/polls.test.ts +71 -0
  86. package/src/matrix/actions/polls.ts +109 -0
  87. package/src/matrix/actions/profile.test.ts +109 -0
  88. package/src/matrix/actions/profile.ts +37 -0
  89. package/src/matrix/actions/reactions.test.ts +135 -0
  90. package/src/matrix/actions/reactions.ts +59 -0
  91. package/src/matrix/actions/room.test.ts +79 -0
  92. package/src/matrix/actions/room.ts +71 -0
  93. package/src/matrix/actions/summary.test.ts +87 -0
  94. package/src/matrix/actions/summary.ts +88 -0
  95. package/src/matrix/actions/types.ts +82 -0
  96. package/src/matrix/actions/verification.test.ts +105 -0
  97. package/src/matrix/actions/verification.ts +237 -0
  98. package/src/matrix/actions.ts +37 -0
  99. package/src/matrix/active-client.ts +26 -0
  100. package/src/matrix/async-lock.ts +18 -0
  101. package/src/matrix/backup-health.ts +115 -0
  102. package/src/matrix/client/config-runtime-api.ts +14 -0
  103. package/src/matrix/client/config-secret-input.runtime.ts +1 -0
  104. package/src/matrix/client/config.ts +982 -0
  105. package/src/matrix/client/create-client.test.ts +115 -0
  106. package/src/matrix/client/create-client.ts +101 -0
  107. package/src/matrix/client/env-auth.ts +6 -0
  108. package/src/matrix/client/file-sync-store.test.ts +265 -0
  109. package/src/matrix/client/file-sync-store.ts +289 -0
  110. package/src/matrix/client/logging.ts +123 -0
  111. package/src/matrix/client/migration-snapshot.runtime.ts +1 -0
  112. package/src/matrix/client/private-network-host.ts +56 -0
  113. package/src/matrix/client/runtime.ts +4 -0
  114. package/src/matrix/client/shared.test.ts +344 -0
  115. package/src/matrix/client/shared.ts +306 -0
  116. package/src/matrix/client/storage.test.ts +634 -0
  117. package/src/matrix/client/storage.ts +544 -0
  118. package/src/matrix/client/types.ts +50 -0
  119. package/src/matrix/client-bootstrap.test.ts +84 -0
  120. package/src/matrix/client-bootstrap.ts +164 -0
  121. package/src/matrix/client-resolver.test-helpers.ts +147 -0
  122. package/src/matrix/client.test.ts +1521 -0
  123. package/src/matrix/client.ts +23 -0
  124. package/src/matrix/config-paths.ts +31 -0
  125. package/src/matrix/config-update.test.ts +237 -0
  126. package/src/matrix/config-update.ts +291 -0
  127. package/src/matrix/credentials-read.ts +206 -0
  128. package/src/matrix/credentials-write.runtime.ts +26 -0
  129. package/src/matrix/credentials.test.ts +501 -0
  130. package/src/matrix/credentials.ts +95 -0
  131. package/src/matrix/deps.test.ts +74 -0
  132. package/src/matrix/deps.ts +225 -0
  133. package/src/matrix/device-health.test.ts +45 -0
  134. package/src/matrix/device-health.ts +31 -0
  135. package/src/matrix/direct-management.test.ts +350 -0
  136. package/src/matrix/direct-management.ts +347 -0
  137. package/src/matrix/direct-room.test.ts +61 -0
  138. package/src/matrix/direct-room.ts +128 -0
  139. package/src/matrix/draft-stream.test.ts +406 -0
  140. package/src/matrix/draft-stream.ts +216 -0
  141. package/src/matrix/encryption-guidance.ts +27 -0
  142. package/src/matrix/errors.ts +21 -0
  143. package/src/matrix/format.test.ts +340 -0
  144. package/src/matrix/format.ts +428 -0
  145. package/src/matrix/legacy-crypto-inspector.ts +95 -0
  146. package/src/matrix/media-errors.ts +20 -0
  147. package/src/matrix/media-text.ts +169 -0
  148. package/src/matrix/monitor/access-state.test.ts +45 -0
  149. package/src/matrix/monitor/access-state.ts +77 -0
  150. package/src/matrix/monitor/ack-config.test.ts +57 -0
  151. package/src/matrix/monitor/ack-config.ts +26 -0
  152. package/src/matrix/monitor/allowlist.test.ts +45 -0
  153. package/src/matrix/monitor/allowlist.ts +94 -0
  154. package/src/matrix/monitor/auto-join.test.ts +203 -0
  155. package/src/matrix/monitor/auto-join.ts +86 -0
  156. package/src/matrix/monitor/config.test.ts +197 -0
  157. package/src/matrix/monitor/config.ts +303 -0
  158. package/src/matrix/monitor/context-summary.ts +43 -0
  159. package/src/matrix/monitor/direct.test.ts +529 -0
  160. package/src/matrix/monitor/direct.ts +270 -0
  161. package/src/matrix/monitor/events.test.ts +1524 -0
  162. package/src/matrix/monitor/events.ts +213 -0
  163. package/src/matrix/monitor/handler.body-for-agent.test.ts +396 -0
  164. package/src/matrix/monitor/handler.group-history.test.ts +648 -0
  165. package/src/matrix/monitor/handler.media-failure.test.ts +267 -0
  166. package/src/matrix/monitor/handler.test-helpers.ts +308 -0
  167. package/src/matrix/monitor/handler.test.ts +2952 -0
  168. package/src/matrix/monitor/handler.thread-root-media.test.ts +82 -0
  169. package/src/matrix/monitor/handler.ts +1679 -0
  170. package/src/matrix/monitor/inbound-dedupe.test.ts +146 -0
  171. package/src/matrix/monitor/inbound-dedupe.ts +267 -0
  172. package/src/matrix/monitor/index.test.ts +920 -0
  173. package/src/matrix/monitor/index.ts +434 -0
  174. package/src/matrix/monitor/legacy-crypto-restore.test.ts +206 -0
  175. package/src/matrix/monitor/legacy-crypto-restore.ts +139 -0
  176. package/src/matrix/monitor/location.ts +100 -0
  177. package/src/matrix/monitor/media.test.ts +159 -0
  178. package/src/matrix/monitor/media.ts +119 -0
  179. package/src/matrix/monitor/mentions.test.ts +289 -0
  180. package/src/matrix/monitor/mentions.ts +177 -0
  181. package/src/matrix/monitor/reaction-events.test.ts +326 -0
  182. package/src/matrix/monitor/reaction-events.ts +187 -0
  183. package/src/matrix/monitor/recent-invite.test.ts +92 -0
  184. package/src/matrix/monitor/recent-invite.ts +30 -0
  185. package/src/matrix/monitor/replies.test.ts +265 -0
  186. package/src/matrix/monitor/replies.ts +136 -0
  187. package/src/matrix/monitor/reply-context.test.ts +276 -0
  188. package/src/matrix/monitor/reply-context.ts +92 -0
  189. package/src/matrix/monitor/room-history.test.ts +258 -0
  190. package/src/matrix/monitor/room-history.ts +301 -0
  191. package/src/matrix/monitor/room-info.test.ts +201 -0
  192. package/src/matrix/monitor/room-info.ts +126 -0
  193. package/src/matrix/monitor/rooms.test.ts +121 -0
  194. package/src/matrix/monitor/rooms.ts +52 -0
  195. package/src/matrix/monitor/route.test.ts +255 -0
  196. package/src/matrix/monitor/route.ts +178 -0
  197. package/src/matrix/monitor/runtime-api.ts +31 -0
  198. package/src/matrix/monitor/startup-verification.test.ts +294 -0
  199. package/src/matrix/monitor/startup-verification.ts +237 -0
  200. package/src/matrix/monitor/startup.test.ts +257 -0
  201. package/src/matrix/monitor/startup.ts +218 -0
  202. package/src/matrix/monitor/status.ts +111 -0
  203. package/src/matrix/monitor/sync-lifecycle.test.ts +224 -0
  204. package/src/matrix/monitor/sync-lifecycle.ts +91 -0
  205. package/src/matrix/monitor/task-runner.ts +38 -0
  206. package/src/matrix/monitor/thread-context.test.ts +149 -0
  207. package/src/matrix/monitor/thread-context.ts +108 -0
  208. package/src/matrix/monitor/threads.test.ts +68 -0
  209. package/src/matrix/monitor/threads.ts +85 -0
  210. package/src/matrix/monitor/types.ts +30 -0
  211. package/src/matrix/monitor/verification-events.ts +627 -0
  212. package/src/matrix/monitor/verification-utils.test.ts +47 -0
  213. package/src/matrix/monitor/verification-utils.ts +46 -0
  214. package/src/matrix/outbound-media-runtime.ts +1 -0
  215. package/src/matrix/poll-summary.ts +110 -0
  216. package/src/matrix/poll-types.test.ts +205 -0
  217. package/src/matrix/poll-types.ts +433 -0
  218. package/src/matrix/probe.runtime.ts +4 -0
  219. package/src/matrix/probe.test.ts +154 -0
  220. package/src/matrix/probe.ts +96 -0
  221. package/src/matrix/profile.test.ts +154 -0
  222. package/src/matrix/profile.ts +184 -0
  223. package/src/matrix/reaction-common.test.ts +96 -0
  224. package/src/matrix/reaction-common.ts +147 -0
  225. package/src/matrix/sdk/crypto-bootstrap.test.ts +505 -0
  226. package/src/matrix/sdk/crypto-bootstrap.ts +341 -0
  227. package/src/matrix/sdk/crypto-facade.test.ts +197 -0
  228. package/src/matrix/sdk/crypto-facade.ts +207 -0
  229. package/src/matrix/sdk/crypto-node.runtime.test.ts +27 -0
  230. package/src/matrix/sdk/crypto-node.runtime.ts +9 -0
  231. package/src/matrix/sdk/crypto-runtime.ts +11 -0
  232. package/src/matrix/sdk/decrypt-bridge.ts +356 -0
  233. package/src/matrix/sdk/event-helpers.test.ts +60 -0
  234. package/src/matrix/sdk/event-helpers.ts +71 -0
  235. package/src/matrix/sdk/http-client.test.ts +134 -0
  236. package/src/matrix/sdk/http-client.ts +87 -0
  237. package/src/matrix/sdk/idb-persistence-lock.ts +51 -0
  238. package/src/matrix/sdk/idb-persistence.lock-order.test.ts +108 -0
  239. package/src/matrix/sdk/idb-persistence.test-helpers.ts +88 -0
  240. package/src/matrix/sdk/idb-persistence.test.ts +149 -0
  241. package/src/matrix/sdk/idb-persistence.ts +283 -0
  242. package/src/matrix/sdk/logger.test.ts +25 -0
  243. package/src/matrix/sdk/logger.ts +108 -0
  244. package/src/matrix/sdk/read-response-with-limit.ts +19 -0
  245. package/src/matrix/sdk/recovery-key-store.test.ts +385 -0
  246. package/src/matrix/sdk/recovery-key-store.ts +430 -0
  247. package/src/matrix/sdk/transport.test.ts +161 -0
  248. package/src/matrix/sdk/transport.ts +344 -0
  249. package/src/matrix/sdk/types.ts +236 -0
  250. package/src/matrix/sdk/verification-manager.test.ts +509 -0
  251. package/src/matrix/sdk/verification-manager.ts +694 -0
  252. package/src/matrix/sdk/verification-status.ts +23 -0
  253. package/src/matrix/sdk.test.ts +2568 -0
  254. package/src/matrix/sdk.ts +1789 -0
  255. package/src/matrix/send/client.test.ts +174 -0
  256. package/src/matrix/send/client.ts +90 -0
  257. package/src/matrix/send/formatting.ts +189 -0
  258. package/src/matrix/send/media.ts +244 -0
  259. package/src/matrix/send/targets.test.ts +254 -0
  260. package/src/matrix/send/targets.ts +104 -0
  261. package/src/matrix/send/types.ts +134 -0
  262. package/src/matrix/send.test.ts +958 -0
  263. package/src/matrix/send.ts +609 -0
  264. package/src/matrix/session-store-metadata.ts +108 -0
  265. package/src/matrix/startup-abort.ts +44 -0
  266. package/src/matrix/sync-state.ts +27 -0
  267. package/src/matrix/target-ids.ts +102 -0
  268. package/src/matrix/thread-bindings-shared.ts +201 -0
  269. package/src/matrix/thread-bindings.test.ts +673 -0
  270. package/src/matrix/thread-bindings.ts +577 -0
  271. package/src/matrix-migration.runtime.ts +9 -0
  272. package/src/migration-config.test.ts +228 -0
  273. package/src/migration-config.ts +243 -0
  274. package/src/migration-snapshot-backup.ts +117 -0
  275. package/src/migration-snapshot.test.ts +184 -0
  276. package/src/migration-snapshot.ts +55 -0
  277. package/src/onboarding.resolve.test.ts +55 -0
  278. package/src/onboarding.test-harness.ts +158 -0
  279. package/src/onboarding.test.ts +665 -0
  280. package/src/onboarding.ts +773 -0
  281. package/src/outbound.test.ts +173 -0
  282. package/src/outbound.ts +78 -0
  283. package/src/plugin-entry.runtime.js +159 -0
  284. package/src/plugin-entry.runtime.test.ts +108 -0
  285. package/src/plugin-entry.runtime.ts +68 -0
  286. package/src/profile-update.ts +68 -0
  287. package/src/record-shared.ts +3 -0
  288. package/src/resolve-targets.test.ts +178 -0
  289. package/src/resolve-targets.ts +175 -0
  290. package/src/resolver.ts +21 -0
  291. package/src/runtime-api.ts +144 -0
  292. package/src/runtime.ts +7 -0
  293. package/src/secret-contract.ts +174 -0
  294. package/src/session-route.test.ts +315 -0
  295. package/src/session-route.ts +113 -0
  296. package/src/setup-bootstrap.ts +94 -0
  297. package/src/setup-config.ts +222 -0
  298. package/src/setup-contract.ts +89 -0
  299. package/src/setup-core.test.ts +326 -0
  300. package/src/setup-core.ts +50 -0
  301. package/src/setup-surface.ts +4 -0
  302. package/src/startup-maintenance.test.ts +227 -0
  303. package/src/startup-maintenance.ts +114 -0
  304. package/src/storage-paths.ts +92 -0
  305. package/src/test-helpers.ts +42 -0
  306. package/src/test-mocks.ts +55 -0
  307. package/src/test-runtime.ts +72 -0
  308. package/src/test-support/monitor-route-test-support.ts +8 -0
  309. package/src/tool-actions.runtime.ts +1 -0
  310. package/src/tool-actions.test.ts +422 -0
  311. package/src/tool-actions.ts +498 -0
  312. package/src/types.ts +230 -0
  313. package/test-api.ts +2 -0
  314. package/thread-bindings-runtime.ts +4 -0
  315. package/tsconfig.json +16 -0
@@ -0,0 +1,207 @@
1
+ import type { MatrixRecoveryKeyStore } from "./recovery-key-store.js";
2
+ import type { EncryptedFile } from "./types.js";
3
+ import type {
4
+ MatrixVerificationCryptoApi,
5
+ MatrixVerificationManager,
6
+ MatrixVerificationMethod,
7
+ MatrixVerificationSummary,
8
+ } from "./verification-manager.js";
9
+
10
+ type MatrixCryptoFacadeClient = {
11
+ getRoom: (roomId: string) => { hasEncryptionStateEvent: () => boolean } | null;
12
+ getCrypto: () => unknown;
13
+ };
14
+
15
+ export type MatrixCryptoFacade = {
16
+ prepare: (joinedRooms: string[]) => Promise<void>;
17
+ updateSyncData: (
18
+ toDeviceMessages: unknown,
19
+ otkCounts: unknown,
20
+ unusedFallbackKeyAlgs: unknown,
21
+ changedDeviceLists: unknown,
22
+ leftDeviceLists: unknown,
23
+ ) => Promise<void>;
24
+ isRoomEncrypted: (roomId: string) => Promise<boolean>;
25
+ requestOwnUserVerification: () => Promise<MatrixVerificationSummary | null>;
26
+ encryptMedia: (buffer: Buffer) => Promise<{ buffer: Buffer; file: Omit<EncryptedFile, "url"> }>;
27
+ decryptMedia: (
28
+ file: EncryptedFile,
29
+ opts?: { maxBytes?: number; readIdleTimeoutMs?: number },
30
+ ) => Promise<Buffer>;
31
+ getRecoveryKey: () => Promise<{
32
+ encodedPrivateKey?: string;
33
+ keyId?: string | null;
34
+ createdAt?: string;
35
+ } | null>;
36
+ listVerifications: () => Promise<MatrixVerificationSummary[]>;
37
+ ensureVerificationDmTracked: (params: {
38
+ roomId: string;
39
+ userId: string;
40
+ }) => Promise<MatrixVerificationSummary | null>;
41
+ requestVerification: (params: {
42
+ ownUser?: boolean;
43
+ userId?: string;
44
+ deviceId?: string;
45
+ roomId?: string;
46
+ }) => Promise<MatrixVerificationSummary>;
47
+ acceptVerification: (id: string) => Promise<MatrixVerificationSummary>;
48
+ cancelVerification: (
49
+ id: string,
50
+ params?: { reason?: string; code?: string },
51
+ ) => Promise<MatrixVerificationSummary>;
52
+ startVerification: (
53
+ id: string,
54
+ method?: MatrixVerificationMethod,
55
+ ) => Promise<MatrixVerificationSummary>;
56
+ generateVerificationQr: (id: string) => Promise<{ qrDataBase64: string }>;
57
+ scanVerificationQr: (id: string, qrDataBase64: string) => Promise<MatrixVerificationSummary>;
58
+ confirmVerificationSas: (id: string) => Promise<MatrixVerificationSummary>;
59
+ mismatchVerificationSas: (id: string) => Promise<MatrixVerificationSummary>;
60
+ confirmVerificationReciprocateQr: (id: string) => Promise<MatrixVerificationSummary>;
61
+ getVerificationSas: (
62
+ id: string,
63
+ ) => Promise<{ decimal?: [number, number, number]; emoji?: Array<[string, string]> }>;
64
+ };
65
+
66
+ type MatrixCryptoNodeRuntime = typeof import("./crypto-node.runtime.js");
67
+ let matrixCryptoNodeRuntimePromise: Promise<MatrixCryptoNodeRuntime> | null = null;
68
+
69
+ async function loadMatrixCryptoNodeRuntime(): Promise<MatrixCryptoNodeRuntime> {
70
+ // Keep the native crypto package out of the main CLI startup graph.
71
+ matrixCryptoNodeRuntimePromise ??= import("./crypto-node.runtime.js");
72
+ return await matrixCryptoNodeRuntimePromise;
73
+ }
74
+
75
+ export function createMatrixCryptoFacade(deps: {
76
+ client: MatrixCryptoFacadeClient;
77
+ verificationManager: MatrixVerificationManager;
78
+ recoveryKeyStore: MatrixRecoveryKeyStore;
79
+ getRoomStateEvent: (
80
+ roomId: string,
81
+ eventType: string,
82
+ stateKey?: string,
83
+ ) => Promise<Record<string, unknown>>;
84
+ downloadContent: (
85
+ mxcUrl: string,
86
+ opts?: { maxBytes?: number; readIdleTimeoutMs?: number },
87
+ ) => Promise<Buffer>;
88
+ }): MatrixCryptoFacade {
89
+ return {
90
+ prepare: async (_joinedRooms: string[]) => {
91
+ // matrix-js-sdk performs crypto prep during startup; no extra work required here.
92
+ },
93
+ updateSyncData: async (
94
+ _toDeviceMessages: unknown,
95
+ _otkCounts: unknown,
96
+ _unusedFallbackKeyAlgs: unknown,
97
+ _changedDeviceLists: unknown,
98
+ _leftDeviceLists: unknown,
99
+ ) => {
100
+ // compatibility no-op
101
+ },
102
+ isRoomEncrypted: async (roomId: string): Promise<boolean> => {
103
+ const room = deps.client.getRoom(roomId);
104
+ if (room?.hasEncryptionStateEvent()) {
105
+ return true;
106
+ }
107
+ try {
108
+ const event = await deps.getRoomStateEvent(roomId, "m.room.encryption", "");
109
+ return typeof event.algorithm === "string" && event.algorithm.length > 0;
110
+ } catch {
111
+ return false;
112
+ }
113
+ },
114
+ requestOwnUserVerification: async () => {
115
+ const crypto = deps.client.getCrypto() as MatrixVerificationCryptoApi | undefined;
116
+ return await deps.verificationManager.requestOwnUserVerification(crypto);
117
+ },
118
+ encryptMedia: async (
119
+ buffer: Buffer,
120
+ ): Promise<{ buffer: Buffer; file: Omit<EncryptedFile, "url"> }> => {
121
+ const { Attachment } = await loadMatrixCryptoNodeRuntime();
122
+ const encrypted = Attachment.encrypt(new Uint8Array(buffer));
123
+ const mediaInfoJson = encrypted.mediaEncryptionInfo;
124
+ if (!mediaInfoJson) {
125
+ throw new Error("Matrix media encryption failed: missing media encryption info");
126
+ }
127
+ const parsed = JSON.parse(mediaInfoJson) as EncryptedFile;
128
+ return {
129
+ buffer: Buffer.from(encrypted.encryptedData),
130
+ file: {
131
+ key: parsed.key,
132
+ iv: parsed.iv,
133
+ hashes: parsed.hashes,
134
+ v: parsed.v,
135
+ },
136
+ };
137
+ },
138
+ decryptMedia: async (
139
+ file: EncryptedFile,
140
+ opts?: { maxBytes?: number; readIdleTimeoutMs?: number },
141
+ ): Promise<Buffer> => {
142
+ const { Attachment, EncryptedAttachment } = await loadMatrixCryptoNodeRuntime();
143
+ const encrypted = await deps.downloadContent(file.url, opts);
144
+ const metadata: EncryptedFile = {
145
+ url: file.url,
146
+ key: file.key,
147
+ iv: file.iv,
148
+ hashes: file.hashes,
149
+ v: file.v,
150
+ };
151
+ const attachment = new EncryptedAttachment(
152
+ new Uint8Array(encrypted),
153
+ JSON.stringify(metadata),
154
+ );
155
+ const decrypted = Attachment.decrypt(attachment);
156
+ return Buffer.from(decrypted);
157
+ },
158
+ getRecoveryKey: async () => {
159
+ return deps.recoveryKeyStore.getRecoveryKeySummary();
160
+ },
161
+ listVerifications: async () => {
162
+ return deps.verificationManager.listVerifications();
163
+ },
164
+ ensureVerificationDmTracked: async ({ roomId, userId }) => {
165
+ const crypto = deps.client.getCrypto() as MatrixVerificationCryptoApi | undefined;
166
+ const request =
167
+ typeof crypto?.findVerificationRequestDMInProgress === "function"
168
+ ? crypto.findVerificationRequestDMInProgress(roomId, userId)
169
+ : undefined;
170
+ if (!request) {
171
+ return null;
172
+ }
173
+ return deps.verificationManager.trackVerificationRequest(request);
174
+ },
175
+ requestVerification: async (params) => {
176
+ const crypto = deps.client.getCrypto() as MatrixVerificationCryptoApi | undefined;
177
+ return await deps.verificationManager.requestVerification(crypto, params);
178
+ },
179
+ acceptVerification: async (id) => {
180
+ return await deps.verificationManager.acceptVerification(id);
181
+ },
182
+ cancelVerification: async (id, params) => {
183
+ return await deps.verificationManager.cancelVerification(id, params);
184
+ },
185
+ startVerification: async (id, method = "sas") => {
186
+ return await deps.verificationManager.startVerification(id, method);
187
+ },
188
+ generateVerificationQr: async (id) => {
189
+ return await deps.verificationManager.generateVerificationQr(id);
190
+ },
191
+ scanVerificationQr: async (id, qrDataBase64) => {
192
+ return await deps.verificationManager.scanVerificationQr(id, qrDataBase64);
193
+ },
194
+ confirmVerificationSas: async (id) => {
195
+ return await deps.verificationManager.confirmVerificationSas(id);
196
+ },
197
+ mismatchVerificationSas: async (id) => {
198
+ return deps.verificationManager.mismatchVerificationSas(id);
199
+ },
200
+ confirmVerificationReciprocateQr: async (id) => {
201
+ return deps.verificationManager.confirmVerificationReciprocateQr(id);
202
+ },
203
+ getVerificationSas: async (id) => {
204
+ return deps.verificationManager.getVerificationSas(id);
205
+ },
206
+ };
207
+ }
@@ -0,0 +1,27 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { build } from "esbuild";
4
+ import { describe, expect, it } from "vitest";
5
+
6
+ const sdkDir = path.dirname(fileURLToPath(import.meta.url));
7
+ const cryptoNodeRuntimePath = path.join(sdkDir, "crypto-node.runtime.ts");
8
+
9
+ describe("crypto-node runtime bundling", () => {
10
+ it("keeps the native Matrix crypto package behind a runtime require boundary", async () => {
11
+ const result = await build({
12
+ entryPoints: [cryptoNodeRuntimePath],
13
+ bundle: true,
14
+ external: ["@matrix-org/matrix-sdk-crypto-nodejs"],
15
+ format: "esm",
16
+ platform: "node",
17
+ write: false,
18
+ });
19
+
20
+ const bundled = result.outputFiles.at(0)?.text ?? "";
21
+
22
+ expect(bundled).toContain('from "node:module"');
23
+ expect(bundled).toContain("createRequire(import.meta.url)");
24
+ expect(bundled).toMatch(/require\d*\("@matrix-org\/matrix-sdk-crypto-nodejs"\)/);
25
+ expect(bundled).not.toContain('from "@matrix-org/matrix-sdk-crypto-nodejs"');
26
+ });
27
+ });
@@ -0,0 +1,9 @@
1
+ import { createRequire } from "node:module";
2
+
3
+ // Load via createRequire so the CJS package gets __dirname (its index.js
4
+ // uses __dirname to locate platform-specific native .node bindings).
5
+ const require = createRequire(import.meta.url);
6
+ const { Attachment, EncryptedAttachment } =
7
+ require("@matrix-org/matrix-sdk-crypto-nodejs") as typeof import("@matrix-org/matrix-sdk-crypto-nodejs");
8
+
9
+ export { Attachment, EncryptedAttachment };
@@ -0,0 +1,11 @@
1
+ import "fake-indexeddb/auto";
2
+
3
+ export { MatrixCryptoBootstrapper } from "./crypto-bootstrap.js";
4
+ export type { MatrixCryptoBootstrapResult } from "./crypto-bootstrap.js";
5
+ export { createMatrixCryptoFacade } from "./crypto-facade.js";
6
+ export type { MatrixCryptoFacade } from "./crypto-facade.js";
7
+ export { MatrixDecryptBridge } from "./decrypt-bridge.js";
8
+ export { persistIdbToDisk, restoreIdbFromDisk } from "./idb-persistence.js";
9
+ export { MatrixVerificationManager } from "./verification-manager.js";
10
+ export type { MatrixVerificationSummary } from "./verification-manager.js";
11
+ export { isMatrixDeviceOwnerVerified } from "./verification-status.js";
@@ -0,0 +1,356 @@
1
+ import { CryptoEvent } from "@archipelagolab/lobi-js-sdk/lib/crypto-api/CryptoEvent.js";
2
+ import { MatrixEventEvent, type MatrixEvent } from "@archipelagolab/lobi-js-sdk/lib/lobi.js";
3
+ import { LogService, noop } from "./logger.js";
4
+
5
+ type MatrixDecryptIfNeededClient = {
6
+ decryptEventIfNeeded?: (
7
+ event: MatrixEvent,
8
+ opts?: {
9
+ isRetry?: boolean;
10
+ },
11
+ ) => Promise<void>;
12
+ };
13
+
14
+ type MatrixDecryptRetryState = {
15
+ event: MatrixEvent;
16
+ roomId: string;
17
+ eventId: string;
18
+ attempts: number;
19
+ inFlight: boolean;
20
+ timer: ReturnType<typeof setTimeout> | null;
21
+ };
22
+
23
+ type DecryptBridgeRawEvent = {
24
+ event_id: string;
25
+ };
26
+
27
+ type MatrixCryptoRetrySignalSource = {
28
+ on: (eventName: string, listener: (...args: unknown[]) => void) => void;
29
+ };
30
+
31
+ const MATRIX_DECRYPT_RETRY_BASE_DELAY_MS = 1_500;
32
+ const MATRIX_DECRYPT_RETRY_MAX_DELAY_MS = 30_000;
33
+ const MATRIX_DECRYPT_RETRY_MAX_ATTEMPTS = 8;
34
+
35
+ function resolveDecryptRetryKey(roomId: string, eventId: string): string | null {
36
+ if (!roomId || !eventId) {
37
+ return null;
38
+ }
39
+ return `${roomId}|${eventId}`;
40
+ }
41
+
42
+ function isDecryptionFailure(event: MatrixEvent): boolean {
43
+ return (
44
+ typeof (event as { isDecryptionFailure?: () => boolean }).isDecryptionFailure === "function" &&
45
+ (event as { isDecryptionFailure: () => boolean }).isDecryptionFailure()
46
+ );
47
+ }
48
+
49
+ export class MatrixDecryptBridge<TRawEvent extends DecryptBridgeRawEvent> {
50
+ private readonly trackedEncryptedEvents = new WeakSet<object>();
51
+ private readonly decryptedMessageDedupe = new Map<string, number>();
52
+ private readonly decryptRetries = new Map<string, MatrixDecryptRetryState>();
53
+ private readonly failedDecryptionsNotified = new Set<string>();
54
+ private activeRetryRuns = 0;
55
+ private readonly retryIdleResolvers = new Set<() => void>();
56
+ private cryptoRetrySignalsBound = false;
57
+
58
+ constructor(
59
+ private readonly deps: {
60
+ client: MatrixDecryptIfNeededClient;
61
+ toRaw: (event: MatrixEvent) => TRawEvent;
62
+ emitDecryptedEvent: (roomId: string, event: TRawEvent) => void;
63
+ emitMessage: (roomId: string, event: TRawEvent) => void;
64
+ emitFailedDecryption: (roomId: string, event: TRawEvent, error: Error) => void;
65
+ },
66
+ ) {}
67
+
68
+ shouldEmitUnencryptedMessage(roomId: string, eventId: string): boolean {
69
+ if (!eventId) {
70
+ return true;
71
+ }
72
+ const key = `${roomId}|${eventId}`;
73
+ const createdAt = this.decryptedMessageDedupe.get(key);
74
+ if (createdAt === undefined) {
75
+ return true;
76
+ }
77
+ this.decryptedMessageDedupe.delete(key);
78
+ return false;
79
+ }
80
+
81
+ attachEncryptedEvent(event: MatrixEvent, roomId: string): void {
82
+ if (this.trackedEncryptedEvents.has(event)) {
83
+ return;
84
+ }
85
+ this.trackedEncryptedEvents.add(event);
86
+ event.on(MatrixEventEvent.Decrypted, (decryptedEvent: MatrixEvent, err?: Error) => {
87
+ this.handleEncryptedEventDecrypted({
88
+ roomId,
89
+ encryptedEvent: event,
90
+ decryptedEvent,
91
+ err,
92
+ });
93
+ });
94
+ }
95
+
96
+ retryPendingNow(reason: string): void {
97
+ const pending = Array.from(this.decryptRetries.entries());
98
+ if (pending.length === 0) {
99
+ return;
100
+ }
101
+ LogService.debug("MatrixClientLite", `Retrying pending decryptions due to ${reason}`);
102
+ for (const [retryKey, state] of pending) {
103
+ if (state.timer) {
104
+ clearTimeout(state.timer);
105
+ state.timer = null;
106
+ }
107
+ if (state.inFlight) {
108
+ continue;
109
+ }
110
+ this.runDecryptRetry(retryKey).catch(noop);
111
+ }
112
+ }
113
+
114
+ bindCryptoRetrySignals(crypto: MatrixCryptoRetrySignalSource | undefined): void {
115
+ if (!crypto || this.cryptoRetrySignalsBound) {
116
+ return;
117
+ }
118
+ this.cryptoRetrySignalsBound = true;
119
+
120
+ const trigger = (reason: string): void => {
121
+ this.retryPendingNow(reason);
122
+ };
123
+
124
+ crypto.on(CryptoEvent.KeyBackupDecryptionKeyCached, () => {
125
+ trigger("crypto.keyBackupDecryptionKeyCached");
126
+ });
127
+ crypto.on(CryptoEvent.RehydrationCompleted, () => {
128
+ trigger("dehydration.RehydrationCompleted");
129
+ });
130
+ crypto.on(CryptoEvent.DevicesUpdated, () => {
131
+ trigger("crypto.devicesUpdated");
132
+ });
133
+ crypto.on(CryptoEvent.KeysChanged, () => {
134
+ trigger("crossSigning.keysChanged");
135
+ });
136
+ }
137
+
138
+ stop(): void {
139
+ for (const retryKey of this.decryptRetries.keys()) {
140
+ this.clearDecryptRetry(retryKey);
141
+ }
142
+ }
143
+
144
+ async drainPendingDecryptions(reason: string): Promise<void> {
145
+ for (let attempts = 0; attempts < MATRIX_DECRYPT_RETRY_MAX_ATTEMPTS; attempts += 1) {
146
+ if (this.decryptRetries.size === 0) {
147
+ return;
148
+ }
149
+ this.retryPendingNow(reason);
150
+ await this.waitForActiveRetryRunsToFinish();
151
+ const hasPendingRetryTimers = Array.from(this.decryptRetries.values()).some(
152
+ (state) => state.timer || state.inFlight,
153
+ );
154
+ if (!hasPendingRetryTimers) {
155
+ return;
156
+ }
157
+ }
158
+ }
159
+
160
+ private handleEncryptedEventDecrypted(params: {
161
+ roomId: string;
162
+ encryptedEvent: MatrixEvent;
163
+ decryptedEvent: MatrixEvent;
164
+ err?: Error;
165
+ }): void {
166
+ const decryptedRoomId = params.decryptedEvent.getRoomId() || params.roomId;
167
+ const decryptedRaw = this.deps.toRaw(params.decryptedEvent);
168
+ const retryEventId = decryptedRaw.event_id || params.encryptedEvent.getId() || "";
169
+ const retryKey = resolveDecryptRetryKey(decryptedRoomId, retryEventId);
170
+
171
+ if (params.err) {
172
+ this.emitFailedDecryptionOnce(retryKey, decryptedRoomId, decryptedRaw, params.err);
173
+ this.scheduleDecryptRetry({
174
+ event: params.encryptedEvent,
175
+ roomId: decryptedRoomId,
176
+ eventId: retryEventId,
177
+ });
178
+ return;
179
+ }
180
+
181
+ if (isDecryptionFailure(params.decryptedEvent)) {
182
+ this.emitFailedDecryptionOnce(
183
+ retryKey,
184
+ decryptedRoomId,
185
+ decryptedRaw,
186
+ new Error("Matrix event failed to decrypt"),
187
+ );
188
+ this.scheduleDecryptRetry({
189
+ event: params.encryptedEvent,
190
+ roomId: decryptedRoomId,
191
+ eventId: retryEventId,
192
+ });
193
+ return;
194
+ }
195
+
196
+ if (retryKey) {
197
+ this.clearDecryptRetry(retryKey);
198
+ }
199
+ this.rememberDecryptedMessage(decryptedRoomId, decryptedRaw.event_id);
200
+ this.deps.emitDecryptedEvent(decryptedRoomId, decryptedRaw);
201
+ this.deps.emitMessage(decryptedRoomId, decryptedRaw);
202
+ }
203
+
204
+ private emitFailedDecryptionOnce(
205
+ retryKey: string | null,
206
+ roomId: string,
207
+ event: TRawEvent,
208
+ error: Error,
209
+ ): void {
210
+ if (retryKey) {
211
+ if (this.failedDecryptionsNotified.has(retryKey)) {
212
+ return;
213
+ }
214
+ this.failedDecryptionsNotified.add(retryKey);
215
+ }
216
+ this.deps.emitFailedDecryption(roomId, event, error);
217
+ }
218
+
219
+ private scheduleDecryptRetry(params: {
220
+ event: MatrixEvent;
221
+ roomId: string;
222
+ eventId: string;
223
+ }): void {
224
+ const retryKey = resolveDecryptRetryKey(params.roomId, params.eventId);
225
+ if (!retryKey) {
226
+ return;
227
+ }
228
+ const existing = this.decryptRetries.get(retryKey);
229
+ if (existing?.timer || existing?.inFlight) {
230
+ return;
231
+ }
232
+ const attempts = (existing?.attempts ?? 0) + 1;
233
+ if (attempts > MATRIX_DECRYPT_RETRY_MAX_ATTEMPTS) {
234
+ this.clearDecryptRetry(retryKey);
235
+ LogService.debug(
236
+ "MatrixClientLite",
237
+ `Giving up decryption retry for ${params.eventId} in ${params.roomId} after ${attempts - 1} attempts`,
238
+ );
239
+ return;
240
+ }
241
+ const delayMs = Math.min(
242
+ MATRIX_DECRYPT_RETRY_BASE_DELAY_MS * 2 ** (attempts - 1),
243
+ MATRIX_DECRYPT_RETRY_MAX_DELAY_MS,
244
+ );
245
+ const next: MatrixDecryptRetryState = {
246
+ event: params.event,
247
+ roomId: params.roomId,
248
+ eventId: params.eventId,
249
+ attempts,
250
+ inFlight: false,
251
+ timer: null,
252
+ };
253
+ next.timer = setTimeout(() => {
254
+ this.runDecryptRetry(retryKey).catch(noop);
255
+ }, delayMs);
256
+ this.decryptRetries.set(retryKey, next);
257
+ }
258
+
259
+ private async runDecryptRetry(retryKey: string): Promise<void> {
260
+ const state = this.decryptRetries.get(retryKey);
261
+ if (!state || state.inFlight) {
262
+ return;
263
+ }
264
+
265
+ state.inFlight = true;
266
+ state.timer = null;
267
+ this.activeRetryRuns += 1;
268
+ const canDecrypt = typeof this.deps.client.decryptEventIfNeeded === "function";
269
+ if (!canDecrypt) {
270
+ this.clearDecryptRetry(retryKey);
271
+ this.activeRetryRuns = Math.max(0, this.activeRetryRuns - 1);
272
+ this.resolveRetryIdleIfNeeded();
273
+ return;
274
+ }
275
+
276
+ try {
277
+ await this.deps.client.decryptEventIfNeeded?.(state.event, {
278
+ isRetry: true,
279
+ });
280
+ } catch {
281
+ // Retry with backoff until we hit the configured retry cap.
282
+ } finally {
283
+ state.inFlight = false;
284
+ this.activeRetryRuns = Math.max(0, this.activeRetryRuns - 1);
285
+ this.resolveRetryIdleIfNeeded();
286
+ }
287
+
288
+ if (this.decryptRetries.get(retryKey) !== state) {
289
+ return;
290
+ }
291
+ if (isDecryptionFailure(state.event)) {
292
+ this.scheduleDecryptRetry(state);
293
+ return;
294
+ }
295
+
296
+ this.clearDecryptRetry(retryKey);
297
+ }
298
+
299
+ private clearDecryptRetry(retryKey: string): void {
300
+ const state = this.decryptRetries.get(retryKey);
301
+ if (state?.timer) {
302
+ clearTimeout(state.timer);
303
+ }
304
+ this.decryptRetries.delete(retryKey);
305
+ this.failedDecryptionsNotified.delete(retryKey);
306
+ }
307
+
308
+ private rememberDecryptedMessage(roomId: string, eventId: string): void {
309
+ if (!eventId) {
310
+ return;
311
+ }
312
+ const now = Date.now();
313
+ this.pruneDecryptedMessageDedupe(now);
314
+ this.decryptedMessageDedupe.set(`${roomId}|${eventId}`, now);
315
+ }
316
+
317
+ private pruneDecryptedMessageDedupe(now: number): void {
318
+ const ttlMs = 30_000;
319
+ for (const [key, createdAt] of this.decryptedMessageDedupe) {
320
+ if (now - createdAt > ttlMs) {
321
+ this.decryptedMessageDedupe.delete(key);
322
+ }
323
+ }
324
+ const maxEntries = 2048;
325
+ while (this.decryptedMessageDedupe.size > maxEntries) {
326
+ const oldest = this.decryptedMessageDedupe.keys().next().value;
327
+ if (oldest === undefined) {
328
+ break;
329
+ }
330
+ this.decryptedMessageDedupe.delete(oldest);
331
+ }
332
+ }
333
+
334
+ private async waitForActiveRetryRunsToFinish(): Promise<void> {
335
+ if (this.activeRetryRuns === 0) {
336
+ return;
337
+ }
338
+ await new Promise<void>((resolve) => {
339
+ this.retryIdleResolvers.add(resolve);
340
+ if (this.activeRetryRuns === 0) {
341
+ this.retryIdleResolvers.delete(resolve);
342
+ resolve();
343
+ }
344
+ });
345
+ }
346
+
347
+ private resolveRetryIdleIfNeeded(): void {
348
+ if (this.activeRetryRuns !== 0) {
349
+ return;
350
+ }
351
+ for (const resolve of this.retryIdleResolvers) {
352
+ resolve();
353
+ }
354
+ this.retryIdleResolvers.clear();
355
+ }
356
+ }
@@ -0,0 +1,60 @@
1
+ import type { MatrixEvent } from "@archipelagolab/lobi-js-sdk";
2
+ import { describe, expect, it } from "vitest";
3
+ import { buildHttpError, matrixEventToRaw, parseMxc } from "./event-helpers.js";
4
+
5
+ describe("event-helpers", () => {
6
+ it("parses mxc URIs", () => {
7
+ expect(parseMxc("mxc://server.example/media-id")).toEqual({
8
+ server: "server.example",
9
+ mediaId: "media-id",
10
+ });
11
+ expect(parseMxc("not-mxc")).toBeNull();
12
+ });
13
+
14
+ it("builds HTTP errors from JSON and plain text payloads", () => {
15
+ const fromJson = buildHttpError(403, JSON.stringify({ error: "forbidden" }));
16
+ expect(fromJson.message).toBe("forbidden");
17
+ expect(fromJson.statusCode).toBe(403);
18
+
19
+ const fromText = buildHttpError(500, "internal failure");
20
+ expect(fromText.message).toBe("internal failure");
21
+ expect(fromText.statusCode).toBe(500);
22
+ });
23
+
24
+ it("serializes Matrix events and resolves state key from available sources", () => {
25
+ const viaGetter = {
26
+ getId: () => "$1",
27
+ getSender: () => "@alice:example.org",
28
+ getType: () => "m.room.member",
29
+ getTs: () => 1000,
30
+ getContent: () => ({ membership: "join" }),
31
+ getUnsigned: () => ({ age: 1 }),
32
+ getStateKey: () => "@alice:example.org",
33
+ } as unknown as MatrixEvent;
34
+ expect(matrixEventToRaw(viaGetter).state_key).toBe("@alice:example.org");
35
+
36
+ const viaWire = {
37
+ getId: () => "$2",
38
+ getSender: () => "@bob:example.org",
39
+ getType: () => "m.room.member",
40
+ getTs: () => 2000,
41
+ getContent: () => ({ membership: "join" }),
42
+ getUnsigned: () => ({}),
43
+ getStateKey: () => undefined,
44
+ getWireContent: () => ({ state_key: "@bob:example.org" }),
45
+ } as unknown as MatrixEvent;
46
+ expect(matrixEventToRaw(viaWire).state_key).toBe("@bob:example.org");
47
+
48
+ const viaRaw = {
49
+ getId: () => "$3",
50
+ getSender: () => "@carol:example.org",
51
+ getType: () => "m.room.member",
52
+ getTs: () => 3000,
53
+ getContent: () => ({ membership: "join" }),
54
+ getUnsigned: () => ({}),
55
+ getStateKey: () => undefined,
56
+ event: { state_key: "@carol:example.org" },
57
+ } as unknown as MatrixEvent;
58
+ expect(matrixEventToRaw(viaRaw).state_key).toBe("@carol:example.org");
59
+ });
60
+ });