@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,2046 @@
1
+ import { EventEmitter } from "events";
2
+ import { htmlEncode } from "htmlencode";
3
+ import { htmlToText } from "html-to-text";
4
+
5
+ import { IStorageProvider } from "./storage/IStorageProvider";
6
+ import { MemoryStorageProvider } from "./storage/MemoryStorageProvider";
7
+ import { IJoinRoomStrategy } from "./strategies/JoinRoomStrategy";
8
+ import { UnstableApis } from "./UnstableApis";
9
+ import { IPreprocessor } from "./preprocessors/IPreprocessor";
10
+ import { getRequestFn } from "./request";
11
+ import { extractRequestError, LogService } from "./logging/LogService";
12
+ import { RichReply } from "./helpers/RichReply";
13
+ import { Metrics } from "./metrics/Metrics";
14
+ import { timedMatrixClientFunctionCall } from "./metrics/decorators";
15
+ import { AdminApis } from "./AdminApis";
16
+ import { Presence } from "./models/Presence";
17
+ import { Membership, MembershipEvent } from "./models/events/MembershipEvent";
18
+ import { RoomEvent, RoomEventContent, StateEvent } from "./models/events/RoomEvent";
19
+ import { EventContext } from "./models/EventContext";
20
+ import { PowerLevelBounds } from "./models/PowerLevelBounds";
21
+ import { EventKind } from "./models/events/EventKind";
22
+ import { IdentityClient } from "./identity/IdentityClient";
23
+ import { OpenIDConnectToken } from "./models/OpenIDConnect";
24
+ import { doHttpRequest } from "./http";
25
+ import { Space, SpaceCreateOptions } from "./models/Spaces";
26
+ import { PowerLevelAction } from "./models/PowerLevelAction";
27
+ import { CryptoClient } from "./e2ee/CryptoClient";
28
+ import {
29
+ FallbackKey,
30
+ IToDeviceMessage,
31
+ MultiUserDeviceListResponse,
32
+ OTKAlgorithm,
33
+ OTKClaimResponse,
34
+ OTKCounts,
35
+ OTKs,
36
+ OwnUserDevice,
37
+ } from "./models/Crypto";
38
+ import { requiresCrypto } from "./e2ee/decorators";
39
+ import { ICryptoStorageProvider } from "./storage/ICryptoStorageProvider";
40
+ import { EncryptedRoomEvent } from "./models/events/EncryptedRoomEvent";
41
+ import { IWhoAmI } from "./models/Account";
42
+ import { RustSdkCryptoStorageProvider } from "./storage/RustSdkCryptoStorageProvider";
43
+ import { DMs } from "./DMs";
44
+ import { ServerVersions } from "./models/ServerVersions";
45
+ import { RoomCreateOptions } from "./models/CreateRoom";
46
+ import { PresenceState } from './models/events/PresenceEvent';
47
+
48
+ const SYNC_BACKOFF_MIN_MS = 5000;
49
+ const SYNC_BACKOFF_MAX_MS = 15000;
50
+ const VERSIONS_CACHE_MS = 7200000; // 2 hours
51
+
52
+ /**
53
+ * A client that is capable of interacting with a matrix homeserver.
54
+ */
55
+ export class MatrixClient extends EventEmitter {
56
+ /**
57
+ * The presence status to use while syncing. The valid values are "online" to set the account as online,
58
+ * "offline" to set the user as offline, "unavailable" for marking the user away, and null for not setting
59
+ * an explicit presence (the default).
60
+ *
61
+ * Has no effect if the client is not syncing. Does not apply until the next sync request.
62
+ */
63
+ public syncingPresence: PresenceState | null = null;
64
+
65
+ /**
66
+ * The number of milliseconds to wait for new events for on the next sync.
67
+ *
68
+ * Has no effect if the client is not syncing. Does not apply until the next sync request.
69
+ */
70
+ public syncingTimeout = 30000;
71
+
72
+ /**
73
+ * The crypto manager instance for this client. Generally speaking, this shouldn't
74
+ * need to be accessed but is made available.
75
+ *
76
+ * Will be null/undefined if crypto is not possible.
77
+ */
78
+ public readonly crypto: CryptoClient;
79
+
80
+ /**
81
+ * The DM manager instance for this client.
82
+ */
83
+ public readonly dms: DMs;
84
+
85
+ private userId: string;
86
+ private requestId = 0;
87
+ private lastJoinedRoomIds: string[] = [];
88
+ private impersonatedUserId: string;
89
+ private impersonatedDeviceId: string;
90
+ private joinStrategy: IJoinRoomStrategy = null;
91
+ private eventProcessors: { [eventType: string]: IPreprocessor[] } = {};
92
+ private filterId = 0;
93
+ private stopSyncing = false;
94
+ private metricsInstance: Metrics = new Metrics();
95
+ private unstableApisInstance = new UnstableApis(this);
96
+ private cachedVersions: ServerVersions;
97
+ private versionsLastFetched = 0;
98
+
99
+ /**
100
+ * Set this to true to have the client only persist the sync token after the sync
101
+ * has been processed successfully. Note that if this is true then when the sync
102
+ * loop throws an error the client will not persist a token.
103
+ */
104
+ protected persistTokenAfterSync = false;
105
+
106
+ /**
107
+ * Creates a new matrix client
108
+ * @param {string} homeserverUrl The homeserver's client-server API URL
109
+ * @param {string} accessToken The access token for the homeserver
110
+ * @param {IStorageProvider} storage The storage provider to use. Defaults to MemoryStorageProvider.
111
+ * @param {ICryptoStorageProvider} cryptoStore Optional crypto storage provider to use. If not supplied,
112
+ * end-to-end encryption will not be functional in this client.
113
+ */
114
+ constructor(
115
+ public readonly homeserverUrl: string,
116
+ public readonly accessToken: string,
117
+ private storage: IStorageProvider = null,
118
+ public readonly cryptoStore: ICryptoStorageProvider = null,
119
+ ) {
120
+ super();
121
+
122
+ if (this.homeserverUrl.endsWith("/")) {
123
+ this.homeserverUrl = this.homeserverUrl.substring(0, this.homeserverUrl.length - 1);
124
+ }
125
+
126
+ if (this.cryptoStore) {
127
+ if (!this.storage || this.storage instanceof MemoryStorageProvider) {
128
+ LogService.warn("MatrixClientLite", "Starting an encryption-capable client with a memory store is not considered a good idea.");
129
+ }
130
+ if (!(this.cryptoStore instanceof RustSdkCryptoStorageProvider)) {
131
+ throw new Error("Cannot support custom encryption stores: Use a RustSdkCryptoStorageProvider");
132
+ }
133
+ this.crypto = new CryptoClient(this);
134
+ this.on("room.event", (roomId, event) => {
135
+ // noinspection JSIgnoredPromiseFromCall
136
+ this.crypto.onRoomEvent(roomId, event);
137
+ });
138
+ this.on("room.join", (roomId) => {
139
+ // noinspection JSIgnoredPromiseFromCall
140
+ this.crypto.onRoomJoin(roomId);
141
+ });
142
+ LogService.debug("MatrixClientLite", "End-to-end encryption client created");
143
+ } else {
144
+ // LogService.trace("MatrixClientLite", "Not setting up encryption");
145
+ }
146
+
147
+ if (!this.storage) this.storage = new MemoryStorageProvider();
148
+
149
+ this.dms = new DMs(this);
150
+ }
151
+
152
+ /**
153
+ * The storage provider for this client. Direct access is usually not required.
154
+ */
155
+ public get storageProvider(): IStorageProvider {
156
+ return this.storage;
157
+ }
158
+
159
+ /**
160
+ * The metrics instance for this client
161
+ */
162
+ public get metrics(): Metrics {
163
+ return this.metricsInstance;
164
+ }
165
+
166
+ /**
167
+ * Assigns a new metrics instance, overwriting the old one.
168
+ * @param {Metrics} metrics The new metrics instance.
169
+ */
170
+ public set metrics(metrics: Metrics) {
171
+ if (!metrics) throw new Error("Metrics cannot be null/undefined");
172
+ this.metricsInstance = metrics;
173
+ }
174
+
175
+ /**
176
+ * Gets the unstable API access class. This is generally not recommended to be
177
+ * used by clients.
178
+ * @return {UnstableApis} The unstable API access class.
179
+ */
180
+ public get unstableApis(): UnstableApis {
181
+ return this.unstableApisInstance;
182
+ }
183
+
184
+ /**
185
+ * Gets the admin API access class.
186
+ * @return {AdminApis} The admin API access class.
187
+ */
188
+ public get adminApis(): AdminApis {
189
+ return new AdminApis(this);
190
+ }
191
+
192
+ /**
193
+ * Sets a user ID to impersonate as. This will assume that the access token for this client
194
+ * is for an application service, and that the userId given is within the reach of the
195
+ * application service. Setting this to null will stop future impersonation. The user ID is
196
+ * assumed to already be valid
197
+ * @param {string} userId The user ID to masquerade as, or `null` to clear masquerading.
198
+ * @param {string} deviceId Optional device ID to impersonate under the given user, if supported
199
+ * by the server. Check the whoami response after setting.
200
+ */
201
+ public impersonateUserId(userId: string | null, deviceId?: string): void {
202
+ this.impersonatedUserId = userId;
203
+ this.userId = userId;
204
+ if (userId) {
205
+ this.impersonatedDeviceId = deviceId;
206
+ } else if (deviceId) {
207
+ throw new Error("Cannot impersonate just a device: need a user ID");
208
+ } else {
209
+ this.impersonatedDeviceId = null;
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Acquires an identity server client for communicating with an identity server. Note that
215
+ * this will automatically do the login portion to establish a usable token with the identity
216
+ * server provided, but it will not automatically accept any terms of service.
217
+ *
218
+ * The identity server name provided will in future be resolved to a server address - for now
219
+ * that resolution is assumed to be prefixing the name with `https://`.
220
+ * @param {string} identityServerName The domain of the identity server to connect to.
221
+ * @returns {Promise<IdentityClient>} Resolves to a prepared identity client.
222
+ */
223
+ public async getIdentityServerClient(identityServerName: string): Promise<IdentityClient> {
224
+ const oidcToken = await this.getOpenIDConnectToken();
225
+ return IdentityClient.acquire(oidcToken, `https://${identityServerName}`, this);
226
+ }
227
+
228
+ /**
229
+ * Sets the strategy to use for when joinRoom is called on this client
230
+ * @param {IJoinRoomStrategy} strategy The strategy to use, or null to use none
231
+ */
232
+ public setJoinStrategy(strategy: IJoinRoomStrategy): void {
233
+ this.joinStrategy = strategy;
234
+ }
235
+
236
+ /**
237
+ * Adds a preprocessor to the event pipeline. When this client encounters an event, it
238
+ * will try to run it through the preprocessors it can in the order they were added.
239
+ * @param {IPreprocessor} preprocessor the preprocessor to add
240
+ */
241
+ public addPreprocessor(preprocessor: IPreprocessor): void {
242
+ if (!preprocessor) throw new Error("Preprocessor cannot be null");
243
+
244
+ const eventTypes = preprocessor.getSupportedEventTypes();
245
+ if (!eventTypes) return; // Nothing to do
246
+
247
+ for (const eventType of eventTypes) {
248
+ if (!this.eventProcessors[eventType]) this.eventProcessors[eventType] = [];
249
+ this.eventProcessors[eventType].push(preprocessor);
250
+ }
251
+ }
252
+
253
+ private async processEvent(event: any): Promise<any> {
254
+ if (!event) return event;
255
+ if (!this.eventProcessors[event["type"]]) return event;
256
+
257
+ for (const processor of this.eventProcessors[event["type"]]) {
258
+ await processor.processEvent(event, this, EventKind.RoomEvent);
259
+ }
260
+
261
+ return event;
262
+ }
263
+
264
+ /**
265
+ * Retrieves the server's supported specification versions and unstable features.
266
+ * @returns {Promise<ServerVersions>} Resolves to the server's supported versions.
267
+ */
268
+ @timedMatrixClientFunctionCall()
269
+ public async getServerVersions(): Promise<ServerVersions> {
270
+ if (!this.cachedVersions || (Date.now() - this.versionsLastFetched) >= VERSIONS_CACHE_MS) {
271
+ this.cachedVersions = await this.doRequest("GET", "/_matrix/client/versions");
272
+ this.versionsLastFetched = Date.now();
273
+ }
274
+
275
+ return this.cachedVersions;
276
+ }
277
+
278
+ /**
279
+ * Determines if the server supports a given unstable feature flag. Useful for determining
280
+ * if the server can support an unstable MSC.
281
+ * @param {string} feature The feature name to look for.
282
+ * @returns {Promise<boolean>} Resolves to true if the server supports the flag, false otherwise.
283
+ */
284
+ public async doesServerSupportUnstableFeature(feature: string): Promise<boolean> {
285
+ return !!(await this.getServerVersions()).unstable_features?.[feature];
286
+ }
287
+
288
+ /**
289
+ * Determines if the server supports a given version of the specification or not.
290
+ * @param {string} version The version to look for. Eg: "v1.1"
291
+ * @returns {Promise<boolean>} Resolves to true if the server supports the version, false otherwise.
292
+ */
293
+ public async doesServerSupportVersion(version: string): Promise<boolean> {
294
+ return (await this.getServerVersions()).versions.includes(version);
295
+ }
296
+
297
+ /**
298
+ * Determines if the server supports at least one of the given specification versions or not.
299
+ * @param {string[]} versions The versions to look for. Eg: ["v1.1"]
300
+ * @returns {Promise<boolean>} Resolves to true if the server supports any of the versions, false otherwise.
301
+ */
302
+ public async doesServerSupportAnyOneVersion(versions: string[]): Promise<boolean> {
303
+ for (const version of versions) {
304
+ if (await this.doesServerSupportVersion(version)) {
305
+ return true;
306
+ }
307
+ }
308
+ return false;
309
+ }
310
+
311
+ /**
312
+ * Retrieves an OpenID Connect token from the homeserver for the current user.
313
+ * @returns {Promise<OpenIDConnectToken>} Resolves to the token.
314
+ */
315
+ @timedMatrixClientFunctionCall()
316
+ public async getOpenIDConnectToken(): Promise<OpenIDConnectToken> {
317
+ const userId = encodeURIComponent(await this.getUserId());
318
+ return this.doRequest("POST", "/_matrix/client/v3/user/" + userId + "/openid/request_token", null, {});
319
+ }
320
+
321
+ /**
322
+ * Retrieves content from account data.
323
+ * @param {string} eventType The type of account data to retrieve.
324
+ * @returns {Promise<any>} Resolves to the content of that account data.
325
+ */
326
+ @timedMatrixClientFunctionCall()
327
+ public async getAccountData<T>(eventType: string): Promise<T> {
328
+ const userId = encodeURIComponent(await this.getUserId());
329
+ eventType = encodeURIComponent(eventType);
330
+ return this.doRequest("GET", "/_matrix/client/v3/user/" + userId + "/account_data/" + eventType);
331
+ }
332
+
333
+ /**
334
+ * Retrieves content from room account data.
335
+ * @param {string} eventType The type of room account data to retrieve.
336
+ * @param {string} roomId The room to read the account data from.
337
+ * @returns {Promise<any>} Resolves to the content of that account data.
338
+ */
339
+ @timedMatrixClientFunctionCall()
340
+ public async getRoomAccountData<T>(eventType: string, roomId: string): Promise<T> {
341
+ const userId = encodeURIComponent(await this.getUserId());
342
+ eventType = encodeURIComponent(eventType);
343
+ roomId = encodeURIComponent(roomId);
344
+ return this.doRequest("GET", "/_matrix/client/v3/user/" + userId + "/rooms/" + roomId + "/account_data/" + eventType);
345
+ }
346
+
347
+ /**
348
+ * Retrieves content from account data. If the account data request throws an error,
349
+ * this simply returns the default provided.
350
+ * @param {string} eventType The type of account data to retrieve.
351
+ * @param {any} defaultContent The default value. Defaults to null.
352
+ * @returns {Promise<any>} Resolves to the content of that account data, or the default.
353
+ */
354
+ @timedMatrixClientFunctionCall()
355
+ public async getSafeAccountData<T>(eventType: string, defaultContent: T = null): Promise<T> {
356
+ try {
357
+ return await this.getAccountData(eventType);
358
+ } catch (e) {
359
+ LogService.warn("MatrixClient", `Error getting ${eventType} account data:`, extractRequestError(e));
360
+ return defaultContent;
361
+ }
362
+ }
363
+
364
+ /**
365
+ * Retrieves content from room account data. If the account data request throws an error,
366
+ * this simply returns the default provided.
367
+ * @param {string} eventType The type of room account data to retrieve.
368
+ * @param {string} roomId The room to read the account data from.
369
+ * @param {any} defaultContent The default value. Defaults to null.
370
+ * @returns {Promise<any>} Resolves to the content of that room account data, or the default.
371
+ */
372
+ @timedMatrixClientFunctionCall()
373
+ public async getSafeRoomAccountData<T>(eventType: string, roomId: string, defaultContent: T = null): Promise<T> {
374
+ try {
375
+ return await this.getRoomAccountData(eventType, roomId);
376
+ } catch (e) {
377
+ LogService.warn("MatrixClient", `Error getting ${eventType} room account data in ${roomId}:`, extractRequestError(e));
378
+ return defaultContent;
379
+ }
380
+ }
381
+
382
+ /**
383
+ * Sets account data.
384
+ * @param {string} eventType The type of account data to set
385
+ * @param {any} content The content to set
386
+ * @returns {Promise<any>} Resolves when updated
387
+ */
388
+ @timedMatrixClientFunctionCall()
389
+ public async setAccountData(eventType: string, content: any): Promise<any> {
390
+ const userId = encodeURIComponent(await this.getUserId());
391
+ eventType = encodeURIComponent(eventType);
392
+ return this.doRequest("PUT", "/_matrix/client/v3/user/" + userId + "/account_data/" + eventType, null, content);
393
+ }
394
+
395
+ /**
396
+ * Sets room account data.
397
+ * @param {string} eventType The type of room account data to set
398
+ * @param {string} roomId The room to set account data in
399
+ * @param {any} content The content to set
400
+ * @returns {Promise<any>} Resolves when updated
401
+ */
402
+ @timedMatrixClientFunctionCall()
403
+ public async setRoomAccountData(eventType: string, roomId: string, content: any): Promise<any> {
404
+ const userId = encodeURIComponent(await this.getUserId());
405
+ eventType = encodeURIComponent(eventType);
406
+ roomId = encodeURIComponent(roomId);
407
+ return this.doRequest("PUT", "/_matrix/client/v3/user/" + userId + "/rooms/" + roomId + "/account_data/" + eventType, null, content);
408
+ }
409
+
410
+ /**
411
+ * Gets the presence information for the current user.
412
+ * @returns {Promise<Presence>} Resolves to the presence status of the user.
413
+ */
414
+ @timedMatrixClientFunctionCall()
415
+ public async getPresenceStatus(): Promise<Presence> {
416
+ return this.getPresenceStatusFor(await this.getUserId());
417
+ }
418
+
419
+ /**
420
+ * Gets the presence information for a given user.
421
+ * @param {string} userId The user ID to look up the presence of.
422
+ * @returns {Promise<Presence>} Resolves to the presence status of the user.
423
+ */
424
+ @timedMatrixClientFunctionCall()
425
+ public async getPresenceStatusFor(userId: string): Promise<Presence> {
426
+ return this.doRequest("GET", "/_matrix/client/v3/presence/" + encodeURIComponent(userId) + "/status").then(r => new Presence(r));
427
+ }
428
+
429
+ /**
430
+ * Sets the presence status for the current user.
431
+ * @param {PresenceState} presence The new presence state for the user.
432
+ * @param {string?} statusMessage Optional status message to include with the presence.
433
+ * @returns {Promise<any>} Resolves when complete.
434
+ */
435
+ @timedMatrixClientFunctionCall()
436
+ public async setPresenceStatus(presence: PresenceState, statusMessage: string | undefined = undefined): Promise<any> {
437
+ return this.doRequest("PUT", "/_matrix/client/v3/presence/" + encodeURIComponent(await this.getUserId()) + "/status", null, {
438
+ presence: presence,
439
+ status_msg: statusMessage,
440
+ });
441
+ }
442
+
443
+ /**
444
+ * Gets a published alias for the given room. These are supplied by the room admins
445
+ * and should point to the room, but may not. This is primarily intended to be used
446
+ * in the context of rendering a mention (pill) for a room.
447
+ * @param {string} roomIdOrAlias The room ID or alias to get an alias for.
448
+ * @returns {Promise<string>} Resolves to a published room alias, or falsey if none found.
449
+ */
450
+ @timedMatrixClientFunctionCall()
451
+ public async getPublishedAlias(roomIdOrAlias: string): Promise<string> {
452
+ try {
453
+ const roomId = await this.resolveRoom(roomIdOrAlias);
454
+ const event = await this.getRoomStateEvent(roomId, "m.room.canonical_alias", "");
455
+ if (!event) return null;
456
+
457
+ const canonical = event['alias'];
458
+ const alt = event['alt_aliases'] || [];
459
+
460
+ return canonical || alt[0];
461
+ } catch (e) {
462
+ // Assume none
463
+ return null;
464
+ }
465
+ }
466
+
467
+ /**
468
+ * Adds a new room alias to the room directory
469
+ * @param {string} alias The alias to add (eg: "#my-room:matrix.org")
470
+ * @param {string} roomId The room ID to add the alias to
471
+ * @returns {Promise} resolves when the alias has been added
472
+ */
473
+ @timedMatrixClientFunctionCall()
474
+ public createRoomAlias(alias: string, roomId: string): Promise<any> {
475
+ alias = encodeURIComponent(alias);
476
+ return this.doRequest("PUT", "/_matrix/client/v3/directory/room/" + alias, null, {
477
+ "room_id": roomId,
478
+ });
479
+ }
480
+
481
+ /**
482
+ * Removes a room alias from the room directory
483
+ * @param {string} alias The alias to remove
484
+ * @returns {Promise} resolves when the alias has been deleted
485
+ */
486
+ @timedMatrixClientFunctionCall()
487
+ public deleteRoomAlias(alias: string): Promise<any> {
488
+ alias = encodeURIComponent(alias);
489
+ return this.doRequest("DELETE", "/_matrix/client/v3/directory/room/" + alias);
490
+ }
491
+
492
+ /**
493
+ * Sets the visibility of a room in the directory.
494
+ * @param {string} roomId The room ID to manipulate the visibility of
495
+ * @param {"public" | "private"} visibility The visibility to set for the room
496
+ * @return {Promise} resolves when the visibility has been updated
497
+ */
498
+ @timedMatrixClientFunctionCall()
499
+ public setDirectoryVisibility(roomId: string, visibility: "public" | "private"): Promise<any> {
500
+ roomId = encodeURIComponent(roomId);
501
+ return this.doRequest("PUT", "/_matrix/client/v3/directory/list/room/" + roomId, null, {
502
+ "visibility": visibility,
503
+ });
504
+ }
505
+
506
+ /**
507
+ * Gets the visibility of a room in the directory.
508
+ * @param {string} roomId The room ID to query the visibility of
509
+ * @return {Promise<"public"|"private">} The visibility of the room
510
+ */
511
+ @timedMatrixClientFunctionCall()
512
+ public getDirectoryVisibility(roomId: string): Promise<"public" | "private"> {
513
+ roomId = encodeURIComponent(roomId);
514
+ return this.doRequest("GET", "/_matrix/client/v3/directory/list/room/" + roomId).then(response => {
515
+ return response["visibility"];
516
+ });
517
+ }
518
+
519
+ /**
520
+ * Resolves a room ID or alias to a room ID. If the given ID or alias looks like a room ID
521
+ * already, it will be returned as-is. If the room ID or alias looks like a room alias, it
522
+ * will be resolved to a room ID if possible. If the room ID or alias is neither, an error
523
+ * will be raised.
524
+ * @param {string} roomIdOrAlias the room ID or alias to resolve to a room ID
525
+ * @returns {Promise<string>} resolves to the room ID
526
+ */
527
+ @timedMatrixClientFunctionCall()
528
+ public async resolveRoom(roomIdOrAlias: string): Promise<string> {
529
+ if (roomIdOrAlias.startsWith("!")) return roomIdOrAlias; // probably
530
+ if (roomIdOrAlias.startsWith("#")) return this.lookupRoomAlias(roomIdOrAlias).then(r => r.roomId);
531
+ throw new Error("Invalid room ID or alias");
532
+ }
533
+
534
+ /**
535
+ * Does a room directory lookup for a given room alias
536
+ * @param {string} roomAlias the room alias to look up in the room directory
537
+ * @returns {Promise<RoomDirectoryLookupResponse>} resolves to the room's information
538
+ */
539
+ @timedMatrixClientFunctionCall()
540
+ public lookupRoomAlias(roomAlias: string): Promise<RoomDirectoryLookupResponse> {
541
+ return this.doRequest("GET", "/_matrix/client/v3/directory/room/" + encodeURIComponent(roomAlias)).then(response => {
542
+ return {
543
+ roomId: response["room_id"],
544
+ residentServers: response["servers"],
545
+ };
546
+ });
547
+ }
548
+
549
+ /**
550
+ * Invites a user to a room.
551
+ * @param {string} userId the user ID to invite
552
+ * @param {string} roomId the room ID to invite the user to
553
+ * @returns {Promise<any>} resolves when completed
554
+ */
555
+ @timedMatrixClientFunctionCall()
556
+ public inviteUser(userId, roomId) {
557
+ return this.doRequest("POST", "/_matrix/client/v3/rooms/" + encodeURIComponent(roomId) + "/invite", null, {
558
+ user_id: userId,
559
+ });
560
+ }
561
+
562
+ /**
563
+ * Kicks a user from a room.
564
+ * @param {string} userId the user ID to kick
565
+ * @param {string} roomId the room ID to kick the user in
566
+ * @param {string?} reason optional reason for the kick
567
+ * @returns {Promise<any>} resolves when completed
568
+ */
569
+ @timedMatrixClientFunctionCall()
570
+ public kickUser(userId, roomId, reason = null) {
571
+ return this.doRequest("POST", "/_matrix/client/v3/rooms/" + encodeURIComponent(roomId) + "/kick", null, {
572
+ user_id: userId,
573
+ reason: reason,
574
+ });
575
+ }
576
+
577
+ /**
578
+ * Bans a user from a room.
579
+ * @param {string} userId the user ID to ban
580
+ * @param {string} roomId the room ID to set the ban in
581
+ * @param {string?} reason optional reason for the ban
582
+ * @returns {Promise<any>} resolves when completed
583
+ */
584
+ @timedMatrixClientFunctionCall()
585
+ public banUser(userId, roomId, reason = null) {
586
+ return this.doRequest("POST", "/_matrix/client/v3/rooms/" + encodeURIComponent(roomId) + "/ban", null, {
587
+ user_id: userId,
588
+ reason: reason,
589
+ });
590
+ }
591
+
592
+ /**
593
+ * Unbans a user in a room.
594
+ * @param {string} userId the user ID to unban
595
+ * @param {string} roomId the room ID to lift the ban in
596
+ * @returns {Promise<any>} resolves when completed
597
+ */
598
+ @timedMatrixClientFunctionCall()
599
+ public unbanUser(userId, roomId) {
600
+ return this.doRequest("POST", "/_matrix/client/v3/rooms/" + encodeURIComponent(roomId) + "/unban", null, {
601
+ user_id: userId,
602
+ });
603
+ }
604
+
605
+ /**
606
+ * Gets the current user ID for this client
607
+ * @returns {Promise<string>} The user ID of this client
608
+ */
609
+ @timedMatrixClientFunctionCall()
610
+ public async getUserId(): Promise<string> {
611
+ if (this.userId) return this.userId;
612
+
613
+ // getWhoAmI should populate `this.userId` for us
614
+ await this.getWhoAmI();
615
+
616
+ return this.userId;
617
+ }
618
+
619
+ /**
620
+ * Gets the user's information from the server directly.
621
+ * @returns {Promise<IWhoAmI>} The "who am I" response.
622
+ */
623
+ public async getWhoAmI(): Promise<IWhoAmI> {
624
+ const whoami = await this.doRequest("GET", "/_matrix/client/v3/account/whoami");
625
+ this.userId = whoami["user_id"];
626
+ return whoami;
627
+ }
628
+
629
+ /**
630
+ * Stops the client from syncing.
631
+ */
632
+ public stop() {
633
+ this.stopSyncing = true;
634
+ }
635
+
636
+ /**
637
+ * Starts syncing the client with an optional filter
638
+ * @param {any} filter The filter to use, or null for none
639
+ * @returns {Promise<any>} Resolves when the client has started syncing
640
+ */
641
+ public async start(filter: any = null): Promise<any> {
642
+ await this.dms.update();
643
+
644
+ this.stopSyncing = false;
645
+ if (!filter || typeof (filter) !== "object") {
646
+ LogService.trace("MatrixClientLite", "No filter given or invalid object - using defaults.");
647
+ filter = null;
648
+ }
649
+
650
+ LogService.trace("MatrixClientLite", "Populating joined rooms to avoid excessive join emits");
651
+ this.lastJoinedRoomIds = await this.getJoinedRooms();
652
+
653
+ const userId = await this.getUserId();
654
+
655
+ if (this.crypto) {
656
+ LogService.debug("MatrixClientLite", "Preparing end-to-end encryption");
657
+ await this.crypto.prepare(this.lastJoinedRoomIds);
658
+ LogService.info("MatrixClientLite", "End-to-end encryption enabled");
659
+ }
660
+
661
+ let createFilter = false;
662
+
663
+ // noinspection ES6RedundantAwait
664
+ const existingFilter = await Promise.resolve(this.storage.getFilter());
665
+ if (existingFilter) {
666
+ LogService.trace("MatrixClientLite", "Found existing filter. Checking consistency with given filter");
667
+ if (JSON.stringify(existingFilter.filter) === JSON.stringify(filter)) {
668
+ LogService.trace("MatrixClientLite", "Filters match");
669
+ this.filterId = existingFilter.id;
670
+ } else {
671
+ createFilter = true;
672
+ }
673
+ } else {
674
+ createFilter = true;
675
+ }
676
+
677
+ if (createFilter && filter) {
678
+ LogService.trace("MatrixClientLite", "Creating new filter");
679
+ await this.doRequest("POST", "/_matrix/client/v3/user/" + encodeURIComponent(userId) + "/filter", null, filter).then(async response => {
680
+ this.filterId = response["filter_id"];
681
+ // noinspection ES6RedundantAwait
682
+ await Promise.resolve(this.storage.setSyncToken(null));
683
+ // noinspection ES6RedundantAwait
684
+ await Promise.resolve(this.storage.setFilter({
685
+ id: this.filterId,
686
+ filter: filter,
687
+ }));
688
+ });
689
+ }
690
+
691
+ LogService.trace("MatrixClientLite", "Starting sync with filter ID " + this.filterId);
692
+ return this.startSyncInternal();
693
+ }
694
+
695
+ protected startSyncInternal(): Promise<any> {
696
+ return this.startSync();
697
+ }
698
+
699
+ protected async startSync(emitFn: (emitEventType: string, ...payload: any[]) => Promise<any> = null) {
700
+ // noinspection ES6RedundantAwait
701
+ let token = await Promise.resolve(this.storage.getSyncToken());
702
+
703
+ const promiseWhile = async () => {
704
+ if (this.stopSyncing) {
705
+ LogService.info("MatrixClientLite", "Client stop requested - stopping sync");
706
+ return;
707
+ }
708
+
709
+ try {
710
+ const response = await this.doSync(token);
711
+ token = response["next_batch"];
712
+
713
+ if (!this.persistTokenAfterSync) {
714
+ await Promise.resolve(this.storage.setSyncToken(token));
715
+ }
716
+
717
+ LogService.debug("MatrixClientLite", "Received sync. Next token: " + token);
718
+ await this.processSync(response, emitFn);
719
+
720
+ if (this.persistTokenAfterSync) {
721
+ await Promise.resolve(this.storage.setSyncToken(token));
722
+ }
723
+ } catch (e) {
724
+ // If we've requested to stop syncing, don't bother checking the error.
725
+ if (this.stopSyncing) {
726
+ LogService.info("MatrixClientLite", "Client stop requested - cancelling sync");
727
+ return;
728
+ }
729
+
730
+ LogService.error("MatrixClientLite", "Error handling sync " + extractRequestError(e));
731
+ const backoffTime = SYNC_BACKOFF_MIN_MS + Math.random() * (SYNC_BACKOFF_MAX_MS - SYNC_BACKOFF_MIN_MS);
732
+ LogService.info("MatrixClientLite", `Backing off for ${backoffTime}ms`);
733
+ await new Promise((r) => setTimeout(r, backoffTime));
734
+ }
735
+
736
+ return promiseWhile();
737
+ };
738
+
739
+ promiseWhile(); // start the loop
740
+ }
741
+
742
+ @timedMatrixClientFunctionCall()
743
+ protected doSync(token: string): Promise<any> {
744
+ LogService.debug("MatrixClientLite", "Performing sync with token " + token);
745
+ const conf = {
746
+ full_state: false,
747
+ timeout: Math.max(0, this.syncingTimeout),
748
+ };
749
+ // synapse complains if the variables are null, so we have to have it unset instead
750
+ if (token) conf["since"] = token;
751
+ if (this.filterId) conf['filter'] = this.filterId;
752
+ if (this.syncingPresence) conf['presence'] = this.syncingPresence;
753
+
754
+ // timeout is 40s if we have a token, otherwise 10min
755
+ return this.doRequest("GET", "/_matrix/client/v3/sync", conf, null, (token ? 40000 : 600000));
756
+ }
757
+
758
+ @timedMatrixClientFunctionCall()
759
+ protected async processSync(raw: any, emitFn: (emitEventType: string, ...payload: any[]) => Promise<any> = null): Promise<any> {
760
+ if (!emitFn) emitFn = (e, ...p) => Promise.resolve<any>(this.emit(e, ...p));
761
+
762
+ if (!raw) return; // nothing to process
763
+
764
+ const inbox: IToDeviceMessage[] = [];
765
+ if (raw['to_device']?.['events']) {
766
+ inbox.push(...raw['to_device']['events']);
767
+ }
768
+ for (const message of inbox) {
769
+ this.emit("to-device", message);
770
+ }
771
+
772
+ if (this.crypto) {
773
+ let unusedFallbacks: OTKAlgorithm[] = [];
774
+ if (raw['org.matrix.msc2732.device_unused_fallback_key_types']) {
775
+ unusedFallbacks = raw['org.matrix.msc2732.device_unused_fallback_key_types'];
776
+ } else if (raw['device_unused_fallback_key_types']) {
777
+ unusedFallbacks = raw['device_unused_fallback_key_types'];
778
+ }
779
+
780
+ const counts = raw['device_one_time_keys_count'] ?? {};
781
+
782
+ const changed = raw['device_lists']?.['changed'] ?? [];
783
+ const left = raw['device_lists']?.['left'] ?? [];
784
+
785
+ await this.crypto.updateSyncData(inbox, counts, unusedFallbacks, changed, left);
786
+ }
787
+
788
+ // Always process device messages first to ensure there are decryption keys
789
+
790
+ if (raw['account_data'] && raw['account_data']['events']) {
791
+ for (const event of raw['account_data']['events']) {
792
+ await emitFn("account_data", event);
793
+ }
794
+ }
795
+
796
+ if (!raw['rooms']) return; // nothing more to process
797
+
798
+ const leftRooms = raw['rooms']['leave'] || {};
799
+ const inviteRooms = raw['rooms']['invite'] || {};
800
+ const joinedRooms = raw['rooms']['join'] || {};
801
+
802
+ // Process rooms we've left first
803
+ for (const roomId in leftRooms) {
804
+ const room = leftRooms[roomId];
805
+
806
+ if (room['account_data'] && room['account_data']['events']) {
807
+ for (const event of room['account_data']['events']) {
808
+ await emitFn("room.account_data", roomId, event);
809
+ }
810
+ }
811
+
812
+ if (!room['timeline'] || !room['timeline']['events']) continue;
813
+
814
+ let leaveEvent = null;
815
+ for (const event of room['timeline']['events']) {
816
+ if (event['type'] !== 'm.room.member') continue;
817
+ if (event['state_key'] !== await this.getUserId()) continue;
818
+
819
+ const membership = event["content"]?.["membership"];
820
+ if (membership !== "leave" && membership !== "ban") continue;
821
+
822
+ const oldAge = leaveEvent && leaveEvent['unsigned'] && leaveEvent['unsigned']['age'] ? leaveEvent['unsigned']['age'] : 0;
823
+ const newAge = event['unsigned'] && event['unsigned']['age'] ? event['unsigned']['age'] : 0;
824
+ if (leaveEvent && oldAge < newAge) continue;
825
+
826
+ leaveEvent = event;
827
+ }
828
+
829
+ if (!leaveEvent) {
830
+ LogService.warn("MatrixClientLite", "Left room " + roomId + " without receiving an event");
831
+ continue;
832
+ }
833
+
834
+ leaveEvent = await this.processEvent(leaveEvent);
835
+ await emitFn("room.leave", roomId, leaveEvent);
836
+ this.lastJoinedRoomIds = this.lastJoinedRoomIds.filter(r => r !== roomId);
837
+ }
838
+
839
+ // Process rooms we've been invited to
840
+ for (const roomId in inviteRooms) {
841
+ const room = inviteRooms[roomId];
842
+ if (!room['invite_state'] || !room['invite_state']['events']) continue;
843
+
844
+ let inviteEvent = null;
845
+ for (const event of room['invite_state']['events']) {
846
+ if (event['type'] !== 'm.room.member') continue;
847
+ if (event['state_key'] !== await this.getUserId()) continue;
848
+ if (!event['content']) continue;
849
+ if (event['content']['membership'] !== "invite") continue;
850
+
851
+ const oldAge = inviteEvent && inviteEvent['unsigned'] && inviteEvent['unsigned']['age'] ? inviteEvent['unsigned']['age'] : 0;
852
+ const newAge = event['unsigned'] && event['unsigned']['age'] ? event['unsigned']['age'] : 0;
853
+ if (inviteEvent && oldAge < newAge) continue;
854
+
855
+ inviteEvent = event;
856
+ }
857
+
858
+ if (!inviteEvent) {
859
+ LogService.warn("MatrixClientLite", "Invited to room " + roomId + " without receiving an event");
860
+ continue;
861
+ }
862
+
863
+ inviteEvent = await this.processEvent(inviteEvent);
864
+ await emitFn("room.invite", roomId, inviteEvent);
865
+ }
866
+
867
+ // Process rooms we've joined and their events
868
+ for (const roomId in joinedRooms) {
869
+ const room = joinedRooms[roomId];
870
+
871
+ if (room['account_data'] && room['account_data']['events']) {
872
+ for (const event of room['account_data']['events']) {
873
+ await emitFn("room.account_data", roomId, event);
874
+ }
875
+ }
876
+
877
+ if (!room['timeline'] || !room['timeline']['events']) continue;
878
+
879
+ for (let event of room['timeline']['events']) {
880
+ if (event['type'] === "m.room.member" && event['state_key'] === await this.getUserId()) {
881
+ if (event['content']?.['membership'] === "join" && this.lastJoinedRoomIds.indexOf(roomId) === -1) {
882
+ await emitFn("room.join", roomId, await this.processEvent(event));
883
+ this.lastJoinedRoomIds.push(roomId);
884
+ }
885
+ }
886
+
887
+ event = await this.processEvent(event);
888
+ if (event['type'] === 'm.room.encrypted' && await this.crypto?.isRoomEncrypted(roomId)) {
889
+ await emitFn("room.encrypted_event", roomId, event);
890
+ try {
891
+ event = (await this.crypto.decryptRoomEvent(new EncryptedRoomEvent(event), roomId)).raw;
892
+ event = await this.processEvent(event);
893
+ await emitFn("room.decrypted_event", roomId, event);
894
+ } catch (e) {
895
+ LogService.error("MatrixClientLite", `Decryption error on ${roomId} ${event['event_id']}`, e);
896
+ await emitFn("room.failed_decryption", roomId, event, e);
897
+ }
898
+ }
899
+ if (event['type'] === 'm.room.message') {
900
+ await emitFn("room.message", roomId, event);
901
+ }
902
+ if (event['type'] === 'm.room.tombstone' && event['state_key'] === '') {
903
+ await emitFn("room.archived", roomId, event);
904
+ }
905
+ if (event['type'] === 'm.room.create' && event['state_key'] === '' && event['content']
906
+ && event['content']['predecessor'] && event['content']['predecessor']['room_id']) {
907
+ await emitFn("room.upgraded", roomId, event);
908
+ }
909
+ await emitFn("room.event", roomId, event);
910
+ }
911
+ }
912
+ }
913
+
914
+ /**
915
+ * Gets an event for a room. If the event is encrypted, and the client supports encryption,
916
+ * and the room is encrypted, then this will return a decrypted event.
917
+ * @param {string} roomId the room ID to get the event in
918
+ * @param {string} eventId the event ID to look up
919
+ * @returns {Promise<any>} resolves to the found event
920
+ */
921
+ @timedMatrixClientFunctionCall()
922
+ public async getEvent(roomId: string, eventId: string): Promise<any> {
923
+ const event = await this.getRawEvent(roomId, eventId);
924
+ if (event['type'] === 'm.room.encrypted' && await this.crypto?.isRoomEncrypted(roomId)) {
925
+ return this.processEvent((await this.crypto.decryptRoomEvent(new EncryptedRoomEvent(event), roomId)).raw);
926
+ }
927
+ return event;
928
+ }
929
+
930
+ /**
931
+ * Gets an event for a room. Returned as a raw event.
932
+ * @param {string} roomId the room ID to get the event in
933
+ * @param {string} eventId the event ID to look up
934
+ * @returns {Promise<any>} resolves to the found event
935
+ */
936
+ @timedMatrixClientFunctionCall()
937
+ public getRawEvent(roomId: string, eventId: string): Promise<any> {
938
+ return this.doRequest("GET", "/_matrix/client/v3/rooms/" + encodeURIComponent(roomId) + "/event/" + encodeURIComponent(eventId))
939
+ .then(ev => this.processEvent(ev));
940
+ }
941
+
942
+ /**
943
+ * Gets the room state for the given room. Returned as raw events.
944
+ * @param {string} roomId the room ID to get state for
945
+ * @returns {Promise<any[]>} resolves to the room's state
946
+ */
947
+ @timedMatrixClientFunctionCall()
948
+ public getRoomState(roomId: string): Promise<any[]> {
949
+ return this.doRequest("GET", "/_matrix/client/v3/rooms/" + encodeURIComponent(roomId) + "/state")
950
+ .then(state => Promise.all(state.map(ev => this.processEvent(ev))));
951
+ }
952
+
953
+ /**
954
+ * Gets the state events for a given room of a given type under the given state key.
955
+ * @param {string} roomId the room ID
956
+ * @param {string} type the event type
957
+ * @param {String} stateKey the state key, falsey if not needed
958
+ * @returns {Promise<any|any[]>} resolves to the state event(s)
959
+ * @deprecated It is not possible to get an array of events - use getRoomStateEvent instead
960
+ */
961
+ @timedMatrixClientFunctionCall()
962
+ public getRoomStateEvents(roomId, type, stateKey): Promise<any | any[]> {
963
+ return this.getRoomStateEvent(roomId, type, stateKey);
964
+ }
965
+
966
+ /**
967
+ * Gets a state event for a given room of a given type under the given state key.
968
+ * @param {string} roomId the room ID
969
+ * @param {string} type the event type
970
+ * @param {String} stateKey the state key
971
+ * @returns {Promise<any>} resolves to the state event
972
+ */
973
+ @timedMatrixClientFunctionCall()
974
+ public getRoomStateEvent(roomId, type, stateKey): Promise<any> {
975
+ const path = "/_matrix/client/v3/rooms/"
976
+ + encodeURIComponent(roomId) + "/state/"
977
+ + encodeURIComponent(type) + "/"
978
+ + encodeURIComponent(stateKey ? stateKey : '');
979
+ return this.doRequest("GET", path)
980
+ .then(ev => this.processEvent(ev));
981
+ }
982
+
983
+ /**
984
+ * Gets the context surrounding an event.
985
+ * @param {string} roomId The room ID to get the context in.
986
+ * @param {string} eventId The event ID to get the context of.
987
+ * @param {number} limit The maximum number of events to return on either side of the event.
988
+ * @returns {Promise<EventContext>} The context of the event
989
+ */
990
+ @timedMatrixClientFunctionCall()
991
+ public async getEventContext(roomId: string, eventId: string, limit = 10): Promise<EventContext> {
992
+ const res = await this.doRequest("GET", "/_matrix/client/v3/rooms/" + encodeURIComponent(roomId) + "/context/" + encodeURIComponent(eventId), { limit });
993
+ return {
994
+ event: new RoomEvent<RoomEventContent>(res['event']),
995
+ before: res['events_before'].map(e => new RoomEvent<RoomEventContent>(e)),
996
+ after: res['events_after'].map(e => new RoomEvent<RoomEventContent>(e)),
997
+ state: res['state'].map(e => new StateEvent<RoomEventContent>(e)),
998
+ };
999
+ }
1000
+
1001
+ /**
1002
+ * Gets the profile for a given user
1003
+ * @param {string} userId the user ID to lookup
1004
+ * @returns {Promise<any>} the profile of the user
1005
+ */
1006
+ @timedMatrixClientFunctionCall()
1007
+ public getUserProfile(userId: string): Promise<any> {
1008
+ return this.doRequest("GET", "/_matrix/client/v3/profile/" + encodeURIComponent(userId));
1009
+ }
1010
+
1011
+ /**
1012
+ * Sets a new display name for the user.
1013
+ * @param {string} displayName the new display name for the user, or null to clear
1014
+ * @returns {Promise<any>} resolves when complete
1015
+ */
1016
+ @timedMatrixClientFunctionCall()
1017
+ public async setDisplayName(displayName: string): Promise<any> {
1018
+ const userId = encodeURIComponent(await this.getUserId());
1019
+ return this.doRequest("PUT", "/_matrix/client/v3/profile/" + userId + "/displayname", null, {
1020
+ displayname: displayName,
1021
+ });
1022
+ }
1023
+
1024
+ /**
1025
+ * Sets a new avatar url for the user.
1026
+ * @param {string} avatarUrl the new avatar URL for the user, in the form of a Matrix Content URI
1027
+ * @returns {Promise<any>} resolves when complete
1028
+ */
1029
+ @timedMatrixClientFunctionCall()
1030
+ public async setAvatarUrl(avatarUrl: string): Promise<any> {
1031
+ const userId = encodeURIComponent(await this.getUserId());
1032
+ return this.doRequest("PUT", "/_matrix/client/v3/profile/" + userId + "/avatar_url", null, {
1033
+ avatar_url: avatarUrl,
1034
+ });
1035
+ }
1036
+
1037
+ /**
1038
+ * Joins the given room
1039
+ * @param {string} roomIdOrAlias the room ID or alias to join
1040
+ * @param {string[]} viaServers the server names to try and join through
1041
+ * @returns {Promise<string>} resolves to the joined room ID
1042
+ */
1043
+ @timedMatrixClientFunctionCall()
1044
+ public async joinRoom(roomIdOrAlias: string, viaServers: string[] = []): Promise<string> {
1045
+ const apiCall = (targetIdOrAlias: string) => {
1046
+ targetIdOrAlias = encodeURIComponent(targetIdOrAlias);
1047
+ const qs = {};
1048
+ if (viaServers.length > 0) qs['server_name'] = viaServers;
1049
+ return this.doRequest("POST", "/_matrix/client/v3/join/" + targetIdOrAlias, qs, {}).then(response => {
1050
+ return response['room_id'];
1051
+ });
1052
+ };
1053
+
1054
+ const userId = await this.getUserId();
1055
+ if (this.joinStrategy) return this.joinStrategy.joinRoom(roomIdOrAlias, userId, apiCall);
1056
+ else return apiCall(roomIdOrAlias);
1057
+ }
1058
+
1059
+ /**
1060
+ * Gets a list of joined room IDs
1061
+ * @returns {Promise<string[]>} resolves to a list of room IDs the client participates in
1062
+ */
1063
+ @timedMatrixClientFunctionCall()
1064
+ public getJoinedRooms(): Promise<string[]> {
1065
+ return this.doRequest("GET", "/_matrix/client/v3/joined_rooms").then(response => response['joined_rooms']);
1066
+ }
1067
+
1068
+ /**
1069
+ * Gets the joined members in a room. The client must be in the room to make this request.
1070
+ * @param {string} roomId The room ID to get the joined members of.
1071
+ * @returns {Promise<string>} The joined user IDs in the room
1072
+ */
1073
+ @timedMatrixClientFunctionCall()
1074
+ public getJoinedRoomMembers(roomId: string): Promise<string[]> {
1075
+ return this.doRequest("GET", "/_matrix/client/v3/rooms/" + encodeURIComponent(roomId) + "/joined_members").then(response => {
1076
+ return Object.keys(response['joined']);
1077
+ });
1078
+ }
1079
+
1080
+ /**
1081
+ * Gets the joined members in a room, as an object mapping userIds to profiles. The client must be in the room to make this request.
1082
+ * @param {string} roomId The room ID to get the joined members of.
1083
+ * @returns {Object} The joined user IDs in the room as an object mapped to a set of profiles.
1084
+ */
1085
+ @timedMatrixClientFunctionCall()
1086
+ public async getJoinedRoomMembersWithProfiles(roomId: string): Promise<{ [userId: string]: { display_name?: string, avatar_url?: string } }> {
1087
+ return (await this.doRequest("GET", "/_matrix/client/v3/rooms/" + encodeURIComponent(roomId) + "/joined_members")).joined;
1088
+ }
1089
+
1090
+ /**
1091
+ * Gets the membership events of users in the room. Defaults to all membership
1092
+ * types, though this can be controlled with the membership and notMembership
1093
+ * arguments. To change the point in time, use the batchToken.
1094
+ * @param {string} roomId The room ID to get members in.
1095
+ * @param {string} batchToken The point in time to get members at (or null for 'now')
1096
+ * @param {string[]} membership The membership kinds to search for.
1097
+ * @param {string[]} notMembership The membership kinds to not search for.
1098
+ * @returns {Promise<MembershipEvent[]>} Resolves to the membership events of the users in the room.
1099
+ * @see getRoomMembersByMembership
1100
+ * @see getRoomMembersWithoutMembership
1101
+ * @see getAllRoomMembers
1102
+ */
1103
+ @timedMatrixClientFunctionCall()
1104
+ public getRoomMembers(roomId: string, batchToken: string = null, membership: Membership[] = null, notMembership: Membership[] = null): Promise<MembershipEvent[]> {
1105
+ if (!membership && !notMembership) {
1106
+ return this.getAllRoomMembers(roomId, batchToken);
1107
+ }
1108
+
1109
+ return Promise.all([
1110
+ ...(membership ?? []).map(m => this.getRoomMembersAt(roomId, m, null, batchToken)),
1111
+ ...(notMembership ?? []).map(m => this.getRoomMembersAt(roomId, null, m, batchToken)),
1112
+ ]).then(r => r.reduce((p, c) => {
1113
+ p.push(...c);
1114
+ return p;
1115
+ }, [])).then(r => {
1116
+ // Shouldn't ever happen, but dedupe just in case.
1117
+ const vals = new Map<string, MembershipEvent>();
1118
+ for (const ev of r) {
1119
+ if (!vals.has(ev.membershipFor)) {
1120
+ vals.set(ev.membershipFor, ev);
1121
+ }
1122
+ }
1123
+ return Array.from(vals.values());
1124
+ });
1125
+ }
1126
+
1127
+ /**
1128
+ * Gets all room members in the room, optionally at a given point in time.
1129
+ * @param {string} roomId The room ID to get members of.
1130
+ * @param {string} atToken Optional batch token to get members at. Leave falsy for "now".
1131
+ * @returns {Promise<MembershipEvent[]>} Resolves to the member events in the room.
1132
+ */
1133
+ @timedMatrixClientFunctionCall()
1134
+ public getAllRoomMembers(roomId: string, atToken?: string): Promise<MembershipEvent[]> {
1135
+ return this.getRoomMembersAt(roomId, null, null, atToken);
1136
+ }
1137
+
1138
+ /**
1139
+ * Gets the membership events of users in the room which have a particular membership type. To change
1140
+ * the point in time the server should return membership events at, use `atToken`.
1141
+ * @param {string} roomId The room ID to get members in.
1142
+ * @param {Membership} membership The membership to search for.
1143
+ * @param {string?} atToken Optional batch token to use, or null for "now".
1144
+ * @returns {Promise<MembershipEvent[]>} Resolves to the membership events of the users in the room.
1145
+ */
1146
+ @timedMatrixClientFunctionCall()
1147
+ public getRoomMembersByMembership(roomId: string, membership: Membership, atToken?: string): Promise<MembershipEvent[]> {
1148
+ return this.getRoomMembersAt(roomId, membership, null, atToken);
1149
+ }
1150
+
1151
+ /**
1152
+ * Gets the membership events of users in the room which lack a particular membership type. To change
1153
+ * the point in time the server should return membership events at, use `atToken`.
1154
+ * @param {string} roomId The room ID to get members in.
1155
+ * @param {Membership} notMembership The membership to NOT search for.
1156
+ * @param {string?} atToken Optional batch token to use, or null for "now".
1157
+ * @returns {Promise<MembershipEvent[]>} Resolves to the membership events of the users in the room.
1158
+ */
1159
+ @timedMatrixClientFunctionCall()
1160
+ public async getRoomMembersWithoutMembership(roomId: string, notMembership: Membership, atToken?: string): Promise<MembershipEvent[]> {
1161
+ return this.getRoomMembersAt(roomId, null, notMembership, atToken);
1162
+ }
1163
+
1164
+ private getRoomMembersAt(roomId: string, membership: Membership | null, notMembership: Membership | null, atToken: string | null): Promise<MembershipEvent[]> {
1165
+ const qs = {};
1166
+ if (atToken) qs["at"] = atToken;
1167
+ if (membership) qs["membership"] = membership;
1168
+ if (notMembership) qs["not_membership"] = notMembership;
1169
+
1170
+ return this.doRequest("GET", "/_matrix/client/v3/rooms/" + encodeURIComponent(roomId) + "/members", qs).then(r => {
1171
+ return r['chunk'].map(e => new MembershipEvent(e));
1172
+ });
1173
+ }
1174
+
1175
+ /**
1176
+ * Leaves the given room
1177
+ * @param {string} roomId the room ID to leave
1178
+ * @param {string=} reason Optional reason to be included as the reason for leaving the room.
1179
+ * @returns {Promise<any>} resolves when left
1180
+ */
1181
+ @timedMatrixClientFunctionCall()
1182
+ public leaveRoom(roomId: string, reason?: string): Promise<any> {
1183
+ return this.doRequest("POST", "/_matrix/client/v3/rooms/" + encodeURIComponent(roomId) + "/leave", null, { reason });
1184
+ }
1185
+
1186
+ /**
1187
+ * Forgets the given room
1188
+ * @param {string} roomId the room ID to forget
1189
+ * @returns {Promise<{}>} Resolves when forgotten
1190
+ */
1191
+ @timedMatrixClientFunctionCall()
1192
+ public forgetRoom(roomId: string): Promise<{}> {
1193
+ return this.doRequest("POST", "/_matrix/client/v3/rooms/" + encodeURIComponent(roomId) + "/forget");
1194
+ }
1195
+
1196
+ /**
1197
+ * Sends a read receipt for an event in a room
1198
+ * @param {string} roomId the room ID to send the receipt to
1199
+ * @param {string} eventId the event ID to set the receipt at
1200
+ * @returns {Promise<any>} resolves when the receipt has been sent
1201
+ */
1202
+ @timedMatrixClientFunctionCall()
1203
+ public sendReadReceipt(roomId: string, eventId: string): Promise<any> {
1204
+ return this.doRequest("POST", "/_matrix/client/v3/rooms/" + encodeURIComponent(roomId) + "/receipt/m.read/" + encodeURIComponent(eventId), null, {});
1205
+ }
1206
+
1207
+ /**
1208
+ * Sets the typing status of the current user in a room
1209
+ * @param {string} roomId the room ID the user is typing in
1210
+ * @param {boolean} typing is the user currently typing
1211
+ * @param {number} timeout how long should the server preserve the typing state, in milliseconds
1212
+ * @returns {Promise<any>} resolves when the typing state has been set
1213
+ */
1214
+ @timedMatrixClientFunctionCall()
1215
+ public async setTyping(roomId: string, typing: boolean, timeout = 30000): Promise<any> {
1216
+ const userId = await this.getUserId();
1217
+ return this.doRequest("PUT", "/_matrix/client/v3/rooms/" + encodeURIComponent(roomId) + "/typing/" + encodeURIComponent(userId), null, {
1218
+ typing,
1219
+ timeout,
1220
+ });
1221
+ }
1222
+
1223
+ /**
1224
+ * Replies to a given event with the given text. The event is sent with a msgtype of m.text.
1225
+ * The message will be encrypted if the client supports encryption and the room is encrypted.
1226
+ * @param {string} roomId the room ID to reply in
1227
+ * @param {any} event the event to reply to
1228
+ * @param {string} text the text to reply with
1229
+ * @param {string} html the HTML to reply with, or falsey to use the `text`
1230
+ * @returns {Promise<string>} resolves to the event ID which was sent
1231
+ */
1232
+ @timedMatrixClientFunctionCall()
1233
+ public replyText(roomId: string, event: any, text: string, html: string = null): Promise<string> {
1234
+ if (!html) html = htmlEncode(text);
1235
+
1236
+ const reply = RichReply.createFor(roomId, event, text, html);
1237
+ return this.sendMessage(roomId, reply);
1238
+ }
1239
+
1240
+ /**
1241
+ * Replies to a given event with the given HTML. The event is sent with a msgtype of m.text.
1242
+ * The message will be encrypted if the client supports encryption and the room is encrypted.
1243
+ * @param {string} roomId the room ID to reply in
1244
+ * @param {any} event the event to reply to
1245
+ * @param {string} html the HTML to reply with.
1246
+ * @returns {Promise<string>} resolves to the event ID which was sent
1247
+ */
1248
+ @timedMatrixClientFunctionCall()
1249
+ public replyHtmlText(roomId: string, event: any, html: string): Promise<string> {
1250
+ const text = htmlToText(html, { wordwrap: false });
1251
+ const reply = RichReply.createFor(roomId, event, text, html);
1252
+ return this.sendMessage(roomId, reply);
1253
+ }
1254
+
1255
+ /**
1256
+ * Replies to a given event with the given text. The event is sent with a msgtype of m.notice.
1257
+ * The message will be encrypted if the client supports encryption and the room is encrypted.
1258
+ * @param {string} roomId the room ID to reply in
1259
+ * @param {any} event the event to reply to
1260
+ * @param {string} text the text to reply with
1261
+ * @param {string} html the HTML to reply with, or falsey to use the `text`
1262
+ * @returns {Promise<string>} resolves to the event ID which was sent
1263
+ */
1264
+ @timedMatrixClientFunctionCall()
1265
+ public replyNotice(roomId: string, event: any, text: string, html: string = null): Promise<string> {
1266
+ if (!html) html = htmlEncode(text);
1267
+
1268
+ const reply = RichReply.createFor(roomId, event, text, html);
1269
+ reply['msgtype'] = 'm.notice';
1270
+ return this.sendMessage(roomId, reply);
1271
+ }
1272
+
1273
+ /**
1274
+ * Replies to a given event with the given HTML. The event is sent with a msgtype of m.notice.
1275
+ * The message will be encrypted if the client supports encryption and the room is encrypted.
1276
+ * @param {string} roomId the room ID to reply in
1277
+ * @param {any} event the event to reply to
1278
+ * @param {string} html the HTML to reply with.
1279
+ * @returns {Promise<string>} resolves to the event ID which was sent
1280
+ */
1281
+ @timedMatrixClientFunctionCall()
1282
+ public replyHtmlNotice(roomId: string, event: any, html: string): Promise<string> {
1283
+ const text = htmlToText(html, { wordwrap: false });
1284
+ const reply = RichReply.createFor(roomId, event, text, html);
1285
+ reply['msgtype'] = 'm.notice';
1286
+ return this.sendMessage(roomId, reply);
1287
+ }
1288
+
1289
+ /**
1290
+ * Sends a notice to the given room. The message will be encrypted if the client supports
1291
+ * encryption and the room is encrypted.
1292
+ * @param {string} roomId the room ID to send the notice to
1293
+ * @param {string} text the text to send
1294
+ * @returns {Promise<string>} resolves to the event ID that represents the message
1295
+ */
1296
+ @timedMatrixClientFunctionCall()
1297
+ public sendNotice(roomId: string, text: string): Promise<string> {
1298
+ return this.sendMessage(roomId, {
1299
+ body: text,
1300
+ msgtype: "m.notice",
1301
+ });
1302
+ }
1303
+
1304
+ /**
1305
+ * Sends a notice to the given room with HTML content. The message will be encrypted if the client supports
1306
+ * encryption and the room is encrypted.
1307
+ * @param {string} roomId the room ID to send the notice to
1308
+ * @param {string} html the HTML to send
1309
+ * @returns {Promise<string>} resolves to the event ID that represents the message
1310
+ */
1311
+ @timedMatrixClientFunctionCall()
1312
+ public sendHtmlNotice(roomId: string, html: string): Promise<string> {
1313
+ return this.sendMessage(roomId, {
1314
+ body: htmlToText(html, { wordwrap: false }),
1315
+ msgtype: "m.notice",
1316
+ format: "org.matrix.custom.html",
1317
+ formatted_body: html,
1318
+ });
1319
+ }
1320
+
1321
+ /**
1322
+ * Sends a text message to the given room. The message will be encrypted if the client supports
1323
+ * encryption and the room is encrypted.
1324
+ * @param {string} roomId the room ID to send the text to
1325
+ * @param {string} text the text to send
1326
+ * @returns {Promise<string>} resolves to the event ID that represents the message
1327
+ */
1328
+ @timedMatrixClientFunctionCall()
1329
+ public sendText(roomId: string, text: string): Promise<string> {
1330
+ return this.sendMessage(roomId, {
1331
+ body: text,
1332
+ msgtype: "m.text",
1333
+ });
1334
+ }
1335
+
1336
+ /**
1337
+ * Sends a text message to the given room with HTML content. The message will be encrypted if the client supports
1338
+ * encryption and the room is encrypted.
1339
+ * @param {string} roomId the room ID to send the text to
1340
+ * @param {string} html the HTML to send
1341
+ * @returns {Promise<string>} resolves to the event ID that represents the message
1342
+ */
1343
+ @timedMatrixClientFunctionCall()
1344
+ public sendHtmlText(roomId: string, html: string): Promise<string> {
1345
+ return this.sendMessage(roomId, {
1346
+ body: htmlToText(html, { wordwrap: false }),
1347
+ msgtype: "m.text",
1348
+ format: "org.matrix.custom.html",
1349
+ formatted_body: html,
1350
+ });
1351
+ }
1352
+
1353
+ /**
1354
+ * Sends a message to the given room. The message will be encrypted if the client supports
1355
+ * encryption and the room is encrypted.
1356
+ * @param {string} roomId the room ID to send the message to
1357
+ * @param {object} content the event content to send
1358
+ * @returns {Promise<string>} resolves to the event ID that represents the message
1359
+ */
1360
+ @timedMatrixClientFunctionCall()
1361
+ public sendMessage(roomId: string, content: any): Promise<string> {
1362
+ return this.sendEvent(roomId, "m.room.message", content);
1363
+ }
1364
+
1365
+ /**
1366
+ * Sends an event to the given room. This will encrypt the event before sending if the room is
1367
+ * encrypted and the client supports encryption. Use sendRawEvent() to avoid this behaviour.
1368
+ * @param {string} roomId the room ID to send the event to
1369
+ * @param {string} eventType the type of event to send
1370
+ * @param {string} content the event body to send
1371
+ * @returns {Promise<string>} resolves to the event ID that represents the event
1372
+ */
1373
+ @timedMatrixClientFunctionCall()
1374
+ public async sendEvent(roomId: string, eventType: string, content: any): Promise<string> {
1375
+ if (await this.crypto?.isRoomEncrypted(roomId)) {
1376
+ content = await this.crypto.encryptRoomEvent(roomId, eventType, content);
1377
+ eventType = "m.room.encrypted";
1378
+ }
1379
+ return this.sendRawEvent(roomId, eventType, content);
1380
+ }
1381
+
1382
+ /**
1383
+ * Sends an event to the given room.
1384
+ * @param {string} roomId the room ID to send the event to
1385
+ * @param {string} eventType the type of event to send
1386
+ * @param {string} content the event body to send
1387
+ * @returns {Promise<string>} resolves to the event ID that represents the event
1388
+ */
1389
+ @timedMatrixClientFunctionCall()
1390
+ public async sendRawEvent(roomId: string, eventType: string, content: any): Promise<string> {
1391
+ const txnId = (new Date().getTime()) + "__inc" + (++this.requestId);
1392
+ const path = "/_matrix/client/v3/rooms/"
1393
+ + encodeURIComponent(roomId) + "/send/"
1394
+ + encodeURIComponent(eventType) + "/"
1395
+ + encodeURIComponent(txnId);
1396
+ return this.doRequest("PUT", path, null, content).then(response => {
1397
+ return response['event_id'];
1398
+ });
1399
+ }
1400
+
1401
+ /**
1402
+ * Sends a state event to the given room
1403
+ * @param {string} roomId the room ID to send the event to
1404
+ * @param {string} type the event type to send
1405
+ * @param {string} stateKey the state key to send, should not be null
1406
+ * @param {string} content the event body to send
1407
+ * @returns {Promise<string>} resolves to the event ID that represents the message
1408
+ */
1409
+ @timedMatrixClientFunctionCall()
1410
+ public sendStateEvent(roomId: string, type: string, stateKey: string, content: any): Promise<string> {
1411
+ const path = "/_matrix/client/v3/rooms/"
1412
+ + encodeURIComponent(roomId) + "/state/"
1413
+ + encodeURIComponent(type) + "/"
1414
+ + encodeURIComponent(stateKey);
1415
+ return this.doRequest("PUT", path, null, content).then(response => {
1416
+ return response['event_id'];
1417
+ });
1418
+ }
1419
+
1420
+ /**
1421
+ * Redact an event in a given room
1422
+ * @param {string} roomId the room ID to send the redaction to
1423
+ * @param {string} eventId the event ID to redact
1424
+ * @param {String} reason an optional reason for redacting the event
1425
+ * @returns {Promise<string>} resolves to the event ID that represents the redaction
1426
+ */
1427
+ @timedMatrixClientFunctionCall()
1428
+ public redactEvent(roomId: string, eventId: string, reason: string | null = null): Promise<string> {
1429
+ const txnId = (new Date().getTime()) + "__inc" + (++this.requestId);
1430
+ const content = reason !== null ? { reason } : {};
1431
+ return this.doRequest("PUT", `/_matrix/client/v3/rooms/${encodeURIComponent(roomId)}/redact/${encodeURIComponent(eventId)}/${txnId}`, null, content).then(response => {
1432
+ return response['event_id'];
1433
+ });
1434
+ }
1435
+
1436
+ /**
1437
+ * Creates a room. See the RoomCreateOptions interface
1438
+ * for more information on what to provide for `properties`. Note that creating
1439
+ * a room may cause the bot/appservice to raise a join event.
1440
+ * @param {RoomCreateOptions} properties the properties of the room.
1441
+ * @returns {Promise<string>} resolves to the room ID that represents the room
1442
+ */
1443
+ @timedMatrixClientFunctionCall()
1444
+ public createRoom(properties: RoomCreateOptions = {}): Promise<string> {
1445
+ return this.doRequest("POST", "/_matrix/client/v3/createRoom", null, properties).then(response => {
1446
+ return response['room_id'];
1447
+ });
1448
+ }
1449
+
1450
+ /**
1451
+ * Checks if a given user has a required power level required to send the given event.
1452
+ * @param {string} userId the user ID to check the power level of
1453
+ * @param {string} roomId the room ID to check the power level in
1454
+ * @param {string} eventType the event type to look for in the `events` property of the power levels
1455
+ * @param {boolean} isState true to indicate the event is intended to be a state event
1456
+ * @returns {Promise<boolean>} resolves to true if the user has the required power level, resolves to false otherwise
1457
+ */
1458
+ @timedMatrixClientFunctionCall()
1459
+ public async userHasPowerLevelFor(userId: string, roomId: string, eventType: string, isState: boolean): Promise<boolean> {
1460
+ const powerLevelsEvent = await this.getRoomStateEvent(roomId, "m.room.power_levels", "");
1461
+ if (!powerLevelsEvent) {
1462
+ // This is technically supposed to be non-fatal, but it's pretty unreasonable for a room to be missing
1463
+ // power levels.
1464
+ throw new Error("No power level event found");
1465
+ }
1466
+
1467
+ let requiredPower = isState ? 50 : 0;
1468
+ if (isState && Number.isFinite(powerLevelsEvent["state_default"])) requiredPower = powerLevelsEvent["state_default"];
1469
+ if (!isState && Number.isFinite(powerLevelsEvent["events_default"])) requiredPower = powerLevelsEvent["events_default"];
1470
+ if (Number.isFinite(powerLevelsEvent["events"]?.[eventType])) requiredPower = powerLevelsEvent["events"][eventType];
1471
+
1472
+ let userPower = 0;
1473
+ if (Number.isFinite(powerLevelsEvent["users_default"])) userPower = powerLevelsEvent["users_default"];
1474
+ if (Number.isFinite(powerLevelsEvent["users"]?.[userId])) userPower = powerLevelsEvent["users"][userId];
1475
+
1476
+ return userPower >= requiredPower;
1477
+ }
1478
+
1479
+ /**
1480
+ * Checks if a given user has a required power level to perform the given action
1481
+ * @param {string} userId the user ID to check the power level of
1482
+ * @param {string} roomId the room ID to check the power level in
1483
+ * @param {PowerLevelAction} action the action to check power level for
1484
+ * @returns {Promise<boolean>} resolves to true if the user has the required power level, resolves to false otherwise
1485
+ */
1486
+ @timedMatrixClientFunctionCall()
1487
+ public async userHasPowerLevelForAction(userId: string, roomId: string, action: PowerLevelAction): Promise<boolean> {
1488
+ const powerLevelsEvent = await this.getRoomStateEvent(roomId, "m.room.power_levels", "");
1489
+ if (!powerLevelsEvent) {
1490
+ // This is technically supposed to be non-fatal, but it's pretty unreasonable for a room to be missing
1491
+ // power levels.
1492
+ throw new Error("No power level event found");
1493
+ }
1494
+
1495
+ const defaultForActions: { [A in PowerLevelAction]: number } = {
1496
+ [PowerLevelAction.Ban]: 50,
1497
+ [PowerLevelAction.Invite]: 50,
1498
+ [PowerLevelAction.Kick]: 50,
1499
+ [PowerLevelAction.RedactEvents]: 50,
1500
+ [PowerLevelAction.NotifyRoom]: 50,
1501
+ };
1502
+
1503
+ let requiredPower = defaultForActions[action];
1504
+
1505
+ let investigate = powerLevelsEvent;
1506
+ action.split('.').forEach(k => (investigate = investigate?.[k]));
1507
+ if (Number.isFinite(investigate)) requiredPower = investigate;
1508
+
1509
+ let userPower = 0;
1510
+ if (Number.isFinite(powerLevelsEvent["users_default"])) userPower = powerLevelsEvent["users_default"];
1511
+ if (Number.isFinite(powerLevelsEvent["users"]?.[userId])) userPower = powerLevelsEvent["users"][userId];
1512
+
1513
+ return userPower >= requiredPower;
1514
+ }
1515
+
1516
+ /**
1517
+ * Determines the boundary conditions for this client's ability to change another user's power level
1518
+ * in a given room. This will identify the maximum possible level this client can change the user to,
1519
+ * and if that change could even be possible. If the returned object indicates that the client can
1520
+ * change the power level of the user, the client is able to set the power level to any value equal
1521
+ * to or less than the maximum value.
1522
+ * @param {string} targetUserId The user ID to compare against.
1523
+ * @param {string} roomId The room ID to compare within.
1524
+ * @returns {Promise<PowerLevelBounds>} The bounds of the client's ability to change the user's power level.
1525
+ */
1526
+ @timedMatrixClientFunctionCall()
1527
+ public async calculatePowerLevelChangeBoundsOn(targetUserId: string, roomId: string): Promise<PowerLevelBounds> {
1528
+ const myUserId = await this.getUserId();
1529
+
1530
+ const canChangePower = await this.userHasPowerLevelFor(myUserId, roomId, "m.room.power_levels", true);
1531
+ if (!canChangePower) return { canModify: false, maximumPossibleLevel: 0 };
1532
+
1533
+ const powerLevelsEvent = await this.getRoomStateEvent(roomId, "m.room.power_levels", "");
1534
+ if (!powerLevelsEvent) {
1535
+ throw new Error("No power level event found");
1536
+ }
1537
+
1538
+ let targetUserPower = 0;
1539
+ let myUserPower = 0;
1540
+ if (powerLevelsEvent["users"] && powerLevelsEvent["users"][targetUserId]) targetUserPower = powerLevelsEvent["users"][targetUserId];
1541
+ if (powerLevelsEvent["users"] && powerLevelsEvent["users"][myUserId]) myUserPower = powerLevelsEvent["users"][myUserId];
1542
+
1543
+ if (myUserId === targetUserId) {
1544
+ return { canModify: true, maximumPossibleLevel: myUserPower };
1545
+ }
1546
+
1547
+ if (targetUserPower >= myUserPower) {
1548
+ return { canModify: false, maximumPossibleLevel: myUserPower };
1549
+ }
1550
+
1551
+ return { canModify: true, maximumPossibleLevel: myUserPower };
1552
+ }
1553
+
1554
+ /**
1555
+ * Sets the power level for a given user ID in the given room. Note that this is not safe to
1556
+ * call multiple times concurrently as changes are not atomic. This will throw an error if
1557
+ * the user lacks enough permission to change the power level, or if a power level event is
1558
+ * missing from the room.
1559
+ * @param {string} userId The user ID to change
1560
+ * @param {string} roomId The room ID to change the power level in
1561
+ * @param {number} newLevel The integer power level to set the user to.
1562
+ * @returns {Promise<any>} Resolves when complete.
1563
+ */
1564
+ @timedMatrixClientFunctionCall()
1565
+ public async setUserPowerLevel(userId: string, roomId: string, newLevel: number): Promise<any> {
1566
+ const currentLevels = await this.getRoomStateEvent(roomId, "m.room.power_levels", "");
1567
+ if (!currentLevels['users']) currentLevels['users'] = {};
1568
+ currentLevels['users'][userId] = newLevel;
1569
+ await this.sendStateEvent(roomId, "m.room.power_levels", "", currentLevels);
1570
+ }
1571
+
1572
+ /**
1573
+ * Converts a MXC URI to an HTTP URL.
1574
+ * @param {string} mxc The MXC URI to convert
1575
+ * @returns {string} The HTTP URL for the content.
1576
+ */
1577
+ public mxcToHttp(mxc: string): string {
1578
+ if (!mxc.startsWith("mxc://")) throw new Error("Not a MXC URI");
1579
+ const parts = mxc.substring("mxc://".length).split('/');
1580
+ const originHomeserver = parts[0];
1581
+ const mediaId = parts.slice(1, parts.length).join('/');
1582
+ return `${this.homeserverUrl}/_matrix/media/v3/download/${encodeURIComponent(originHomeserver)}/${encodeURIComponent(mediaId)}`;
1583
+ }
1584
+
1585
+ /**
1586
+ * Converts a MXC URI to an HTTP URL for downsizing the content.
1587
+ * @param {string} mxc The MXC URI to convert and downsize.
1588
+ * @param {number} width The width, as an integer, for the thumbnail.
1589
+ * @param {number} height The height, as an intenger, for the thumbnail.
1590
+ * @param {"crop"|"scale"} method Whether to crop or scale (preserve aspect ratio) the content.
1591
+ * @returns {string} The HTTP URL for the downsized content.
1592
+ */
1593
+ public mxcToHttpThumbnail(mxc: string, width: number, height: number, method: "crop" | "scale"): string {
1594
+ const downloadUri = this.mxcToHttp(mxc);
1595
+ return downloadUri.replace("/_matrix/media/v3/download", "/_matrix/media/v3/thumbnail")
1596
+ + `?width=${width}&height=${height}&method=${encodeURIComponent(method)}`;
1597
+ }
1598
+
1599
+ /**
1600
+ * Uploads data to the homeserver's media repository. Note that this will <b>not</b> automatically encrypt
1601
+ * media as it cannot determine if the media should be encrypted.
1602
+ * @param {Buffer} data the content to upload.
1603
+ * @param {string} contentType the content type of the file. Defaults to application/octet-stream
1604
+ * @param {string} filename the name of the file. Optional.
1605
+ * @returns {Promise<string>} resolves to the MXC URI of the content
1606
+ */
1607
+ @timedMatrixClientFunctionCall()
1608
+ public uploadContent(data: Buffer, contentType = "application/octet-stream", filename: string = null): Promise<string> {
1609
+ // TODO: Make doRequest take an object for options
1610
+ return this.doRequest("POST", "/_matrix/media/v3/upload", { filename: filename }, data, 60000, false, contentType)
1611
+ .then(response => response["content_uri"]);
1612
+ }
1613
+
1614
+ /**
1615
+ * Download content from the homeserver's media repository. Note that this will <b>not</b> automatically decrypt
1616
+ * media as it cannot determine if the media is encrypted.
1617
+ * @param {string} mxcUrl The MXC URI for the content.
1618
+ * @param {string} allowRemote Indicates to the server that it should not attempt to fetch the
1619
+ * media if it is deemed remote. This is to prevent routing loops where the server contacts itself.
1620
+ * Defaults to true if not provided.
1621
+ * @returns {Promise<{data: Buffer, contentType: string}>} Resolves to the downloaded content.
1622
+ */
1623
+ public async downloadContent(mxcUrl: string, allowRemote = true): Promise<{ data: Buffer, contentType: string }> {
1624
+ if (!mxcUrl.toLowerCase().startsWith("mxc://")) {
1625
+ throw Error("'mxcUrl' does not begin with mxc://");
1626
+ }
1627
+ const urlParts = mxcUrl.substring("mxc://".length).split("/");
1628
+ const domain = encodeURIComponent(urlParts[0]);
1629
+ const mediaId = encodeURIComponent(urlParts[1].split("/")[0]);
1630
+ const path = `/_matrix/client/v1/media/download/${domain}/${mediaId}`;
1631
+ const res = await this.doRequest("GET", path, { allow_remote: allowRemote }, null, null, true, null, true);
1632
+ return {
1633
+ data: res.body,
1634
+ contentType: res.headers["content-type"],
1635
+ };
1636
+ }
1637
+
1638
+ /**
1639
+ * Uploads data to the homeserver's media repository after downloading it from the
1640
+ * provided URL.
1641
+ * @param {string} url The URL to download content from.
1642
+ * @returns {Promise<string>} Resolves to the MXC URI of the content
1643
+ */
1644
+ @timedMatrixClientFunctionCall()
1645
+ public uploadContentFromUrl(url: string): Promise<string> {
1646
+ return new Promise<{ body: Buffer, contentType: string }>((resolve, reject) => {
1647
+ const requestId = ++this.requestId;
1648
+ const params = {
1649
+ uri: url,
1650
+ method: "GET",
1651
+ encoding: null,
1652
+ };
1653
+ getRequestFn()(params, (err, response, resBody) => {
1654
+ if (err) {
1655
+ LogService.error("MatrixClientLite", "(REQ-" + requestId + ")", extractRequestError(err));
1656
+ reject(err);
1657
+ } else {
1658
+ const contentType = response.headers['content-type'] || "application/octet-stream";
1659
+
1660
+ LogService.trace("MatrixClientLite", "(REQ-" + requestId + " RESP-H" + response.statusCode + ")", "<data>");
1661
+ if (response.statusCode < 200 || response.statusCode >= 300) {
1662
+ LogService.error("MatrixClientLite", "(REQ-" + requestId + ")", "<data>");
1663
+ reject(response);
1664
+ } else resolve({ body: resBody, contentType: contentType });
1665
+ }
1666
+ });
1667
+ }).then(obj => {
1668
+ return this.uploadContent(obj.body, obj.contentType);
1669
+ });
1670
+ }
1671
+
1672
+ /**
1673
+ * Determines the upgrade history for a given room as a doubly-linked list styled structure. Given
1674
+ * a room ID in the history of upgrades, the resulting `previous` array will hold any rooms which
1675
+ * are older than the given room. The resulting `newer` array will hold any rooms which are newer
1676
+ * versions of the room. Both arrays will be defined, but may be empty individually. Element zero
1677
+ * of each will always be the nearest to the given room ID and the last element will be the furthest
1678
+ * from the room. The given room will never be in either array.
1679
+ * @param {string} roomId the room ID to get the history of
1680
+ * @returns {Promise<{previous: RoomReference[], newer: RoomReference[]}>} Resolves to the room's
1681
+ * upgrade history
1682
+ */
1683
+ @timedMatrixClientFunctionCall()
1684
+ public async getRoomUpgradeHistory(roomId: string): Promise<{ previous: RoomReference[], newer: RoomReference[], current: RoomReference }> {
1685
+ const result = { previous: [], newer: [], current: null };
1686
+
1687
+ const chaseCreates = async (findRoomId) => {
1688
+ try {
1689
+ const createEvent = await this.getRoomStateEvent(findRoomId, "m.room.create", "");
1690
+ if (!createEvent) return;
1691
+
1692
+ if (findRoomId === roomId && !result.current) {
1693
+ const version = createEvent['room_version'] || '1';
1694
+ result.current = {
1695
+ roomId: roomId,
1696
+ version: version,
1697
+ refEventId: null,
1698
+ };
1699
+ }
1700
+
1701
+ if (createEvent['predecessor'] && createEvent['predecessor']['room_id']) {
1702
+ const prevRoomId = createEvent['predecessor']['room_id'];
1703
+ if (prevRoomId === findRoomId) return; // Recursion is bad
1704
+ if (result.previous.find(r => r.roomId === prevRoomId)) return; // Already found
1705
+
1706
+ let tombstoneEventId = null;
1707
+ let prevVersion = "1";
1708
+ try {
1709
+ const roomState = await this.getRoomState(prevRoomId);
1710
+ const tombstone = roomState.find(e => e['type'] === 'm.room.tombstone' && e['state_key'] === '');
1711
+ const create = roomState.find(e => e['type'] === 'm.room.create' && e['state_key'] === '');
1712
+
1713
+ if (tombstone) {
1714
+ if (!tombstone['content']) tombstone['content'] = {};
1715
+ const tombstoneRefRoomId = tombstone['content']['replacement_room'];
1716
+ if (tombstoneRefRoomId === findRoomId) tombstoneEventId = tombstone['event_id'];
1717
+ }
1718
+
1719
+ if (create) {
1720
+ if (!create['content']) create['content'] = {};
1721
+ prevVersion = create['content']['room_version'] || "1";
1722
+ }
1723
+ } catch (e) {
1724
+ // state not available
1725
+ }
1726
+
1727
+ result.previous.push({
1728
+ roomId: prevRoomId,
1729
+ version: prevVersion,
1730
+ refEventId: tombstoneEventId,
1731
+ });
1732
+
1733
+ return chaseCreates(prevRoomId);
1734
+ }
1735
+ } catch (e) {
1736
+ // no create event - that's fine
1737
+ }
1738
+ };
1739
+
1740
+ const chaseTombstones = async (findRoomId) => {
1741
+ try {
1742
+ const tombstoneEvent = await this.getRoomStateEvent(findRoomId, "m.room.tombstone", "");
1743
+ if (!tombstoneEvent) return;
1744
+ if (!tombstoneEvent['replacement_room']) return;
1745
+
1746
+ const newRoomId = tombstoneEvent['replacement_room'];
1747
+ if (newRoomId === findRoomId) return; // Recursion is bad
1748
+ if (result.newer.find(r => r.roomId === newRoomId)) return; // Already found
1749
+
1750
+ let newRoomVersion = "1";
1751
+ let createEventId = null;
1752
+ try {
1753
+ const roomState = await this.getRoomState(newRoomId);
1754
+ const create = roomState.find(e => e['type'] === 'm.room.create' && e['state_key'] === '');
1755
+
1756
+ if (create) {
1757
+ if (!create['content']) create['content'] = {};
1758
+
1759
+ const predecessor = create['content']['predecessor'] || {};
1760
+ const refPrevRoomId = predecessor['room_id'];
1761
+ if (refPrevRoomId === findRoomId) {
1762
+ createEventId = create['event_id'];
1763
+ }
1764
+
1765
+ newRoomVersion = create['content']['room_version'] || "1";
1766
+ }
1767
+ } catch (e) {
1768
+ // state not available
1769
+ }
1770
+
1771
+ result.newer.push({
1772
+ roomId: newRoomId,
1773
+ version: newRoomVersion,
1774
+ refEventId: createEventId,
1775
+ });
1776
+
1777
+ return await chaseTombstones(newRoomId);
1778
+ } catch (e) {
1779
+ // no tombstone - that's fine
1780
+ }
1781
+ };
1782
+
1783
+ await chaseCreates(roomId);
1784
+ await chaseTombstones(roomId);
1785
+ return result;
1786
+ }
1787
+
1788
+ /**
1789
+ * Creates a Space room.
1790
+ * @param {SpaceCreateOptions} opts The creation options.
1791
+ * @returns {Promise<Space>} Resolves to the created space.
1792
+ */
1793
+ @timedMatrixClientFunctionCall()
1794
+ public async createSpace(opts: SpaceCreateOptions): Promise<Space> {
1795
+ const roomCreateOpts: RoomCreateOptions = {
1796
+ name: opts.name,
1797
+ topic: opts.topic || "",
1798
+ preset: opts.isPublic ? "public_chat" : "private_chat",
1799
+ room_alias_name: opts.localpart,
1800
+ initial_state: [
1801
+ {
1802
+ type: "m.room.history_visibility",
1803
+ state_key: "",
1804
+ content: {
1805
+ history_visibility: opts.isPublic ? 'world_readable' : 'shared',
1806
+ },
1807
+ },
1808
+ ],
1809
+ creation_content: {
1810
+ type: "m.space",
1811
+ },
1812
+ invite: opts.invites || [],
1813
+ power_level_content_override: {
1814
+ ban: 100,
1815
+ events_default: 50,
1816
+ invite: 50,
1817
+ kick: 100,
1818
+ notifications: {
1819
+ room: 100,
1820
+ },
1821
+ redact: 100,
1822
+ state_default: 100,
1823
+ users: {
1824
+ [await this.getUserId()]: 100,
1825
+ },
1826
+ users_default: 0,
1827
+ },
1828
+ };
1829
+ if (opts.avatarUrl) {
1830
+ roomCreateOpts.initial_state.push({
1831
+ type: 'm.room.avatar',
1832
+ state_key: "",
1833
+ content: {
1834
+ url: opts.avatarUrl,
1835
+ },
1836
+ });
1837
+ }
1838
+ const roomId = await this.createRoom(roomCreateOpts);
1839
+ return new Space(roomId, this);
1840
+ }
1841
+
1842
+ /**
1843
+ * Gets a Space.
1844
+ * This API does not work with unstable spaces (e.g. org.matrix.msc.1772.space)
1845
+ *
1846
+ * @throws If the room is not a space or there was an error
1847
+ * @returns {Promise<Space>} Resolves to the space.
1848
+ */
1849
+ @timedMatrixClientFunctionCall()
1850
+ public async getSpace(roomIdOrAlias: string): Promise<Space> {
1851
+ const roomId = await this.resolveRoom(roomIdOrAlias);
1852
+ const createEvent = await this.getRoomStateEvent(roomId, "m.room.create", "");
1853
+ if (createEvent["type"] !== "m.space") {
1854
+ throw new Error("Room is not a space");
1855
+ }
1856
+ return new Space(roomId, this);
1857
+ }
1858
+
1859
+ /**
1860
+ * Uploads One Time Keys for the current device.
1861
+ * @param {OTKs} keys The keys to upload.
1862
+ * @returns {Promise<OTKCounts>} Resolves to the current One Time Key counts when complete.
1863
+ */
1864
+ @timedMatrixClientFunctionCall()
1865
+ @requiresCrypto()
1866
+ public async uploadDeviceOneTimeKeys(keys: OTKs): Promise<OTKCounts> {
1867
+ return this.doRequest("POST", "/_matrix/client/v3/keys/upload", null, {
1868
+ one_time_keys: keys,
1869
+ }).then(r => r['one_time_key_counts']);
1870
+ }
1871
+
1872
+ /**
1873
+ * Gets the current One Time Key counts.
1874
+ * @returns {Promise<OTKCounts>} Resolves to the One Time Key counts.
1875
+ */
1876
+ @timedMatrixClientFunctionCall()
1877
+ @requiresCrypto()
1878
+ public async checkOneTimeKeyCounts(): Promise<OTKCounts> {
1879
+ return this.doRequest("POST", "/_matrix/client/v3/keys/upload", null, {})
1880
+ .then(r => r['one_time_key_counts']);
1881
+ }
1882
+
1883
+ /**
1884
+ * Uploads a fallback One Time Key to the server for usage. This will replace the existing fallback
1885
+ * key.
1886
+ * @param {FallbackKey} fallbackKey The fallback key.
1887
+ * @returns {Promise<OTKCounts>} Resolves to the One Time Key counts.
1888
+ */
1889
+ @timedMatrixClientFunctionCall()
1890
+ @requiresCrypto()
1891
+ public async uploadFallbackKey(fallbackKey: FallbackKey): Promise<OTKCounts> {
1892
+ const keyObj = {
1893
+ [`${OTKAlgorithm.Signed}:${fallbackKey.keyId}`]: fallbackKey.key,
1894
+ };
1895
+ return this.doRequest("POST", "/_matrix/client/v3/keys/upload", null, {
1896
+ "org.matrix.msc2732.fallback_keys": keyObj,
1897
+ "fallback_keys": keyObj,
1898
+ }).then(r => r['one_time_key_counts']);
1899
+ }
1900
+
1901
+ /**
1902
+ * Gets <b>unverified</b> device lists for the given users. The caller is expected to validate
1903
+ * and verify the device lists, including that the returned devices belong to the claimed users.
1904
+ *
1905
+ * Failures with federation are reported in the returned object. Users which did not fail a federation
1906
+ * lookup but have no devices will not appear in either the failures or in the returned devices.
1907
+ *
1908
+ * See https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-keys-query for more
1909
+ * information.
1910
+ * @param {string[]} userIds The user IDs to query.
1911
+ * @param {number} federationTimeoutMs The default timeout for requesting devices over federation. Defaults to
1912
+ * 10 seconds.
1913
+ * @returns {Promise<MultiUserDeviceListResponse>} Resolves to the device list/errors for the requested user IDs.
1914
+ */
1915
+ @timedMatrixClientFunctionCall()
1916
+ public async getUserDevices(userIds: string[], federationTimeoutMs = 10000): Promise<MultiUserDeviceListResponse> {
1917
+ const req = {};
1918
+ for (const userId of userIds) {
1919
+ req[userId] = [];
1920
+ }
1921
+ return this.doRequest("POST", "/_matrix/client/v3/keys/query", {}, {
1922
+ timeout: federationTimeoutMs,
1923
+ device_keys: req,
1924
+ });
1925
+ }
1926
+
1927
+ /**
1928
+ * Gets a device list for the client's own account, with metadata. The devices are not verified
1929
+ * in this response, but should be active on the account.
1930
+ * @returns {Promise<OwnUserDevice[]>} Resolves to the active devices on the account.
1931
+ */
1932
+ @timedMatrixClientFunctionCall()
1933
+ public async getOwnDevices(): Promise<OwnUserDevice[]> {
1934
+ return this.doRequest("GET", "/_matrix/client/v3/devices").then(r => {
1935
+ return r['devices'];
1936
+ });
1937
+ }
1938
+
1939
+ /**
1940
+ * Claims One Time Keys for a set of user devices, returning those keys. The caller is expected to verify
1941
+ * and validate the returned keys.
1942
+ *
1943
+ * Failures with federation are reported in the returned object.
1944
+ * @param {Record<string, Record<string, OTKAlgorithm>>} userDeviceMap The map of user IDs to device IDs to
1945
+ * OTKAlgorithm to request a claim for.
1946
+ * @param {number} federationTimeoutMs The default timeout for claiming keys over federation. Defaults to
1947
+ * 10 seconds.
1948
+ */
1949
+ @timedMatrixClientFunctionCall()
1950
+ @requiresCrypto()
1951
+ public async claimOneTimeKeys(userDeviceMap: Record<string, Record<string, OTKAlgorithm>>, federationTimeoutMs = 10000): Promise<OTKClaimResponse> {
1952
+ return this.doRequest("POST", "/_matrix/client/v3/keys/claim", {}, {
1953
+ timeout: federationTimeoutMs,
1954
+ one_time_keys: userDeviceMap,
1955
+ });
1956
+ }
1957
+
1958
+ /**
1959
+ * Sends to-device messages to the respective users/devices.
1960
+ * @param {string} type The message type being sent.
1961
+ * @param {Record<string, Record<string, any>>} messages The messages to send, mapped as user ID to
1962
+ * device ID (or "*" to denote all of the user's devices) to message payload (content).
1963
+ * @returns {Promise<void>} Resolves when complete.
1964
+ */
1965
+ @timedMatrixClientFunctionCall()
1966
+ public async sendToDevices(type: string, messages: Record<string, Record<string, any>>): Promise<void> {
1967
+ const txnId = (new Date().getTime()) + "_TDEV__inc" + (++this.requestId);
1968
+ return this.doRequest("PUT", `/_matrix/client/v3/sendToDevice/${encodeURIComponent(type)}/${encodeURIComponent(txnId)}`, null, {
1969
+ messages: messages,
1970
+ });
1971
+ }
1972
+
1973
+ /**
1974
+ * Get relations for a given event.
1975
+ * @param {string} roomId The room ID to for the given event.
1976
+ * @param {string} eventId The event ID to list relations for.
1977
+ * @param {string?} relationType The type of relations (e.g. `m.room.member`) to filter for. Optional.
1978
+ * @param {string?} eventType The type of event to look for (e.g. `m.room.member`). Optional.
1979
+ * @returns {Promise<{chunk: any[]}>} Resolves to an object containing the chunk of relations
1980
+ */
1981
+ @timedMatrixClientFunctionCall()
1982
+ public async getRelationsForEvent(roomId: string, eventId: string, relationType?: string, eventType?: string): Promise<{ chunk: any[] }> {
1983
+ let url = `/_matrix/client/v1/rooms/${encodeURIComponent(roomId)}/relations/${encodeURIComponent(eventId)}`;
1984
+ if (relationType) {
1985
+ url += `/${relationType}`;
1986
+ }
1987
+ if (eventType) {
1988
+ url += `/${eventType}`;
1989
+ }
1990
+ return this.doRequest("GET", url);
1991
+ }
1992
+
1993
+ /**
1994
+ * Performs a web request to the homeserver, applying appropriate authorization headers for
1995
+ * this client.
1996
+ * @param {"GET"|"POST"|"PUT"|"DELETE"} method The HTTP method to use in the request
1997
+ * @param {string} endpoint The endpoint to call. For example: "/_matrix/client/v3/account/whoami"
1998
+ * @param {any} qs The query string to send. Optional.
1999
+ * @param {any} body The request body to send. Optional. Will be converted to JSON unless the type is a Buffer.
2000
+ * @param {number} timeout The number of milliseconds to wait before timing out.
2001
+ * @param {boolean} raw If true, the raw response will be returned instead of the response body.
2002
+ * @param {string} contentType The content type to send. Only used if the `body` is a Buffer.
2003
+ * @param {string} noEncoding Set to true to disable encoding, and return a Buffer. Defaults to false
2004
+ * @returns {Promise<any>} Resolves to the response (body), rejected if a non-2xx status code was returned.
2005
+ */
2006
+ @timedMatrixClientFunctionCall()
2007
+ public doRequest(method, endpoint, qs = null, body = null, timeout = 60000, raw = false, contentType = "application/json", noEncoding = false): Promise<any> {
2008
+ if (this.impersonatedUserId) {
2009
+ if (!qs) qs = { "user_id": this.impersonatedUserId };
2010
+ else qs["user_id"] = this.impersonatedUserId;
2011
+ }
2012
+ if (this.impersonatedDeviceId) {
2013
+ if (!qs) qs = { "org.matrix.msc3202.device_id": this.impersonatedDeviceId };
2014
+ else qs["org.matrix.msc3202.device_id"] = this.impersonatedDeviceId;
2015
+ }
2016
+ const headers = {};
2017
+ if (this.accessToken) {
2018
+ headers["Authorization"] = `Bearer ${this.accessToken}`;
2019
+ }
2020
+ return doHttpRequest(this.homeserverUrl, method, endpoint, qs, body, headers, timeout, raw, contentType, noEncoding);
2021
+ }
2022
+ }
2023
+
2024
+ export interface RoomDirectoryLookupResponse {
2025
+ roomId: string;
2026
+ residentServers: string[];
2027
+ }
2028
+
2029
+ export interface RoomReference {
2030
+ /**
2031
+ * The room ID being referenced
2032
+ */
2033
+ roomId: string;
2034
+
2035
+ /**
2036
+ * The version of the room at the time
2037
+ */
2038
+ version: string;
2039
+
2040
+ /**
2041
+ * If going backwards, the tombstone event ID, otherwise the creation
2042
+ * event. If the room can't be verified, this will be null. Will be
2043
+ * null if this reference is to the current room.
2044
+ */
2045
+ refEventId: string;
2046
+ }