Source: bridge.js

  1. "use strict";
  2. const Datastore = require("nedb");
  3. const Promise = require("bluebird");
  4. const fs = require("fs");
  5. const util = require("util");
  6. const yaml = require("js-yaml");
  7. const AppServiceRegistration = require("matrix-appservice").AppServiceRegistration;
  8. const AppService = require("matrix-appservice").AppService;
  9. const MatrixScheduler = require("matrix-js-sdk").MatrixScheduler;
  10. const BridgeContext = require("./components/bridge-context");
  11. const ClientFactory = require("./components/client-factory");
  12. const AppServiceBot = require("./components/app-service-bot");
  13. const RequestFactory = require("./components/request-factory");
  14. const Intent = require("./components/intent");
  15. const RoomBridgeStore = require("./components/room-bridge-store");
  16. const UserBridgeStore = require("./components/user-bridge-store");
  17. const EventBridgeStore = require("./components/event-bridge-store");
  18. const MatrixUser = require("./models/users/matrix");
  19. const MatrixRoom = require("./models/rooms/matrix");
  20. const PrometheusMetrics = require("./components/prometheusmetrics");
  21. const MembershipCache = require("./components/membership-cache");
  22. const RoomLinkValidator = require("./components/room-link-validator").RoomLinkValidator;
  23. const RLVStatus = require("./components/room-link-validator").validationStatuses;
  24. const RoomUpgradeHandler = require("./components/room-upgrade-handler");
  25. const EventNotHandledError = require("./errors").EventNotHandledError;
  26. const InternalError = require("./errors").InternalError;
  27. const EventQueue = require("./components/event-queue").EventQueue;
  28. const log = require("./components/logging").get("bridge");
  29. // The frequency at which we will check the list of accumulated Intent objects.
  30. const INTENT_CULL_CHECK_PERIOD_MS = 1000 * 60; // once per minute
  31. // How long a given Intent object can hang around unused for.
  32. const INTENT_CULL_EVICT_AFTER_MS = 1000 * 60 * 15; // 15 minutes
  33. /**
  34. * @constructor
  35. * @param {Object} opts Options to pass to the bridge
  36. * @param {AppServiceRegistration|string} opts.registration Application service
  37. * registration object or path to the registration file.
  38. * @param {string} opts.homeserverUrl The base HS url
  39. * @param {string} opts.domain The domain part for user_ids and room aliases
  40. * e.g. "bar" in "@foo:bar".
  41. * @param {string} opts.networkName A human readable string that will be used when
  42. * the bridge signals errors to the client. Will not include in error events if ommited.
  43. * @param {Object} opts.controller The controller logic for the bridge.
  44. * @param {Bridge~onEvent} opts.controller.onEvent Function. Called when
  45. * an event has been received from the HS.
  46. * @param {Bridge~onUserQuery=} opts.controller.onUserQuery Function. If supplied,
  47. * the bridge will invoke this function when queried via onUserQuery. If
  48. * not supplied, no users will be provisioned on user queries. Provisioned users
  49. * will automatically be stored in the associated <code>userStore</code>.
  50. * @param {Bridge~onAliasQuery=} opts.controller.onAliasQuery Function. If supplied,
  51. * the bridge will invoke this function when queried via onAliasQuery. If
  52. * not supplied, no rooms will be provisioned on alias queries. Provisioned rooms
  53. * will automatically be stored in the associated <code>roomStore</code>.
  54. * @param {Bridge~onAliasQueried=} opts.controller.onAliasQueried Function.
  55. * If supplied, the bridge will invoke this function when a room has been created
  56. * via onAliasQuery.
  57. * @param {Bridge~onLog=} opts.controller.onLog Function. Invoked when
  58. * logging. Defaults to a function which logs to the console.
  59. * @param {Bridge~thirdPartyLookup=} opts.controller.thirdPartyLookup Object. If
  60. * supplied, the bridge will respond to third-party entity lookups using the
  61. * contained helper functions.
  62. * @param {Bridge~onRoomUpgrade=} opts.controller.onRoomUpgrade Function. If
  63. * supplied, the bridge will invoke this function when it sees an upgrade event
  64. * for a room.
  65. * @param {(RoomBridgeStore|string)=} opts.roomStore The room store instance to
  66. * use, or the path to the room .db file to load. A database will be created if
  67. * this is not specified.
  68. * @param {(UserBridgeStore|string)=} opts.userStore The user store instance to
  69. * use, or the path to the user .db file to load. A database will be created if
  70. * this is not specified.
  71. * @param {(EventBridgeStore|string)=} opts.eventStore The event store instance to
  72. * use, or the path to the event .db file to load. This will NOT be created if it
  73. * isn't specified.
  74. * @param {MembershipCache=} opts.membershipCache The membership cache instance
  75. * to use, which can be manually created by a bridge for greater control over
  76. * caching. By default a membership cache will be created internally.
  77. * @param {boolean=} opts.suppressEcho True to stop receiving onEvent callbacks
  78. * for events which were sent by a bridge user. Default: true.
  79. * @param {ClientFactory=} opts.clientFactory The client factory instance to
  80. * use. If not supplied, one will be created.
  81. * @param {boolean} opts.logRequestOutcome True to enable SUCCESS/FAILED log lines
  82. * to be sent to onLog. Default: true.
  83. * @param {Object=} opts.intentOptions Options to supply to created Intent instances.
  84. * @param {Object=} opts.intentOptions.bot Options to supply to the bot intent.
  85. * @param {Object=} opts.intentOptions.clients Options to supply to the client intents.
  86. * @param {Object=} opts.escapeUserIds Escape userIds for non-bot intents with
  87. * {@link MatrixUser~escapeUserId}
  88. * Default: true
  89. * @param {Object=} opts.queue Options for the onEvent queue. When the bridge
  90. * receives an incoming transaction, it needs to asyncly query the data store for
  91. * contextual info before calling onEvent. A queue is used to keep the onEvent
  92. * calls consistent with the arrival order from the incoming transactions.
  93. * @param {string=} opts.queue.type The type of queue to use when feeding through
  94. * to {@link Bridge~onEvent}. One of: "none", single", "per_room". If "none",
  95. * events are fed through as soon as contextual info is obtained, which may result
  96. * in out of order events but stops HOL blocking. If "single", onEvent calls will
  97. * be in order but may be slower due to HOL blocking. If "per_room", a queue per
  98. * room ID is made which reduces the impact of HOL blocking to be scoped to a room.
  99. * Default: "single".
  100. * @param {boolean=} opts.queue.perRequest True to only feed through the next
  101. * event after the request object in the previous call succeeds or fails. It is
  102. * <b>vital</b> that you consistently resolve/reject the request if this is 'true',
  103. * else you will not get any further events from this queue. To aid debugging this,
  104. * consider setting a delayed listener on the request factory. If false, the mere
  105. * invockation of onEvent is enough to trigger the next event in the queue.
  106. * You probably want to set this to 'true' if your {@link Bridge~onEvent} is
  107. * performing async operations where ordering matters (e.g. messages). Default: false.
  108. * @param {boolean=} opts.disableContext True to disable {@link Bridge~BridgeContext}
  109. * parameters in {@link Bridge~onEvent}. Disabling the context makes the
  110. * bridge do fewer database lookups, but prevents there from being a
  111. * <code>context</code> parameter. Default: false.
  112. * @param {boolean=} opts.disableStores True to disable enabling of stores.
  113. * This should be used by bridges that use their own database instances and
  114. * do not need any of the included store objects. This implies setting
  115. * disableContext to True. Default: false.
  116. * @param {Object=} opts.roomLinkValidation Options to supply to the room link
  117. * validator. If not defined then all room links are accepted.
  118. * @param {string} opts.roomLinkValidation.ruleFile A file containing rules
  119. * on which matrix rooms can be bridged.
  120. * @param {Object=} opts.roomLinkValidation.rules A object containing rules
  121. * on which matrix rooms can be bridged. This is used if ruleFile is undefined.
  122. * @param {boolean=} opts.roomLinkValidation.triggerEndpoint Enable the endpoint
  123. * to trigger a reload of the rules file.
  124. * Default: false
  125. * @param {RoomUpgradeHandler~Options} opts.roomUpgradeOpts Options to supply to
  126. * the room upgrade handler. If not defined then upgrades are NOT handled by the bridge.
  127. */
  128. function Bridge(opts) {
  129. if (typeof opts !== "object") {
  130. throw new Error("opts must be supplied.");
  131. }
  132. var required = [
  133. "homeserverUrl", "registration", "domain", "controller"
  134. ];
  135. required.forEach(function(key) {
  136. if (!opts[key]) {
  137. throw new Error("Missing '" + key + "' in opts.");
  138. }
  139. });
  140. if (typeof opts.controller.onEvent !== "function") {
  141. throw new Error("controller.onEvent is a required function");
  142. }
  143. if (opts.disableContext === undefined) {
  144. opts.disableContext = false;
  145. }
  146. if (opts.disableStores === true) {
  147. opts.disableStores = true;
  148. opts.disableContext = true;
  149. }
  150. else {
  151. opts.disableStores = false;
  152. }
  153. opts.userStore = opts.userStore || "user-store.db";
  154. opts.roomStore = opts.roomStore || "room-store.db";
  155. opts.eventStore = opts.eventStore || null; // Must be enabled
  156. opts.queue = opts.queue || {};
  157. opts.intentOptions = opts.intentOptions || {};
  158. opts.queue.type = opts.queue.type || "single";
  159. if (opts.queue.perRequest === undefined) {
  160. opts.queue.perRequest = false;
  161. }
  162. if (opts.logRequestOutcome === undefined) {
  163. opts.logRequestOutcome = true;
  164. }
  165. // Default: logger -> log to console
  166. opts.controller.onLog = opts.controller.onLog || function(text, isError) {
  167. if (isError) {
  168. log.error(text);
  169. return;
  170. }
  171. log.info(text);
  172. };
  173. // Default: suppress echo -> True
  174. if (opts.suppressEcho === undefined) {
  175. opts.suppressEcho = true;
  176. }
  177. // we'll init these at runtime
  178. this.appService = null;
  179. this.opts = opts;
  180. this._clientFactory = null;
  181. this._botClient = null;
  182. this._appServiceBot = null;
  183. this._requestFactory = null;
  184. this._botIntent = null;
  185. this._intents = {
  186. // user_id + request_id : Intent
  187. };
  188. this._intentLastAccessed = Object.create(null); // user_id + request_id : timestamp
  189. this._intentLastAccessedTimeout = null;
  190. this._powerLevelMap = {
  191. // room_id: event.content
  192. };
  193. this._membershipCache = opts.membershipCache || new MembershipCache();
  194. this._intentBackingStore = {
  195. setMembership: this._membershipCache.setMemberEntry.bind(this._membershipCache),
  196. setPowerLevelContent: this._setPowerLevelEntry.bind(this),
  197. getMembership: this._membershipCache.getMemberEntry.bind(this._membershipCache),
  198. getPowerLevelContent: this._getPowerLevelEntry.bind(this)
  199. };
  200. this._queue = EventQueue.create(this.opts.queue, this._onConsume.bind(this));
  201. this._prevRequestPromise = Promise.resolve();
  202. this._metrics = null; // an optional PrometheusMetrics instance
  203. this._roomLinkValidator = null;
  204. if (opts.roomUpgradeOpts) {
  205. opts.roomUpgradeOpts.consumeEvent = opts.roomUpgradeOpts.consumeEvent !== false ? true : false;
  206. if (this.opts.disableStores) {
  207. opts.roomUpgradeOpts.migrateStoreEntries = false;
  208. }
  209. this._roomUpgradeHandler = new RoomUpgradeHandler(opts.roomUpgradeOpts, this);
  210. }
  211. else {
  212. this._roomUpgradeHandler = null;
  213. }
  214. }
  215. /**
  216. * Load the user and room databases. Access them via getUserStore() and getRoomStore().
  217. * @return {Promise} Resolved/rejected when the user/room databases have been loaded.
  218. */
  219. Bridge.prototype.loadDatabases = function() {
  220. if (this.disableStores) {
  221. return Promise.resolve();
  222. }
  223. // Load up the databases if they provided file paths to them (or defaults)
  224. if (typeof this.opts.userStore === "string") {
  225. this.opts.userStore = loadDatabase(this.opts.userStore, UserBridgeStore);
  226. }
  227. if (typeof this.opts.roomStore === "string") {
  228. this.opts.roomStore = loadDatabase(this.opts.roomStore, RoomBridgeStore);
  229. }
  230. if (typeof this.opts.eventStore === "string") {
  231. this.opts.eventStore = loadDatabase(this.opts.eventStore, EventBridgeStore);
  232. }
  233. // This works because if they provided a string we converted it to a Promise
  234. // which will be resolved when we have the db instance. If they provided a
  235. // db instance then this will resolve immediately.
  236. return Promise.all([
  237. Promise.resolve(this.opts.userStore).then((db) => {
  238. this._userStore = db;
  239. }),
  240. Promise.resolve(this.opts.roomStore).then((db) => {
  241. this._roomStore = db;
  242. }),
  243. Promise.resolve(this.opts.eventStore).then((db) => {
  244. this._eventStore = db;
  245. })
  246. ]);
  247. };
  248. /**
  249. * Run the bridge (start listening)
  250. * @param {Number} port The port to listen on.
  251. * @param {Object} config Configuration options
  252. * @param {AppService=} appServiceInstance The AppService instance to attach to.
  253. * If not provided, one will be created.
  254. * @param {String} hostname Optional hostname to bind to. (e.g. 0.0.0.0)
  255. */
  256. Bridge.prototype.run = function(port, config, appServiceInstance, hostname) {
  257. var self = this;
  258. // Load the registration file into an AppServiceRegistration object.
  259. if (typeof self.opts.registration === "string") {
  260. var regObj = yaml.safeLoad(fs.readFileSync(self.opts.registration, 'utf8'));
  261. self.opts.registration = AppServiceRegistration.fromObject(regObj);
  262. if (self.opts.registration === null) {
  263. throw new Error("Failed to parse registration file");
  264. }
  265. }
  266. this._clientFactory = self.opts.clientFactory || new ClientFactory({
  267. url: self.opts.homeserverUrl,
  268. token: self.opts.registration.as_token,
  269. appServiceUserId: (
  270. "@" + self.opts.registration.sender_localpart + ":" + self.opts.domain
  271. ),
  272. clientSchedulerBuilder: function() {
  273. return new MatrixScheduler(retryAlgorithm, queueAlgorithm);
  274. },
  275. });
  276. this._clientFactory.setLogFunction(function(text, isErr) {
  277. if (!self.opts.controller.onLog) {
  278. return;
  279. }
  280. self.opts.controller.onLog(text, isErr);
  281. });
  282. this._botClient = this._clientFactory.getClientAs();
  283. this._appServiceBot = new AppServiceBot(
  284. this._botClient, self.opts.registration, this._membershipCache
  285. );
  286. if (this.opts.roomLinkValidation !== undefined) {
  287. this._roomLinkValidator = new RoomLinkValidator(
  288. this.opts.roomLinkValidation,
  289. this._appServiceBot
  290. );
  291. }
  292. this._requestFactory = new RequestFactory();
  293. if (this.opts.controller.onLog && this.opts.logRequestOutcome) {
  294. this._requestFactory.addDefaultResolveCallback(function(req, res) {
  295. self.opts.controller.onLog(
  296. "[" + req.getId() + "] SUCCESS (" + req.getDuration() + "ms)"
  297. );
  298. });
  299. this._requestFactory.addDefaultRejectCallback(function(req, err) {
  300. self.opts.controller.onLog(
  301. "[" + req.getId() + "] FAILED (" + req.getDuration() + "ms) " +
  302. (err ? util.inspect(err) : "")
  303. );
  304. });
  305. }
  306. var botIntentOpts = {
  307. registered: true,
  308. backingStore: this._intentBackingStore,
  309. };
  310. if (this.opts.intentOptions.bot) { // copy across opts
  311. Object.keys(this.opts.intentOptions.bot).forEach(function(k) {
  312. botIntentOpts[k] = self.opts.intentOptions.bot[k];
  313. });
  314. }
  315. this._botIntent = new Intent(this._botClient, this._botClient, botIntentOpts);
  316. this._intents = {
  317. // user_id + request_id : Intent
  318. };
  319. this.appService = appServiceInstance || new AppService({
  320. homeserverToken: this.opts.registration.getHomeserverToken()
  321. });
  322. this.appService.onUserQuery = (userId) => Promise.cast(this._onUserQuery(userId));
  323. this.appService.onAliasQuery = this._onAliasQuery.bind(this);
  324. this.appService.on("event", this._onEvent.bind(this));
  325. this.appService.on("http-log", function(line) {
  326. if (!self.opts.controller.onLog) {
  327. return;
  328. }
  329. self.opts.controller.onLog(line, false);
  330. });
  331. this._customiseAppservice();
  332. this._setupIntentCulling();
  333. if (this._metrics) {
  334. this._metrics.addAppServicePath(this);
  335. }
  336. this.appService.listen(port, hostname);
  337. return this.loadDatabases();
  338. };
  339. /**
  340. * Apply any customisations required on the appService object.
  341. */
  342. Bridge.prototype._customiseAppservice = function() {
  343. if (this.opts.controller.thirdPartyLookup) {
  344. this._customiseAppserviceThirdPartyLookup(this.opts.controller.thirdPartyLookup);
  345. }
  346. if (this.opts.roomLinkValidation && this.opts.roomLinkValidation.triggerEndpoint) {
  347. this.addAppServicePath({
  348. method: "POST",
  349. path: "/_bridge/roomLinkValidator/reload",
  350. handler: (req, res) => {
  351. if (!this._requestCheckToken(req, res)) {
  352. return;
  353. }
  354. try {
  355. // Will use filename if provided, or the config
  356. // one otherwised.
  357. this._roomLinkValidator.readRuleFile(req.query.filename);
  358. res.status(200).send("Success");
  359. }
  360. catch (e) {
  361. res.status(500).send("Failed: " + e);
  362. }
  363. },
  364. });
  365. }
  366. };
  367. // Set a timer going which will periodically remove Intent objects to prevent
  368. // them from accumulating too much. Removal is based on access time (calls to
  369. // getIntent). Intents expire after INTENT_CULL_EVICT_AFTER_MS of not being called.
  370. Bridge.prototype._setupIntentCulling = function() {
  371. if (this._intentLastAccessedTimeout) {
  372. clearTimeout(this._intentLastAccessedTimeout);
  373. }
  374. var self = this;
  375. this._intentLastAccessedTimeout = setTimeout(function() {
  376. var now = Date.now();
  377. Object.keys(self._intentLastAccessed).forEach(function(key) {
  378. if ((self._intentLastAccessed[key] + INTENT_CULL_EVICT_AFTER_MS) < now) {
  379. delete self._intentLastAccessed[key];
  380. delete self._intents[key];
  381. }
  382. });
  383. self._intentLastAccessedTimeout = null;
  384. // repeat forever. We have no cancellation mechanism but we don't expect
  385. // Bridge objects to be continually recycled so this is fine.
  386. self._setupIntentCulling();
  387. }, INTENT_CULL_CHECK_PERIOD_MS);
  388. }
  389. Bridge.prototype._customiseAppserviceThirdPartyLookup = function(lookupController) {
  390. var protocols = lookupController.protocols || [];
  391. var _respondErr = function(e, res) {
  392. if (typeof e === "object" && e.code && e.err) {
  393. res.status(e.code).json({error: e.err});
  394. }
  395. else {
  396. res.status(500).send("Failed: " + e);
  397. }
  398. }
  399. if (lookupController.getProtocol) {
  400. var getProtocolFunc = lookupController.getProtocol;
  401. this.addAppServicePath({
  402. method: "GET",
  403. path: "/_matrix/app/:ver/thirdparty/protocol/:protocol",
  404. handler: function(req, res) {
  405. if (req.params.ver !== "unstable") {
  406. res.status(404).json(
  407. {err: "Unrecognised API version " + req.params.ver}
  408. );
  409. return;
  410. }
  411. var protocol = req.params.protocol;
  412. if (protocols.length && protocols.indexOf(protocol) === -1) {
  413. res.status(404).json({err: "Unknown 3PN protocol " + protocol});
  414. return;
  415. }
  416. getProtocolFunc(protocol).then(
  417. function(result) { res.status(200).json(result) },
  418. function(e) { _respondErr(e, res) }
  419. );
  420. },
  421. });
  422. }
  423. if (lookupController.getLocation) {
  424. var getLocationFunc = lookupController.getLocation;
  425. this.addAppServicePath({
  426. method: "GET",
  427. path: "/_matrix/app/:ver/thirdparty/location/:protocol",
  428. handler: function(req, res) {
  429. if (req.params.ver !== "unstable") {
  430. res.status(404).json(
  431. {err: "Unrecognised API version " + req.params.ver}
  432. );
  433. return;
  434. }
  435. var protocol = req.params.protocol;
  436. if (protocols.length && protocols.indexOf(protocol) === -1) {
  437. res.status(404).json({err: "Unknown 3PN protocol " + protocol});
  438. return;
  439. }
  440. getLocationFunc(protocol, req.query).then(
  441. function(result) { res.status(200).json(result) },
  442. function(e) { _respondErr(e, res) }
  443. );
  444. },
  445. });
  446. }
  447. if (lookupController.parseLocation) {
  448. var parseLocationFunc = lookupController.parseLocation;
  449. this.addAppServicePath({
  450. method: "GET",
  451. path: "/_matrix/app/:ver/thirdparty/location",
  452. handler: function(req, res) {
  453. if (req.params.ver !== "unstable") {
  454. res.status(404).json(
  455. {err: "Unrecognised API version " + req.params.ver}
  456. );
  457. return;
  458. }
  459. var alias = req.query.alias;
  460. if (!alias) {
  461. res.status(400).send({err: "Missing 'alias' parameter"});
  462. return;
  463. }
  464. parseLocationFunc(alias).then(
  465. function(result) { res.status(200).json(result) },
  466. function(e) { _respondErr(e, res) }
  467. );
  468. },
  469. });
  470. }
  471. if (lookupController.getUser) {
  472. var getUserFunc = lookupController.getUser;
  473. this.addAppServicePath({
  474. method: "GET",
  475. path: "/_matrix/app/:ver/thirdparty/user/:protocol",
  476. handler: function(req, res) {
  477. if (req.params.ver !== "unstable") {
  478. res.status(404).json(
  479. {err: "Unrecognised API version " + req.params.ver}
  480. );
  481. return;
  482. }
  483. var protocol = req.params.protocol;
  484. if (protocols.length && protocols.indexOf(protocol) === -1) {
  485. res.status(404).json({err: "Unknown 3PN protocol " + protocol});
  486. return;
  487. }
  488. getUserFunc(protocol, req.query).then(
  489. function(result) { res.status(200).json(result) },
  490. function(e) { _respondErr(e, res) }
  491. );
  492. }
  493. });
  494. }
  495. if (lookupController.parseUser) {
  496. var parseUserFunc = lookupController.parseUser;
  497. this.addAppServicePath({
  498. method: "GET",
  499. path: "/_matrix/app/:ver/thirdparty/user",
  500. handler: function(req, res) {
  501. if (req.params.ver !== "unstable") {
  502. res.status(404).json(
  503. {err: "Unrecognised API version " + req.params.ver}
  504. );
  505. return;
  506. }
  507. var userid = req.query.userid;
  508. if (!userid) {
  509. res.status(400).send({err: "Missing 'userid' parameter"});
  510. return;
  511. }
  512. parseUserFunc(userid).then(
  513. function(result) { res.status(200).json(result) },
  514. function(e) { _respondErr(e, res) }
  515. );
  516. },
  517. });
  518. }
  519. };
  520. /**
  521. * Install a custom handler for an incoming HTTP API request. This allows
  522. * callers to add extra functionality, implement new APIs, etc...
  523. * @param {Object} opts Named options
  524. * @param {string} opts.method The HTTP method name.
  525. * @param {string} opts.path Path to the endpoint.
  526. * @param {Bridge~appServicePathHandler} opts.handler Function to handle requests
  527. * to this endpoint.
  528. */
  529. Bridge.prototype.addAppServicePath = function(opts) {
  530. // TODO(paul): This is gut-wrenching into the AppService instance itself.
  531. // Maybe an API on that object would be good?
  532. var app = this.appService.app;
  533. // TODO(paul): Consider more options:
  534. // opts.versions - automatic version filtering and rejecting of
  535. // unrecognised API versions
  536. // Consider automatic "/_matrix/app/:version" path prefix
  537. app[opts.method.toLowerCase()](opts.path, opts.handler);
  538. };
  539. /**
  540. * Retrieve the connected room store instance.
  541. * @return {?RoomBridgeStore} The connected instance ready for querying.
  542. */
  543. Bridge.prototype.getRoomStore = function() {
  544. return this._roomStore;
  545. };
  546. /**
  547. * Retrieve the connected user store instance.
  548. * @return {?UserBridgeStore} The connected instance ready for querying.
  549. */
  550. Bridge.prototype.getUserStore = function() {
  551. return this._userStore;
  552. };
  553. /**
  554. * Retrieve the connected event store instance, if one was configured.
  555. * @return {?EventBridgeStore} The connected instance ready for querying.
  556. */
  557. Bridge.prototype.getEventStore = function() {
  558. return this._eventStore;
  559. };
  560. /**
  561. * Retrieve the request factory used to create incoming requests.
  562. * @return {RequestFactory}
  563. */
  564. Bridge.prototype.getRequestFactory = function() {
  565. return this._requestFactory;
  566. };
  567. /**
  568. * Retrieve the matrix client factory used when sending matrix requests.
  569. * @return {ClientFactory}
  570. */
  571. Bridge.prototype.getClientFactory = function() {
  572. return this._clientFactory;
  573. };
  574. /**
  575. * Get the AS bot instance.
  576. * @return {AppServiceBot}
  577. */
  578. Bridge.prototype.getBot = function() {
  579. return this._appServiceBot;
  580. };
  581. /**
  582. * Determines whether a room should be provisoned based on
  583. * user provided rules and the room state. Will default to true
  584. * if no rules have been provided.
  585. * @param {string} roomId The room to check.
  586. * @param {boolean} cache Should the validator check it's cache.
  587. * @returns {Promise} resolves if can and rejects if it cannot.
  588. * A status code is returned on both.
  589. */
  590. Bridge.prototype.canProvisionRoom = function(roomId, cache=true) {
  591. if (this._roomLinkValidator === null) {
  592. return Promise.resolve(RLVStatus.PASSED);
  593. }
  594. return this._roomLinkValidator.validateRoom(roomId, cache);
  595. }
  596. Bridge.prototype.getRoomLinkValidator = function() {
  597. return this._roomLinkValidator;
  598. }
  599. /**
  600. * Retrieve an Intent instance for the specified user ID. If no ID is given, an
  601. * instance for the bot itself is returned.
  602. * @param {?string} userId The user ID to get an Intent for.
  603. * @param {Request=} request Optional. The request instance to tie the MatrixClient
  604. * instance to. Useful for logging contextual request IDs.
  605. * @return {Intent} The intent instance
  606. */
  607. Bridge.prototype.getIntent = function(userId, request) {
  608. if (!userId) {
  609. return this._botIntent;
  610. }
  611. if (this.opts.escapeUserIds === undefined || this.opts.escapeUserIds) {
  612. userId = new MatrixUser(userId).getId(); // Escape the ID
  613. }
  614. const key = userId + (request ? request.getId() : "");
  615. if (!this._intents[key]) {
  616. const client = this._clientFactory.getClientAs(userId, request);
  617. const clientIntentOpts = {
  618. backingStore: this._intentBackingStore
  619. };
  620. if (this.opts.intentOptions.clients) {
  621. Object.keys(this.opts.intentOptions.clients).forEach((k) => {
  622. clientIntentOpts[k] = this.opts.intentOptions.clients[k];
  623. });
  624. }
  625. clientIntentOpts.registered = this._membershipCache.isUserRegistered(userId);
  626. this._intents[key] = new Intent(client, this._botClient, clientIntentOpts);
  627. }
  628. this._intentLastAccessed[key] = Date.now();
  629. return this._intents[key];
  630. };
  631. /**
  632. * Retrieve an Intent instance for the specified user ID localpart. This <i>must
  633. * be the complete user localpart</i>.
  634. * @param {?string} localpart The user ID localpart to get an Intent for.
  635. * @param {Request=} request Optional. The request instance to tie the MatrixClient
  636. * instance to. Useful for logging contextual request IDs.
  637. * @return {Intent} The intent instance
  638. */
  639. Bridge.prototype.getIntentFromLocalpart = function(localpart, request) {
  640. return this.getIntent(
  641. "@" + localpart + ":" + this.opts.domain
  642. );
  643. };
  644. /**
  645. * Provision a user on the homeserver.
  646. * @param {MatrixUser} matrixUser The virtual user to be provisioned.
  647. * @param {Bridge~ProvisionedUser} provisionedUser Provisioning information.
  648. * @return {Promise} Resolved when provisioned.
  649. */
  650. Bridge.prototype.provisionUser = function (matrixUser, provisionedUser) {
  651. // For backwards compat
  652. return Promise.cast(this._provisionUser(matrixUser, provisionedUser));
  653. };
  654. Bridge.prototype._provisionUser = async function(matrixUser, provisionedUser) {
  655. await this._botClient.register(matrixUser.localpart);
  656. if (!this.disableStores) {
  657. await this._userStore.setMatrixUser(matrixUser);
  658. if (provisionedUser.remote) {
  659. await this._userStore.linkUsers(matrixUser, provisionedUser.remote);
  660. }
  661. }
  662. const userClient = this._clientFactory.getClientAs(matrixUser.getId());
  663. if (provisionedUser.name) {
  664. await userClient.setDisplayName(provisionedUser.name);
  665. }
  666. if (provisionedUser.url) {
  667. await userClient.setAvatarUrl(provisionedUser.url);
  668. }
  669. };
  670. Bridge.prototype._onUserQuery = async function(userId) {
  671. if (!this.opts.controller.onUserQuery) {
  672. return;
  673. }
  674. const matrixUser = new MatrixUser(userId);
  675. try {
  676. const provisionedUser = await this.opts.controller.onUserQuery(matrixUser);
  677. if (!provisionedUser) {
  678. log.warn(`Not provisioning user for ${userId}`);
  679. return;
  680. }
  681. await this.provisionUser(matrixUser, provisionedUser);
  682. }
  683. catch (ex) {
  684. log.error(`Failed _onUserQuery for ${userId}`, ex);
  685. }
  686. };
  687. Bridge.prototype._onAliasQuery = function (alias) {
  688. // For backwards compat
  689. return Promise.cast(this.__onAliasQuery(alias));
  690. };
  691. Bridge.prototype.__onAliasQuery = async function(alias) {
  692. if (!this.opts.controller.onAliasQuery) {
  693. return;
  694. }
  695. const aliasLocalpart = alias.split(":")[0].substring(1);
  696. const provisionedRoom = await this.opts.controller.onAliasQuery(alias, aliasLocalpart);
  697. if (!provisionedRoom) {
  698. throw new Error("Not provisioning room for this alias");
  699. }
  700. const createRoomResponse = await this._botClient.createRoom(
  701. provisionedRoom.creationOpts
  702. );
  703. const roomId = createRoomResponse.room_id;
  704. if (!this.opts.disableStores) {
  705. const matrixRoom = new MatrixRoom(roomId);
  706. const remoteRoom = provisionedRoom.remote;
  707. if (remoteRoom) {
  708. await this._roomStore.linkRooms(matrixRoom, remoteRoom);
  709. }
  710. else {
  711. // store the matrix room only
  712. await this._roomStore.setMatrixRoom(matrixRoom);
  713. }
  714. }
  715. if (this.opts.controller.onAliasQueried) {
  716. await this.opts.controller.onAliasQueried(alias, roomId);
  717. }
  718. }
  719. Bridge.prototype._onEvent = function (event) {
  720. return Promise.cast(this.__onEvent(event));
  721. };
  722. // returns a Promise for the request linked to this event for testing.
  723. Bridge.prototype.__onEvent = async function(event) {
  724. const isCanonicalState = event.state_key === "";
  725. this._updateIntents(event);
  726. if (this.opts.suppressEcho &&
  727. this.opts.registration.isUserMatch(event.user_id, true)) {
  728. return null;
  729. }
  730. if (this._roomUpgradeHandler) {
  731. // m.room.tombstone is the event that signals a room upgrade.
  732. if (event.type === "m.room.tombstone" && isCanonicalState && this._roomUpgradeHandler) {
  733. this._roomUpgradeHandler.onTombstone(event);
  734. if (this.opts.roomUpgradeOpts.consumeEvent) {
  735. return null;
  736. }
  737. }
  738. else if (event.type === "m.room.member" &&
  739. event.state_key === this._appServiceBot.getUserId() &&
  740. event.content.membership === "invite") {
  741. // A invite-only room that has been upgraded won't have been joinable,
  742. // so we are listening for any invites to the new room.
  743. const isUpgradeInvite = this._roomUpgradeHandler.onInvite(event);
  744. if (isUpgradeInvite &&
  745. this.opts.roomUpgradeOpts.consumeEvent) {
  746. return null;
  747. }
  748. }
  749. }
  750. const request = this._requestFactory.newRequest({ data: event });
  751. const contextReady = this._getBridgeContext(event);
  752. const dataReady = contextReady.then(context => ({ request, context }));
  753. const dataReadyLimited = this._limited(dataReady, request);
  754. this._queue.push(event, dataReadyLimited);
  755. this._queue.consume();
  756. const reqPromise = request.getPromise();
  757. // We *must* return the result of the request.
  758. return reqPromise.catch(
  759. EventNotHandledError,
  760. e => {
  761. this._handleEventError(event, e)
  762. }
  763. );
  764. };
  765. /**
  766. * Restricts the promise according to the bridges `perRequest` setting.
  767. *
  768. * `perRequest` enabled:
  769. * Returns a promise similar to `promise`, with the addition of it only
  770. * resolving after `request`.
  771. * `perRequest` disabled:
  772. * Returns the promise unchanged.
  773. */
  774. Bridge.prototype._limited = async function(promise, request) {
  775. // queue.perRequest controls whether multiple request can be processed by
  776. // the bridge at once.
  777. if (this.opts.queue.perRequest) {
  778. const promiseLimited = this._prevRequestPromise.reflect().return(promise);
  779. this._prevRequestPromise = request.getPromise();
  780. return promiseLimited;
  781. }
  782. return promise;
  783. }
  784. Bridge.prototype._onConsume = function(err, data) {
  785. if (err) {
  786. // The data for the event could not be retrieved.
  787. this.opts.controller.onLog("onEvent failure: " + err, true);
  788. return;
  789. }
  790. this.opts.controller.onEvent(data.request, data.context);
  791. };
  792. Bridge.prototype._getBridgeContext = async function(event) {
  793. if (this.opts.disableContext) {
  794. return null;
  795. }
  796. const context = new BridgeContext({
  797. sender: event.user_id,
  798. target: event.type === "m.room.member" ? event.state_key : null,
  799. room: event.room_id
  800. });
  801. return context.get(this._roomStore, this._userStore);
  802. }
  803. Bridge.prototype._handleEventError = function(event, error) {
  804. if (!(error instanceof EventNotHandledError)) {
  805. error = wrap(error, InternalError);
  806. }
  807. // TODO[V02460@gmail.com]: Send via different means when the bridge bot is
  808. // unavailable. _MSC2162: Signaling Errors at Bridges_ will have details on
  809. // how this should be done.
  810. this._botIntent.unstableSignalBridgeError(
  811. event.room_id,
  812. event.event_id,
  813. this.opts.networkName,
  814. error.reason,
  815. this._getUserRegex(),
  816. );
  817. };
  818. /**
  819. * Returns a regex matching all users of the bridge.
  820. *
  821. * @return {string} Super regex composed of all user regexes.
  822. */
  823. Bridge.prototype._getUserRegex = function() {
  824. const reg = this.opts.registration;
  825. return reg.namespaces["users"].map(o => o.regex);
  826. };
  827. Bridge.prototype._updateIntents = function(event) {
  828. if (event.type === "m.room.member") {
  829. this._membershipCache.setMemberEntry(
  830. event.room_id,
  831. event.state_key,
  832. event.content ? event.content.membership : null
  833. );
  834. }
  835. else if (event.type === "m.room.power_levels") {
  836. this._setPowerLevelEntry(event.room_id, event.content);
  837. }
  838. };
  839. Bridge.prototype._setPowerLevelEntry = function(roomId, content) {
  840. this._powerLevelMap[roomId] = content;
  841. };
  842. Bridge.prototype._getPowerLevelEntry = function(roomId) {
  843. return this._powerLevelMap[roomId];
  844. };
  845. /**
  846. * Returns a PrometheusMetrics instance stored on the bridge, creating it first
  847. * if required. The instance will be registered with the HTTP server so it can
  848. * serve the "/metrics" page in the usual way.
  849. * The instance will automatically register the Matrix SDK metrics by calling
  850. * {PrometheusMetrics~registerMatrixSdkMetrics}.
  851. */
  852. Bridge.prototype.getPrometheusMetrics = function() {
  853. if (this._metrics) {
  854. return this._metrics;
  855. }
  856. var metrics = this._metrics = new PrometheusMetrics();
  857. metrics.registerMatrixSdkMetrics();
  858. // TODO(paul): register some bridge-wide standard ones here
  859. // In case we're called after .run()
  860. if (this.appService) {
  861. metrics.addAppServicePath(this);
  862. }
  863. return metrics;
  864. };
  865. /**
  866. * A convenient shortcut to calling registerBridgeGauges() on the
  867. * PrometheusMetrics instance directly. This version will supply the value of
  868. * the matrixGhosts field if the counter function did not return it, for
  869. * convenience.
  870. * @param {PrometheusMetrics~BridgeGaugesCallback} counterFunc A function that
  871. * when invoked returns the current counts of various items in the bridge.
  872. *
  873. * @example
  874. * bridge.registerBridgeGauges(() => {
  875. * return {
  876. * matrixRoomConfigs: Object.keys(this.matrixRooms).length,
  877. * remoteRoomConfigs: Object.keys(this.remoteRooms).length,
  878. *
  879. * remoteGhosts: Object.keys(this.remoteGhosts).length,
  880. *
  881. * ...
  882. * }
  883. * })
  884. */
  885. Bridge.prototype.registerBridgeGauges = function(counterFunc) {
  886. var self = this;
  887. this.getPrometheusMetrics().registerBridgeGauges(function() {
  888. var counts = counterFunc();
  889. if (!("matrixGhosts" in counts)) {
  890. counts.matrixGhosts = Object.keys(self._intents).length;
  891. }
  892. return counts;
  893. });
  894. };
  895. Bridge.prototype._requestCheckToken = function(req, res) {
  896. if (req.query.access_token !== this.opts.registration.hs_token) {
  897. res.status(403).send({
  898. errcode: "M_FORBIDDEN",
  899. error: "Bad token supplied,"
  900. });
  901. return false;
  902. }
  903. return true;
  904. }
  905. function loadDatabase(path, Cls) {
  906. var defer = Promise.defer();
  907. var db = new Datastore({
  908. filename: path,
  909. autoload: true,
  910. onload: function(err) {
  911. if (err) {
  912. defer.reject(err);
  913. }
  914. else {
  915. defer.resolve(new Cls(db));
  916. }
  917. }
  918. });
  919. return defer.promise;
  920. }
  921. function retryAlgorithm(event, attempts, err) {
  922. if (err.httpStatus === 400 || err.httpStatus === 403 || err.httpStatus === 401) {
  923. // client error; no amount of retrying with save you now.
  924. return -1;
  925. }
  926. // we ship with browser-request which returns { cors: rejected } when trying
  927. // with no connection, so if we match that, give up since they have no conn.
  928. if (err.cors === "rejected") {
  929. return -1;
  930. }
  931. if (err.name === "M_LIMIT_EXCEEDED") {
  932. var waitTime = err.data.retry_after_ms;
  933. if (waitTime) {
  934. return waitTime;
  935. }
  936. }
  937. if (attempts > 4) {
  938. return -1; // give up
  939. }
  940. return 1000 + (1000 * attempts);
  941. }
  942. function queueAlgorithm(event) {
  943. if (event.getType() === "m.room.message") {
  944. // use a separate queue for each room ID
  945. return "message_" + event.getRoomId();
  946. }
  947. // allow all other events continue concurrently.
  948. return null;
  949. }
  950. module.exports = Bridge;
  951. /**
  952. * @typedef Bridge~ProvisionedUser
  953. * @type {Object}
  954. * @property {string=} name The display name to set for the provisioned user.
  955. * @property {string=} url The avatar URL to set for the provisioned user.
  956. * @property {RemoteUser=} remote The remote user to link to the provisioned user.
  957. */
  958. /**
  959. * @typedef Bridge~ProvisionedRoom
  960. * @type {Object}
  961. * @property {Object} creationOpts Room creation options to use when creating the
  962. * room. Required.
  963. * @property {RemoteRoom=} remote The remote room to link to the provisioned room.
  964. */
  965. /**
  966. * Invoked when the bridge receives a user query from the homeserver. Supports
  967. * both sync return values and async return values via promises.
  968. * @callback Bridge~onUserQuery
  969. * @param {MatrixUser} matrixUser The matrix user queried. Use <code>getId()</code>
  970. * to get the user ID.
  971. * @return {?Bridge~ProvisionedUser|Promise<Bridge~ProvisionedUser, Error>}
  972. * Reject the promise / return null to not provision the user. Resolve the
  973. * promise / return a {@link Bridge~ProvisionedUser} object to provision the user.
  974. * @example
  975. * new Bridge({
  976. * controller: {
  977. * onUserQuery: function(matrixUser) {
  978. * var remoteUser = new RemoteUser("some_remote_id");
  979. * return {
  980. * name: matrixUser.localpart + " (Bridged)",
  981. * url: "http://someurl.com/pic.jpg",
  982. * user: remoteUser
  983. * };
  984. * }
  985. * }
  986. * });
  987. */
  988. /**
  989. * Invoked when the bridge receives an alias query from the homeserver. Supports
  990. * both sync return values and async return values via promises.
  991. * @callback Bridge~onAliasQuery
  992. * @param {string} alias The alias queried.
  993. * @param {string} aliasLocalpart The parsed localpart of the alias.
  994. * @return {?Bridge~ProvisionedRoom|Promise<Bridge~ProvisionedRoom, Error>}
  995. * Reject the promise / return null to not provision the room. Resolve the
  996. * promise / return a {@link Bridge~ProvisionedRoom} object to provision the room.
  997. * @example
  998. * new Bridge({
  999. * controller: {
  1000. * onAliasQuery: function(alias, aliasLocalpart) {
  1001. * return {
  1002. * creationOpts: {
  1003. * room_alias_name: aliasLocalpart, // IMPORTANT: must be set to make the link
  1004. * name: aliasLocalpart,
  1005. * topic: "Auto-generated bridged room"
  1006. * }
  1007. * };
  1008. * }
  1009. * }
  1010. * });
  1011. */
  1012. /**
  1013. * Invoked when a response is returned from onAliasQuery. Supports
  1014. * both sync return values and async return values via promises.
  1015. * @callback Bridge~onAliasQueried
  1016. * @param {string} alias The alias queried.
  1017. * @param {string} roomId The parsed localpart of the alias.
  1018. */
  1019. /**
  1020. * @callback Bridge~onRoomUpgrade
  1021. * @param {string} oldRoomId The roomId of the old room.
  1022. * @param {string} newRoomId The roomId of the new room.
  1023. * @param {string} newVersion The new room version.
  1024. * @param {Bridge~BridgeContext} context Context for the upgrade event.
  1025. */
  1026. /**
  1027. * Invoked when the bridge receives an event from the homeserver.
  1028. * @callback Bridge~onEvent
  1029. * @param {Request} request The request to resolve or reject depending on the
  1030. * outcome of this request. The 'data' attached to this Request is the raw event
  1031. * JSON received (accessed via <code>request.getData()</code>)
  1032. * @param {Bridge~BridgeContext} context Context for this event, including
  1033. * instantiated client instances.
  1034. */
  1035. /**
  1036. * Invoked when the bridge is attempting to log something.
  1037. * @callback Bridge~onLog
  1038. * @param {string} line The text to be logged.
  1039. * @param {boolean} isError True if this line should be treated as an error msg.
  1040. */
  1041. /**
  1042. * Handler function for custom applied HTTP API request paths. This is invoked
  1043. * as defined by expressjs.
  1044. * @callback Bridge~appServicePathHandler
  1045. * @param {Request} req An expressjs Request object the handler can use to
  1046. * inspect the incoming request.
  1047. * @param {Response} res An expressjs Response object the handler can use to
  1048. * send the outgoing response.
  1049. */
  1050. /**
  1051. * @typedef Bridge~thirdPartyLookup
  1052. * @type {Object}
  1053. * @property {string[]} protocols Optional list of recognised protocol names.
  1054. * If present, lookups for unrecognised protocols will be automatically
  1055. * rejected.
  1056. * @property {Bridge~getProtocol} getProtocol Function. Called for requests
  1057. * for 3PE query metadata.
  1058. * @property {Bridge~getLocation} getLocation Function. Called for requests
  1059. * for 3PLs.
  1060. * @property {Bridge~parseLocation} parseLocation Function. Called for reverse
  1061. * parse requests on 3PL aliases.
  1062. * @property {Bridge~getUser} getUser Function. Called for requests for 3PUs.
  1063. * @property {Bridge~parseUser} parseUser Function. Called for reverse parse
  1064. * requests on 3PU user IDs.
  1065. */
  1066. /**
  1067. * Invoked on requests for 3PE query metadata
  1068. * @callback Bridge~getProtocol
  1069. * @param {string} protocol The name of the 3PE protocol to query
  1070. * @return {Promise<Bridge~thirdPartyProtocolResult>} A Promise of metadata
  1071. * about 3PE queries that can be made for this protocol.
  1072. */
  1073. /**
  1074. * Returned by getProtocol third-party query metadata requests
  1075. * @typedef Bridge~thirdPartyProtocolResult
  1076. * @type {Object}
  1077. * @property {string[]} [location_fields] Names of the fields required for
  1078. * location lookups if location queries are supported.
  1079. * @property {string[]} [user_fields] Names of the fields required for user
  1080. * lookups if user queries are supported.
  1081. /**
  1082. * Invoked on requests for 3PLs
  1083. * @callback Bridge~getLocation
  1084. * @param {string} protocol The name of the 3PE protocol
  1085. * @param {Object} fields The location query field data as specified by the
  1086. * specific protocol.
  1087. * @return {Promise<Bridge~thirdPartyLocationResult[]>} A Promise of a list of
  1088. * 3PL lookup results.
  1089. */
  1090. /**
  1091. * Invoked on requests to parse 3PL aliases
  1092. * @callback Bridge~parseLocation
  1093. * @param {string} alias The room alias to parse.
  1094. * @return {Promise<Bridge~thirdPartyLocationResult[]>} A Promise of a list of
  1095. * 3PL lookup results.
  1096. */
  1097. /**
  1098. * Returned by getLocation and parseLocation third-party location lookups
  1099. * @typedef Bridge~thirdPartyLocationResult
  1100. * @type {Object}
  1101. * @property {string} alias The Matrix room alias to the portal room
  1102. * representing this 3PL
  1103. * @property {string} protocol The name of the 3PE protocol
  1104. * @property {object} fields The normalised values of the location query field
  1105. * data.
  1106. */
  1107. /**
  1108. * Invoked on requests for 3PUs
  1109. * @callback Bridge~getUser
  1110. * @param {string} protocol The name of the 3PE protocol
  1111. * @param {Object} fields The user query field data as specified by the
  1112. * specific protocol.
  1113. * @return {Promise<Bridge~thirdPartyUserResult[]>} A Promise of a list of 3PU
  1114. * lookup results.
  1115. */
  1116. /**
  1117. * Invoked on requests to parse 3PU user IDs
  1118. * @callback Bridge~parseUser
  1119. * @param {string} userid The user ID to parse.
  1120. * @return {Promise<Bridge~thirdPartyUserResult[]>} A Promise of a list of 3PU
  1121. * lookup results.
  1122. */
  1123. /**
  1124. * Returned by getUser and parseUser third-party user lookups
  1125. * @typedef Bridge~thirdPartyUserResult
  1126. * @type {Object}
  1127. * @property {string} userid The Matrix user ID for the ghost representing
  1128. * this 3PU
  1129. * @property {string} protocol The name of the 3PE protocol
  1130. * @property {object} fields The normalised values of the user query field
  1131. * data.
  1132. */