@jeongsu816/matrix-bot-sdk 0.0.0-stage → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (377) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +50 -2
  3. package/lib/AdminApis.d.ts +52 -0
  4. package/lib/AdminApis.js +29 -0
  5. package/lib/AdminApis.js.map +1 -0
  6. package/lib/DMs.d.ts +48 -0
  7. package/lib/DMs.js +160 -0
  8. package/lib/DMs.js.map +1 -0
  9. package/lib/IFilter.d.ts +7 -0
  10. package/lib/IFilter.js +3 -0
  11. package/lib/IFilter.js.map +1 -0
  12. package/lib/MatrixAuth.d.ts +45 -0
  13. package/lib/MatrixAuth.js +125 -0
  14. package/lib/MatrixAuth.js.map +1 -0
  15. package/lib/MatrixClient.d.ts +834 -0
  16. package/lib/MatrixClient.js +2268 -0
  17. package/lib/MatrixClient.js.map +1 -0
  18. package/lib/PantalaimonClient.d.ts +42 -0
  19. package/lib/PantalaimonClient.js +44 -0
  20. package/lib/PantalaimonClient.js.map +1 -0
  21. package/lib/SynapseAdminApis.d.ts +350 -0
  22. package/lib/SynapseAdminApis.js +244 -0
  23. package/lib/SynapseAdminApis.js.map +1 -0
  24. package/lib/SynchronousMatrixClient.d.ts +82 -0
  25. package/lib/SynchronousMatrixClient.js +130 -0
  26. package/lib/SynchronousMatrixClient.js.map +1 -0
  27. package/lib/UnstableApis.d.ts +43 -0
  28. package/lib/UnstableApis.js +74 -0
  29. package/lib/UnstableApis.js.map +1 -0
  30. package/lib/appservice/Appservice.d.ts +328 -0
  31. package/lib/appservice/Appservice.js +803 -0
  32. package/lib/appservice/Appservice.js.map +1 -0
  33. package/lib/appservice/Intent.d.ts +113 -0
  34. package/lib/appservice/Intent.js +363 -0
  35. package/lib/appservice/Intent.js.map +1 -0
  36. package/lib/appservice/MatrixBridge.d.ts +85 -0
  37. package/lib/appservice/MatrixBridge.js +111 -0
  38. package/lib/appservice/MatrixBridge.js.map +1 -0
  39. package/lib/appservice/UnstableAppserviceApis.d.ts +41 -0
  40. package/lib/appservice/UnstableAppserviceApis.js +62 -0
  41. package/lib/appservice/UnstableAppserviceApis.js.map +1 -0
  42. package/lib/appservice/http_responses.d.ts +77 -0
  43. package/lib/appservice/http_responses.js +3 -0
  44. package/lib/appservice/http_responses.js.map +1 -0
  45. package/lib/b64.d.ts +42 -0
  46. package/lib/b64.js +68 -0
  47. package/lib/b64.js.map +1 -0
  48. package/lib/e2ee/CrossSigningBootstrap.d.ts +73 -0
  49. package/lib/e2ee/CrossSigningBootstrap.js +86 -0
  50. package/lib/e2ee/CrossSigningBootstrap.js.map +1 -0
  51. package/lib/e2ee/CryptoClient.d.ts +163 -0
  52. package/lib/e2ee/CryptoClient.js +382 -0
  53. package/lib/e2ee/CryptoClient.js.map +1 -0
  54. package/lib/e2ee/ICryptoRoomInformation.d.ts +8 -0
  55. package/lib/e2ee/ICryptoRoomInformation.js +3 -0
  56. package/lib/e2ee/ICryptoRoomInformation.js.map +1 -0
  57. package/lib/e2ee/RoomTracker.d.ts +41 -0
  58. package/lib/e2ee/RoomTracker.js +96 -0
  59. package/lib/e2ee/RoomTracker.js.map +1 -0
  60. package/lib/e2ee/RustEngine.d.ts +149 -0
  61. package/lib/e2ee/RustEngine.js +467 -0
  62. package/lib/e2ee/RustEngine.js.map +1 -0
  63. package/lib/e2ee/decorators.d.ts +10 -0
  64. package/lib/e2ee/decorators.js +37 -0
  65. package/lib/e2ee/decorators.js.map +1 -0
  66. package/lib/helpers/MatrixEntity.d.ts +37 -0
  67. package/lib/helpers/MatrixEntity.js +67 -0
  68. package/lib/helpers/MatrixEntity.js.map +1 -0
  69. package/lib/helpers/MatrixGlob.d.ts +22 -0
  70. package/lib/helpers/MatrixGlob.js +35 -0
  71. package/lib/helpers/MatrixGlob.js.map +1 -0
  72. package/lib/helpers/MentionPill.d.ts +40 -0
  73. package/lib/helpers/MentionPill.js +91 -0
  74. package/lib/helpers/MentionPill.js.map +1 -0
  75. package/lib/helpers/Permalinks.d.ts +58 -0
  76. package/lib/helpers/Permalinks.js +73 -0
  77. package/lib/helpers/Permalinks.js.map +1 -0
  78. package/lib/helpers/ProfileCache.d.ts +48 -0
  79. package/lib/helpers/ProfileCache.js +114 -0
  80. package/lib/helpers/ProfileCache.js.map +1 -0
  81. package/lib/helpers/RichReply.d.ts +17 -0
  82. package/lib/helpers/RichReply.js +47 -0
  83. package/lib/helpers/RichReply.js.map +1 -0
  84. package/lib/helpers/UnpaddedBase64.d.ts +31 -0
  85. package/lib/helpers/UnpaddedBase64.js +45 -0
  86. package/lib/helpers/UnpaddedBase64.js.map +1 -0
  87. package/lib/http.d.ts +17 -0
  88. package/lib/http.js +147 -0
  89. package/lib/http.js.map +1 -0
  90. package/lib/identity/IdentityClient.d.ts +93 -0
  91. package/lib/identity/IdentityClient.js +248 -0
  92. package/lib/identity/IdentityClient.js.map +1 -0
  93. package/lib/index.d.ts +91 -0
  94. package/lib/index.js +125 -0
  95. package/lib/index.js.map +1 -0
  96. package/lib/logging/ConsoleLogger.d.ts +12 -0
  97. package/lib/logging/ConsoleLogger.js +28 -0
  98. package/lib/logging/ConsoleLogger.js.map +1 -0
  99. package/lib/logging/ILogger.d.ts +36 -0
  100. package/lib/logging/ILogger.js +3 -0
  101. package/lib/logging/ILogger.js.map +1 -0
  102. package/lib/logging/LogService.d.ts +100 -0
  103. package/lib/logging/LogService.js +169 -0
  104. package/lib/logging/LogService.js.map +1 -0
  105. package/lib/logging/RichConsoleLogger.d.ts +20 -0
  106. package/lib/logging/RichConsoleLogger.js +41 -0
  107. package/lib/logging/RichConsoleLogger.js.map +1 -0
  108. package/lib/metrics/IMetricListener.d.ts +52 -0
  109. package/lib/metrics/IMetricListener.js +3 -0
  110. package/lib/metrics/IMetricListener.js.map +1 -0
  111. package/lib/metrics/Metrics.d.ts +66 -0
  112. package/lib/metrics/Metrics.js +128 -0
  113. package/lib/metrics/Metrics.js.map +1 -0
  114. package/lib/metrics/contexts.d.ts +51 -0
  115. package/lib/metrics/contexts.js +3 -0
  116. package/lib/metrics/contexts.js.map +1 -0
  117. package/lib/metrics/decorators.d.ts +15 -0
  118. package/lib/metrics/decorators.js +92 -0
  119. package/lib/metrics/decorators.js.map +1 -0
  120. package/lib/metrics/names.d.ts +45 -0
  121. package/lib/metrics/names.js +49 -0
  122. package/lib/metrics/names.js.map +1 -0
  123. package/lib/mixins/AutojoinRoomsMixin.d.ts +10 -0
  124. package/lib/mixins/AutojoinRoomsMixin.js +25 -0
  125. package/lib/mixins/AutojoinRoomsMixin.js.map +1 -0
  126. package/lib/mixins/AutojoinUpgradedRoomsMixin.d.ts +10 -0
  127. package/lib/mixins/AutojoinUpgradedRoomsMixin.js +40 -0
  128. package/lib/mixins/AutojoinUpgradedRoomsMixin.js.map +1 -0
  129. package/lib/models/Account.d.ts +8 -0
  130. package/lib/models/Account.js +3 -0
  131. package/lib/models/Account.js.map +1 -0
  132. package/lib/models/CreateRoom.d.ts +125 -0
  133. package/lib/models/CreateRoom.js +3 -0
  134. package/lib/models/CreateRoom.js.map +1 -0
  135. package/lib/models/Crypto.d.ts +167 -0
  136. package/lib/models/Crypto.js +31 -0
  137. package/lib/models/Crypto.js.map +1 -0
  138. package/lib/models/EventContext.d.ts +19 -0
  139. package/lib/models/EventContext.js +3 -0
  140. package/lib/models/EventContext.js.map +1 -0
  141. package/lib/models/IdentityServerModels.d.ts +20 -0
  142. package/lib/models/IdentityServerModels.js +3 -0
  143. package/lib/models/IdentityServerModels.js.map +1 -0
  144. package/lib/models/MSC2176.d.ts +42 -0
  145. package/lib/models/MSC2176.js +3 -0
  146. package/lib/models/MSC2176.js.map +1 -0
  147. package/lib/models/MatrixError.d.ts +38 -0
  148. package/lib/models/MatrixError.js +30 -0
  149. package/lib/models/MatrixError.js.map +1 -0
  150. package/lib/models/MatrixProfile.d.ts +43 -0
  151. package/lib/models/MatrixProfile.js +43 -0
  152. package/lib/models/MatrixProfile.js.map +1 -0
  153. package/lib/models/OpenIDConnect.d.ts +10 -0
  154. package/lib/models/OpenIDConnect.js +3 -0
  155. package/lib/models/OpenIDConnect.js.map +1 -0
  156. package/lib/models/Policies.d.ts +25 -0
  157. package/lib/models/Policies.js +3 -0
  158. package/lib/models/Policies.js.map +1 -0
  159. package/lib/models/PowerLevelAction.d.ts +27 -0
  160. package/lib/models/PowerLevelAction.js +32 -0
  161. package/lib/models/PowerLevelAction.js.map +1 -0
  162. package/lib/models/PowerLevelBounds.d.ts +15 -0
  163. package/lib/models/PowerLevelBounds.js +3 -0
  164. package/lib/models/PowerLevelBounds.js.map +1 -0
  165. package/lib/models/Presence.d.ts +25 -0
  166. package/lib/models/Presence.js +38 -0
  167. package/lib/models/Presence.js.map +1 -0
  168. package/lib/models/ServerVersions.d.ts +8 -0
  169. package/lib/models/ServerVersions.js +3 -0
  170. package/lib/models/ServerVersions.js.map +1 -0
  171. package/lib/models/Spaces.d.ts +116 -0
  172. package/lib/models/Spaces.js +91 -0
  173. package/lib/models/Spaces.js.map +1 -0
  174. package/lib/models/Threepid.d.ts +8 -0
  175. package/lib/models/Threepid.js +3 -0
  176. package/lib/models/Threepid.js.map +1 -0
  177. package/lib/models/events/AliasesEvent.d.ts +27 -0
  178. package/lib/models/events/AliasesEvent.js +27 -0
  179. package/lib/models/events/AliasesEvent.js.map +1 -0
  180. package/lib/models/events/CanonicalAliasEvent.d.ts +23 -0
  181. package/lib/models/events/CanonicalAliasEvent.js +21 -0
  182. package/lib/models/events/CanonicalAliasEvent.js.map +1 -0
  183. package/lib/models/events/CreateEvent.d.ts +63 -0
  184. package/lib/models/events/CreateEvent.js +33 -0
  185. package/lib/models/events/CreateEvent.js.map +1 -0
  186. package/lib/models/events/EncryptedRoomEvent.d.ts +42 -0
  187. package/lib/models/events/EncryptedRoomEvent.js +28 -0
  188. package/lib/models/events/EncryptedRoomEvent.js.map +1 -0
  189. package/lib/models/events/EncryptionEvent.d.ts +47 -0
  190. package/lib/models/events/EncryptionEvent.js +42 -0
  191. package/lib/models/events/EncryptionEvent.js.map +1 -0
  192. package/lib/models/events/Event.d.ts +26 -0
  193. package/lib/models/events/Event.js +40 -0
  194. package/lib/models/events/Event.js.map +1 -0
  195. package/lib/models/events/EventKind.d.ts +15 -0
  196. package/lib/models/events/EventKind.js +20 -0
  197. package/lib/models/events/EventKind.js.map +1 -0
  198. package/lib/models/events/InvalidEventError.d.ts +14 -0
  199. package/lib/models/events/InvalidEventError.js +24 -0
  200. package/lib/models/events/InvalidEventError.js.map +1 -0
  201. package/lib/models/events/JoinRulesEvent.d.ts +29 -0
  202. package/lib/models/events/JoinRulesEvent.js +21 -0
  203. package/lib/models/events/JoinRulesEvent.js.map +1 -0
  204. package/lib/models/events/MembershipEvent.d.ts +54 -0
  205. package/lib/models/events/MembershipEvent.js +49 -0
  206. package/lib/models/events/MembershipEvent.js.map +1 -0
  207. package/lib/models/events/MessageEvent.d.ts +233 -0
  208. package/lib/models/events/MessageEvent.js +49 -0
  209. package/lib/models/events/MessageEvent.js.map +1 -0
  210. package/lib/models/events/PinnedEventsEvent.d.ts +23 -0
  211. package/lib/models/events/PinnedEventsEvent.js +21 -0
  212. package/lib/models/events/PinnedEventsEvent.js.map +1 -0
  213. package/lib/models/events/PowerLevelsEvent.d.ts +96 -0
  214. package/lib/models/events/PowerLevelsEvent.js +70 -0
  215. package/lib/models/events/PowerLevelsEvent.js.map +1 -0
  216. package/lib/models/events/PresenceEvent.d.ts +56 -0
  217. package/lib/models/events/PresenceEvent.js +21 -0
  218. package/lib/models/events/PresenceEvent.js.map +1 -0
  219. package/lib/models/events/RedactionEvent.d.ts +28 -0
  220. package/lib/models/events/RedactionEvent.js +36 -0
  221. package/lib/models/events/RedactionEvent.js.map +1 -0
  222. package/lib/models/events/RoomAvatarEvent.d.ts +28 -0
  223. package/lib/models/events/RoomAvatarEvent.js +21 -0
  224. package/lib/models/events/RoomAvatarEvent.js.map +1 -0
  225. package/lib/models/events/RoomEvent.d.ts +58 -0
  226. package/lib/models/events/RoomEvent.js +57 -0
  227. package/lib/models/events/RoomEvent.js.map +1 -0
  228. package/lib/models/events/RoomNameEvent.d.ts +20 -0
  229. package/lib/models/events/RoomNameEvent.js +21 -0
  230. package/lib/models/events/RoomNameEvent.js.map +1 -0
  231. package/lib/models/events/RoomTopicEvent.d.ts +20 -0
  232. package/lib/models/events/RoomTopicEvent.js +21 -0
  233. package/lib/models/events/RoomTopicEvent.js.map +1 -0
  234. package/lib/models/events/SpaceChildEvent.d.ts +53 -0
  235. package/lib/models/events/SpaceChildEvent.js +49 -0
  236. package/lib/models/events/SpaceChildEvent.js.map +1 -0
  237. package/lib/models/events/converter.d.ts +8 -0
  238. package/lib/models/events/converter.js +50 -0
  239. package/lib/models/events/converter.js.map +1 -0
  240. package/lib/models/unstable/MediaInfo.d.ts +16 -0
  241. package/lib/models/unstable/MediaInfo.js +3 -0
  242. package/lib/models/unstable/MediaInfo.js.map +1 -0
  243. package/lib/preprocessors/IPreprocessor.d.ts +22 -0
  244. package/lib/preprocessors/IPreprocessor.js +3 -0
  245. package/lib/preprocessors/IPreprocessor.js.map +1 -0
  246. package/lib/preprocessors/RichRepliesPreprocessor.d.ts +67 -0
  247. package/lib/preprocessors/RichRepliesPreprocessor.js +112 -0
  248. package/lib/preprocessors/RichRepliesPreprocessor.js.map +1 -0
  249. package/lib/request.d.ts +13 -0
  250. package/lib/request.js +23 -0
  251. package/lib/request.js.map +1 -0
  252. package/lib/simple-validation.d.ts +10 -0
  253. package/lib/simple-validation.js +30 -0
  254. package/lib/simple-validation.js.map +1 -0
  255. package/lib/storage/IAppserviceStorageProvider.d.ts +48 -0
  256. package/lib/storage/IAppserviceStorageProvider.js +3 -0
  257. package/lib/storage/IAppserviceStorageProvider.js.map +1 -0
  258. package/lib/storage/ICryptoStorageProvider.d.ts +32 -0
  259. package/lib/storage/ICryptoStorageProvider.js +3 -0
  260. package/lib/storage/ICryptoStorageProvider.js.map +1 -0
  261. package/lib/storage/IStorageProvider.d.ts +47 -0
  262. package/lib/storage/IStorageProvider.js +3 -0
  263. package/lib/storage/IStorageProvider.js.map +1 -0
  264. package/lib/storage/MemoryStorageProvider.d.ts +26 -0
  265. package/lib/storage/MemoryStorageProvider.js +55 -0
  266. package/lib/storage/MemoryStorageProvider.js.map +1 -0
  267. package/lib/storage/RustSdkCryptoStorageProvider.d.ts +38 -0
  268. package/lib/storage/RustSdkCryptoStorageProvider.js +69 -0
  269. package/lib/storage/RustSdkCryptoStorageProvider.js.map +1 -0
  270. package/lib/storage/SimpleFsStorageProvider.d.ts +31 -0
  271. package/lib/storage/SimpleFsStorageProvider.js +122 -0
  272. package/lib/storage/SimpleFsStorageProvider.js.map +1 -0
  273. package/lib/storage/SimplePostgresStorageProvider.d.ts +32 -0
  274. package/lib/storage/SimplePostgresStorageProvider.js +148 -0
  275. package/lib/storage/SimplePostgresStorageProvider.js.map +1 -0
  276. package/lib/strategies/AppserviceJoinRoomStrategy.d.ts +14 -0
  277. package/lib/strategies/AppserviceJoinRoomStrategy.js +54 -0
  278. package/lib/strategies/AppserviceJoinRoomStrategy.js.map +1 -0
  279. package/lib/strategies/JoinRoomStrategy.d.ts +11 -0
  280. package/lib/strategies/JoinRoomStrategy.js +44 -0
  281. package/lib/strategies/JoinRoomStrategy.js.map +1 -0
  282. package/package.json +136 -4
  283. package/src/AdminApis.ts +60 -0
  284. package/src/DMs.ts +164 -0
  285. package/src/IFilter.ts +7 -0
  286. package/src/MatrixAuth.ts +129 -0
  287. package/src/MatrixClient.ts +2046 -0
  288. package/src/PantalaimonClient.ts +62 -0
  289. package/src/SynapseAdminApis.ts +482 -0
  290. package/src/SynchronousMatrixClient.ts +129 -0
  291. package/src/UnstableApis.ts +75 -0
  292. package/src/appservice/Appservice.ts +1089 -0
  293. package/src/appservice/Intent.ts +342 -0
  294. package/src/appservice/MatrixBridge.ts +139 -0
  295. package/src/appservice/UnstableAppserviceApis.ts +63 -0
  296. package/src/appservice/http_responses.ts +77 -0
  297. package/src/b64.ts +63 -0
  298. package/src/e2ee/CrossSigningBootstrap.ts +141 -0
  299. package/src/e2ee/CryptoClient.ts +378 -0
  300. package/src/e2ee/ICryptoRoomInformation.ts +9 -0
  301. package/src/e2ee/RoomTracker.ts +99 -0
  302. package/src/e2ee/RustEngine.ts +580 -0
  303. package/src/e2ee/decorators.ts +38 -0
  304. package/src/helpers/MatrixEntity.ts +65 -0
  305. package/src/helpers/MatrixGlob.ts +38 -0
  306. package/src/helpers/MentionPill.ts +95 -0
  307. package/src/helpers/Permalinks.ts +101 -0
  308. package/src/helpers/ProfileCache.ts +119 -0
  309. package/src/helpers/RichReply.ts +46 -0
  310. package/src/helpers/UnpaddedBase64.ts +44 -0
  311. package/src/http.ts +166 -0
  312. package/src/identity/IdentityClient.ts +225 -0
  313. package/src/index.ts +125 -0
  314. package/src/logging/ConsoleLogger.ts +31 -0
  315. package/src/logging/ILogger.ts +40 -0
  316. package/src/logging/LogService.ts +167 -0
  317. package/src/logging/RichConsoleLogger.ts +70 -0
  318. package/src/metrics/IMetricListener.ts +57 -0
  319. package/src/metrics/Metrics.ts +134 -0
  320. package/src/metrics/contexts.ts +56 -0
  321. package/src/metrics/decorators.ts +94 -0
  322. package/src/metrics/names.ts +53 -0
  323. package/src/mixins/AutojoinRoomsMixin.ts +24 -0
  324. package/src/mixins/AutojoinUpgradedRoomsMixin.ts +37 -0
  325. package/src/models/Account.ts +8 -0
  326. package/src/models/CreateRoom.ts +144 -0
  327. package/src/models/Crypto.ts +188 -0
  328. package/src/models/EventContext.ts +23 -0
  329. package/src/models/IdentityServerModels.ts +18 -0
  330. package/src/models/MSC2176.ts +47 -0
  331. package/src/models/MatrixError.ts +39 -0
  332. package/src/models/MatrixProfile.ts +55 -0
  333. package/src/models/OpenIDConnect.ts +10 -0
  334. package/src/models/Policies.ts +28 -0
  335. package/src/models/PowerLevelAction.ts +31 -0
  336. package/src/models/PowerLevelBounds.ts +16 -0
  337. package/src/models/Presence.ts +38 -0
  338. package/src/models/ServerVersions.ts +8 -0
  339. package/src/models/Spaces.ts +167 -0
  340. package/src/models/Threepid.ts +8 -0
  341. package/src/models/events/AliasesEvent.ts +37 -0
  342. package/src/models/events/CanonicalAliasEvent.ts +30 -0
  343. package/src/models/events/CreateEvent.ts +82 -0
  344. package/src/models/events/EncryptedRoomEvent.ts +58 -0
  345. package/src/models/events/EncryptionEvent.ts +63 -0
  346. package/src/models/events/Event.ts +38 -0
  347. package/src/models/events/EventKind.ts +16 -0
  348. package/src/models/events/InvalidEventError.ts +19 -0
  349. package/src/models/events/JoinRulesEvent.ts +37 -0
  350. package/src/models/events/MembershipEvent.ts +77 -0
  351. package/src/models/events/MessageEvent.ts +292 -0
  352. package/src/models/events/PinnedEventsEvent.ts +30 -0
  353. package/src/models/events/PowerLevelsEvent.ts +135 -0
  354. package/src/models/events/PresenceEvent.ts +69 -0
  355. package/src/models/events/RedactionEvent.ts +44 -0
  356. package/src/models/events/RoomAvatarEvent.ts +36 -0
  357. package/src/models/events/RoomEvent.ts +83 -0
  358. package/src/models/events/RoomNameEvent.ts +27 -0
  359. package/src/models/events/RoomTopicEvent.ts +27 -0
  360. package/src/models/events/SpaceChildEvent.ts +74 -0
  361. package/src/models/events/_MissingEvents.md +35 -0
  362. package/src/models/events/converter.ts +47 -0
  363. package/src/models/unstable/MediaInfo.ts +16 -0
  364. package/src/preprocessors/IPreprocessor.ts +24 -0
  365. package/src/preprocessors/RichRepliesPreprocessor.ts +159 -0
  366. package/src/request.ts +21 -0
  367. package/src/simple-validation.ts +30 -0
  368. package/src/storage/IAppserviceStorageProvider.ts +54 -0
  369. package/src/storage/ICryptoStorageProvider.ts +36 -0
  370. package/src/storage/IStorageProvider.ts +53 -0
  371. package/src/storage/MemoryStorageProvider.ts +65 -0
  372. package/src/storage/RustSdkCryptoStorageProvider.ts +81 -0
  373. package/src/storage/SimpleFsStorageProvider.ts +142 -0
  374. package/src/storage/SimplePostgresStorageProvider.ts +168 -0
  375. package/src/strategies/AppserviceJoinRoomStrategy.ts +47 -0
  376. package/src/strategies/JoinRoomStrategy.ts +45 -0
  377. package/tsconfig.json +22 -0
@@ -0,0 +1,1089 @@
1
+ import * as express from "express";
2
+ import { EventEmitter } from "events";
3
+ import * as morgan from "morgan";
4
+ import * as LRU from "lru-cache";
5
+ import { stringify } from "querystring";
6
+
7
+ import { Intent } from "./Intent";
8
+ import {
9
+ AppserviceJoinRoomStrategy,
10
+ EncryptedRoomEvent,
11
+ EventKind,
12
+ IAppserviceCryptoStorageProvider,
13
+ IAppserviceStorageProvider,
14
+ IJoinRoomStrategy,
15
+ IPreprocessor,
16
+ LogService,
17
+ MatrixClient,
18
+ MemoryStorageProvider,
19
+ Metrics,
20
+ MSC3983KeyClaimResponse,
21
+ MSC3984KeyQueryResponse,
22
+ OTKAlgorithm,
23
+ redactObjectForLogging,
24
+ UserID,
25
+ } from "..";
26
+ import { MatrixBridge } from "./MatrixBridge";
27
+ import { IApplicationServiceProtocol } from "./http_responses";
28
+
29
+ const EDU_ANNOTATION_KEY = "io.t2bot.sdk.bot.type";
30
+
31
+ enum EduAnnotation {
32
+ ToDevice = "to_device",
33
+ Ephemeral = "ephemeral",
34
+ }
35
+
36
+ /**
37
+ * Represents an application service's registration file. This is expected to be
38
+ * loaded from another source, such as a YAML file.
39
+ * @category Application services
40
+ */
41
+ export interface IAppserviceRegistration {
42
+ /**
43
+ * Optional ID for the appplication service. Used by homeservers to track which application
44
+ * service registers what.
45
+ */
46
+ id?: string;
47
+
48
+ /**
49
+ * Optional URL at which the application service can be contacted.
50
+ */
51
+ url?: string;
52
+
53
+ /**
54
+ * The token the application service uses to communicate with the homeserver.
55
+ */
56
+ as_token: string;
57
+
58
+ /**
59
+ * The token the homeserver uses to communicate with the application service.
60
+ */
61
+ hs_token: string;
62
+
63
+ /**
64
+ * The application service's own localpart (eg: "_irc_bot" in the user ID "@_irc_bot:domain.com")
65
+ */
66
+ sender_localpart: string;
67
+
68
+ /**
69
+ * The various namespaces the application service can support.
70
+ */
71
+ namespaces: {
72
+ /**
73
+ * The user namespaces the application service is requesting.
74
+ */
75
+ users: {
76
+ /**
77
+ * Whether or not the application service holds an exclusive lock on the namespace. This
78
+ * means that no other user on the homeserver may register users that match this namespace.
79
+ */
80
+ exclusive: boolean;
81
+
82
+ /**
83
+ * The regular expression that the homeserver uses to determine if a user is in this namespace.
84
+ */
85
+ regex: string;
86
+ }[];
87
+
88
+ /**
89
+ * The room namespaces the application service is requesting. This is not for alises.
90
+ */
91
+ rooms: {
92
+ /**
93
+ * Whether or not the application service holds an exclusive lock on the namespace.
94
+ */
95
+ exclusive: boolean;
96
+
97
+ /**
98
+ * The regular expression that the homeserver uses to determine if a user is in this namespace.
99
+ */
100
+ regex: string;
101
+ }[];
102
+
103
+ /**
104
+ * The room alias namespaces the application service is requesting.
105
+ */
106
+ aliases: {
107
+ /**
108
+ * Whether or not the application service holds an exclusive lock on the namespace. This means
109
+ * that no other user on the homeserver may register aliases that match this namespace.
110
+ */
111
+ exclusive: boolean;
112
+
113
+ /**
114
+ * The regular expression that the homeserver uses to determine if an alias is in this namespace.
115
+ */
116
+ regex: string;
117
+ }[];
118
+ };
119
+
120
+ /**
121
+ * The protocols the application service supports. Optional.
122
+ */
123
+ protocols?: string[];
124
+
125
+ /**
126
+ * If the application service is rate limited by the homeserver. Optional.
127
+ */
128
+ rate_limited?: boolean;
129
+
130
+ /**
131
+ * **Experimental**
132
+ *
133
+ * Should the application service receive ephemeral events from the homeserver. Optional.
134
+ * @see https://github.com/matrix-org/matrix-doc/pull/2409
135
+ */
136
+ "de.sorunome.msc2409.push_ephemeral"?: boolean;
137
+
138
+ // not interested in other options
139
+ }
140
+
141
+ /**
142
+ * General options for the application service
143
+ * @category Application services
144
+ */
145
+ export interface IAppserviceOptions {
146
+ /**
147
+ * The port to listen for requests from the homeserver on.
148
+ */
149
+ port: number;
150
+
151
+ /**
152
+ * The bind address to listen for requests on.
153
+ */
154
+ bindAddress: string;
155
+
156
+ /**
157
+ * The name of the homeserver, as presented over federation (eg: "matrix.org")
158
+ */
159
+ homeserverName: string;
160
+
161
+ /**
162
+ * The URL to the homeserver's client server API (eg: "https://matrix.org")
163
+ */
164
+ homeserverUrl: string;
165
+
166
+ /**
167
+ * The storage provider to use for this application service.
168
+ */
169
+ storage?: IAppserviceStorageProvider;
170
+
171
+ /**
172
+ * The storage provider to use for setting up encryption. Encryption will be
173
+ * disabled for all intents and the appservice if not configured.
174
+ */
175
+ cryptoStorage?: IAppserviceCryptoStorageProvider;
176
+
177
+ /**
178
+ * The registration for this application service.
179
+ */
180
+ registration: IAppserviceRegistration;
181
+
182
+ /**
183
+ * The join strategy to use for all intents, if any.
184
+ */
185
+ joinStrategy?: IJoinRoomStrategy;
186
+
187
+ /**
188
+ * Options for how Intents are handled.
189
+ */
190
+ intentOptions?: {
191
+ /**
192
+ * The maximum number of intents to keep cached. Defaults to 10 thousand.
193
+ */
194
+ maxCached?: number;
195
+
196
+ /**
197
+ * The maximum age in milliseconds to keep an Intent around for, provided
198
+ * the maximum number of intents has been reached. Defaults to 60 minutes.
199
+ */
200
+ maxAgeMs?: number;
201
+
202
+ /**
203
+ * If false (default), crypto will not be automatically set up for all intent
204
+ * instances - it will need to be manually enabled with
205
+ * `await intent.enableEncryption()`.
206
+ *
207
+ * If true, crypto will be automatically set up.
208
+ *
209
+ * Note that the appservice bot account is considered an intent.
210
+ */
211
+ encryption?: boolean;
212
+ };
213
+ }
214
+
215
+ /**
216
+ * Represents an application service. This provides helper utilities such as tracking
217
+ * of user intents (clients that are aware of their membership in rooms).
218
+ * @category Application services
219
+ */
220
+ export class Appservice extends EventEmitter {
221
+ /**
222
+ * The metrics instance for this appservice. This will raise all metrics
223
+ * from this appservice instance as well as any intents/MatrixClients created
224
+ * by the appservice.
225
+ */
226
+ public readonly metrics: Metrics = new Metrics();
227
+
228
+ private readonly userPrefix: string | null;
229
+ private readonly aliasPrefix: string | null;
230
+ private readonly registration: IAppserviceRegistration;
231
+ private readonly storage: IAppserviceStorageProvider;
232
+ private readonly cryptoStorage: IAppserviceCryptoStorageProvider;
233
+ private readonly bridgeInstance = new MatrixBridge(this);
234
+
235
+ private app = express();
236
+ private appServer: any;
237
+ private intentsCache: LRU.LRUCache<string, Intent>;
238
+ private eventProcessors: { [eventType: string]: IPreprocessor[] } = {};
239
+ private pendingTransactions: { [txnId: string]: Promise<any> } = {};
240
+
241
+ /**
242
+ * Creates a new application service.
243
+ * @param {IAppserviceOptions} options The options for the application service.
244
+ */
245
+ constructor(private options: IAppserviceOptions) {
246
+ super();
247
+
248
+ options.joinStrategy = new AppserviceJoinRoomStrategy(options.joinStrategy, this);
249
+
250
+ if (!options.intentOptions) options.intentOptions = {};
251
+ if (!options.intentOptions.maxAgeMs) options.intentOptions.maxAgeMs = 60 * 60 * 1000;
252
+ if (!options.intentOptions.maxCached) options.intentOptions.maxCached = 10000;
253
+
254
+ this.intentsCache = new LRU.LRUCache({
255
+ max: options.intentOptions.maxCached,
256
+ ttl: options.intentOptions.maxAgeMs,
257
+ });
258
+
259
+ this.registration = options.registration;
260
+
261
+ // If protocol is not defined, define an empty array.
262
+ if (!this.registration.protocols) {
263
+ this.registration.protocols = [];
264
+ }
265
+
266
+ this.storage = options.storage || new MemoryStorageProvider();
267
+ options.storage = this.storage;
268
+ this.cryptoStorage = options.cryptoStorage;
269
+
270
+ this.app.use(express.json({ limit: Number.MAX_SAFE_INTEGER })); // disable limits, use a reverse proxy
271
+ morgan.token('url-safe', (req: express.Request) =>
272
+ `${req.path}?${stringify(redactObjectForLogging(req.query ?? {}))}`,
273
+ );
274
+
275
+ this.app.use(morgan(
276
+ // Same as "combined", but with sensitive values removed from requests.
277
+ ':remote-addr - :remote-user [:date[clf]] ":method :url-safe HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"',
278
+ { stream: { write: LogService.info.bind(LogService, 'Appservice') } },
279
+ ));
280
+
281
+ // ETag headers break the tests sometimes, and we don't actually need them anyways for
282
+ // appservices - none of this should be cached.
283
+ this.app.set('etag', false);
284
+
285
+ this.app.get("/users/:userId", this.onUser.bind(this));
286
+ this.app.get("/rooms/:roomAlias", this.onRoomAlias.bind(this));
287
+ this.app.put("/transactions/:txnId", this.onTransaction.bind(this));
288
+ this.app.get("/_matrix/app/v1/users/:userId", this.onUser.bind(this));
289
+ this.app.get("/_matrix/app/v1/rooms/:roomAlias", this.onRoomAlias.bind(this));
290
+ this.app.put("/_matrix/app/v1/transactions/:txnId", this.onTransaction.bind(this));
291
+ this.app.get("/_matrix/app/v1/thirdparty/protocol/:protocol", this.onThirdpartyProtocol.bind(this));
292
+ this.app.get("/_matrix/app/v1/thirdparty/user/:protocol", this.onThirdpartyUser.bind(this));
293
+ this.app.get("/_matrix/app/v1/thirdparty/user", this.onThirdpartyUser.bind(this));
294
+ this.app.get("/_matrix/app/v1/thirdparty/location/:protocol", this.onThirdpartyLocation.bind(this));
295
+ this.app.get("/_matrix/app/v1/thirdparty/location", this.onThirdpartyLocation.bind(this));
296
+ this.app.post("/_matrix/app/unstable/org.matrix.msc3983/keys/claim", this.onKeysClaim.bind(this));
297
+ this.app.post("/_matrix/app/unstable/org.matrix.msc3984/keys/query", this.onKeysQuery.bind(this));
298
+
299
+ // Workaround for https://github.com/matrix-org/synapse/issues/3780
300
+ this.app.post("/_matrix/app/v1/unstable/org.matrix.msc3983/keys/claim", this.onKeysClaim.bind(this));
301
+ this.app.post("/unstable/org.matrix.msc3983/keys/claim", this.onKeysClaim.bind(this));
302
+ this.app.post("/_matrix/app/v1/unstable/org.matrix.msc3984/keys/query", this.onKeysQuery.bind(this));
303
+ this.app.post("/unstable/org.matrix.msc3984/keys/query", this.onKeysQuery.bind(this));
304
+
305
+ // We register the 404 handler in the `begin()` function to allow consumers to add their own endpoints.
306
+
307
+ if (!this.registration.namespaces || !this.registration.namespaces.users || this.registration.namespaces.users.length === 0) {
308
+ throw new Error("No user namespaces in registration");
309
+ }
310
+ if (this.registration.namespaces.users.length !== 1) {
311
+ throw new Error("Too many user namespaces registered: expecting exactly one");
312
+ }
313
+
314
+ const userPrefix = (this.registration.namespaces.users[0].regex || "").split(":")[0];
315
+ if (!userPrefix.endsWith(".*") && !userPrefix.endsWith(".+")) {
316
+ this.userPrefix = null;
317
+ } else {
318
+ this.userPrefix = userPrefix.substring(0, userPrefix.length - 2); // trim off the .* part
319
+ }
320
+
321
+ if (!this.registration.namespaces?.aliases || this.registration.namespaces.aliases.length !== 1) {
322
+ this.aliasPrefix = null;
323
+ } else {
324
+ this.aliasPrefix = (this.registration.namespaces.aliases[0].regex || "").split(":")[0];
325
+ if (!this.aliasPrefix.endsWith(".*") && !this.aliasPrefix.endsWith(".+")) {
326
+ this.aliasPrefix = null;
327
+ } else {
328
+ this.aliasPrefix = this.aliasPrefix.substring(0, this.aliasPrefix.length - 2); // trim off the .* part
329
+ }
330
+ }
331
+ }
332
+
333
+ /**
334
+ * Gets the express app instance which is serving requests. Not recommended for
335
+ * general usage, but may be used to append routes to the web server.
336
+ */
337
+ public get expressAppInstance() {
338
+ return this.app;
339
+ }
340
+
341
+ /**
342
+ * Gets the bridge-specific APIs for this application service.
343
+ */
344
+ public get bridge(): MatrixBridge {
345
+ return this.bridgeInstance;
346
+ }
347
+
348
+ /**
349
+ * Get the application service's "bot" user ID (the sender_localpart).
350
+ */
351
+ public get botUserId(): string {
352
+ return this.getUserId(this.registration.sender_localpart);
353
+ }
354
+
355
+ /**
356
+ * Get the application service's "bot" Intent (the sender_localpart).
357
+ * @returns {Intent} The intent for the application service itself.
358
+ */
359
+ public get botIntent(): Intent {
360
+ return this.getIntentForUserId(this.botUserId);
361
+ }
362
+
363
+ /**
364
+ * Get the application service's "bot" MatrixClient (the sender_localpart).
365
+ * Normally the botIntent should be used to ensure that the bot user is safely
366
+ * handled.
367
+ * @returns {MatrixClient} The client for the application service itself.
368
+ */
369
+ public get botClient(): MatrixClient {
370
+ return this.botIntent.underlyingClient;
371
+ }
372
+
373
+ /**
374
+ * Starts the application service, opening the bind address to begin processing requests.
375
+ * @returns {Promise<void>} resolves when started
376
+ */
377
+ public begin(): Promise<void> {
378
+ return new Promise<void>((resolve, reject) => {
379
+ // Per constructor, all other endpoints should 404.
380
+ // Technically, according to https://spec.matrix.org/v1.6/application-service-api/#unknown-routes we should
381
+ // be returning 405 for *known* endpoints with the wrong method.
382
+ this.app.all("*", (req: express.Request, res: express.Response) => {
383
+ res.status(404).json({ errcode: "M_UNRECOGNIZED", error: "Endpoint not implemented" });
384
+ });
385
+
386
+ this.appServer = this.app.listen(this.options.port, this.options.bindAddress, () => resolve());
387
+ }).then(async () => {
388
+ if (this.options.intentOptions?.encryption) {
389
+ await this.botIntent.enableEncryption();
390
+ } else {
391
+ await this.botIntent.ensureRegistered();
392
+ }
393
+ });
394
+ }
395
+
396
+ /**
397
+ * Stops the application service, freeing the web server.
398
+ */
399
+ public stop(): void {
400
+ if (!this.appServer) return;
401
+ this.appServer.close();
402
+ }
403
+
404
+ /**
405
+ * Gets an intent for a given localpart. The user ID will be formed with the domain name given
406
+ * in the constructor.
407
+ * @param localpart The localpart to get an Intent for.
408
+ * @returns {Intent} An Intent for the user.
409
+ */
410
+ public getIntent(localpart: string): Intent {
411
+ return this.getIntentForUserId(this.getUserId(localpart));
412
+ }
413
+
414
+ /**
415
+ * Gets a full user ID for a given localpart. The user ID will be formed with the domain name given
416
+ * in the constructor.
417
+ * @param localpart The localpart to get a user ID for.
418
+ * @returns {string} The user's ID.
419
+ */
420
+ public getUserId(localpart: string): string {
421
+ return `@${localpart}:${this.options.homeserverName}`;
422
+ }
423
+
424
+ /**
425
+ * Gets an Intent for a given user suffix. The prefix is automatically detected from the registration
426
+ * options.
427
+ * @param suffix The user's suffix
428
+ * @returns {Intent} An Intent for the user.
429
+ */
430
+ public getIntentForSuffix(suffix: string): Intent {
431
+ return this.getIntentForUserId(this.getUserIdForSuffix(suffix));
432
+ }
433
+
434
+ /**
435
+ * Gets a full user ID for a given suffix. The prefix is automatically detected from the registration
436
+ * options.
437
+ * @param suffix The user's suffix
438
+ * @returns {string} The user's ID.
439
+ */
440
+ public getUserIdForSuffix(suffix: string): string {
441
+ if (!this.userPrefix) {
442
+ throw new Error(`Cannot use getUserIdForSuffix, provided namespace did not include a valid suffix`);
443
+ }
444
+ return `${this.userPrefix}${suffix}:${this.options.homeserverName}`;
445
+ }
446
+
447
+ /**
448
+ * Gets an Intent for a given user ID.
449
+ * @param {string} userId The user ID to get an Intent for.
450
+ * @returns {Intent} An Intent for the user.
451
+ */
452
+ public getIntentForUserId(userId: string): Intent {
453
+ let intent: Intent = this.intentsCache.get(userId);
454
+ if (!intent) {
455
+ intent = new Intent(this.options, userId, this);
456
+ this.intentsCache.set(userId, intent);
457
+ if (this.options.intentOptions.encryption) {
458
+ intent.enableEncryption().catch(e => {
459
+ LogService.error("Appservice", `Failed to set up crypto on intent ${userId}`, e);
460
+ throw e; // re-throw to cause unhandled exception
461
+ });
462
+ }
463
+ }
464
+ return intent;
465
+ }
466
+
467
+ /**
468
+ * Gets the suffix for the provided user ID. If the user ID is not a namespaced
469
+ * user, this will return a falsey value.
470
+ * @param {string} userId The user ID to parse
471
+ * @returns {string} The suffix from the user ID.
472
+ */
473
+ public getSuffixForUserId(userId: string): string {
474
+ if (!this.userPrefix) {
475
+ throw new Error(`Cannot use getUserIdForSuffix, provided namespace did not include a valid suffix`);
476
+ }
477
+ if (!userId || !userId.startsWith(this.userPrefix) || !userId.endsWith(`:${this.options.homeserverName}`)) {
478
+ // Invalid ID
479
+ return null;
480
+ }
481
+
482
+ return userId
483
+ .split('')
484
+ .slice(this.userPrefix.length)
485
+ .reverse()
486
+ .slice(this.options.homeserverName.length + 1)
487
+ .reverse()
488
+ .join('');
489
+ }
490
+
491
+ /**
492
+ * Determines if a given user ID is namespaced by this application service.
493
+ * @param {string} userId The user ID to check
494
+ * @returns {boolean} true if the user is namespaced, false otherwise
495
+ */
496
+ public isNamespacedUser(userId: string): boolean {
497
+ return userId === this.botUserId ||
498
+ !!this.registration.namespaces?.users.find(({ regex }) =>
499
+ new RegExp(regex).test(userId),
500
+ );
501
+ }
502
+
503
+ /**
504
+ * Gets a full alias for a given localpart. The alias will be formed with the domain name given
505
+ * in the constructor.
506
+ * @param localpart The localpart to get an alias for.
507
+ * @returns {string} The alias.
508
+ */
509
+ public getAlias(localpart: string): string {
510
+ return `#${localpart}:${this.options.homeserverName}`;
511
+ }
512
+
513
+ /**
514
+ * Gets a full alias for a given suffix. The prefix is automatically detected from the registration
515
+ * options.
516
+ * @param suffix The alias's suffix
517
+ * @returns {string} The alias.
518
+ */
519
+ public getAliasForSuffix(suffix: string): string {
520
+ if (!this.aliasPrefix) {
521
+ throw new Error("Invalid configured alias prefix");
522
+ }
523
+ return `${this.aliasPrefix}${suffix}:${this.options.homeserverName}`;
524
+ }
525
+
526
+ /**
527
+ * Gets the localpart of an alias for a given suffix. The prefix is automatically detected from the registration
528
+ * options. Useful for the createRoom endpoint.
529
+ * @param suffix The alias's suffix
530
+ * @returns {string} The alias localpart.
531
+ */
532
+ public getAliasLocalpartForSuffix(suffix: string): string {
533
+ if (!this.aliasPrefix) {
534
+ throw new Error("Invalid configured alias prefix");
535
+ }
536
+ return `${this.aliasPrefix.substr(1)}${suffix}`;
537
+ }
538
+
539
+ /**
540
+ * Gets the suffix for the provided alias. If the alias is not a namespaced
541
+ * alias, this will return a falsey value.
542
+ * @param {string} alias The alias to parse
543
+ * @returns {string} The suffix from the alias.
544
+ */
545
+ public getSuffixForAlias(alias: string): string {
546
+ if (!this.aliasPrefix) {
547
+ throw new Error("Invalid configured alias prefix");
548
+ }
549
+ if (!alias || !this.isNamespacedAlias(alias)) {
550
+ // Invalid ID
551
+ return null;
552
+ }
553
+
554
+ return alias
555
+ .split('')
556
+ .slice(this.aliasPrefix.length)
557
+ .reverse()
558
+ .slice(this.options.homeserverName.length + 1)
559
+ .reverse()
560
+ .join('');
561
+ }
562
+
563
+ /**
564
+ * Determines if a given alias is namespaced by this application service.
565
+ * @param {string} alias The alias to check
566
+ * @returns {boolean} true if the alias is namespaced, false otherwise
567
+ */
568
+ public isNamespacedAlias(alias: string): boolean {
569
+ if (!this.aliasPrefix) {
570
+ throw new Error("Invalid configured alias prefix");
571
+ }
572
+ return alias.startsWith(this.aliasPrefix) && alias.endsWith(":" + this.options.homeserverName);
573
+ }
574
+
575
+ /**
576
+ * Adds a preprocessor to the event pipeline. When this appservice encounters an event, it
577
+ * will try to run it through the preprocessors it can in the order they were added.
578
+ * @param {IPreprocessor} preprocessor the preprocessor to add
579
+ */
580
+ public addPreprocessor(preprocessor: IPreprocessor): void {
581
+ if (!preprocessor) throw new Error("Preprocessor cannot be null");
582
+
583
+ const eventTypes = preprocessor.getSupportedEventTypes();
584
+ if (!eventTypes) return; // Nothing to do
585
+
586
+ for (const eventType of eventTypes) {
587
+ if (!this.eventProcessors[eventType]) this.eventProcessors[eventType] = [];
588
+ this.eventProcessors[eventType].push(preprocessor);
589
+ }
590
+ }
591
+
592
+ /**
593
+ * Sets the visibility of a room in the appservice's room directory.
594
+ * @param {string} networkId The network ID to group the room under.
595
+ * @param {string} roomId The room ID to manipulate the visibility of.
596
+ * @param {"public" | "private"} visibility The visibility to set for the room.
597
+ * @return {Promise<any>} resolves when the visibility has been updated.
598
+ */
599
+ public setRoomDirectoryVisibility(networkId: string, roomId: string, visibility: "public" | "private") {
600
+ roomId = encodeURIComponent(roomId);
601
+ networkId = encodeURIComponent(networkId);
602
+ return this.botClient.doRequest("PUT", `/_matrix/client/v3/directory/list/appservice/${networkId}/${roomId}`, null, {
603
+ visibility,
604
+ });
605
+ }
606
+
607
+ private async processEphemeralEvent(event: any): Promise<any> {
608
+ if (!event) return event;
609
+ if (!this.eventProcessors[event["type"]]) return event;
610
+
611
+ for (const processor of this.eventProcessors[event["type"]]) {
612
+ await processor.processEvent(event, this.botIntent.underlyingClient, EventKind.EphemeralEvent);
613
+ }
614
+
615
+ return event;
616
+ }
617
+
618
+ private async processEvent(event: any): Promise<any> {
619
+ if (!event) return event;
620
+ if (!this.eventProcessors[event["type"]]) return event;
621
+
622
+ for (const processor of this.eventProcessors[event["type"]]) {
623
+ await processor.processEvent(event, this.botIntent.underlyingClient, EventKind.RoomEvent);
624
+ }
625
+
626
+ return event;
627
+ }
628
+
629
+ private async processMembershipEvent(event: any): Promise<void> {
630
+ if (!event["content"]) return;
631
+
632
+ const domain = new UserID(event['state_key']).domain;
633
+ const botDomain = new UserID(this.botUserId).domain;
634
+ if (domain !== botDomain) return; // can't be impersonated, so don't try
635
+
636
+ // Update the target intent's joined rooms (fixes transition errors with the cache, like join->kick->join)
637
+ const intent = this.getIntentForUserId(event['state_key']);
638
+ await intent.refreshJoinedRooms();
639
+
640
+ const targetMembership = event["content"]["membership"];
641
+ if (targetMembership === "join") {
642
+ this.emit("room.join", event["room_id"], event);
643
+ await intent.underlyingClient.crypto?.onRoomJoin(event["room_id"]);
644
+ } else if (targetMembership === "ban" || targetMembership === "leave") {
645
+ this.emit("room.leave", event["room_id"], event);
646
+ } else if (targetMembership === "invite") {
647
+ this.emit("room.invite", event["room_id"], event);
648
+ }
649
+ }
650
+
651
+ private isAuthed(req: any): boolean {
652
+ let providedToken = req.query ? req.query["access_token"] : null;
653
+ if (req.headers && req.headers["authorization"]) {
654
+ const authHeader = req.headers["authorization"];
655
+ if (!authHeader.startsWith("Bearer ")) providedToken = null;
656
+ else providedToken = authHeader.substring("Bearer ".length);
657
+ }
658
+
659
+ return providedToken === this.registration.hs_token;
660
+ }
661
+
662
+ private async onTransaction(req: express.Request, res: express.Response): Promise<any> {
663
+ if (!this.isAuthed(req)) {
664
+ res.status(401).json({ errcode: "AUTH_FAILED", error: "Authentication failed" });
665
+ return;
666
+ }
667
+
668
+ if (typeof (req.body) !== "object") {
669
+ res.status(400).json({ errcode: "BAD_REQUEST", error: "Expected JSON" });
670
+ return;
671
+ }
672
+
673
+ if (!req.body["events"] || !Array.isArray(req.body["events"])) {
674
+ res.status(400).json({ errcode: "BAD_REQUEST", error: "Invalid JSON: expected events" });
675
+ return;
676
+ }
677
+
678
+ const txnId = req.params["txnId"];
679
+
680
+ if (await Promise.resolve(this.storage.isTransactionCompleted(txnId))) {
681
+ res.status(200).json({});
682
+ return;
683
+ }
684
+
685
+ if (this.pendingTransactions[txnId]) {
686
+ try {
687
+ await this.pendingTransactions[txnId];
688
+ res.status(200).json({});
689
+ } catch (e) {
690
+ LogService.error("Appservice", e);
691
+ res.status(500).json({});
692
+ }
693
+ return;
694
+ }
695
+
696
+ LogService.info("Appservice", "Processing transaction " + txnId);
697
+ // eslint-disable-next-line no-async-promise-executor
698
+ this.pendingTransactions[txnId] = new Promise<void>(async (resolve) => {
699
+ // Process all the crypto stuff first to ensure that future transactions (if not this one)
700
+ // will decrypt successfully. We start with EDUs because we need structures to put counts
701
+ // and such into in a later stage, and EDUs are independent of crypto.
702
+
703
+ const byUserId: {
704
+ [userId: string]: {
705
+ counts: Record<string, Number>;
706
+ toDevice: any[];
707
+ unusedFallbacks: OTKAlgorithm[];
708
+ };
709
+ } = {};
710
+
711
+ const orderedEdus = [];
712
+ if (Array.isArray(req.body["de.sorunome.msc2409.to_device"])) {
713
+ orderedEdus.push(...req.body["de.sorunome.msc2409.to_device"].map(e => ({
714
+ ...e,
715
+ unsigned: {
716
+ ...e['unsigned'],
717
+ [EDU_ANNOTATION_KEY]: EduAnnotation.ToDevice,
718
+ },
719
+ })));
720
+ }
721
+ if (Array.isArray(req.body["de.sorunome.msc2409.ephemeral"])) {
722
+ orderedEdus.push(...req.body["de.sorunome.msc2409.ephemeral"].map(e => ({
723
+ ...e,
724
+ unsigned: {
725
+ ...e['unsigned'],
726
+ [EDU_ANNOTATION_KEY]: EduAnnotation.Ephemeral,
727
+ },
728
+ })));
729
+ }
730
+ for (let event of orderedEdus) {
731
+ if (event['edu_type']) event['type'] = event['edu_type']; // handle property change during MSC2409's course
732
+
733
+ LogService.info("Appservice", `Processing ${event['unsigned'][EDU_ANNOTATION_KEY]} event of type ${event["type"]}`);
734
+ event = await this.processEphemeralEvent(event);
735
+
736
+ // These events aren't tied to rooms, so just emit them generically
737
+ this.emit("ephemeral.event", event);
738
+
739
+ if (this.cryptoStorage && (event["type"] === "m.room.encrypted" || event.unsigned?.[EDU_ANNOTATION_KEY] === EduAnnotation.ToDevice)) {
740
+ const toUser = event["to_user_id"];
741
+ const intent = this.getIntentForUserId(toUser);
742
+ await intent.enableEncryption();
743
+
744
+ if (!byUserId[toUser]) byUserId[toUser] = { counts: null, toDevice: null, unusedFallbacks: null };
745
+ if (!byUserId[toUser].toDevice) byUserId[toUser].toDevice = [];
746
+ byUserId[toUser].toDevice.push(event);
747
+ }
748
+ }
749
+
750
+ const deviceLists: { changed: string[], removed: string[] } = req.body["org.matrix.msc3202.device_lists"] ?? {
751
+ changed: [],
752
+ removed: [],
753
+ };
754
+
755
+ if (!deviceLists.changed) deviceLists.changed = [];
756
+ if (!deviceLists.removed) deviceLists.removed = [];
757
+
758
+ if (deviceLists.changed.length || deviceLists.removed.length) {
759
+ this.emit("device_lists", deviceLists);
760
+ }
761
+
762
+ let otks = req.body["org.matrix.msc3202.device_one_time_keys_count"];
763
+ const otks2 = req.body["org.matrix.msc3202.device_one_time_key_counts"];
764
+ if (otks2 && !otks) {
765
+ LogService.warn(
766
+ "Appservice",
767
+ "Your homeserver is using an outdated field (device_one_time_key_counts) to talk to this appservice. " +
768
+ "If you're using Synapse, please upgrade to 1.73.0 or higher.",
769
+ );
770
+ otks = otks2;
771
+ }
772
+ if (otks) {
773
+ this.emit("otk.counts", otks);
774
+ }
775
+ if (otks && this.cryptoStorage) {
776
+ for (const userId of Object.keys(otks)) {
777
+ const intent = this.getIntentForUserId(userId);
778
+ await intent.enableEncryption();
779
+ const otksForUser = otks[userId][intent.underlyingClient.crypto.clientDeviceId];
780
+ if (otksForUser) {
781
+ if (!byUserId[userId]) {
782
+ byUserId[userId] = {
783
+ counts: null,
784
+ toDevice: null,
785
+ unusedFallbacks: null,
786
+ };
787
+ }
788
+ byUserId[userId].counts = otksForUser;
789
+ }
790
+ }
791
+ }
792
+
793
+ const fallbacks = req.body["org.matrix.msc3202.device_unused_fallback_key_types"];
794
+ if (fallbacks) {
795
+ this.emit("otk.unused_fallback_keys", fallbacks);
796
+ }
797
+ if (fallbacks && this.cryptoStorage) {
798
+ for (const userId of Object.keys(fallbacks)) {
799
+ const intent = this.getIntentForUserId(userId);
800
+ await intent.enableEncryption();
801
+ const fallbacksForUser = fallbacks[userId][intent.underlyingClient.crypto.clientDeviceId];
802
+ if (Array.isArray(fallbacksForUser) && !fallbacksForUser.includes(OTKAlgorithm.Signed)) {
803
+ if (!byUserId[userId]) {
804
+ byUserId[userId] = {
805
+ counts: null,
806
+ toDevice: null,
807
+ unusedFallbacks: null,
808
+ };
809
+ }
810
+ byUserId[userId].unusedFallbacks = fallbacksForUser;
811
+ }
812
+ }
813
+ }
814
+
815
+ if (this.cryptoStorage) {
816
+ for (const userId of Object.keys(byUserId)) {
817
+ const intent = this.getIntentForUserId(userId);
818
+ await intent.enableEncryption();
819
+ const info = byUserId[userId];
820
+ const userStorage = this.storage.storageForUser(userId);
821
+
822
+ if (!info.toDevice) info.toDevice = [];
823
+ if (!info.unusedFallbacks) info.unusedFallbacks = JSON.parse(await userStorage.readValue("last_unused_fallbacks") || "[]");
824
+ if (!info.counts) info.counts = JSON.parse(await userStorage.readValue("last_counts") || "{}");
825
+
826
+ LogService.info("Appservice", `Updating crypto state for ${userId}`);
827
+ await intent.underlyingClient.crypto.updateSyncData(info.toDevice, info.counts, info.unusedFallbacks, deviceLists.changed, deviceLists.removed);
828
+ }
829
+ }
830
+
831
+ for (let event of req.body["events"]) {
832
+ LogService.info("Appservice", `Processing event of type ${event["type"]}`);
833
+ event = await this.processEvent(event);
834
+ if (event['type'] === 'm.room.encrypted') {
835
+ this.emit("room.encrypted_event", event["room_id"], event);
836
+ if (this.cryptoStorage) {
837
+ try {
838
+ const encrypted = new EncryptedRoomEvent(event);
839
+ const roomId = event['room_id'];
840
+ try {
841
+ event = (await this.botClient.crypto.decryptRoomEvent(encrypted, roomId)).raw;
842
+ event = await this.processEvent(event);
843
+ this.emit("room.decrypted_event", roomId, event);
844
+
845
+ // For logging purposes: show that the event was decrypted
846
+ LogService.info("Appservice", `Processing decrypted event of type ${event["type"]}`);
847
+ } catch (e1) {
848
+ LogService.warn("Appservice", `Bot client was not able to decrypt ${roomId} ${event['event_id']} - trying other intents`);
849
+
850
+ let tryUserId: string;
851
+ try {
852
+ // TODO: This could be more efficient
853
+ const userIdsInRoom = await this.botClient.getJoinedRoomMembers(roomId);
854
+ tryUserId = userIdsInRoom.find(u => this.isNamespacedUser(u));
855
+ } catch (e) {
856
+ LogService.error("Appservice", "Failed to get members of room - cannot decrypt message");
857
+ }
858
+
859
+ if (tryUserId) {
860
+ const intent = this.getIntentForUserId(tryUserId);
861
+
862
+ event = (await intent.underlyingClient.crypto.decryptRoomEvent(encrypted, roomId)).raw;
863
+ event = await this.processEvent(event);
864
+ this.emit("room.decrypted_event", roomId, event);
865
+
866
+ // For logging purposes: show that the event was decrypted
867
+ LogService.info("Appservice", `Processing decrypted event of type ${event["type"]}`);
868
+ } else {
869
+ // noinspection ExceptionCaughtLocallyJS
870
+ throw e1;
871
+ }
872
+ }
873
+ } catch (e) {
874
+ LogService.error("Appservice", `Decryption error on ${event['room_id']} ${event['event_id']}`, e);
875
+ this.emit("room.failed_decryption", event['room_id'], event, e);
876
+ }
877
+ }
878
+ }
879
+ this.emit("room.event", event["room_id"], event);
880
+ if (event['type'] === 'm.room.message') {
881
+ this.emit("room.message", event["room_id"], event);
882
+ }
883
+ if (event['type'] === 'm.room.member' && this.isNamespacedUser(event['state_key'])) {
884
+ await this.processMembershipEvent(event);
885
+ }
886
+ if (event['type'] === 'm.room.tombstone' && event['state_key'] === '') {
887
+ this.emit("room.archived", event['room_id'], event);
888
+ }
889
+ if (event['type'] === 'm.room.create' && event['state_key'] === '' && event['content'] && event['content']['predecessor']) {
890
+ this.emit("room.upgraded", event['room_id'], event);
891
+ }
892
+ }
893
+
894
+ resolve();
895
+ });
896
+
897
+ try {
898
+ await this.pendingTransactions[txnId];
899
+ await Promise.resolve(this.storage.setTransactionCompleted(txnId));
900
+ res.status(200).json({});
901
+ } catch (e) {
902
+ LogService.error("Appservice", e);
903
+ res.status(500).json({});
904
+ }
905
+ }
906
+
907
+ private async onUser(req: express.Request, res: express.Response): Promise<any> {
908
+ if (!this.isAuthed(req)) {
909
+ res.status(401).json({ errcode: "AUTH_FAILED", error: "Authentication failed" });
910
+ return;
911
+ }
912
+
913
+ const userId = req.params["userId"];
914
+ this.emit("query.user", userId, async (result) => {
915
+ if (result.then) result = await result;
916
+ if (result === false) {
917
+ res.status(404).json({ errcode: "USER_DOES_NOT_EXIST", error: "User not created" });
918
+ } else {
919
+ const intent = this.getIntentForUserId(userId);
920
+ await intent.ensureRegistered();
921
+ if (result.display_name) await intent.underlyingClient.setDisplayName(result.display_name);
922
+ if (result.avatar_mxc) await intent.underlyingClient.setAvatarUrl(result.avatar_mxc);
923
+ res.status(200).json(result); // return result for debugging + testing
924
+ }
925
+ });
926
+ }
927
+
928
+ private async onRoomAlias(req: express.Request, res: express.Response): Promise<any> {
929
+ if (!this.isAuthed(req)) {
930
+ res.status(401).json({ errcode: "AUTH_FAILED", error: "Authentication failed" });
931
+ return;
932
+ }
933
+
934
+ const roomAlias = req.params["roomAlias"];
935
+ this.emit("query.room", roomAlias, async (result) => {
936
+ if (result.then) result = await result;
937
+ if (result === false) {
938
+ res.status(404).json({ errcode: "ROOM_DOES_NOT_EXIST", error: "Room not created" });
939
+ } else {
940
+ const intent = this.botIntent;
941
+ await intent.ensureRegistered();
942
+
943
+ result["room_alias_name"] = roomAlias.substring(1).split(':')[0];
944
+ result["__roomId"] = await intent.underlyingClient.createRoom(result);
945
+
946
+ res.status(200).json(result); // return result for debugging + testing
947
+ }
948
+ });
949
+ }
950
+
951
+ private async onKeysClaim(req: express.Request, res: express.Response): Promise<any> {
952
+ if (!this.isAuthed(req)) {
953
+ res.status(401).json({ errcode: "AUTH_FAILED", error: "Authentication failed" });
954
+ return;
955
+ }
956
+
957
+ if (typeof (req.body) !== "object") {
958
+ res.status(400).json({ errcode: "BAD_REQUEST", error: "Expected JSON" });
959
+ return;
960
+ }
961
+
962
+ let responded = false;
963
+ this.emit("query.key_claim", req.body, (result: MSC3983KeyClaimResponse | Promise<MSC3983KeyClaimResponse> | undefined | null) => {
964
+ responded = true;
965
+
966
+ const handleResult = (result2: MSC3983KeyClaimResponse) => {
967
+ if (!result2) {
968
+ res.status(404).json({ errcode: "M_UNRECOGNIZED", error: "Endpoint not implemented" });
969
+ return;
970
+ }
971
+
972
+ res.status(200).json(result2);
973
+ };
974
+
975
+ Promise.resolve(result).then(r => handleResult(r)).catch(e => {
976
+ LogService.error("Appservice", "Error handling key claim API", e);
977
+ res.status(500).json({ errcode: "M_UNKNOWN", error: "Error handling key claim API" });
978
+ });
979
+ });
980
+ if (!responded) {
981
+ res.status(404).json({ errcode: "M_UNRECOGNIZED", error: "Endpoint not implemented" });
982
+ }
983
+ }
984
+
985
+ private async onKeysQuery(req: express.Request, res: express.Response): Promise<any> {
986
+ if (!this.isAuthed(req)) {
987
+ res.status(401).json({ errcode: "AUTH_FAILED", error: "Authentication failed" });
988
+ return;
989
+ }
990
+
991
+ if (typeof (req.body) !== "object") {
992
+ res.status(400).json({ errcode: "BAD_REQUEST", error: "Expected JSON" });
993
+ return;
994
+ }
995
+
996
+ let responded = false;
997
+ this.emit("query.key", req.body, (result: MSC3984KeyQueryResponse | Promise<MSC3984KeyQueryResponse> | undefined | null) => {
998
+ responded = true;
999
+
1000
+ const handleResult = (result2: MSC3984KeyQueryResponse) => {
1001
+ if (!result2) {
1002
+ res.status(404).json({ errcode: "M_UNRECOGNIZED", error: "Endpoint not implemented" });
1003
+ return;
1004
+ }
1005
+
1006
+ // Implementation note: we could probably query the device keys from our storage if we wanted to.
1007
+
1008
+ res.status(200).json(result2);
1009
+ };
1010
+
1011
+ Promise.resolve(result).then(r => handleResult(r)).catch(e => {
1012
+ LogService.error("Appservice", "Error handling key query API", e);
1013
+ res.status(500).json({ errcode: "M_UNKNOWN", error: "Error handling key query API" });
1014
+ });
1015
+ });
1016
+ if (!responded) {
1017
+ res.status(404).json({ errcode: "M_UNRECOGNIZED", error: "Endpoint not implemented" });
1018
+ }
1019
+ }
1020
+
1021
+ private onThirdpartyProtocol(req: express.Request, res: express.Response) {
1022
+ if (!this.isAuthed(req)) {
1023
+ res.status(401).json({ errcode: "AUTH_FAILED", error: "Authentication failed" });
1024
+ return;
1025
+ }
1026
+
1027
+ const protocol = req.params["protocol"];
1028
+ if (!this.registration.protocols.includes(protocol)) {
1029
+ res.status(404).json({
1030
+ errcode: "PROTOCOL_NOT_HANDLED",
1031
+ error: "Protocol is not handled by this appservice",
1032
+ });
1033
+ return;
1034
+ }
1035
+ this.emit("thirdparty.protocol", protocol, (protocolResponse: IApplicationServiceProtocol) => {
1036
+ res.status(200).json(protocolResponse);
1037
+ });
1038
+ }
1039
+
1040
+ private handleThirdpartyObject(req: express.Request, res: express.Response, objType: string, matrixId?: string) {
1041
+ if (!this.isAuthed(req)) {
1042
+ res.status(401).json({ errcode: "AUTH_FAILED", error: "Authentication failed" });
1043
+ return;
1044
+ }
1045
+
1046
+ const protocol = req.params["protocol"];
1047
+ const responseFunc = (items: any[]) => {
1048
+ if (items && items.length > 0) {
1049
+ res.status(200).json(items);
1050
+ return;
1051
+ }
1052
+ res.status(404).json({
1053
+ errcode: "NO_MAPPING_FOUND",
1054
+ error: "No mappings found",
1055
+ });
1056
+ };
1057
+
1058
+ // Lookup remote objects(s)
1059
+ if (protocol) { // If protocol is given, we are looking up a objects based on fields
1060
+ if (!this.registration.protocols.includes(protocol)) {
1061
+ res.status(404).json({
1062
+ errcode: "PROTOCOL_NOT_HANDLED",
1063
+ error: "Protocol is not handled by this appservice",
1064
+ });
1065
+ return;
1066
+ }
1067
+ // Remove the access_token
1068
+ delete req.query.access_token;
1069
+ this.emit(`thirdparty.${objType}.remote`, protocol, req.query, responseFunc);
1070
+ return;
1071
+ } else if (matrixId) { // If a user ID is given, we are looking up a remote objects based on a id
1072
+ this.emit(`thirdparty.${objType}.matrix`, matrixId, responseFunc);
1073
+ return;
1074
+ }
1075
+
1076
+ res.status(400).json({
1077
+ errcode: "INVALID_PARAMETERS",
1078
+ error: "Invalid parameters given",
1079
+ });
1080
+ }
1081
+
1082
+ private onThirdpartyUser(req: express.Request, res: express.Response) {
1083
+ return this.handleThirdpartyObject(req, res, "user", req.query["userid"] as string);
1084
+ }
1085
+
1086
+ private onThirdpartyLocation(req: express.Request, res: express.Response) {
1087
+ return this.handleThirdpartyObject(req, res, "location", req.query["alias"] as string);
1088
+ }
1089
+ }