UNPKG

157 kBJavaScriptView Raw
1/*
2Copyright 2015, 2016 OpenMarket Ltd
3Copyright 2017 Vector Creations Ltd
4Copyright 2018 New Vector Ltd
5
6Licensed under the Apache License, Version 2.0 (the "License");
7you may not use this file except in compliance with the License.
8You may obtain a copy of the License at
9
10 http://www.apache.org/licenses/LICENSE-2.0
11
12Unless required by applicable law or agreed to in writing, software
13distributed under the License is distributed on an "AS IS" BASIS,
14WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15See the License for the specific language governing permissions and
16limitations under the License.
17*/
18"use strict";
19
20var _assign = require("babel-runtime/core-js/object/assign");
21
22var _assign2 = _interopRequireDefault(_assign);
23
24var _stringify = require("babel-runtime/core-js/json/stringify");
25
26var _stringify2 = _interopRequireDefault(_stringify);
27
28var _set = require("babel-runtime/core-js/set");
29
30var _set2 = _interopRequireDefault(_set);
31
32var _keys = require("babel-runtime/core-js/object/keys");
33
34var _keys2 = _interopRequireDefault(_keys);
35
36var _typeof2 = require("babel-runtime/helpers/typeof");
37
38var _typeof3 = _interopRequireDefault(_typeof2);
39
40var _bluebird = require("bluebird");
41
42var _bluebird2 = _interopRequireDefault(_bluebird);
43
44var _regenerator = require("babel-runtime/regenerator");
45
46var _regenerator2 = _interopRequireDefault(_regenerator);
47
48var _slicedToArray2 = require("babel-runtime/helpers/slicedToArray");
49
50var _slicedToArray3 = _interopRequireDefault(_slicedToArray2);
51
52var _entries = require("babel-runtime/core-js/object/entries");
53
54var _entries2 = _interopRequireDefault(_entries);
55
56var _getIterator2 = require("babel-runtime/core-js/get-iterator");
57
58var _getIterator3 = _interopRequireDefault(_getIterator2);
59
60var _setDeviceVerification = function () {
61 var _ref4 = (0, _bluebird.coroutine)( /*#__PURE__*/_regenerator2.default.mark(function _callee2(client, userId, deviceId, verified, blocked, known) {
62 var dev;
63 return _regenerator2.default.wrap(function _callee2$(_context2) {
64 while (1) {
65 switch (_context2.prev = _context2.next) {
66 case 0:
67 if (client._crypto) {
68 _context2.next = 2;
69 break;
70 }
71
72 throw new Error("End-to-End encryption disabled");
73
74 case 2:
75 _context2.next = 4;
76 return (0, _bluebird.resolve)(client._crypto.setDeviceVerification(userId, deviceId, verified, blocked, known));
77
78 case 4:
79 dev = _context2.sent;
80
81 client.emit("deviceVerificationChanged", userId, deviceId, dev);
82
83 case 6:
84 case "end":
85 return _context2.stop();
86 }
87 }
88 }, _callee2, this);
89 }));
90
91 return function _setDeviceVerification(_x4, _x5, _x6, _x7, _x8, _x9) {
92 return _ref4.apply(this, arguments);
93 };
94}();
95
96/**
97 * Set the global override for whether the client should ever send encrypted
98 * messages to unverified devices. This provides the default for rooms which
99 * do not specify a value.
100 *
101 * @param {boolean} value whether to blacklist all unverified devices by default
102 */
103
104
105var _ReEmitter = require("./ReEmitter");
106
107var _ReEmitter2 = _interopRequireDefault(_ReEmitter);
108
109var _RoomList = require("./crypto/RoomList");
110
111var _RoomList2 = _interopRequireDefault(_RoomList);
112
113var _crypto = require("./crypto");
114
115var _crypto2 = _interopRequireDefault(_crypto);
116
117var _recoverykey = require("./crypto/recoverykey");
118
119var _backup_password = require("./crypto/backup_password");
120
121var _randomstring = require("./randomstring");
122
123function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
124
125var PushProcessor = require('./pushprocessor');
126
127/**
128 * This is an internal module. See {@link MatrixClient} for the public class.
129 * @module client
130 */
131var EventEmitter = require("events").EventEmitter;
132
133var url = require('url');
134
135var httpApi = require("./http-api");
136var MatrixEvent = require("./models/event").MatrixEvent;
137var EventStatus = require("./models/event").EventStatus;
138var EventTimeline = require("./models/event-timeline");
139var SearchResult = require("./models/search-result");
140var StubStore = require("./store/stub");
141var webRtcCall = require("./webrtc/call");
142var utils = require("./utils");
143var contentRepo = require("./content-repo");
144var Filter = require("./filter");
145var SyncApi = require("./sync");
146var MatrixBaseApis = require("./base-apis");
147var MatrixError = httpApi.MatrixError;
148var ContentHelpers = require("./content-helpers");
149var olmlib = require("./crypto/olmlib");
150
151// Disable warnings for now: we use deprecated bluebird functions
152// and need to migrate, but they spam the console with warnings.
153_bluebird2.default.config({ warnings: false });
154
155var SCROLLBACK_DELAY_MS = 3000;
156var CRYPTO_ENABLED = (0, _crypto.isCryptoAvailable)();
157
158function keysFromRecoverySession(sessions, decryptionKey, roomId) {
159 var keys = [];
160 var _iteratorNormalCompletion = true;
161 var _didIteratorError = false;
162 var _iteratorError = undefined;
163
164 try {
165 for (var _iterator = (0, _getIterator3.default)((0, _entries2.default)(sessions)), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
166 var _step$value = (0, _slicedToArray3.default)(_step.value, 2),
167 sessionId = _step$value[0],
168 sessionData = _step$value[1];
169
170 try {
171 var decrypted = keyFromRecoverySession(sessionData, decryptionKey);
172 decrypted.session_id = sessionId;
173 decrypted.room_id = roomId;
174 keys.push(decrypted);
175 } catch (e) {
176 console.log("Failed to decrypt session from backup");
177 }
178 }
179 } catch (err) {
180 _didIteratorError = true;
181 _iteratorError = err;
182 } finally {
183 try {
184 if (!_iteratorNormalCompletion && _iterator.return) {
185 _iterator.return();
186 }
187 } finally {
188 if (_didIteratorError) {
189 throw _iteratorError;
190 }
191 }
192 }
193
194 return keys;
195}
196
197function keyFromRecoverySession(session, decryptionKey) {
198 return JSON.parse(decryptionKey.decrypt(session.session_data.ephemeral, session.session_data.mac, session.session_data.ciphertext));
199}
200
201/**
202 * Construct a Matrix Client. Only directly construct this if you want to use
203 * custom modules. Normally, {@link createClient} should be used
204 * as it specifies 'sensible' defaults for these modules.
205 * @constructor
206 * @extends {external:EventEmitter}
207 * @extends {module:base-apis~MatrixBaseApis}
208 *
209 * @param {Object} opts The configuration options for this client.
210 * @param {string} opts.baseUrl Required. The base URL to the client-server
211 * HTTP API.
212 * @param {string} opts.idBaseUrl Optional. The base identity server URL for
213 * identity server requests.
214 * @param {Function} opts.request Required. The function to invoke for HTTP
215 * requests. The value of this property is typically <code>require("request")
216 * </code> as it returns a function which meets the required interface. See
217 * {@link requestFunction} for more information.
218 *
219 * @param {string} opts.accessToken The access_token for this user.
220 *
221 * @param {string} opts.userId The user ID for this user.
222 *
223 * @param {Object=} opts.store The data store to use. If not specified,
224 * this client will not store any HTTP responses.
225 *
226 * @param {string=} opts.deviceId A unique identifier for this device; used for
227 * tracking things like crypto keys and access tokens. If not specified,
228 * end-to-end crypto will be disabled.
229 *
230 * @param {Object=} opts.sessionStore A store to be used for end-to-end crypto
231 * session data. This should be a {@link
232 * module:store/session/webstorage~WebStorageSessionStore|WebStorageSessionStore},
233 * or an object implementing the same interface. If not specified,
234 * end-to-end crypto will be disabled.
235 *
236 * @param {Object} opts.scheduler Optional. The scheduler to use. If not
237 * specified, this client will not retry requests on failure. This client
238 * will supply its own processing function to
239 * {@link module:scheduler~MatrixScheduler#setProcessFunction}.
240 *
241 * @param {Object} opts.queryParams Optional. Extra query parameters to append
242 * to all requests with this client. Useful for application services which require
243 * <code>?user_id=</code>.
244 *
245 * @param {Number=} opts.localTimeoutMs Optional. The default maximum amount of
246 * time to wait before timing out HTTP requests. If not specified, there is no timeout.
247 *
248 * @param {boolean} [opts.useAuthorizationHeader = false] Set to true to use
249 * Authorization header instead of query param to send the access token to the server.
250 *
251 * @param {boolean} [opts.timelineSupport = false] Set to true to enable
252 * improved timeline support ({@link
253 * module:client~MatrixClient#getEventTimeline getEventTimeline}). It is
254 * disabled by default for compatibility with older clients - in particular to
255 * maintain support for back-paginating the live timeline after a '/sync'
256 * result with a gap.
257 *
258 * @param {module:crypto.store.base~CryptoStore} opts.cryptoStore
259 * crypto store implementation.
260 */
261function MatrixClient(opts) {
262 // Allow trailing slash in HS url
263 if (opts.baseUrl && opts.baseUrl.endsWith("/")) {
264 opts.baseUrl = opts.baseUrl.substr(0, opts.baseUrl.length - 1);
265 }
266
267 // Allow trailing slash in IS url
268 if (opts.idBaseUrl && opts.idBaseUrl.endsWith("/")) {
269 opts.idBaseUrl = opts.idBaseUrl.substr(0, opts.idBaseUrl.length - 1);
270 }
271
272 MatrixBaseApis.call(this, opts);
273
274 this.olmVersion = null; // Populated after initCrypto is done
275
276 this.reEmitter = new _ReEmitter2.default(this);
277
278 this.store = opts.store || new StubStore();
279
280 this.deviceId = opts.deviceId || null;
281
282 var userId = opts.userId || null;
283 this.credentials = {
284 userId: userId
285 };
286
287 this.scheduler = opts.scheduler;
288 if (this.scheduler) {
289 var self = this;
290 this.scheduler.setProcessFunction(function (eventToSend) {
291 var room = self.getRoom(eventToSend.getRoomId());
292 if (eventToSend.status !== EventStatus.SENDING) {
293 _updatePendingEventStatus(room, eventToSend, EventStatus.SENDING);
294 }
295 return _sendEventHttpRequest(self, eventToSend);
296 });
297 }
298 this.clientRunning = false;
299
300 this.callList = {
301 // callId: MatrixCall
302 };
303
304 // try constructing a MatrixCall to see if we are running in an environment
305 // which has WebRTC. If we are, listen for and handle m.call.* events.
306 var call = webRtcCall.createNewMatrixCall(this);
307 this._supportsVoip = false;
308 if (call) {
309 setupCallEventHandler(this);
310 this._supportsVoip = true;
311 }
312 this._syncingRetry = null;
313 this._syncApi = null;
314 this._peekSync = null;
315 this._isGuest = false;
316 this._ongoingScrollbacks = {};
317 this.timelineSupport = Boolean(opts.timelineSupport);
318 this.urlPreviewCache = {};
319 this._notifTimelineSet = null;
320
321 this._crypto = null;
322 this._cryptoStore = opts.cryptoStore;
323 this._sessionStore = opts.sessionStore;
324
325 this._forceTURN = opts.forceTURN || false;
326
327 // List of which rooms have encryption enabled: separate from crypto because
328 // we still want to know which rooms are encrypted even if crypto is disabled:
329 // we don't want to start sending unencrypted events to them.
330 this._roomList = new _RoomList2.default(this._cryptoStore, this._sessionStore);
331
332 // The pushprocessor caches useful things, so keep one and re-use it
333 this._pushProcessor = new PushProcessor(this);
334
335 this._serverSupportsLazyLoading = null;
336}
337utils.inherits(MatrixClient, EventEmitter);
338utils.extend(MatrixClient.prototype, MatrixBaseApis.prototype);
339
340/**
341 * Clear any data out of the persistent stores used by the client.
342 *
343 * @returns {Promise} Promise which resolves when the stores have been cleared.
344 */
345MatrixClient.prototype.clearStores = function () {
346 if (this._clientRunning) {
347 throw new Error("Cannot clear stores while client is running");
348 }
349
350 var promises = [];
351
352 promises.push(this.store.deleteAllData());
353 if (this._cryptoStore) {
354 promises.push(this._cryptoStore.deleteAllData());
355 }
356 return _bluebird2.default.all(promises);
357};
358
359/**
360 * Get the user-id of the logged-in user
361 *
362 * @return {?string} MXID for the logged-in user, or null if not logged in
363 */
364MatrixClient.prototype.getUserId = function () {
365 if (this.credentials && this.credentials.userId) {
366 return this.credentials.userId;
367 }
368 return null;
369};
370
371/**
372 * Get the domain for this client's MXID
373 * @return {?string} Domain of this MXID
374 */
375MatrixClient.prototype.getDomain = function () {
376 if (this.credentials && this.credentials.userId) {
377 return this.credentials.userId.replace(/^.*?:/, '');
378 }
379 return null;
380};
381
382/**
383 * Get the local part of the current user ID e.g. "foo" in "@foo:bar".
384 * @return {?string} The user ID localpart or null.
385 */
386MatrixClient.prototype.getUserIdLocalpart = function () {
387 if (this.credentials && this.credentials.userId) {
388 return this.credentials.userId.split(":")[0].substring(1);
389 }
390 return null;
391};
392
393/**
394 * Get the device ID of this client
395 * @return {?string} device ID
396 */
397MatrixClient.prototype.getDeviceId = function () {
398 return this.deviceId;
399};
400
401/**
402 * Check if the runtime environment supports VoIP calling.
403 * @return {boolean} True if VoIP is supported.
404 */
405MatrixClient.prototype.supportsVoip = function () {
406 return this._supportsVoip;
407};
408
409/**
410 * Set whether VoIP calls are forced to use only TURN
411 * candidates. This is the same as the forceTURN option
412 * when creating the client.
413 * @param {bool} forceTURN True to force use of TURN servers
414 */
415MatrixClient.prototype.setForceTURN = function (forceTURN) {
416 this._forceTURN = forceTURN;
417};
418
419/**
420 * Get the current sync state.
421 * @return {?string} the sync state, which may be null.
422 * @see module:client~MatrixClient#event:"sync"
423 */
424MatrixClient.prototype.getSyncState = function () {
425 if (!this._syncApi) {
426 return null;
427 }
428 return this._syncApi.getSyncState();
429};
430
431/**
432 * Returns the additional data object associated with
433 * the current sync state, or null if there is no
434 * such data.
435 * Sync errors, if available, are put in the 'error' key of
436 * this object.
437 * @return {?Object}
438 */
439MatrixClient.prototype.getSyncStateData = function () {
440 if (!this._syncApi) {
441 return null;
442 }
443 return this._syncApi.getSyncStateData();
444};
445
446/**
447 * Return whether the client is configured for a guest account.
448 * @return {boolean} True if this is a guest access_token (or no token is supplied).
449 */
450MatrixClient.prototype.isGuest = function () {
451 return this._isGuest;
452};
453
454/**
455 * Return the provided scheduler, if any.
456 * @return {?module:scheduler~MatrixScheduler} The scheduler or null
457 */
458MatrixClient.prototype.getScheduler = function () {
459 return this.scheduler;
460};
461
462/**
463 * Set whether this client is a guest account. <b>This method is experimental
464 * and may change without warning.</b>
465 * @param {boolean} isGuest True if this is a guest account.
466 */
467MatrixClient.prototype.setGuest = function (isGuest) {
468 // EXPERIMENTAL:
469 // If the token is a macaroon, it should be encoded in it that it is a 'guest'
470 // access token, which means that the SDK can determine this entirely without
471 // the dev manually flipping this flag.
472 this._isGuest = isGuest;
473};
474
475/**
476 * Retry a backed off syncing request immediately. This should only be used when
477 * the user <b>explicitly</b> attempts to retry their lost connection.
478 * @return {boolean} True if this resulted in a request being retried.
479 */
480MatrixClient.prototype.retryImmediately = function () {
481 return this._syncApi.retryImmediately();
482};
483
484/**
485 * Return the global notification EventTimelineSet, if any
486 *
487 * @return {EventTimelineSet} the globl notification EventTimelineSet
488 */
489MatrixClient.prototype.getNotifTimelineSet = function () {
490 return this._notifTimelineSet;
491};
492
493/**
494 * Set the global notification EventTimelineSet
495 *
496 * @param {EventTimelineSet} notifTimelineSet
497 */
498MatrixClient.prototype.setNotifTimelineSet = function (notifTimelineSet) {
499 this._notifTimelineSet = notifTimelineSet;
500};
501
502// Crypto bits
503// ===========
504
505/**
506 * Initialise support for end-to-end encryption in this client
507 *
508 * You should call this method after creating the matrixclient, but *before*
509 * calling `startClient`, if you want to support end-to-end encryption.
510 *
511 * It will return a Promise which will resolve when the crypto layer has been
512 * successfully initialised.
513 */
514MatrixClient.prototype.initCrypto = (0, _bluebird.coroutine)( /*#__PURE__*/_regenerator2.default.mark(function _callee() {
515 var userId, crypto;
516 return _regenerator2.default.wrap(function _callee$(_context) {
517 while (1) {
518 switch (_context.prev = _context.next) {
519 case 0:
520 if ((0, _crypto.isCryptoAvailable)()) {
521 _context.next = 2;
522 break;
523 }
524
525 throw new Error("End-to-end encryption not supported in this js-sdk build: did " + "you remember to load the olm library?");
526
527 case 2:
528 if (!this._crypto) {
529 _context.next = 5;
530 break;
531 }
532
533 console.warn("Attempt to re-initialise e2e encryption on MatrixClient");
534 return _context.abrupt("return");
535
536 case 5:
537 if (this._sessionStore) {
538 _context.next = 7;
539 break;
540 }
541
542 throw new Error("Cannot enable encryption: no sessionStore provided");
543
544 case 7:
545 if (this._cryptoStore) {
546 _context.next = 9;
547 break;
548 }
549
550 throw new Error("Cannot enable encryption: no cryptoStore provided");
551
552 case 9:
553 _context.next = 11;
554 return (0, _bluebird.resolve)(this._roomList.init());
555
556 case 11:
557 userId = this.getUserId();
558
559 if (!(userId === null)) {
560 _context.next = 14;
561 break;
562 }
563
564 throw new Error("Cannot enable encryption on MatrixClient with unknown userId: " + "ensure userId is passed in createClient().");
565
566 case 14:
567 if (!(this.deviceId === null)) {
568 _context.next = 16;
569 break;
570 }
571
572 throw new Error("Cannot enable encryption on MatrixClient with unknown deviceId: " + "ensure deviceId is passed in createClient().");
573
574 case 16:
575 crypto = new _crypto2.default(this, this._sessionStore, userId, this.deviceId, this.store, this._cryptoStore, this._roomList);
576
577
578 this.reEmitter.reEmit(crypto, ["crypto.roomKeyRequest", "crypto.roomKeyRequestCancellation", "crypto.warning"]);
579
580 _context.next = 20;
581 return (0, _bluebird.resolve)(crypto.init());
582
583 case 20:
584
585 this.olmVersion = _crypto2.default.getOlmVersion();
586
587 // if crypto initialisation was successful, tell it to attach its event
588 // handlers.
589 crypto.registerEventHandlers(this);
590 this._crypto = crypto;
591
592 case 23:
593 case "end":
594 return _context.stop();
595 }
596 }
597 }, _callee, this);
598}));
599
600/**
601 * Is end-to-end crypto enabled for this client.
602 * @return {boolean} True if end-to-end is enabled.
603 */
604MatrixClient.prototype.isCryptoEnabled = function () {
605 return this._crypto !== null;
606};
607
608/**
609 * Get the Ed25519 key for this device
610 *
611 * @return {?string} base64-encoded ed25519 key. Null if crypto is
612 * disabled.
613 */
614MatrixClient.prototype.getDeviceEd25519Key = function () {
615 if (!this._crypto) {
616 return null;
617 }
618 return this._crypto.getDeviceEd25519Key();
619};
620
621/**
622 * Upload the device keys to the homeserver.
623 * @return {object} A promise that will resolve when the keys are uploaded.
624 */
625MatrixClient.prototype.uploadKeys = function () {
626 if (this._crypto === null) {
627 throw new Error("End-to-end encryption disabled");
628 }
629
630 return this._crypto.uploadDeviceKeys();
631};
632
633/**
634 * Download the keys for a list of users and stores the keys in the session
635 * store.
636 * @param {Array} userIds The users to fetch.
637 * @param {bool} forceDownload Always download the keys even if cached.
638 *
639 * @return {Promise} A promise which resolves to a map userId->deviceId->{@link
640 * module:crypto~DeviceInfo|DeviceInfo}.
641 */
642MatrixClient.prototype.downloadKeys = function (userIds, forceDownload) {
643 if (this._crypto === null) {
644 return _bluebird2.default.reject(new Error("End-to-end encryption disabled"));
645 }
646 return this._crypto.downloadKeys(userIds, forceDownload);
647};
648
649/**
650 * Get the stored device keys for a user id
651 *
652 * @param {string} userId the user to list keys for.
653 *
654 * @return {Promise<module:crypto-deviceinfo[]>} list of devices
655 */
656MatrixClient.prototype.getStoredDevicesForUser = function () {
657 var _ref2 = (0, _bluebird.method)(function (userId) {
658 if (this._crypto === null) {
659 throw new Error("End-to-end encryption disabled");
660 }
661 return this._crypto.getStoredDevicesForUser(userId) || [];
662 });
663
664 return function (_x) {
665 return _ref2.apply(this, arguments);
666 };
667}();
668
669/**
670 * Get the stored device key for a user id and device id
671 *
672 * @param {string} userId the user to list keys for.
673 * @param {string} deviceId unique identifier for the device
674 *
675 * @return {Promise<?module:crypto-deviceinfo>} device or null
676 */
677MatrixClient.prototype.getStoredDevice = function () {
678 var _ref3 = (0, _bluebird.method)(function (userId, deviceId) {
679 if (this._crypto === null) {
680 throw new Error("End-to-end encryption disabled");
681 }
682 return this._crypto.getStoredDevice(userId, deviceId) || null;
683 });
684
685 return function (_x2, _x3) {
686 return _ref3.apply(this, arguments);
687 };
688}();
689
690/**
691 * Mark the given device as verified
692 *
693 * @param {string} userId owner of the device
694 * @param {string} deviceId unique identifier for the device
695 *
696 * @param {boolean=} verified whether to mark the device as verified. defaults
697 * to 'true'.
698 *
699 * @returns {Promise}
700 *
701 * @fires module:client~event:MatrixClient"deviceVerificationChanged"
702 */
703MatrixClient.prototype.setDeviceVerified = function (userId, deviceId, verified) {
704 if (verified === undefined) {
705 verified = true;
706 }
707 var prom = _setDeviceVerification(this, userId, deviceId, verified, null);
708
709 // if one of the user's own devices is being marked as verified / unverified,
710 // check the key backup status, since whether or not we use this depends on
711 // whether it has a signature from a verified device
712 if (userId == this.credentials.userId) {
713 this._crypto.checkKeyBackup();
714 }
715 return prom;
716};
717
718/**
719 * Mark the given device as blocked/unblocked
720 *
721 * @param {string} userId owner of the device
722 * @param {string} deviceId unique identifier for the device
723 *
724 * @param {boolean=} blocked whether to mark the device as blocked. defaults
725 * to 'true'.
726 *
727 * @returns {Promise}
728 *
729 * @fires module:client~event:MatrixClient"deviceVerificationChanged"
730 */
731MatrixClient.prototype.setDeviceBlocked = function (userId, deviceId, blocked) {
732 if (blocked === undefined) {
733 blocked = true;
734 }
735 return _setDeviceVerification(this, userId, deviceId, null, blocked);
736};
737
738/**
739 * Mark the given device as known/unknown
740 *
741 * @param {string} userId owner of the device
742 * @param {string} deviceId unique identifier for the device
743 *
744 * @param {boolean=} known whether to mark the device as known. defaults
745 * to 'true'.
746 *
747 * @returns {Promise}
748 *
749 * @fires module:client~event:MatrixClient"deviceVerificationChanged"
750 */
751MatrixClient.prototype.setDeviceKnown = function (userId, deviceId, known) {
752 if (known === undefined) {
753 known = true;
754 }
755 return _setDeviceVerification(this, userId, deviceId, null, null, known);
756};
757
758MatrixClient.prototype.setGlobalBlacklistUnverifiedDevices = function (value) {
759 if (this._crypto === null) {
760 throw new Error("End-to-end encryption disabled");
761 }
762 this._crypto.setGlobalBlacklistUnverifiedDevices(value);
763};
764
765/**
766 * @return {boolean} whether to blacklist all unverified devices by default
767 */
768MatrixClient.prototype.getGlobalBlacklistUnverifiedDevices = function () {
769 if (this._crypto === null) {
770 throw new Error("End-to-end encryption disabled");
771 }
772 return this._crypto.getGlobalBlacklistUnverifiedDevices();
773};
774
775/**
776 * Get e2e information on the device that sent an event
777 *
778 * @param {MatrixEvent} event event to be checked
779 *
780 * @return {Promise<module:crypto/deviceinfo?>}
781 */
782MatrixClient.prototype.getEventSenderDeviceInfo = function () {
783 var _ref5 = (0, _bluebird.method)(function (event) {
784 if (!this._crypto) {
785 return null;
786 }
787
788 return this._crypto.getEventSenderDeviceInfo(event);
789 });
790
791 return function (_x10) {
792 return _ref5.apply(this, arguments);
793 };
794}();
795
796/**
797 * Check if the sender of an event is verified
798 *
799 * @param {MatrixEvent} event event to be checked
800 *
801 * @return {boolean} true if the sender of this event has been verified using
802 * {@link module:client~MatrixClient#setDeviceVerified|setDeviceVerified}.
803 */
804MatrixClient.prototype.isEventSenderVerified = function () {
805 var _ref6 = (0, _bluebird.coroutine)( /*#__PURE__*/_regenerator2.default.mark(function _callee3(event) {
806 var device;
807 return _regenerator2.default.wrap(function _callee3$(_context3) {
808 while (1) {
809 switch (_context3.prev = _context3.next) {
810 case 0:
811 _context3.next = 2;
812 return (0, _bluebird.resolve)(this.getEventSenderDeviceInfo(event));
813
814 case 2:
815 device = _context3.sent;
816
817 if (device) {
818 _context3.next = 5;
819 break;
820 }
821
822 return _context3.abrupt("return", false);
823
824 case 5:
825 return _context3.abrupt("return", device.isVerified());
826
827 case 6:
828 case "end":
829 return _context3.stop();
830 }
831 }
832 }, _callee3, this);
833 }));
834
835 return function (_x11) {
836 return _ref6.apply(this, arguments);
837 };
838}();
839
840/**
841 * Cancel a room key request for this event if one is ongoing and resend the
842 * request.
843 * @param {MatrixEvent} event event of which to cancel and resend the room
844 * key request.
845 */
846MatrixClient.prototype.cancelAndResendEventRoomKeyRequest = function (event) {
847 event.cancelAndResendKeyRequest(this._crypto);
848};
849
850/**
851 * Enable end-to-end encryption for a room.
852 * @param {string} roomId The room ID to enable encryption in.
853 * @param {object} config The encryption config for the room.
854 * @return {Promise} A promise that will resolve when encryption is set up.
855 */
856MatrixClient.prototype.setRoomEncryption = function (roomId, config) {
857 if (!this._crypto) {
858 throw new Error("End-to-End encryption disabled");
859 }
860 return this._crypto.setRoomEncryption(roomId, config);
861};
862
863/**
864 * Whether encryption is enabled for a room.
865 * @param {string} roomId the room id to query.
866 * @return {bool} whether encryption is enabled.
867 */
868MatrixClient.prototype.isRoomEncrypted = function (roomId) {
869 var room = this.getRoom(roomId);
870 if (!room) {
871 // we don't know about this room, so can't determine if it should be
872 // encrypted. Let's assume not.
873 return false;
874 }
875
876 // if there is an 'm.room.encryption' event in this room, it should be
877 // encrypted (independently of whether we actually support encryption)
878 var ev = room.currentState.getStateEvents("m.room.encryption", "");
879 if (ev) {
880 return true;
881 }
882
883 // we don't have an m.room.encrypted event, but that might be because
884 // the server is hiding it from us. Check the store to see if it was
885 // previously encrypted.
886 return this._roomList.isRoomEncrypted(roomId);
887};
888
889/**
890 * Forces the current outbound group session to be discarded such
891 * that another one will be created next time an event is sent.
892 *
893 * @param {string} roomId The ID of the room to discard the session for
894 *
895 * This should not normally be necessary.
896 */
897MatrixClient.prototype.forceDiscardSession = function (roomId) {
898 if (!this._crypto) {
899 throw new Error("End-to-End encryption disabled");
900 }
901 this._crypto.forceDiscardSession(roomId);
902};
903
904/**
905 * Get a list containing all of the room keys
906 *
907 * This should be encrypted before returning it to the user.
908 *
909 * @return {module:client.Promise} a promise which resolves to a list of
910 * session export objects
911 */
912MatrixClient.prototype.exportRoomKeys = function () {
913 if (!this._crypto) {
914 return _bluebird2.default.reject(new Error("End-to-end encryption disabled"));
915 }
916 return this._crypto.exportRoomKeys();
917};
918
919/**
920 * Import a list of room keys previously exported by exportRoomKeys
921 *
922 * @param {Object[]} keys a list of session export objects
923 *
924 * @return {module:client.Promise} a promise which resolves when the keys
925 * have been imported
926 */
927MatrixClient.prototype.importRoomKeys = function (keys) {
928 if (!this._crypto) {
929 throw new Error("End-to-end encryption disabled");
930 }
931 return this._crypto.importRoomKeys(keys);
932};
933
934/**
935 * Get information about the current key backup.
936 * @returns {Promise} Information object from API or null
937 */
938MatrixClient.prototype.getKeyBackupVersion = function () {
939 return this._http.authedRequest(undefined, "GET", "/room_keys/version").then(function (res) {
940 if (res.algorithm !== olmlib.MEGOLM_BACKUP_ALGORITHM) {
941 var err = "Unknown backup algorithm: " + res.algorithm;
942 return _bluebird2.default.reject(err);
943 } else if (!((0, _typeof3.default)(res.auth_data) === "object") || !res.auth_data.public_key) {
944 var _err = "Invalid backup data returned";
945 return _bluebird2.default.reject(_err);
946 } else {
947 return res;
948 }
949 }).catch(function (e) {
950 if (e.errcode === 'M_NOT_FOUND') {
951 return null;
952 } else {
953 throw e;
954 }
955 });
956};
957
958/**
959 * @param {object} info key backup info dict from getKeyBackupVersion()
960 * @return {object} {
961 * usable: [bool], // is the backup trusted, true iff there is a sig that is valid & from a trusted device
962 * sigs: [
963 * valid: [bool],
964 * device: [DeviceInfo],
965 * ]
966 * }
967 */
968MatrixClient.prototype.isKeyBackupTrusted = function (info) {
969 return this._crypto.isKeyBackupTrusted(info);
970};
971
972/**
973 * @returns {bool} true if the client is configured to back up keys to
974 * the server, otherwise false.
975 */
976MatrixClient.prototype.getKeyBackupEnabled = function () {
977 if (this._crypto === null) {
978 throw new Error("End-to-end encryption disabled");
979 }
980 return Boolean(this._crypto.backupKey);
981};
982
983/**
984 * Enable backing up of keys, using data previously returned from
985 * getKeyBackupVersion.
986 *
987 * @param {object} info Backup information object as returned by getKeyBackupVersion
988 */
989MatrixClient.prototype.enableKeyBackup = function (info) {
990 if (this._crypto === null) {
991 throw new Error("End-to-end encryption disabled");
992 }
993
994 this._crypto.backupInfo = info;
995 if (this._crypto.backupKey) this._crypto.backupKey.free();
996 this._crypto.backupKey = new global.Olm.PkEncryption();
997 this._crypto.backupKey.set_recipient_key(info.auth_data.public_key);
998
999 this.emit('crypto.keyBackupStatus', true);
1000};
1001
1002/**
1003 * Disable backing up of keys.
1004 */
1005MatrixClient.prototype.disableKeyBackup = function () {
1006 if (this._crypto === null) {
1007 throw new Error("End-to-end encryption disabled");
1008 }
1009
1010 this._crypto.backupInfo = null;
1011 if (this._crypto.backupKey) this._crypto.backupKey.free();
1012 this._crypto.backupKey = null;
1013
1014 this.emit('crypto.keyBackupStatus', false);
1015};
1016
1017/**
1018 * Set up the data required to create a new backup version. The backup version
1019 * will not be created and enabled until createKeyBackupVersion is called.
1020 *
1021 * @param {string} password Passphrase string that can be entered by the user
1022 * when restoring the backup as an alternative to entering the recovery key.
1023 * Optional.
1024 *
1025 * @returns {Promise<object>} Object that can be passed to createKeyBackupVersion and
1026 * additionally has a 'recovery_key' member with the user-facing recovery key string.
1027 */
1028MatrixClient.prototype.prepareKeyBackupVersion = function () {
1029 var _ref7 = (0, _bluebird.coroutine)( /*#__PURE__*/_regenerator2.default.mark(function _callee4(password) {
1030 var decryption, publicKey, authData, keyInfo;
1031 return _regenerator2.default.wrap(function _callee4$(_context4) {
1032 while (1) {
1033 switch (_context4.prev = _context4.next) {
1034 case 0:
1035 if (!(this._crypto === null)) {
1036 _context4.next = 2;
1037 break;
1038 }
1039
1040 throw new Error("End-to-end encryption disabled");
1041
1042 case 2:
1043 decryption = new global.Olm.PkDecryption();
1044 _context4.prev = 3;
1045 publicKey = void 0;
1046 authData = {};
1047
1048 if (!password) {
1049 _context4.next = 15;
1050 break;
1051 }
1052
1053 _context4.next = 9;
1054 return (0, _bluebird.resolve)((0, _backup_password.keyForNewBackup)(password));
1055
1056 case 9:
1057 keyInfo = _context4.sent;
1058
1059 publicKey = decryption.init_with_private_key(keyInfo.key);
1060 authData.private_key_salt = keyInfo.salt;
1061 authData.private_key_iterations = keyInfo.iterations;
1062 _context4.next = 16;
1063 break;
1064
1065 case 15:
1066 publicKey = decryption.generate_key();
1067
1068 case 16:
1069
1070 authData.public_key = publicKey;
1071
1072 return _context4.abrupt("return", {
1073 algorithm: olmlib.MEGOLM_BACKUP_ALGORITHM,
1074 auth_data: authData,
1075 recovery_key: (0, _recoverykey.encodeRecoveryKey)(decryption.get_private_key())
1076 });
1077
1078 case 18:
1079 _context4.prev = 18;
1080
1081 decryption.free();
1082 return _context4.finish(18);
1083
1084 case 21:
1085 case "end":
1086 return _context4.stop();
1087 }
1088 }
1089 }, _callee4, this, [[3,, 18, 21]]);
1090 }));
1091
1092 return function (_x12) {
1093 return _ref7.apply(this, arguments);
1094 };
1095}();
1096
1097/**
1098 * Create a new key backup version and enable it, using the information return
1099 * from prepareKeyBackupVersion.
1100 *
1101 * @param {object} info Info object from prepareKeyBackupVersion
1102 * @returns {Promise<object>} Object with 'version' param indicating the version created
1103 */
1104MatrixClient.prototype.createKeyBackupVersion = function (info) {
1105 var _this = this;
1106
1107 if (this._crypto === null) {
1108 throw new Error("End-to-end encryption disabled");
1109 }
1110
1111 var data = {
1112 algorithm: info.algorithm,
1113 auth_data: info.auth_data
1114 };
1115 return this._crypto._signObject(data.auth_data).then(function () {
1116 return _this._http.authedRequest(undefined, "POST", "/room_keys/version", undefined, data);
1117 }).then(function (res) {
1118 _this.enableKeyBackup({
1119 algorithm: info.algorithm,
1120 auth_data: info.auth_data,
1121 version: res.version
1122 });
1123 return res;
1124 });
1125};
1126
1127MatrixClient.prototype.deleteKeyBackupVersion = function (version) {
1128 if (this._crypto === null) {
1129 throw new Error("End-to-end encryption disabled");
1130 }
1131
1132 // If we're currently backing up to this backup... stop.
1133 // (We start using it automatically in createKeyBackupVersion
1134 // so this is symmetrical).
1135 if (this._crypto.backupInfo && this._crypto.backupInfo.version === version) {
1136 this.disableKeyBackup();
1137 }
1138
1139 var path = utils.encodeUri("/room_keys/version/$version", {
1140 $version: version
1141 });
1142
1143 return this._http.authedRequest(undefined, "DELETE", path, undefined, undefined);
1144};
1145
1146MatrixClient.prototype._makeKeyBackupPath = function (roomId, sessionId, version) {
1147 var path = void 0;
1148 if (sessionId !== undefined) {
1149 path = utils.encodeUri("/room_keys/keys/$roomId/$sessionId", {
1150 $roomId: roomId,
1151 $sessionId: sessionId
1152 });
1153 } else if (roomId !== undefined) {
1154 path = utils.encodeUri("/room_keys/keys/$roomId", {
1155 $roomId: roomId
1156 });
1157 } else {
1158 path = "/room_keys/keys";
1159 }
1160 var queryData = version === undefined ? undefined : { version: version };
1161 return {
1162 path: path,
1163 queryData: queryData
1164 };
1165};
1166
1167/**
1168 * Back up session keys to the homeserver.
1169 * @param {string} roomId ID of the room that the keys are for Optional.
1170 * @param {string} sessionId ID of the session that the keys are for Optional.
1171 * @param {integer} version backup version Optional.
1172 * @param {object} data Object keys to send
1173 * @return {module:client.Promise} a promise that will resolve when the keys
1174 * are uploaded
1175 */
1176MatrixClient.prototype.sendKeyBackup = function (roomId, sessionId, version, data) {
1177 if (this._crypto === null) {
1178 throw new Error("End-to-end encryption disabled");
1179 }
1180
1181 var path = this._makeKeyBackupPath(roomId, sessionId, version);
1182 return this._http.authedRequest(undefined, "PUT", path.path, path.queryData, data);
1183};
1184
1185MatrixClient.prototype.backupAllGroupSessions = function (version) {
1186 if (this._crypto === null) {
1187 throw new Error("End-to-end encryption disabled");
1188 }
1189
1190 return this._crypto.backupAllGroupSessions(version);
1191};
1192
1193MatrixClient.prototype.isValidRecoveryKey = function (recoveryKey) {
1194 try {
1195 (0, _recoverykey.decodeRecoveryKey)(recoveryKey);
1196 return true;
1197 } catch (e) {
1198 return false;
1199 }
1200};
1201
1202MatrixClient.prototype.restoreKeyBackupWithPassword = function () {
1203 var _ref8 = (0, _bluebird.coroutine)( /*#__PURE__*/_regenerator2.default.mark(function _callee5(password, targetRoomId, targetSessionId, version) {
1204 var backupInfo, privKey;
1205 return _regenerator2.default.wrap(function _callee5$(_context5) {
1206 while (1) {
1207 switch (_context5.prev = _context5.next) {
1208 case 0:
1209 _context5.next = 2;
1210 return (0, _bluebird.resolve)(this.getKeyBackupVersion());
1211
1212 case 2:
1213 backupInfo = _context5.sent;
1214 _context5.next = 5;
1215 return (0, _bluebird.resolve)((0, _backup_password.keyForExistingBackup)(backupInfo, password));
1216
1217 case 5:
1218 privKey = _context5.sent;
1219 return _context5.abrupt("return", this._restoreKeyBackup(privKey, targetRoomId, targetSessionId, version));
1220
1221 case 7:
1222 case "end":
1223 return _context5.stop();
1224 }
1225 }
1226 }, _callee5, this);
1227 }));
1228
1229 return function (_x13, _x14, _x15, _x16) {
1230 return _ref8.apply(this, arguments);
1231 };
1232}();
1233
1234MatrixClient.prototype.restoreKeyBackupWithRecoveryKey = function (recoveryKey, targetRoomId, targetSessionId, version) {
1235 var privKey = (0, _recoverykey.decodeRecoveryKey)(recoveryKey);
1236 return this._restoreKeyBackup(privKey, targetRoomId, targetSessionId, version);
1237};
1238
1239MatrixClient.prototype._restoreKeyBackup = function (privKey, targetRoomId, targetSessionId, version) {
1240 var _this2 = this;
1241
1242 if (this._crypto === null) {
1243 throw new Error("End-to-end encryption disabled");
1244 }
1245 var totalKeyCount = 0;
1246 var keys = [];
1247
1248 var path = this._makeKeyBackupPath(targetRoomId, targetSessionId, version);
1249
1250 var decryption = new global.Olm.PkDecryption();
1251 try {
1252 decryption.init_with_private_key(privKey);
1253 } catch (e) {
1254 decryption.free();
1255 throw e;
1256 }
1257
1258 return this._http.authedRequest(undefined, "GET", path.path, path.queryData).then(function (res) {
1259 if (res.rooms) {
1260 var _iteratorNormalCompletion2 = true;
1261 var _didIteratorError2 = false;
1262 var _iteratorError2 = undefined;
1263
1264 try {
1265 for (var _iterator2 = (0, _getIterator3.default)((0, _entries2.default)(res.rooms)), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
1266 var _step2$value = (0, _slicedToArray3.default)(_step2.value, 2),
1267 roomId = _step2$value[0],
1268 roomData = _step2$value[1];
1269
1270 if (!roomData.sessions) continue;
1271
1272 totalKeyCount += (0, _keys2.default)(roomData.sessions).length;
1273 var roomKeys = keysFromRecoverySession(roomData.sessions, decryption, roomId, roomKeys);
1274 var _iteratorNormalCompletion3 = true;
1275 var _didIteratorError3 = false;
1276 var _iteratorError3 = undefined;
1277
1278 try {
1279 for (var _iterator3 = (0, _getIterator3.default)(roomKeys), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
1280 var k = _step3.value;
1281
1282 k.room_id = roomId;
1283 keys.push(k);
1284 }
1285 } catch (err) {
1286 _didIteratorError3 = true;
1287 _iteratorError3 = err;
1288 } finally {
1289 try {
1290 if (!_iteratorNormalCompletion3 && _iterator3.return) {
1291 _iterator3.return();
1292 }
1293 } finally {
1294 if (_didIteratorError3) {
1295 throw _iteratorError3;
1296 }
1297 }
1298 }
1299 }
1300 } catch (err) {
1301 _didIteratorError2 = true;
1302 _iteratorError2 = err;
1303 } finally {
1304 try {
1305 if (!_iteratorNormalCompletion2 && _iterator2.return) {
1306 _iterator2.return();
1307 }
1308 } finally {
1309 if (_didIteratorError2) {
1310 throw _iteratorError2;
1311 }
1312 }
1313 }
1314 } else if (res.sessions) {
1315 totalKeyCount = (0, _keys2.default)(res.sessions).length;
1316 keys = keysFromRecoverySession(res.sessions, decryption, targetRoomId, keys);
1317 } else {
1318 totalKeyCount = 1;
1319 try {
1320 var key = keyFromRecoverySession(res, decryption);
1321 key.room_id = targetRoomId;
1322 key.session_id = targetSessionId;
1323 keys.push(key);
1324 } catch (e) {
1325 console.log("Failed to decrypt session from backup");
1326 }
1327 }
1328
1329 return _this2.importRoomKeys(keys);
1330 }).then(function () {
1331 return { total: totalKeyCount, imported: keys.length };
1332 }).finally(function () {
1333 decryption.free();
1334 });
1335};
1336
1337MatrixClient.prototype.deleteKeysFromBackup = function (roomId, sessionId, version) {
1338 if (this._crypto === null) {
1339 throw new Error("End-to-end encryption disabled");
1340 }
1341
1342 var path = this._makeKeyBackupPath(roomId, sessionId, version);
1343 return this._http.authedRequest(undefined, "DELETE", path.path, path.queryData);
1344};
1345
1346// Group ops
1347// =========
1348// Operations on groups that come down the sync stream (ie. ones the
1349// user is a member of or invited to)
1350
1351/**
1352 * Get the group for the given group ID.
1353 * This function will return a valid group for any group for which a Group event
1354 * has been emitted.
1355 * @param {string} groupId The group ID
1356 * @return {Group} The Group or null if the group is not known or there is no data store.
1357 */
1358MatrixClient.prototype.getGroup = function (groupId) {
1359 return this.store.getGroup(groupId);
1360};
1361
1362/**
1363 * Retrieve all known groups.
1364 * @return {Group[]} A list of groups, or an empty list if there is no data store.
1365 */
1366MatrixClient.prototype.getGroups = function () {
1367 return this.store.getGroups();
1368};
1369
1370/**
1371 * Get the config for the media repository.
1372 * @param {module:client.callback} callback Optional.
1373 * @return {module:client.Promise} Resolves with an object containing the config.
1374 */
1375MatrixClient.prototype.getMediaConfig = function (callback) {
1376 return this._http.authedRequestWithPrefix(callback, "GET", "/config", undefined, undefined, httpApi.PREFIX_MEDIA_R0);
1377};
1378
1379// Room ops
1380// ========
1381
1382/**
1383 * Get the room for the given room ID.
1384 * This function will return a valid room for any room for which a Room event
1385 * has been emitted. Note in particular that other events, eg. RoomState.members
1386 * will be emitted for a room before this function will return the given room.
1387 * @param {string} roomId The room ID
1388 * @return {Room} The Room or null if it doesn't exist or there is no data store.
1389 */
1390MatrixClient.prototype.getRoom = function (roomId) {
1391 return this.store.getRoom(roomId);
1392};
1393
1394/**
1395 * Retrieve all known rooms.
1396 * @return {Room[]} A list of rooms, or an empty list if there is no data store.
1397 */
1398MatrixClient.prototype.getRooms = function () {
1399 return this.store.getRooms();
1400};
1401
1402/**
1403 * Retrieve all rooms that should be displayed to the user
1404 * This is essentially getRooms() with some rooms filtered out, eg. old versions
1405 * of rooms that have been replaced or (in future) other rooms that have been
1406 * marked at the protocol level as not to be displayed to the user.
1407 * @return {Room[]} A list of rooms, or an empty list if there is no data store.
1408 */
1409MatrixClient.prototype.getVisibleRooms = function () {
1410 var allRooms = this.store.getRooms();
1411
1412 var replacedRooms = new _set2.default();
1413 var _iteratorNormalCompletion4 = true;
1414 var _didIteratorError4 = false;
1415 var _iteratorError4 = undefined;
1416
1417 try {
1418 for (var _iterator4 = (0, _getIterator3.default)(allRooms), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {
1419 var r = _step4.value;
1420
1421 var createEvent = r.currentState.getStateEvents('m.room.create', '');
1422 // invites are included in this list and we don't know their create events yet
1423 if (createEvent) {
1424 var predecessor = createEvent.getContent()['predecessor'];
1425 if (predecessor && predecessor['room_id']) {
1426 replacedRooms.add(predecessor['room_id']);
1427 }
1428 }
1429 }
1430 } catch (err) {
1431 _didIteratorError4 = true;
1432 _iteratorError4 = err;
1433 } finally {
1434 try {
1435 if (!_iteratorNormalCompletion4 && _iterator4.return) {
1436 _iterator4.return();
1437 }
1438 } finally {
1439 if (_didIteratorError4) {
1440 throw _iteratorError4;
1441 }
1442 }
1443 }
1444
1445 return allRooms.filter(function (r) {
1446 var tombstone = r.currentState.getStateEvents('m.room.tombstone', '');
1447 if (tombstone && replacedRooms.has(r.roomId)) {
1448 return false;
1449 }
1450 return true;
1451 });
1452};
1453
1454/**
1455 * Retrieve a user.
1456 * @param {string} userId The user ID to retrieve.
1457 * @return {?User} A user or null if there is no data store or the user does
1458 * not exist.
1459 */
1460MatrixClient.prototype.getUser = function (userId) {
1461 return this.store.getUser(userId);
1462};
1463
1464/**
1465 * Retrieve all known users.
1466 * @return {User[]} A list of users, or an empty list if there is no data store.
1467 */
1468MatrixClient.prototype.getUsers = function () {
1469 return this.store.getUsers();
1470};
1471
1472// User Account Data operations
1473// ============================
1474
1475/**
1476 * Set account data event for the current user.
1477 * @param {string} eventType The event type
1478 * @param {Object} contents the contents object for the event
1479 * @param {module:client.callback} callback Optional.
1480 * @return {module:client.Promise} Resolves: TODO
1481 * @return {module:http-api.MatrixError} Rejects: with an error response.
1482 */
1483MatrixClient.prototype.setAccountData = function (eventType, contents, callback) {
1484 var path = utils.encodeUri("/user/$userId/account_data/$type", {
1485 $userId: this.credentials.userId,
1486 $type: eventType
1487 });
1488 return this._http.authedRequest(callback, "PUT", path, undefined, contents);
1489};
1490
1491/**
1492 * Get account data event of given type for the current user.
1493 * @param {string} eventType The event type
1494 * @return {?object} The contents of the given account data event
1495 */
1496MatrixClient.prototype.getAccountData = function (eventType) {
1497 return this.store.getAccountData(eventType);
1498};
1499
1500/**
1501 * Gets the users that are ignored by this client
1502 * @returns {string[]} The array of users that are ignored (empty if none)
1503 */
1504MatrixClient.prototype.getIgnoredUsers = function () {
1505 var event = this.getAccountData("m.ignored_user_list");
1506 if (!event || !event.getContent() || !event.getContent()["ignored_users"]) return [];
1507 return (0, _keys2.default)(event.getContent()["ignored_users"]);
1508};
1509
1510/**
1511 * Sets the users that the current user should ignore.
1512 * @param {string[]} userIds the user IDs to ignore
1513 * @param {module:client.callback} [callback] Optional.
1514 * @return {module:client.Promise} Resolves: Account data event
1515 * @return {module:http-api.MatrixError} Rejects: with an error response.
1516 */
1517MatrixClient.prototype.setIgnoredUsers = function (userIds, callback) {
1518 var content = { ignored_users: {} };
1519 userIds.map(function (u) {
1520 return content.ignored_users[u] = {};
1521 });
1522 return this.setAccountData("m.ignored_user_list", content, callback);
1523};
1524
1525/**
1526 * Gets whether or not a specific user is being ignored by this client.
1527 * @param {string} userId the user ID to check
1528 * @returns {boolean} true if the user is ignored, false otherwise
1529 */
1530MatrixClient.prototype.isUserIgnored = function (userId) {
1531 return this.getIgnoredUsers().indexOf(userId) !== -1;
1532};
1533
1534// Room operations
1535// ===============
1536
1537/**
1538 * Join a room. If you have already joined the room, this will no-op.
1539 * @param {string} roomIdOrAlias The room ID or room alias to join.
1540 * @param {Object} opts Options when joining the room.
1541 * @param {boolean} opts.syncRoom True to do a room initial sync on the resulting
1542 * room. If false, the <strong>returned Room object will have no current state.
1543 * </strong> Default: true.
1544 * @param {boolean} opts.inviteSignUrl If the caller has a keypair 3pid invite,
1545 * the signing URL is passed in this parameter.
1546 * @param {string[]} opts.viaServers The server names to try and join through in
1547 * addition to those that are automatically chosen.
1548 * @param {module:client.callback} callback Optional.
1549 * @return {module:client.Promise} Resolves: Room object.
1550 * @return {module:http-api.MatrixError} Rejects: with an error response.
1551 */
1552MatrixClient.prototype.joinRoom = function (roomIdOrAlias, opts, callback) {
1553 // to help people when upgrading..
1554 if (utils.isFunction(opts)) {
1555 throw new Error("Expected 'opts' object, got function.");
1556 }
1557 opts = opts || {};
1558 if (opts.syncRoom === undefined) {
1559 opts.syncRoom = true;
1560 }
1561
1562 var room = this.getRoom(roomIdOrAlias);
1563 if (room && room.hasMembershipState(this.credentials.userId, "join")) {
1564 return _bluebird2.default.resolve(room);
1565 }
1566
1567 var sign_promise = _bluebird2.default.resolve();
1568
1569 if (opts.inviteSignUrl) {
1570 sign_promise = this._http.requestOtherUrl(undefined, 'POST', opts.inviteSignUrl, { mxid: this.credentials.userId });
1571 }
1572
1573 var queryString = {};
1574 if (opts.viaServers) {
1575 queryString["server_name"] = opts.viaServers;
1576 }
1577
1578 var reqOpts = { qsStringifyOptions: { arrayFormat: 'repeat' } };
1579
1580 var defer = _bluebird2.default.defer();
1581
1582 var self = this;
1583 sign_promise.then(function (signed_invite_object) {
1584 var data = {};
1585 if (signed_invite_object) {
1586 data.third_party_signed = signed_invite_object;
1587 }
1588
1589 var path = utils.encodeUri("/join/$roomid", { $roomid: roomIdOrAlias });
1590 return self._http.authedRequest(undefined, "POST", path, queryString, data, reqOpts);
1591 }).then(function (res) {
1592 var roomId = res.room_id;
1593 var syncApi = new SyncApi(self, self._clientOpts);
1594 var room = syncApi.createRoom(roomId);
1595 if (opts.syncRoom) {
1596 // v2 will do this for us
1597 // return syncApi.syncRoom(room);
1598 }
1599 return _bluebird2.default.resolve(room);
1600 }).done(function (room) {
1601 _resolve(callback, defer, room);
1602 }, function (err) {
1603 _reject(callback, defer, err);
1604 });
1605 return defer.promise;
1606};
1607
1608/**
1609 * Resend an event.
1610 * @param {MatrixEvent} event The event to resend.
1611 * @param {Room} room Optional. The room the event is in. Will update the
1612 * timeline entry if provided.
1613 * @return {module:client.Promise} Resolves: TODO
1614 * @return {module:http-api.MatrixError} Rejects: with an error response.
1615 */
1616MatrixClient.prototype.resendEvent = function (event, room) {
1617 _updatePendingEventStatus(room, event, EventStatus.SENDING);
1618 return _sendEvent(this, room, event);
1619};
1620
1621/**
1622 * Cancel a queued or unsent event.
1623 *
1624 * @param {MatrixEvent} event Event to cancel
1625 * @throws Error if the event is not in QUEUED or NOT_SENT state
1626 */
1627MatrixClient.prototype.cancelPendingEvent = function (event) {
1628 if ([EventStatus.QUEUED, EventStatus.NOT_SENT].indexOf(event.status) < 0) {
1629 throw new Error("cannot cancel an event with status " + event.status);
1630 }
1631
1632 // first tell the scheduler to forget about it, if it's queued
1633 if (this.scheduler) {
1634 this.scheduler.removeEventFromQueue(event);
1635 }
1636
1637 // then tell the room about the change of state, which will remove it
1638 // from the room's list of pending events.
1639 var room = this.getRoom(event.getRoomId());
1640 _updatePendingEventStatus(room, event, EventStatus.CANCELLED);
1641};
1642
1643/**
1644 * @param {string} roomId
1645 * @param {string} name
1646 * @param {module:client.callback} callback Optional.
1647 * @return {module:client.Promise} Resolves: TODO
1648 * @return {module:http-api.MatrixError} Rejects: with an error response.
1649 */
1650MatrixClient.prototype.setRoomName = function (roomId, name, callback) {
1651 return this.sendStateEvent(roomId, "m.room.name", { name: name }, undefined, callback);
1652};
1653
1654/**
1655 * @param {string} roomId
1656 * @param {string} topic
1657 * @param {module:client.callback} callback Optional.
1658 * @return {module:client.Promise} Resolves: TODO
1659 * @return {module:http-api.MatrixError} Rejects: with an error response.
1660 */
1661MatrixClient.prototype.setRoomTopic = function (roomId, topic, callback) {
1662 return this.sendStateEvent(roomId, "m.room.topic", { topic: topic }, undefined, callback);
1663};
1664
1665/**
1666 * @param {string} roomId
1667 * @param {module:client.callback} callback Optional.
1668 * @return {module:client.Promise} Resolves: TODO
1669 * @return {module:http-api.MatrixError} Rejects: with an error response.
1670 */
1671MatrixClient.prototype.getRoomTags = function (roomId, callback) {
1672 var path = utils.encodeUri("/user/$userId/rooms/$roomId/tags/", {
1673 $userId: this.credentials.userId,
1674 $roomId: roomId
1675 });
1676 return this._http.authedRequest(callback, "GET", path, undefined);
1677};
1678
1679/**
1680 * @param {string} roomId
1681 * @param {string} tagName name of room tag to be set
1682 * @param {object} metadata associated with that tag to be stored
1683 * @param {module:client.callback} callback Optional.
1684 * @return {module:client.Promise} Resolves: TODO
1685 * @return {module:http-api.MatrixError} Rejects: with an error response.
1686 */
1687MatrixClient.prototype.setRoomTag = function (roomId, tagName, metadata, callback) {
1688 var path = utils.encodeUri("/user/$userId/rooms/$roomId/tags/$tag", {
1689 $userId: this.credentials.userId,
1690 $roomId: roomId,
1691 $tag: tagName
1692 });
1693 return this._http.authedRequest(callback, "PUT", path, undefined, metadata);
1694};
1695
1696/**
1697 * @param {string} roomId
1698 * @param {string} tagName name of room tag to be removed
1699 * @param {module:client.callback} callback Optional.
1700 * @return {module:client.Promise} Resolves: TODO
1701 * @return {module:http-api.MatrixError} Rejects: with an error response.
1702 */
1703MatrixClient.prototype.deleteRoomTag = function (roomId, tagName, callback) {
1704 var path = utils.encodeUri("/user/$userId/rooms/$roomId/tags/$tag", {
1705 $userId: this.credentials.userId,
1706 $roomId: roomId,
1707 $tag: tagName
1708 });
1709 return this._http.authedRequest(callback, "DELETE", path, undefined, undefined);
1710};
1711
1712/**
1713 * @param {string} roomId
1714 * @param {string} eventType event type to be set
1715 * @param {object} content event content
1716 * @param {module:client.callback} callback Optional.
1717 * @return {module:client.Promise} Resolves: TODO
1718 * @return {module:http-api.MatrixError} Rejects: with an error response.
1719 */
1720MatrixClient.prototype.setRoomAccountData = function (roomId, eventType, content, callback) {
1721 var path = utils.encodeUri("/user/$userId/rooms/$roomId/account_data/$type", {
1722 $userId: this.credentials.userId,
1723 $roomId: roomId,
1724 $type: eventType
1725 });
1726 return this._http.authedRequest(callback, "PUT", path, undefined, content);
1727};
1728
1729/**
1730 * Set a user's power level.
1731 * @param {string} roomId
1732 * @param {string} userId
1733 * @param {Number} powerLevel
1734 * @param {MatrixEvent} event
1735 * @param {module:client.callback} callback Optional.
1736 * @return {module:client.Promise} Resolves: TODO
1737 * @return {module:http-api.MatrixError} Rejects: with an error response.
1738 */
1739MatrixClient.prototype.setPowerLevel = function (roomId, userId, powerLevel, event, callback) {
1740 var content = {
1741 users: {}
1742 };
1743 if (event && event.getType() === "m.room.power_levels") {
1744 // take a copy of the content to ensure we don't corrupt
1745 // existing client state with a failed power level change
1746 content = utils.deepCopy(event.getContent());
1747 }
1748 content.users[userId] = powerLevel;
1749 var path = utils.encodeUri("/rooms/$roomId/state/m.room.power_levels", {
1750 $roomId: roomId
1751 });
1752 return this._http.authedRequest(callback, "PUT", path, undefined, content);
1753};
1754
1755/**
1756 * @param {string} roomId
1757 * @param {string} eventType
1758 * @param {Object} content
1759 * @param {string} txnId Optional.
1760 * @param {module:client.callback} callback Optional.
1761 * @return {module:client.Promise} Resolves: TODO
1762 * @return {module:http-api.MatrixError} Rejects: with an error response.
1763 */
1764MatrixClient.prototype.sendEvent = function (roomId, eventType, content, txnId, callback) {
1765 if (utils.isFunction(txnId)) {
1766 callback = txnId;txnId = undefined;
1767 }
1768
1769 if (!txnId) {
1770 txnId = this.makeTxnId();
1771 }
1772
1773 console.log("sendEvent of type " + eventType + " in " + roomId + " with txnId " + txnId);
1774
1775 // we always construct a MatrixEvent when sending because the store and
1776 // scheduler use them. We'll extract the params back out if it turns out
1777 // the client has no scheduler or store.
1778 var room = this.getRoom(roomId);
1779 var localEvent = new MatrixEvent({
1780 event_id: "~" + roomId + ":" + txnId,
1781 user_id: this.credentials.userId,
1782 room_id: roomId,
1783 type: eventType,
1784 origin_server_ts: new Date().getTime(),
1785 content: content
1786 });
1787 localEvent._txnId = txnId;
1788 localEvent.status = EventStatus.SENDING;
1789
1790 // add this event immediately to the local store as 'sending'.
1791 if (room) {
1792 room.addPendingEvent(localEvent, txnId);
1793 }
1794
1795 // addPendingEvent can change the state to NOT_SENT if it believes
1796 // that there's other events that have failed. We won't bother to
1797 // try sending the event if the state has changed as such.
1798 if (localEvent.status === EventStatus.NOT_SENT) {
1799 return _bluebird2.default.reject(new Error("Event blocked by other events not yet sent"));
1800 }
1801
1802 return _sendEvent(this, room, localEvent, callback);
1803};
1804
1805// encrypts the event if necessary
1806// adds the event to the queue, or sends it
1807// marks the event as sent/unsent
1808// returns a promise which resolves with the result of the send request
1809function _sendEvent(client, room, event, callback) {
1810 // Add an extra Promise.resolve() to turn synchronous exceptions into promise rejections,
1811 // so that we can handle synchronous and asynchronous exceptions with the
1812 // same code path.
1813 return _bluebird2.default.resolve().then(function () {
1814 var encryptionPromise = _encryptEventIfNeeded(client, event, room);
1815
1816 if (!encryptionPromise) {
1817 return null;
1818 }
1819
1820 _updatePendingEventStatus(room, event, EventStatus.ENCRYPTING);
1821 return encryptionPromise.then(function () {
1822 _updatePendingEventStatus(room, event, EventStatus.SENDING);
1823 });
1824 }).then(function () {
1825 var promise = void 0;
1826 // this event may be queued
1827 if (client.scheduler) {
1828 // if this returns a promsie then the scheduler has control now and will
1829 // resolve/reject when it is done. Internally, the scheduler will invoke
1830 // processFn which is set to this._sendEventHttpRequest so the same code
1831 // path is executed regardless.
1832 promise = client.scheduler.queueEvent(event);
1833 if (promise && client.scheduler.getQueueForEvent(event).length > 1) {
1834 // event is processed FIFO so if the length is 2 or more we know
1835 // this event is stuck behind an earlier event.
1836 _updatePendingEventStatus(room, event, EventStatus.QUEUED);
1837 }
1838 }
1839
1840 if (!promise) {
1841 promise = _sendEventHttpRequest(client, event);
1842 }
1843 return promise;
1844 }).then(function (res) {
1845 // the request was sent OK
1846 if (room) {
1847 room.updatePendingEvent(event, EventStatus.SENT, res.event_id);
1848 }
1849 if (callback) {
1850 callback(null, res);
1851 }
1852 return res;
1853 }, function (err) {
1854 // the request failed to send.
1855 console.error("Error sending event", err.stack || err);
1856
1857 try {
1858 // set the error on the event before we update the status:
1859 // updating the status emits the event, so the state should be
1860 // consistent at that point.
1861 event.error = err;
1862 _updatePendingEventStatus(room, event, EventStatus.NOT_SENT);
1863 // also put the event object on the error: the caller will need this
1864 // to resend or cancel the event
1865 err.event = event;
1866
1867 if (callback) {
1868 callback(err);
1869 }
1870 } catch (err2) {
1871 console.error("Exception in error handler!", err2.stack || err);
1872 }
1873 throw err;
1874 });
1875}
1876
1877/**
1878 * Encrypt an event according to the configuration of the room, if necessary.
1879 *
1880 * @param {MatrixClient} client
1881 *
1882 * @param {module:models/event.MatrixEvent} event event to be sent
1883 *
1884 * @param {module:models/room?} room destination room. Null if the destination
1885 * is not a room we have seen over the sync pipe.
1886 *
1887 * @return {module:client.Promise?} Promise which resolves when the event has been
1888 * encrypted, or null if nothing was needed
1889 */
1890
1891function _encryptEventIfNeeded(client, event, room) {
1892 if (event.isEncrypted()) {
1893 // this event has already been encrypted; this happens if the
1894 // encryption step succeeded, but the send step failed on the first
1895 // attempt.
1896 return null;
1897 }
1898
1899 if (!client.isRoomEncrypted(event.getRoomId())) {
1900 // looks like this room isn't encrypted.
1901 return null;
1902 }
1903
1904 if (!client._crypto) {
1905 throw new Error("This room is configured to use encryption, but your client does " + "not support encryption.");
1906 }
1907
1908 return client._crypto.encryptEvent(event, room);
1909}
1910
1911function _updatePendingEventStatus(room, event, newStatus) {
1912 if (room) {
1913 room.updatePendingEvent(event, newStatus);
1914 } else {
1915 event.status = newStatus;
1916 }
1917}
1918
1919function _sendEventHttpRequest(client, event) {
1920 var txnId = event._txnId ? event._txnId : client.makeTxnId();
1921
1922 var pathParams = {
1923 $roomId: event.getRoomId(),
1924 $eventType: event.getWireType(),
1925 $stateKey: event.getStateKey(),
1926 $txnId: txnId
1927 };
1928
1929 var path = void 0;
1930
1931 if (event.isState()) {
1932 var pathTemplate = "/rooms/$roomId/state/$eventType";
1933 if (event.getStateKey() && event.getStateKey().length > 0) {
1934 pathTemplate = "/rooms/$roomId/state/$eventType/$stateKey";
1935 }
1936 path = utils.encodeUri(pathTemplate, pathParams);
1937 } else {
1938 path = utils.encodeUri("/rooms/$roomId/send/$eventType/$txnId", pathParams);
1939 }
1940
1941 return client._http.authedRequest(undefined, "PUT", path, undefined, event.getWireContent()).then(function (res) {
1942 console.log("Event sent to " + event.getRoomId() + " with event id " + res.event_id);
1943 return res;
1944 });
1945}
1946
1947/**
1948 * @param {string} roomId
1949 * @param {Object} content
1950 * @param {string} txnId Optional.
1951 * @param {module:client.callback} callback Optional.
1952 * @return {module:client.Promise} Resolves: TODO
1953 * @return {module:http-api.MatrixError} Rejects: with an error response.
1954 */
1955MatrixClient.prototype.sendMessage = function (roomId, content, txnId, callback) {
1956 if (utils.isFunction(txnId)) {
1957 callback = txnId;txnId = undefined;
1958 }
1959 return this.sendEvent(roomId, "m.room.message", content, txnId, callback);
1960};
1961
1962/**
1963 * @param {string} roomId
1964 * @param {string} body
1965 * @param {string} txnId Optional.
1966 * @param {module:client.callback} callback Optional.
1967 * @return {module:client.Promise} Resolves: TODO
1968 * @return {module:http-api.MatrixError} Rejects: with an error response.
1969 */
1970MatrixClient.prototype.sendTextMessage = function (roomId, body, txnId, callback) {
1971 var content = ContentHelpers.makeTextMessage(body);
1972 return this.sendMessage(roomId, content, txnId, callback);
1973};
1974
1975/**
1976 * @param {string} roomId
1977 * @param {string} body
1978 * @param {string} txnId Optional.
1979 * @param {module:client.callback} callback Optional.
1980 * @return {module:client.Promise} Resolves: TODO
1981 * @return {module:http-api.MatrixError} Rejects: with an error response.
1982 */
1983MatrixClient.prototype.sendNotice = function (roomId, body, txnId, callback) {
1984 var content = ContentHelpers.makeNotice(body);
1985 return this.sendMessage(roomId, content, txnId, callback);
1986};
1987
1988/**
1989 * @param {string} roomId
1990 * @param {string} body
1991 * @param {string} txnId Optional.
1992 * @param {module:client.callback} callback Optional.
1993 * @return {module:client.Promise} Resolves: TODO
1994 * @return {module:http-api.MatrixError} Rejects: with an error response.
1995 */
1996MatrixClient.prototype.sendEmoteMessage = function (roomId, body, txnId, callback) {
1997 var content = ContentHelpers.makeEmoteMessage(body);
1998 return this.sendMessage(roomId, content, txnId, callback);
1999};
2000
2001/**
2002 * @param {string} roomId
2003 * @param {string} url
2004 * @param {Object} info
2005 * @param {string} text
2006 * @param {module:client.callback} callback Optional.
2007 * @return {module:client.Promise} Resolves: TODO
2008 * @return {module:http-api.MatrixError} Rejects: with an error response.
2009 */
2010MatrixClient.prototype.sendImageMessage = function (roomId, url, info, text, callback) {
2011 if (utils.isFunction(text)) {
2012 callback = text;text = undefined;
2013 }
2014 if (!text) {
2015 text = "Image";
2016 }
2017 var content = {
2018 msgtype: "m.image",
2019 url: url,
2020 info: info,
2021 body: text
2022 };
2023 return this.sendMessage(roomId, content, callback);
2024};
2025
2026/**
2027 * @param {string} roomId
2028 * @param {string} url
2029 * @param {Object} info
2030 * @param {string} text
2031 * @param {module:client.callback} callback Optional.
2032 * @return {module:client.Promise} Resolves: TODO
2033 * @return {module:http-api.MatrixError} Rejects: with an error response.
2034 */
2035MatrixClient.prototype.sendStickerMessage = function (roomId, url, info, text, callback) {
2036 if (utils.isFunction(text)) {
2037 callback = text;text = undefined;
2038 }
2039 if (!text) {
2040 text = "Sticker";
2041 }
2042 var content = {
2043 url: url,
2044 info: info,
2045 body: text
2046 };
2047 return this.sendEvent(roomId, "m.sticker", content, callback, undefined);
2048};
2049
2050/**
2051 * @param {string} roomId
2052 * @param {string} body
2053 * @param {string} htmlBody
2054 * @param {module:client.callback} callback Optional.
2055 * @return {module:client.Promise} Resolves: TODO
2056 * @return {module:http-api.MatrixError} Rejects: with an error response.
2057 */
2058MatrixClient.prototype.sendHtmlMessage = function (roomId, body, htmlBody, callback) {
2059 var content = ContentHelpers.makeHtmlMessage(body, htmlBody);
2060 return this.sendMessage(roomId, content, callback);
2061};
2062
2063/**
2064 * @param {string} roomId
2065 * @param {string} body
2066 * @param {string} htmlBody
2067 * @param {module:client.callback} callback Optional.
2068 * @return {module:client.Promise} Resolves: TODO
2069 * @return {module:http-api.MatrixError} Rejects: with an error response.
2070 */
2071MatrixClient.prototype.sendHtmlNotice = function (roomId, body, htmlBody, callback) {
2072 var content = ContentHelpers.makeHtmlNotice(body, htmlBody);
2073 return this.sendMessage(roomId, content, callback);
2074};
2075
2076/**
2077 * @param {string} roomId
2078 * @param {string} body
2079 * @param {string} htmlBody
2080 * @param {module:client.callback} callback Optional.
2081 * @return {module:client.Promise} Resolves: TODO
2082 * @return {module:http-api.MatrixError} Rejects: with an error response.
2083 */
2084MatrixClient.prototype.sendHtmlEmote = function (roomId, body, htmlBody, callback) {
2085 var content = ContentHelpers.makeHtmlEmote(body, htmlBody);
2086 return this.sendMessage(roomId, content, callback);
2087};
2088
2089/**
2090 * Send a receipt.
2091 * @param {Event} event The event being acknowledged
2092 * @param {string} receiptType The kind of receipt e.g. "m.read"
2093 * @param {module:client.callback} callback Optional.
2094 * @return {module:client.Promise} Resolves: TODO
2095 * @return {module:http-api.MatrixError} Rejects: with an error response.
2096 */
2097MatrixClient.prototype.sendReceipt = function (event, receiptType, callback) {
2098 if (this.isGuest()) {
2099 return _bluebird2.default.resolve({}); // guests cannot send receipts so don't bother.
2100 }
2101
2102 var path = utils.encodeUri("/rooms/$roomId/receipt/$receiptType/$eventId", {
2103 $roomId: event.getRoomId(),
2104 $receiptType: receiptType,
2105 $eventId: event.getId()
2106 });
2107 var promise = this._http.authedRequest(callback, "POST", path, undefined, {});
2108
2109 var room = this.getRoom(event.getRoomId());
2110 if (room) {
2111 room._addLocalEchoReceipt(this.credentials.userId, event, receiptType);
2112 }
2113 return promise;
2114};
2115
2116/**
2117 * Send a read receipt.
2118 * @param {Event} event The event that has been read.
2119 * @param {module:client.callback} callback Optional.
2120 * @return {module:client.Promise} Resolves: TODO
2121 * @return {module:http-api.MatrixError} Rejects: with an error response.
2122 */
2123MatrixClient.prototype.sendReadReceipt = function (event, callback) {
2124 return this.sendReceipt(event, "m.read", callback);
2125};
2126
2127/**
2128 * Set a marker to indicate the point in a room before which the user has read every
2129 * event. This can be retrieved from room account data (the event type is `m.fully_read`)
2130 * and displayed as a horizontal line in the timeline that is visually distinct to the
2131 * position of the user's own read receipt.
2132 * @param {string} roomId ID of the room that has been read
2133 * @param {string} eventId ID of the event that has been read
2134 * @param {string} rrEvent the event tracked by the read receipt. This is here for
2135 * convenience because the RR and the RM are commonly updated at the same time as each
2136 * other. The local echo of this receipt will be done if set. Optional.
2137 * @return {module:client.Promise} Resolves: the empty object, {}.
2138 */
2139MatrixClient.prototype.setRoomReadMarkers = function (roomId, eventId, rrEvent) {
2140 var rmEventId = eventId;
2141 var rrEventId = void 0;
2142
2143 // Add the optional RR update, do local echo like `sendReceipt`
2144 if (rrEvent) {
2145 rrEventId = rrEvent.getId();
2146 var room = this.getRoom(roomId);
2147 if (room) {
2148 room._addLocalEchoReceipt(this.credentials.userId, rrEvent, "m.read");
2149 }
2150 }
2151
2152 return this.setRoomReadMarkersHttpRequest(roomId, rmEventId, rrEventId);
2153};
2154
2155/**
2156 * Get a preview of the given URL as of (roughly) the given point in time,
2157 * described as an object with OpenGraph keys and associated values.
2158 * Attributes may be synthesized where actual OG metadata is lacking.
2159 * Caches results to prevent hammering the server.
2160 * @param {string} url The URL to get preview data for
2161 * @param {Number} ts The preferred point in time that the preview should
2162 * describe (ms since epoch). The preview returned will either be the most
2163 * recent one preceding this timestamp if available, or failing that the next
2164 * most recent available preview.
2165 * @param {module:client.callback} callback Optional.
2166 * @return {module:client.Promise} Resolves: Object of OG metadata.
2167 * @return {module:http-api.MatrixError} Rejects: with an error response.
2168 * May return synthesized attributes if the URL lacked OG meta.
2169 */
2170MatrixClient.prototype.getUrlPreview = function (url, ts, callback) {
2171 var key = ts + "_" + url;
2172 var og = this.urlPreviewCache[key];
2173 if (og) {
2174 return _bluebird2.default.resolve(og);
2175 }
2176
2177 var self = this;
2178 return this._http.authedRequestWithPrefix(callback, "GET", "/preview_url", {
2179 url: url,
2180 ts: ts
2181 }, undefined, httpApi.PREFIX_MEDIA_R0).then(function (response) {
2182 // TODO: expire cache occasionally
2183 self.urlPreviewCache[key] = response;
2184 return response;
2185 });
2186};
2187
2188/**
2189 * @param {string} roomId
2190 * @param {boolean} isTyping
2191 * @param {Number} timeoutMs
2192 * @param {module:client.callback} callback Optional.
2193 * @return {module:client.Promise} Resolves: TODO
2194 * @return {module:http-api.MatrixError} Rejects: with an error response.
2195 */
2196MatrixClient.prototype.sendTyping = function (roomId, isTyping, timeoutMs, callback) {
2197 if (this.isGuest()) {
2198 return _bluebird2.default.resolve({}); // guests cannot send typing notifications so don't bother.
2199 }
2200
2201 var path = utils.encodeUri("/rooms/$roomId/typing/$userId", {
2202 $roomId: roomId,
2203 $userId: this.credentials.userId
2204 });
2205 var data = {
2206 typing: isTyping
2207 };
2208 if (isTyping) {
2209 data.timeout = timeoutMs ? timeoutMs : 20000;
2210 }
2211 return this._http.authedRequest(callback, "PUT", path, undefined, data);
2212};
2213
2214/**
2215 * @param {string} roomId
2216 * @param {string} userId
2217 * @param {module:client.callback} callback Optional.
2218 * @return {module:client.Promise} Resolves: TODO
2219 * @return {module:http-api.MatrixError} Rejects: with an error response.
2220 */
2221MatrixClient.prototype.invite = function (roomId, userId, callback) {
2222 return _membershipChange(this, roomId, userId, "invite", undefined, callback);
2223};
2224
2225/**
2226 * Invite a user to a room based on their email address.
2227 * @param {string} roomId The room to invite the user to.
2228 * @param {string} email The email address to invite.
2229 * @param {module:client.callback} callback Optional.
2230 * @return {module:client.Promise} Resolves: TODO
2231 * @return {module:http-api.MatrixError} Rejects: with an error response.
2232 */
2233MatrixClient.prototype.inviteByEmail = function (roomId, email, callback) {
2234 return this.inviteByThreePid(roomId, "email", email, callback);
2235};
2236
2237/**
2238 * Invite a user to a room based on a third-party identifier.
2239 * @param {string} roomId The room to invite the user to.
2240 * @param {string} medium The medium to invite the user e.g. "email".
2241 * @param {string} address The address for the specified medium.
2242 * @param {module:client.callback} callback Optional.
2243 * @return {module:client.Promise} Resolves: TODO
2244 * @return {module:http-api.MatrixError} Rejects: with an error response.
2245 */
2246MatrixClient.prototype.inviteByThreePid = function (roomId, medium, address, callback) {
2247 var path = utils.encodeUri("/rooms/$roomId/invite", { $roomId: roomId });
2248
2249 var identityServerUrl = this.getIdentityServerUrl(true);
2250 if (!identityServerUrl) {
2251 return _bluebird2.default.reject(new MatrixError({
2252 error: "No supplied identity server URL",
2253 errcode: "ORG.MATRIX.JSSDK_MISSING_PARAM"
2254 }));
2255 }
2256
2257 return this._http.authedRequest(callback, "POST", path, undefined, {
2258 id_server: identityServerUrl,
2259 medium: medium,
2260 address: address
2261 });
2262};
2263
2264/**
2265 * @param {string} roomId
2266 * @param {module:client.callback} callback Optional.
2267 * @return {module:client.Promise} Resolves: TODO
2268 * @return {module:http-api.MatrixError} Rejects: with an error response.
2269 */
2270MatrixClient.prototype.leave = function (roomId, callback) {
2271 return _membershipChange(this, roomId, undefined, "leave", undefined, callback);
2272};
2273
2274/**
2275 * @param {string} roomId
2276 * @param {string} userId
2277 * @param {string} reason Optional.
2278 * @param {module:client.callback} callback Optional.
2279 * @return {module:client.Promise} Resolves: TODO
2280 * @return {module:http-api.MatrixError} Rejects: with an error response.
2281 */
2282MatrixClient.prototype.ban = function (roomId, userId, reason, callback) {
2283 return _membershipChange(this, roomId, userId, "ban", reason, callback);
2284};
2285
2286/**
2287 * @param {string} roomId
2288 * @param {boolean} deleteRoom True to delete the room from the store on success.
2289 * Default: true.
2290 * @param {module:client.callback} callback Optional.
2291 * @return {module:client.Promise} Resolves: TODO
2292 * @return {module:http-api.MatrixError} Rejects: with an error response.
2293 */
2294MatrixClient.prototype.forget = function (roomId, deleteRoom, callback) {
2295 if (deleteRoom === undefined) {
2296 deleteRoom = true;
2297 }
2298 var promise = _membershipChange(this, roomId, undefined, "forget", undefined, callback);
2299 if (!deleteRoom) {
2300 return promise;
2301 }
2302 var self = this;
2303 return promise.then(function (response) {
2304 self.store.removeRoom(roomId);
2305 self.emit("deleteRoom", roomId);
2306 return response;
2307 });
2308};
2309
2310/**
2311 * @param {string} roomId
2312 * @param {string} userId
2313 * @param {module:client.callback} callback Optional.
2314 * @return {module:client.Promise} Resolves: Object (currently empty)
2315 * @return {module:http-api.MatrixError} Rejects: with an error response.
2316 */
2317MatrixClient.prototype.unban = function (roomId, userId, callback) {
2318 // unbanning != set their state to leave: this used to be
2319 // the case, but was then changed so that leaving was always
2320 // a revoking of priviledge, otherwise two people racing to
2321 // kick / ban someone could end up banning and then un-banning
2322 // them.
2323 var path = utils.encodeUri("/rooms/$roomId/unban", {
2324 $roomId: roomId
2325 });
2326 var data = {
2327 user_id: userId
2328 };
2329 return this._http.authedRequest(callback, "POST", path, undefined, data);
2330};
2331
2332/**
2333 * @param {string} roomId
2334 * @param {string} userId
2335 * @param {string} reason Optional.
2336 * @param {module:client.callback} callback Optional.
2337 * @return {module:client.Promise} Resolves: TODO
2338 * @return {module:http-api.MatrixError} Rejects: with an error response.
2339 */
2340MatrixClient.prototype.kick = function (roomId, userId, reason, callback) {
2341 return _setMembershipState(this, roomId, userId, "leave", reason, callback);
2342};
2343
2344/**
2345 * This is an internal method.
2346 * @param {MatrixClient} client
2347 * @param {string} roomId
2348 * @param {string} userId
2349 * @param {string} membershipValue
2350 * @param {string} reason
2351 * @param {module:client.callback} callback Optional.
2352 * @return {module:client.Promise} Resolves: TODO
2353 * @return {module:http-api.MatrixError} Rejects: with an error response.
2354 */
2355function _setMembershipState(client, roomId, userId, membershipValue, reason, callback) {
2356 if (utils.isFunction(reason)) {
2357 callback = reason;reason = undefined;
2358 }
2359
2360 var path = utils.encodeUri("/rooms/$roomId/state/m.room.member/$userId", { $roomId: roomId, $userId: userId });
2361
2362 return client._http.authedRequest(callback, "PUT", path, undefined, {
2363 membership: membershipValue,
2364 reason: reason
2365 });
2366}
2367
2368/**
2369 * This is an internal method.
2370 * @param {MatrixClient} client
2371 * @param {string} roomId
2372 * @param {string} userId
2373 * @param {string} membership
2374 * @param {string} reason
2375 * @param {module:client.callback} callback Optional.
2376 * @return {module:client.Promise} Resolves: TODO
2377 * @return {module:http-api.MatrixError} Rejects: with an error response.
2378 */
2379function _membershipChange(client, roomId, userId, membership, reason, callback) {
2380 if (utils.isFunction(reason)) {
2381 callback = reason;reason = undefined;
2382 }
2383
2384 var path = utils.encodeUri("/rooms/$room_id/$membership", {
2385 $room_id: roomId,
2386 $membership: membership
2387 });
2388 return client._http.authedRequest(callback, "POST", path, undefined, {
2389 user_id: userId, // may be undefined e.g. on leave
2390 reason: reason
2391 });
2392}
2393
2394/**
2395 * Obtain a dict of actions which should be performed for this event according
2396 * to the push rules for this user. Caches the dict on the event.
2397 * @param {MatrixEvent} event The event to get push actions for.
2398 * @return {module:pushprocessor~PushAction} A dict of actions to perform.
2399 */
2400MatrixClient.prototype.getPushActionsForEvent = function (event) {
2401 if (!event.getPushActions()) {
2402 event.setPushActions(this._pushProcessor.actionsForEvent(event));
2403 }
2404 return event.getPushActions();
2405};
2406
2407// Profile operations
2408// ==================
2409
2410/**
2411 * @param {string} info The kind of info to set (e.g. 'avatar_url')
2412 * @param {Object} data The JSON object to set.
2413 * @param {module:client.callback} callback Optional.
2414 * @return {module:client.Promise} Resolves: TODO
2415 * @return {module:http-api.MatrixError} Rejects: with an error response.
2416 */
2417MatrixClient.prototype.setProfileInfo = function (info, data, callback) {
2418 var path = utils.encodeUri("/profile/$userId/$info", {
2419 $userId: this.credentials.userId,
2420 $info: info
2421 });
2422 return this._http.authedRequest(callback, "PUT", path, undefined, data);
2423};
2424
2425/**
2426 * @param {string} name
2427 * @param {module:client.callback} callback Optional.
2428 * @return {module:client.Promise} Resolves: TODO
2429 * @return {module:http-api.MatrixError} Rejects: with an error response.
2430 */
2431MatrixClient.prototype.setDisplayName = function (name, callback) {
2432 return this.setProfileInfo("displayname", { displayname: name }, callback);
2433};
2434
2435/**
2436 * @param {string} url
2437 * @param {module:client.callback} callback Optional.
2438 * @return {module:client.Promise} Resolves: TODO
2439 * @return {module:http-api.MatrixError} Rejects: with an error response.
2440 */
2441MatrixClient.prototype.setAvatarUrl = function (url, callback) {
2442 return this.setProfileInfo("avatar_url", { avatar_url: url }, callback);
2443};
2444
2445/**
2446 * Turn an MXC URL into an HTTP one. <strong>This method is experimental and
2447 * may change.</strong>
2448 * @param {string} mxcUrl The MXC URL
2449 * @param {Number} width The desired width of the thumbnail.
2450 * @param {Number} height The desired height of the thumbnail.
2451 * @param {string} resizeMethod The thumbnail resize method to use, either
2452 * "crop" or "scale".
2453 * @param {Boolean} allowDirectLinks If true, return any non-mxc URLs
2454 * directly. Fetching such URLs will leak information about the user to
2455 * anyone they share a room with. If false, will return null for such URLs.
2456 * @return {?string} the avatar URL or null.
2457 */
2458MatrixClient.prototype.mxcUrlToHttp = function (mxcUrl, width, height, resizeMethod, allowDirectLinks) {
2459 return contentRepo.getHttpUriForMxc(this.baseUrl, mxcUrl, width, height, resizeMethod, allowDirectLinks);
2460};
2461
2462/**
2463 * @param {Object} opts Options to apply
2464 * @param {string} opts.presence One of "online", "offline" or "unavailable"
2465 * @param {string} opts.status_msg The status message to attach.
2466 * @param {module:client.callback} callback Optional.
2467 * @return {module:client.Promise} Resolves: TODO
2468 * @return {module:http-api.MatrixError} Rejects: with an error response.
2469 * @throws If 'presence' isn't a valid presence enum value.
2470 */
2471MatrixClient.prototype.setPresence = function (opts, callback) {
2472 var path = utils.encodeUri("/presence/$userId/status", {
2473 $userId: this.credentials.userId
2474 });
2475
2476 if (typeof opts === "string") {
2477 opts = { presence: opts };
2478 }
2479
2480 var validStates = ["offline", "online", "unavailable"];
2481 if (validStates.indexOf(opts.presence) == -1) {
2482 throw new Error("Bad presence value: " + opts.presence);
2483 }
2484 return this._http.authedRequest(callback, "PUT", path, undefined, opts);
2485};
2486
2487function _presenceList(callback, client, opts, method) {
2488 var path = utils.encodeUri("/presence/list/$userId", {
2489 $userId: client.credentials.userId
2490 });
2491 return client._http.authedRequest(callback, method, path, undefined, opts);
2492}
2493
2494/**
2495* Retrieve current user presence list.
2496* @param {module:client.callback} callback Optional.
2497* @return {module:client.Promise} Resolves: TODO
2498* @return {module:http-api.MatrixError} Rejects: with an error response.
2499*/
2500MatrixClient.prototype.getPresenceList = function (callback) {
2501 return _presenceList(callback, this, undefined, "GET");
2502};
2503
2504/**
2505* Add users to the current user presence list.
2506* @param {module:client.callback} callback Optional.
2507* @param {string[]} userIds
2508* @return {module:client.Promise} Resolves: TODO
2509* @return {module:http-api.MatrixError} Rejects: with an error response.
2510*/
2511MatrixClient.prototype.inviteToPresenceList = function (callback, userIds) {
2512 var opts = { "invite": userIds };
2513 return _presenceList(callback, this, opts, "POST");
2514};
2515
2516/**
2517* Drop users from the current user presence list.
2518* @param {module:client.callback} callback Optional.
2519* @param {string[]} userIds
2520* @return {module:client.Promise} Resolves: TODO
2521* @return {module:http-api.MatrixError} Rejects: with an error response.
2522**/
2523MatrixClient.prototype.dropFromPresenceList = function (callback, userIds) {
2524 var opts = { "drop": userIds };
2525 return _presenceList(callback, this, opts, "POST");
2526};
2527
2528/**
2529 * Retrieve older messages from the given room and put them in the timeline.
2530 *
2531 * If this is called multiple times whilst a request is ongoing, the <i>same</i>
2532 * Promise will be returned. If there was a problem requesting scrollback, there
2533 * will be a small delay before another request can be made (to prevent tight-looping
2534 * when there is no connection).
2535 *
2536 * @param {Room} room The room to get older messages in.
2537 * @param {Integer} limit Optional. The maximum number of previous events to
2538 * pull in. Default: 30.
2539 * @param {module:client.callback} callback Optional.
2540 * @return {module:client.Promise} Resolves: Room. If you are at the beginning
2541 * of the timeline, <code>Room.oldState.paginationToken</code> will be
2542 * <code>null</code>.
2543 * @return {module:http-api.MatrixError} Rejects: with an error response.
2544 */
2545MatrixClient.prototype.scrollback = function (room, limit, callback) {
2546 if (utils.isFunction(limit)) {
2547 callback = limit;limit = undefined;
2548 }
2549 limit = limit || 30;
2550 var timeToWaitMs = 0;
2551
2552 var info = this._ongoingScrollbacks[room.roomId] || {};
2553 if (info.promise) {
2554 return info.promise;
2555 } else if (info.errorTs) {
2556 var timeWaitedMs = Date.now() - info.errorTs;
2557 timeToWaitMs = Math.max(SCROLLBACK_DELAY_MS - timeWaitedMs, 0);
2558 }
2559
2560 if (room.oldState.paginationToken === null) {
2561 return _bluebird2.default.resolve(room); // already at the start.
2562 }
2563 // attempt to grab more events from the store first
2564 var numAdded = this.store.scrollback(room, limit).length;
2565 if (numAdded === limit) {
2566 // store contained everything we needed.
2567 return _bluebird2.default.resolve(room);
2568 }
2569 // reduce the required number of events appropriately
2570 limit = limit - numAdded;
2571
2572 var defer = _bluebird2.default.defer();
2573 info = {
2574 promise: defer.promise,
2575 errorTs: null
2576 };
2577 var self = this;
2578 // wait for a time before doing this request
2579 // (which may be 0 in order not to special case the code paths)
2580 _bluebird2.default.delay(timeToWaitMs).then(function () {
2581 return self._createMessagesRequest(room.roomId, room.oldState.paginationToken, limit, 'b');
2582 }).done(function (res) {
2583 var matrixEvents = utils.map(res.chunk, _PojoToMatrixEventMapper(self));
2584 if (res.state) {
2585 var stateEvents = utils.map(res.state, _PojoToMatrixEventMapper(self));
2586 room.currentState.setUnknownStateEvents(stateEvents);
2587 }
2588 room.addEventsToTimeline(matrixEvents, true, room.getLiveTimeline());
2589 room.oldState.paginationToken = res.end;
2590 if (res.chunk.length === 0) {
2591 room.oldState.paginationToken = null;
2592 }
2593 self.store.storeEvents(room, matrixEvents, res.end, true);
2594 self._ongoingScrollbacks[room.roomId] = null;
2595 _resolve(callback, defer, room);
2596 }, function (err) {
2597 self._ongoingScrollbacks[room.roomId] = {
2598 errorTs: Date.now()
2599 };
2600 _reject(callback, defer, err);
2601 });
2602 this._ongoingScrollbacks[room.roomId] = info;
2603 return defer.promise;
2604};
2605
2606/**
2607 * Get an EventTimeline for the given event
2608 *
2609 * <p>If the EventTimelineSet object already has the given event in its store, the
2610 * corresponding timeline will be returned. Otherwise, a /context request is
2611 * made, and used to construct an EventTimeline.
2612 *
2613 * @param {EventTimelineSet} timelineSet The timelineSet to look for the event in
2614 * @param {string} eventId The ID of the event to look for
2615 *
2616 * @return {module:client.Promise} Resolves:
2617 * {@link module:models/event-timeline~EventTimeline} including the given
2618 * event
2619 */
2620MatrixClient.prototype.getEventTimeline = function (timelineSet, eventId) {
2621 // don't allow any timeline support unless it's been enabled.
2622 if (!this.timelineSupport) {
2623 throw new Error("timeline support is disabled. Set the 'timelineSupport'" + " parameter to true when creating MatrixClient to enable" + " it.");
2624 }
2625
2626 if (timelineSet.getTimelineForEvent(eventId)) {
2627 return _bluebird2.default.resolve(timelineSet.getTimelineForEvent(eventId));
2628 }
2629
2630 var path = utils.encodeUri("/rooms/$roomId/context/$eventId", {
2631 $roomId: timelineSet.room.roomId,
2632 $eventId: eventId
2633 });
2634
2635 var params = undefined;
2636 if (this._clientOpts.lazyLoadMembers) {
2637 params = { filter: (0, _stringify2.default)(Filter.LAZY_LOADING_MESSAGES_FILTER) };
2638 }
2639
2640 // TODO: we should implement a backoff (as per scrollback()) to deal more
2641 // nicely with HTTP errors.
2642 var self = this;
2643 var promise = self._http.authedRequest(undefined, "GET", path, params).then(function (res) {
2644 if (!res.event) {
2645 throw new Error("'event' not in '/context' result - homeserver too old?");
2646 }
2647
2648 // by the time the request completes, the event might have ended up in
2649 // the timeline.
2650 if (timelineSet.getTimelineForEvent(eventId)) {
2651 return timelineSet.getTimelineForEvent(eventId);
2652 }
2653
2654 // we start with the last event, since that's the point at which we
2655 // have known state.
2656 // events_after is already backwards; events_before is forwards.
2657 res.events_after.reverse();
2658 var events = res.events_after.concat([res.event]).concat(res.events_before);
2659 var matrixEvents = utils.map(events, self.getEventMapper());
2660
2661 var timeline = timelineSet.getTimelineForEvent(matrixEvents[0].getId());
2662 if (!timeline) {
2663 timeline = timelineSet.addTimeline();
2664 timeline.initialiseState(utils.map(res.state, self.getEventMapper()));
2665 timeline.getState(EventTimeline.FORWARDS).paginationToken = res.end;
2666 } else {
2667 var stateEvents = utils.map(res.state, self.getEventMapper());
2668 timeline.getState(EventTimeline.BACKWARDS).setUnknownStateEvents(stateEvents);
2669 }
2670 timelineSet.addEventsToTimeline(matrixEvents, true, timeline, res.start);
2671
2672 // there is no guarantee that the event ended up in "timeline" (we
2673 // might have switched to a neighbouring timeline) - so check the
2674 // room's index again. On the other hand, there's no guarantee the
2675 // event ended up anywhere, if it was later redacted, so we just
2676 // return the timeline we first thought of.
2677 var tl = timelineSet.getTimelineForEvent(eventId) || timeline;
2678 return tl;
2679 });
2680 return promise;
2681};
2682
2683/**
2684 * Makes a request to /messages with the appropriate lazy loading filter set.
2685 * XXX: if we do get rid of scrollback (as it's not used at the moment),
2686 * we could inline this method again in paginateEventTimeline as that would
2687 * then be the only call-site
2688 * @param {string} roomId
2689 * @param {string} fromToken
2690 * @param {number} limit the maximum amount of events the retrieve
2691 * @param {string} dir 'f' or 'b'
2692 * @param {Filter} timelineFilter the timeline filter to pass
2693 * @return {Promise}
2694 */
2695MatrixClient.prototype._createMessagesRequest = function (roomId, fromToken, limit, dir) {
2696 var timelineFilter = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : undefined;
2697
2698 var path = utils.encodeUri("/rooms/$roomId/messages", { $roomId: roomId });
2699 if (limit === undefined) {
2700 limit = 30;
2701 }
2702 var params = {
2703 from: fromToken,
2704 limit: limit,
2705 dir: dir
2706 };
2707
2708 var filter = null;
2709 if (this._clientOpts.lazyLoadMembers) {
2710 // create a shallow copy of LAZY_LOADING_MESSAGES_FILTER,
2711 // so the timelineFilter doesn't get written into it below
2712 filter = (0, _assign2.default)({}, Filter.LAZY_LOADING_MESSAGES_FILTER);
2713 }
2714 if (timelineFilter) {
2715 // XXX: it's horrific that /messages' filter parameter doesn't match
2716 // /sync's one - see https://matrix.org/jira/browse/SPEC-451
2717 filter = filter || {};
2718 (0, _assign2.default)(filter, timelineFilter.getRoomTimelineFilterComponent());
2719 }
2720 if (filter) {
2721 params.filter = (0, _stringify2.default)(filter);
2722 }
2723 return this._http.authedRequest(undefined, "GET", path, params);
2724};
2725
2726/**
2727 * Take an EventTimeline, and back/forward-fill results.
2728 *
2729 * @param {module:models/event-timeline~EventTimeline} eventTimeline timeline
2730 * object to be updated
2731 * @param {Object} [opts]
2732 * @param {bool} [opts.backwards = false] true to fill backwards,
2733 * false to go forwards
2734 * @param {number} [opts.limit = 30] number of events to request
2735 *
2736 * @return {module:client.Promise} Resolves to a boolean: false if there are no
2737 * events and we reached either end of the timeline; else true.
2738 */
2739MatrixClient.prototype.paginateEventTimeline = function (eventTimeline, opts) {
2740 var isNotifTimeline = eventTimeline.getTimelineSet() === this._notifTimelineSet;
2741
2742 // TODO: we should implement a backoff (as per scrollback()) to deal more
2743 // nicely with HTTP errors.
2744 opts = opts || {};
2745 var backwards = opts.backwards || false;
2746
2747 if (isNotifTimeline) {
2748 if (!backwards) {
2749 throw new Error("paginateNotifTimeline can only paginate backwards");
2750 }
2751 }
2752
2753 var dir = backwards ? EventTimeline.BACKWARDS : EventTimeline.FORWARDS;
2754
2755 var token = eventTimeline.getPaginationToken(dir);
2756 if (!token) {
2757 // no token - no results.
2758 return _bluebird2.default.resolve(false);
2759 }
2760
2761 var pendingRequest = eventTimeline._paginationRequests[dir];
2762
2763 if (pendingRequest) {
2764 // already a request in progress - return the existing promise
2765 return pendingRequest;
2766 }
2767
2768 var path = void 0,
2769 params = void 0,
2770 promise = void 0;
2771 var self = this;
2772
2773 if (isNotifTimeline) {
2774 path = "/notifications";
2775 params = {
2776 limit: 'limit' in opts ? opts.limit : 30,
2777 only: 'highlight'
2778 };
2779
2780 if (token && token !== "end") {
2781 params.from = token;
2782 }
2783
2784 promise = this._http.authedRequestWithPrefix(undefined, "GET", path, params, undefined, httpApi.PREFIX_UNSTABLE).then(function (res) {
2785 var token = res.next_token;
2786 var matrixEvents = [];
2787
2788 for (var i = 0; i < res.notifications.length; i++) {
2789 var notification = res.notifications[i];
2790 var event = self.getEventMapper()(notification.event);
2791 event.setPushActions(PushProcessor.actionListToActionsObject(notification.actions));
2792 event.event.room_id = notification.room_id; // XXX: gutwrenching
2793 matrixEvents[i] = event;
2794 }
2795
2796 eventTimeline.getTimelineSet().addEventsToTimeline(matrixEvents, backwards, eventTimeline, token);
2797
2798 // if we've hit the end of the timeline, we need to stop trying to
2799 // paginate. We need to keep the 'forwards' token though, to make sure
2800 // we can recover from gappy syncs.
2801 if (backwards && !res.next_token) {
2802 eventTimeline.setPaginationToken(null, dir);
2803 }
2804 return res.next_token ? true : false;
2805 }).finally(function () {
2806 eventTimeline._paginationRequests[dir] = null;
2807 });
2808 eventTimeline._paginationRequests[dir] = promise;
2809 } else {
2810 var room = this.getRoom(eventTimeline.getRoomId());
2811 if (!room) {
2812 throw new Error("Unknown room " + eventTimeline.getRoomId());
2813 }
2814
2815 promise = this._createMessagesRequest(eventTimeline.getRoomId(), token, opts.limit, dir, eventTimeline.getFilter());
2816 promise.then(function (res) {
2817 if (res.state) {
2818 var roomState = eventTimeline.getState(dir);
2819 var stateEvents = utils.map(res.state, self.getEventMapper());
2820 roomState.setUnknownStateEvents(stateEvents);
2821 }
2822 var token = res.end;
2823 var matrixEvents = utils.map(res.chunk, self.getEventMapper());
2824 eventTimeline.getTimelineSet().addEventsToTimeline(matrixEvents, backwards, eventTimeline, token);
2825
2826 // if we've hit the end of the timeline, we need to stop trying to
2827 // paginate. We need to keep the 'forwards' token though, to make sure
2828 // we can recover from gappy syncs.
2829 if (backwards && res.end == res.start) {
2830 eventTimeline.setPaginationToken(null, dir);
2831 }
2832 return res.end != res.start;
2833 }).finally(function () {
2834 eventTimeline._paginationRequests[dir] = null;
2835 });
2836 eventTimeline._paginationRequests[dir] = promise;
2837 }
2838
2839 return promise;
2840};
2841
2842/**
2843 * Reset the notifTimelineSet entirely, paginating in some historical notifs as
2844 * a starting point for subsequent pagination.
2845 */
2846MatrixClient.prototype.resetNotifTimelineSet = function () {
2847 if (!this._notifTimelineSet) {
2848 return;
2849 }
2850
2851 // FIXME: This thing is a total hack, and results in duplicate events being
2852 // added to the timeline both from /sync and /notifications, and lots of
2853 // slow and wasteful processing and pagination. The correct solution is to
2854 // extend /messages or /search or something to filter on notifications.
2855
2856 // use the fictitious token 'end'. in practice we would ideally give it
2857 // the oldest backwards pagination token from /sync, but /sync doesn't
2858 // know about /notifications, so we have no choice but to start paginating
2859 // from the current point in time. This may well overlap with historical
2860 // notifs which are then inserted into the timeline by /sync responses.
2861 this._notifTimelineSet.resetLiveTimeline('end', null);
2862
2863 // we could try to paginate a single event at this point in order to get
2864 // a more valid pagination token, but it just ends up with an out of order
2865 // timeline. given what a mess this is and given we're going to have duplicate
2866 // events anyway, just leave it with the dummy token for now.
2867 /*
2868 this.paginateNotifTimeline(this._notifTimelineSet.getLiveTimeline(), {
2869 backwards: true,
2870 limit: 1
2871 });
2872 */
2873};
2874
2875/**
2876 * Peek into a room and receive updates about the room. This only works if the
2877 * history visibility for the room is world_readable.
2878 * @param {String} roomId The room to attempt to peek into.
2879 * @return {module:client.Promise} Resolves: Room object
2880 * @return {module:http-api.MatrixError} Rejects: with an error response.
2881 */
2882MatrixClient.prototype.peekInRoom = function (roomId) {
2883 if (this._peekSync) {
2884 this._peekSync.stopPeeking();
2885 }
2886 this._peekSync = new SyncApi(this, this._clientOpts);
2887 return this._peekSync.peek(roomId);
2888};
2889
2890/**
2891 * Stop any ongoing room peeking.
2892 */
2893MatrixClient.prototype.stopPeeking = function () {
2894 if (this._peekSync) {
2895 this._peekSync.stopPeeking();
2896 this._peekSync = null;
2897 }
2898};
2899
2900/**
2901 * Set r/w flags for guest access in a room.
2902 * @param {string} roomId The room to configure guest access in.
2903 * @param {Object} opts Options
2904 * @param {boolean} opts.allowJoin True to allow guests to join this room. This
2905 * implicitly gives guests write access. If false or not given, guests are
2906 * explicitly forbidden from joining the room.
2907 * @param {boolean} opts.allowRead True to set history visibility to
2908 * be world_readable. This gives guests read access *from this point forward*.
2909 * If false or not given, history visibility is not modified.
2910 * @return {module:client.Promise} Resolves: TODO
2911 * @return {module:http-api.MatrixError} Rejects: with an error response.
2912 */
2913MatrixClient.prototype.setGuestAccess = function (roomId, opts) {
2914 var writePromise = this.sendStateEvent(roomId, "m.room.guest_access", {
2915 guest_access: opts.allowJoin ? "can_join" : "forbidden"
2916 });
2917
2918 var readPromise = _bluebird2.default.resolve();
2919 if (opts.allowRead) {
2920 readPromise = this.sendStateEvent(roomId, "m.room.history_visibility", {
2921 history_visibility: "world_readable"
2922 });
2923 }
2924
2925 return _bluebird2.default.all([readPromise, writePromise]);
2926};
2927
2928// Registration/Login operations
2929// =============================
2930
2931/**
2932 * Requests an email verification token for the purposes of registration.
2933 * This API proxies the Identity Server /validate/email/requestToken API,
2934 * adding registration-specific behaviour. Specifically, if an account with
2935 * the given email address already exists, it will either send an email
2936 * to the address informing them of this or return M_THREEPID_IN_USE
2937 * (which one is up to the Home Server).
2938 *
2939 * requestEmailToken calls the equivalent API directly on the ID server,
2940 * therefore bypassing the registration-specific logic.
2941 *
2942 * Parameters and return value are as for requestEmailToken
2943
2944 * @param {string} email As requestEmailToken
2945 * @param {string} clientSecret As requestEmailToken
2946 * @param {number} sendAttempt As requestEmailToken
2947 * @param {string} nextLink As requestEmailToken
2948 * @return {module:client.Promise} Resolves: As requestEmailToken
2949 */
2950MatrixClient.prototype.requestRegisterEmailToken = function (email, clientSecret, sendAttempt, nextLink) {
2951 return this._requestTokenFromEndpoint("/register/email/requestToken", {
2952 email: email,
2953 client_secret: clientSecret,
2954 send_attempt: sendAttempt,
2955 next_link: nextLink
2956 });
2957};
2958
2959/**
2960 * Requests a text message verification token for the purposes of registration.
2961 * This API proxies the Identity Server /validate/msisdn/requestToken API,
2962 * adding registration-specific behaviour, as with requestRegisterEmailToken.
2963 *
2964 * @param {string} phoneCountry The ISO 3166-1 alpha-2 code for the country in which
2965 * phoneNumber should be parsed relative to.
2966 * @param {string} phoneNumber The phone number, in national or international format
2967 * @param {string} clientSecret As requestEmailToken
2968 * @param {number} sendAttempt As requestEmailToken
2969 * @param {string} nextLink As requestEmailToken
2970 * @return {module:client.Promise} Resolves: As requestEmailToken
2971 */
2972MatrixClient.prototype.requestRegisterMsisdnToken = function (phoneCountry, phoneNumber, clientSecret, sendAttempt, nextLink) {
2973 return this._requestTokenFromEndpoint("/register/msisdn/requestToken", {
2974 country: phoneCountry,
2975 phone_number: phoneNumber,
2976 client_secret: clientSecret,
2977 send_attempt: sendAttempt,
2978 next_link: nextLink
2979 });
2980};
2981
2982/**
2983 * Requests an email verification token for the purposes of adding a
2984 * third party identifier to an account.
2985 * This API proxies the Identity Server /validate/email/requestToken API,
2986 * adding specific behaviour for the addition of email addresses to an
2987 * account. Specifically, if an account with
2988 * the given email address already exists, it will either send an email
2989 * to the address informing them of this or return M_THREEPID_IN_USE
2990 * (which one is up to the Home Server).
2991 *
2992 * requestEmailToken calls the equivalent API directly on the ID server,
2993 * therefore bypassing the email addition specific logic.
2994 *
2995 * @param {string} email As requestEmailToken
2996 * @param {string} clientSecret As requestEmailToken
2997 * @param {number} sendAttempt As requestEmailToken
2998 * @param {string} nextLink As requestEmailToken
2999 * @return {module:client.Promise} Resolves: As requestEmailToken
3000 */
3001MatrixClient.prototype.requestAdd3pidEmailToken = function (email, clientSecret, sendAttempt, nextLink) {
3002 return this._requestTokenFromEndpoint("/account/3pid/email/requestToken", {
3003 email: email,
3004 client_secret: clientSecret,
3005 send_attempt: sendAttempt,
3006 next_link: nextLink
3007 });
3008};
3009
3010/**
3011 * Requests a text message verification token for the purposes of adding a
3012 * third party identifier to an account.
3013 * This API proxies the Identity Server /validate/email/requestToken API,
3014 * adding specific behaviour for the addition of phone numbers to an
3015 * account, as requestAdd3pidEmailToken.
3016 *
3017 * @param {string} phoneCountry As requestRegisterMsisdnToken
3018 * @param {string} phoneNumber As requestRegisterMsisdnToken
3019 * @param {string} clientSecret As requestEmailToken
3020 * @param {number} sendAttempt As requestEmailToken
3021 * @param {string} nextLink As requestEmailToken
3022 * @return {module:client.Promise} Resolves: As requestEmailToken
3023 */
3024MatrixClient.prototype.requestAdd3pidMsisdnToken = function (phoneCountry, phoneNumber, clientSecret, sendAttempt, nextLink) {
3025 return this._requestTokenFromEndpoint("/account/3pid/msisdn/requestToken", {
3026 country: phoneCountry,
3027 phone_number: phoneNumber,
3028 client_secret: clientSecret,
3029 send_attempt: sendAttempt,
3030 next_link: nextLink
3031 });
3032};
3033
3034/**
3035 * Requests an email verification token for the purposes of resetting
3036 * the password on an account.
3037 * This API proxies the Identity Server /validate/email/requestToken API,
3038 * adding specific behaviour for the password resetting. Specifically,
3039 * if no account with the given email address exists, it may either
3040 * return M_THREEPID_NOT_FOUND or send an email
3041 * to the address informing them of this (which one is up to the Home Server).
3042 *
3043 * requestEmailToken calls the equivalent API directly on the ID server,
3044 * therefore bypassing the password reset specific logic.
3045 *
3046 * @param {string} email As requestEmailToken
3047 * @param {string} clientSecret As requestEmailToken
3048 * @param {number} sendAttempt As requestEmailToken
3049 * @param {string} nextLink As requestEmailToken
3050 * @param {module:client.callback} callback Optional. As requestEmailToken
3051 * @return {module:client.Promise} Resolves: As requestEmailToken
3052 */
3053MatrixClient.prototype.requestPasswordEmailToken = function (email, clientSecret, sendAttempt, nextLink) {
3054 return this._requestTokenFromEndpoint("/account/password/email/requestToken", {
3055 email: email,
3056 client_secret: clientSecret,
3057 send_attempt: sendAttempt,
3058 next_link: nextLink
3059 });
3060};
3061
3062/**
3063 * Requests a text message verification token for the purposes of resetting
3064 * the password on an account.
3065 * This API proxies the Identity Server /validate/email/requestToken API,
3066 * adding specific behaviour for the password resetting, as requestPasswordEmailToken.
3067 *
3068 * @param {string} phoneCountry As requestRegisterMsisdnToken
3069 * @param {string} phoneNumber As requestRegisterMsisdnToken
3070 * @param {string} clientSecret As requestEmailToken
3071 * @param {number} sendAttempt As requestEmailToken
3072 * @param {string} nextLink As requestEmailToken
3073 * @return {module:client.Promise} Resolves: As requestEmailToken
3074 */
3075MatrixClient.prototype.requestPasswordMsisdnToken = function (phoneCountry, phoneNumber, clientSecret, sendAttempt, nextLink) {
3076 return this._requestTokenFromEndpoint("/account/password/msisdn/requestToken", {
3077 country: phoneCountry,
3078 phone_number: phoneNumber,
3079 client_secret: clientSecret,
3080 send_attempt: sendAttempt,
3081 next_link: nextLink
3082 });
3083};
3084
3085/**
3086 * Internal utility function for requesting validation tokens from usage-specific
3087 * requestToken endpoints.
3088 *
3089 * @param {string} endpoint The endpoint to send the request to
3090 * @param {object} params Parameters for the POST request
3091 * @return {module:client.Promise} Resolves: As requestEmailToken
3092 */
3093MatrixClient.prototype._requestTokenFromEndpoint = function (endpoint, params) {
3094 var id_server_url = url.parse(this.idBaseUrl);
3095 if (id_server_url.host === null) {
3096 throw new Error("Invalid ID server URL: " + this.idBaseUrl);
3097 }
3098
3099 var postParams = (0, _assign2.default)({}, params, {
3100 id_server: id_server_url.host
3101 });
3102 return this._http.request(undefined, "POST", endpoint, undefined, postParams);
3103};
3104
3105// Push operations
3106// ===============
3107
3108/**
3109 * Get the room-kind push rule associated with a room.
3110 * @param {string} scope "global" or device-specific.
3111 * @param {string} roomId the id of the room.
3112 * @return {object} the rule or undefined.
3113 */
3114MatrixClient.prototype.getRoomPushRule = function (scope, roomId) {
3115 // There can be only room-kind push rule per room
3116 // and its id is the room id.
3117 if (this.pushRules) {
3118 for (var i = 0; i < this.pushRules[scope].room.length; i++) {
3119 var rule = this.pushRules[scope].room[i];
3120 if (rule.rule_id === roomId) {
3121 return rule;
3122 }
3123 }
3124 } else {
3125 throw new Error("SyncApi.sync() must be done before accessing to push rules.");
3126 }
3127};
3128
3129/**
3130 * Set a room-kind muting push rule in a room.
3131 * The operation also updates MatrixClient.pushRules at the end.
3132 * @param {string} scope "global" or device-specific.
3133 * @param {string} roomId the id of the room.
3134 * @param {string} mute the mute state.
3135 * @return {module:client.Promise} Resolves: result object
3136 * @return {module:http-api.MatrixError} Rejects: with an error response.
3137 */
3138MatrixClient.prototype.setRoomMutePushRule = function (scope, roomId, mute) {
3139 var self = this;
3140 var deferred = void 0,
3141 hasDontNotifyRule = void 0;
3142
3143 // Get the existing room-kind push rule if any
3144 var roomPushRule = this.getRoomPushRule(scope, roomId);
3145 if (roomPushRule) {
3146 if (0 <= roomPushRule.actions.indexOf("dont_notify")) {
3147 hasDontNotifyRule = true;
3148 }
3149 }
3150
3151 if (!mute) {
3152 // Remove the rule only if it is a muting rule
3153 if (hasDontNotifyRule) {
3154 deferred = this.deletePushRule(scope, "room", roomPushRule.rule_id);
3155 }
3156 } else {
3157 if (!roomPushRule) {
3158 deferred = this.addPushRule(scope, "room", roomId, {
3159 actions: ["dont_notify"]
3160 });
3161 } else if (!hasDontNotifyRule) {
3162 // Remove the existing one before setting the mute push rule
3163 // This is a workaround to SYN-590 (Push rule update fails)
3164 deferred = _bluebird2.default.defer();
3165 this.deletePushRule(scope, "room", roomPushRule.rule_id).done(function () {
3166 self.addPushRule(scope, "room", roomId, {
3167 actions: ["dont_notify"]
3168 }).done(function () {
3169 deferred.resolve();
3170 }, function (err) {
3171 deferred.reject(err);
3172 });
3173 }, function (err) {
3174 deferred.reject(err);
3175 });
3176
3177 deferred = deferred.promise;
3178 }
3179 }
3180
3181 if (deferred) {
3182 // Update this.pushRules when the operation completes
3183 var ruleRefreshDeferred = _bluebird2.default.defer();
3184 deferred.done(function () {
3185 self.getPushRules().done(function (result) {
3186 self.pushRules = result;
3187 ruleRefreshDeferred.resolve();
3188 }, function (err) {
3189 ruleRefreshDeferred.reject(err);
3190 });
3191 }, function (err) {
3192 // Update it even if the previous operation fails. This can help the
3193 // app to recover when push settings has been modifed from another client
3194 self.getPushRules().done(function (result) {
3195 self.pushRules = result;
3196 ruleRefreshDeferred.reject(err);
3197 }, function (err2) {
3198 ruleRefreshDeferred.reject(err);
3199 });
3200 });
3201 return ruleRefreshDeferred.promise;
3202 }
3203};
3204
3205// Search
3206// ======
3207
3208/**
3209 * Perform a server-side search for messages containing the given text.
3210 * @param {Object} opts Options for the search.
3211 * @param {string} opts.query The text to query.
3212 * @param {string=} opts.keys The keys to search on. Defaults to all keys. One
3213 * of "content.body", "content.name", "content.topic".
3214 * @param {module:client.callback} callback Optional.
3215 * @return {module:client.Promise} Resolves: TODO
3216 * @return {module:http-api.MatrixError} Rejects: with an error response.
3217 */
3218MatrixClient.prototype.searchMessageText = function (opts, callback) {
3219 var roomEvents = {
3220 search_term: opts.query
3221 };
3222
3223 if ('keys' in opts) {
3224 roomEvents.keys = opts.keys;
3225 }
3226
3227 return this.search({
3228 body: {
3229 search_categories: {
3230 room_events: roomEvents
3231 }
3232 }
3233 }, callback);
3234};
3235
3236/**
3237 * Perform a server-side search for room events.
3238 *
3239 * The returned promise resolves to an object containing the fields:
3240 *
3241 * * {number} count: estimate of the number of results
3242 * * {string} next_batch: token for back-pagination; if undefined, there are
3243 * no more results
3244 * * {Array} highlights: a list of words to highlight from the stemming
3245 * algorithm
3246 * * {Array} results: a list of results
3247 *
3248 * Each entry in the results list is a {module:models/search-result.SearchResult}.
3249 *
3250 * @param {Object} opts
3251 * @param {string} opts.term the term to search for
3252 * @param {Object} opts.filter a JSON filter object to pass in the request
3253 * @return {module:client.Promise} Resolves: result object
3254 * @return {module:http-api.MatrixError} Rejects: with an error response.
3255 */
3256MatrixClient.prototype.searchRoomEvents = function (opts) {
3257 // TODO: support groups
3258
3259 var body = {
3260 search_categories: {
3261 room_events: {
3262 search_term: opts.term,
3263 filter: opts.filter,
3264 order_by: "recent",
3265 event_context: {
3266 before_limit: 1,
3267 after_limit: 1,
3268 include_profile: true
3269 }
3270 }
3271 }
3272 };
3273
3274 var searchResults = {
3275 _query: body,
3276 results: [],
3277 highlights: []
3278 };
3279
3280 return this.search({ body: body }).then(this._processRoomEventsSearch.bind(this, searchResults));
3281};
3282
3283/**
3284 * Take a result from an earlier searchRoomEvents call, and backfill results.
3285 *
3286 * @param {object} searchResults the results object to be updated
3287 * @return {module:client.Promise} Resolves: updated result object
3288 * @return {Error} Rejects: with an error response.
3289 */
3290MatrixClient.prototype.backPaginateRoomEventsSearch = function (searchResults) {
3291 // TODO: we should implement a backoff (as per scrollback()) to deal more
3292 // nicely with HTTP errors.
3293
3294 if (!searchResults.next_batch) {
3295 return _bluebird2.default.reject(new Error("Cannot backpaginate event search any further"));
3296 }
3297
3298 if (searchResults.pendingRequest) {
3299 // already a request in progress - return the existing promise
3300 return searchResults.pendingRequest;
3301 }
3302
3303 var searchOpts = {
3304 body: searchResults._query,
3305 next_batch: searchResults.next_batch
3306 };
3307
3308 var promise = this.search(searchOpts).then(this._processRoomEventsSearch.bind(this, searchResults)).finally(function () {
3309 searchResults.pendingRequest = null;
3310 });
3311 searchResults.pendingRequest = promise;
3312
3313 return promise;
3314};
3315
3316/**
3317 * helper for searchRoomEvents and backPaginateRoomEventsSearch. Processes the
3318 * response from the API call and updates the searchResults
3319 *
3320 * @param {Object} searchResults
3321 * @param {Object} response
3322 * @return {Object} searchResults
3323 * @private
3324 */
3325MatrixClient.prototype._processRoomEventsSearch = function (searchResults, response) {
3326 var room_events = response.search_categories.room_events;
3327
3328 searchResults.count = room_events.count;
3329 searchResults.next_batch = room_events.next_batch;
3330
3331 // combine the highlight list with our existing list; build an object
3332 // to avoid O(N^2) fail
3333 var highlights = {};
3334 room_events.highlights.forEach(function (hl) {
3335 highlights[hl] = 1;
3336 });
3337 searchResults.highlights.forEach(function (hl) {
3338 highlights[hl] = 1;
3339 });
3340
3341 // turn it back into a list.
3342 searchResults.highlights = (0, _keys2.default)(highlights);
3343
3344 // append the new results to our existing results
3345 for (var i = 0; i < room_events.results.length; i++) {
3346 var sr = SearchResult.fromJson(room_events.results[i], this.getEventMapper());
3347 searchResults.results.push(sr);
3348 }
3349 return searchResults;
3350};
3351
3352/**
3353 * Populate the store with rooms the user has left.
3354 * @return {module:client.Promise} Resolves: TODO - Resolved when the rooms have
3355 * been added to the data store.
3356 * @return {module:http-api.MatrixError} Rejects: with an error response.
3357 */
3358MatrixClient.prototype.syncLeftRooms = function () {
3359 // Guard against multiple calls whilst ongoing and multiple calls post success
3360 if (this._syncedLeftRooms) {
3361 return _bluebird2.default.resolve([]); // don't call syncRooms again if it succeeded.
3362 }
3363 if (this._syncLeftRoomsPromise) {
3364 return this._syncLeftRoomsPromise; // return the ongoing request
3365 }
3366 var self = this;
3367 var syncApi = new SyncApi(this, this._clientOpts);
3368 this._syncLeftRoomsPromise = syncApi.syncLeftRooms();
3369
3370 // cleanup locks
3371 this._syncLeftRoomsPromise.then(function (res) {
3372 console.log("Marking success of sync left room request");
3373 self._syncedLeftRooms = true; // flip the bit on success
3374 }).finally(function () {
3375 self._syncLeftRoomsPromise = null; // cleanup ongoing request state
3376 });
3377
3378 return this._syncLeftRoomsPromise;
3379};
3380
3381// Filters
3382// =======
3383
3384/**
3385 * Create a new filter.
3386 * @param {Object} content The HTTP body for the request
3387 * @return {Filter} Resolves to a Filter object.
3388 * @return {module:http-api.MatrixError} Rejects: with an error response.
3389 */
3390MatrixClient.prototype.createFilter = function (content) {
3391 var self = this;
3392 var path = utils.encodeUri("/user/$userId/filter", {
3393 $userId: this.credentials.userId
3394 });
3395 return this._http.authedRequest(undefined, "POST", path, undefined, content).then(function (response) {
3396 // persist the filter
3397 var filter = Filter.fromJson(self.credentials.userId, response.filter_id, content);
3398 self.store.storeFilter(filter);
3399 return filter;
3400 });
3401};
3402
3403/**
3404 * Retrieve a filter.
3405 * @param {string} userId The user ID of the filter owner
3406 * @param {string} filterId The filter ID to retrieve
3407 * @param {boolean} allowCached True to allow cached filters to be returned.
3408 * Default: True.
3409 * @return {module:client.Promise} Resolves: TODO
3410 * @return {module:http-api.MatrixError} Rejects: with an error response.
3411 */
3412MatrixClient.prototype.getFilter = function (userId, filterId, allowCached) {
3413 if (allowCached) {
3414 var filter = this.store.getFilter(userId, filterId);
3415 if (filter) {
3416 return _bluebird2.default.resolve(filter);
3417 }
3418 }
3419
3420 var self = this;
3421 var path = utils.encodeUri("/user/$userId/filter/$filterId", {
3422 $userId: userId,
3423 $filterId: filterId
3424 });
3425
3426 return this._http.authedRequest(undefined, "GET", path, undefined, undefined).then(function (response) {
3427 // persist the filter
3428 var filter = Filter.fromJson(userId, filterId, response);
3429 self.store.storeFilter(filter);
3430 return filter;
3431 });
3432};
3433
3434/**
3435 * @param {string} filterName
3436 * @param {Filter} filter
3437 * @return {Promise<String>} Filter ID
3438 */
3439MatrixClient.prototype.getOrCreateFilter = function (filterName, filter) {
3440 var filterId = this.store.getFilterIdByName(filterName);
3441 var promise = _bluebird2.default.resolve();
3442 var self = this;
3443
3444 if (filterId) {
3445 // check that the existing filter matches our expectations
3446 promise = self.getFilter(self.credentials.userId, filterId, true).then(function (existingFilter) {
3447 var oldDef = existingFilter.getDefinition();
3448 var newDef = filter.getDefinition();
3449
3450 if (utils.deepCompare(oldDef, newDef)) {
3451 // super, just use that.
3452 // debuglog("Using existing filter ID %s: %s", filterId,
3453 // JSON.stringify(oldDef));
3454 return _bluebird2.default.resolve(filterId);
3455 }
3456 // debuglog("Existing filter ID %s: %s; new filter: %s",
3457 // filterId, JSON.stringify(oldDef), JSON.stringify(newDef));
3458 self.store.setFilterIdByName(filterName, undefined);
3459 return undefined;
3460 }, function (error) {
3461 // Synapse currently returns the following when the filter cannot be found:
3462 // {
3463 // errcode: "M_UNKNOWN",
3464 // name: "M_UNKNOWN",
3465 // message: "No row found",
3466 // data: Object, httpStatus: 404
3467 // }
3468 if (error.httpStatus === 404 && (error.errcode === "M_UNKNOWN" || error.errcode === "M_NOT_FOUND")) {
3469 // Clear existing filterId from localStorage
3470 // if it no longer exists on the server
3471 self.store.setFilterIdByName(filterName, undefined);
3472 // Return a undefined value for existingId further down the promise chain
3473 return undefined;
3474 } else {
3475 throw error;
3476 }
3477 });
3478 }
3479
3480 return promise.then(function (existingId) {
3481 if (existingId) {
3482 return existingId;
3483 }
3484
3485 // create a new filter
3486 return self.createFilter(filter.getDefinition()).then(function (createdFilter) {
3487 // debuglog("Created new filter ID %s: %s", createdFilter.filterId,
3488 // JSON.stringify(createdFilter.getDefinition()));
3489 self.store.setFilterIdByName(filterName, createdFilter.filterId);
3490 return createdFilter.filterId;
3491 });
3492 });
3493};
3494
3495/**
3496 * Gets a bearer token from the Home Server that the user can
3497 * present to a third party in order to prove their ownership
3498 * of the Matrix account they are logged into.
3499 * @return {module:client.Promise} Resolves: Token object
3500 * @return {module:http-api.MatrixError} Rejects: with an error response.
3501 */
3502MatrixClient.prototype.getOpenIdToken = function () {
3503 var path = utils.encodeUri("/user/$userId/openid/request_token", {
3504 $userId: this.credentials.userId
3505 });
3506
3507 return this._http.authedRequest(undefined, "POST", path, undefined, {});
3508};
3509
3510// VoIP operations
3511// ===============
3512
3513/**
3514 * @param {module:client.callback} callback Optional.
3515 * @return {module:client.Promise} Resolves: TODO
3516 * @return {module:http-api.MatrixError} Rejects: with an error response.
3517 */
3518MatrixClient.prototype.turnServer = function (callback) {
3519 return this._http.authedRequest(callback, "GET", "/voip/turnServer");
3520};
3521
3522/**
3523 * Get the TURN servers for this home server.
3524 * @return {Array<Object>} The servers or an empty list.
3525 */
3526MatrixClient.prototype.getTurnServers = function () {
3527 return this._turnServers || [];
3528};
3529
3530// Higher level APIs
3531// =================
3532
3533// TODO: stuff to handle:
3534// local echo
3535// event dup suppression? - apparently we should still be doing this
3536// tracking current display name / avatar per-message
3537// pagination
3538// re-sending (including persisting pending messages to be sent)
3539// - Need a nice way to callback the app for arbitrary events like
3540// displayname changes
3541// due to ambiguity (or should this be on a chat-specific layer)?
3542// reconnect after connectivity outages
3543
3544
3545/**
3546 * High level helper method to begin syncing and poll for new events. To listen for these
3547 * events, add a listener for {@link module:client~MatrixClient#event:"event"}
3548 * via {@link module:client~MatrixClient#on}. Alternatively, listen for specific
3549 * state change events.
3550 * @param {Object=} opts Options to apply when syncing.
3551 * @param {Number=} opts.initialSyncLimit The event <code>limit=</code> to apply
3552 * to initial sync. Default: 8.
3553 * @param {Boolean=} opts.includeArchivedRooms True to put <code>archived=true</code>
3554 * on the <code>/initialSync</code> request. Default: false.
3555 * @param {Boolean=} opts.resolveInvitesToProfiles True to do /profile requests
3556 * on every invite event if the displayname/avatar_url is not known for this user ID.
3557 * Default: false.
3558 *
3559 * @param {String=} opts.pendingEventOrdering Controls where pending messages
3560 * appear in a room's timeline. If "<b>chronological</b>", messages will appear
3561 * in the timeline when the call to <code>sendEvent</code> was made. If
3562 * "<b>detached</b>", pending messages will appear in a separate list,
3563 * accessbile via {@link module:models/room#getPendingEvents}. Default:
3564 * "chronological".
3565 *
3566 * @param {Number=} opts.pollTimeout The number of milliseconds to wait on /sync.
3567 * Default: 30000 (30 seconds).
3568 *
3569 * @param {Filter=} opts.filter The filter to apply to /sync calls. This will override
3570 * the opts.initialSyncLimit, which would normally result in a timeline limit filter.
3571 *
3572 * @param {Boolean=} opts.disablePresence True to perform syncing without automatically
3573 * updating presence.
3574 * @param {Boolean=} opts.lazyLoadMembers True to not load all membership events during
3575 * initial sync but fetch them when needed by calling `loadOutOfBandMembers`
3576 * This will override the filter option at this moment.
3577 */
3578MatrixClient.prototype.startClient = function () {
3579 var _ref9 = (0, _bluebird.method)(function (opts) {
3580 var _this3 = this;
3581
3582 if (this.clientRunning) {
3583 // client is already running.
3584 return;
3585 }
3586 this.clientRunning = true;
3587 // backwards compat for when 'opts' was 'historyLen'.
3588 if (typeof opts === "number") {
3589 opts = {
3590 initialSyncLimit: opts
3591 };
3592 }
3593
3594 if (this._crypto) {
3595 this._crypto.uploadDeviceKeys().done();
3596 this._crypto.start();
3597 }
3598
3599 // periodically poll for turn servers if we support voip
3600 checkTurnServers(this);
3601
3602 if (this._syncApi) {
3603 // This shouldn't happen since we thought the client was not running
3604 console.error("Still have sync object whilst not running: stopping old one");
3605 this._syncApi.stop();
3606 }
3607
3608 // shallow-copy the opts dict before modifying and storing it
3609 opts = (0, _assign2.default)({}, opts);
3610
3611 opts.crypto = this._crypto;
3612 opts.canResetEntireTimeline = function (roomId) {
3613 if (!_this3._canResetTimelineCallback) {
3614 return false;
3615 }
3616 return _this3._canResetTimelineCallback(roomId);
3617 };
3618 this._clientOpts = opts;
3619 this._syncApi = new SyncApi(this, opts);
3620 this._syncApi.sync();
3621 });
3622
3623 return function (_x18) {
3624 return _ref9.apply(this, arguments);
3625 };
3626}();
3627
3628/**
3629 * store client options with boolean/string/numeric values
3630 * to know in the next session what flags the sync data was
3631 * created with (e.g. lazy loading)
3632 * @param {object} opts the complete set of client options
3633 * @return {Promise} for store operation */
3634MatrixClient.prototype._storeClientOptions = function () {
3635 var primTypes = ["boolean", "string", "number"];
3636 var serializableOpts = (0, _entries2.default)(this._clientOpts).filter(function (_ref10) {
3637 var _ref11 = (0, _slicedToArray3.default)(_ref10, 2),
3638 key = _ref11[0],
3639 value = _ref11[1];
3640
3641 return primTypes.includes(typeof value === "undefined" ? "undefined" : (0, _typeof3.default)(value));
3642 }).reduce(function (obj, _ref12) {
3643 var _ref13 = (0, _slicedToArray3.default)(_ref12, 2),
3644 key = _ref13[0],
3645 value = _ref13[1];
3646
3647 obj[key] = value;
3648 return obj;
3649 }, {});
3650 return this.store.storeClientOptions(serializableOpts);
3651};
3652
3653/**
3654 * High level helper method to stop the client from polling and allow a
3655 * clean shutdown.
3656 */
3657MatrixClient.prototype.stopClient = function () {
3658 console.log('stopping MatrixClient');
3659
3660 this.clientRunning = false;
3661 // TODO: f.e. Room => self.store.storeRoom(room) ?
3662 if (this._syncApi) {
3663 this._syncApi.stop();
3664 this._syncApi = null;
3665 }
3666 if (this._crypto) {
3667 this._crypto.stop();
3668 }
3669 if (this._peekSync) {
3670 this._peekSync.stopPeeking();
3671 }
3672 global.clearTimeout(this._checkTurnServersTimeoutID);
3673};
3674
3675/*
3676 * Query the server to see if it support members lazy loading
3677 * @return {Promise<boolean>} true if server supports lazy loading
3678 */
3679MatrixClient.prototype.doesServerSupportLazyLoading = (0, _bluebird.coroutine)( /*#__PURE__*/_regenerator2.default.mark(function _callee6() {
3680 var response, unstableFeatures;
3681 return _regenerator2.default.wrap(function _callee6$(_context6) {
3682 while (1) {
3683 switch (_context6.prev = _context6.next) {
3684 case 0:
3685 if (!(this._serverSupportsLazyLoading === null)) {
3686 _context6.next = 6;
3687 break;
3688 }
3689
3690 _context6.next = 3;
3691 return (0, _bluebird.resolve)(this._http.request(undefined, // callback
3692 "GET", "/_matrix/client/versions", undefined, // queryParams
3693 undefined, // data
3694 {
3695 prefix: ''
3696 }));
3697
3698 case 3:
3699 response = _context6.sent;
3700 unstableFeatures = response["unstable_features"];
3701
3702 this._serverSupportsLazyLoading = unstableFeatures && unstableFeatures["m.lazy_load_members"];
3703
3704 case 6:
3705 return _context6.abrupt("return", this._serverSupportsLazyLoading);
3706
3707 case 7:
3708 case "end":
3709 return _context6.stop();
3710 }
3711 }
3712 }, _callee6, this);
3713}));
3714
3715/*
3716 * Get if lazy loading members is being used.
3717 * @return {boolean} Whether or not members are lazy loaded by this client
3718 */
3719MatrixClient.prototype.hasLazyLoadMembersEnabled = function () {
3720 return !!this._clientOpts.lazyLoadMembers;
3721};
3722
3723/*
3724 * Set a function which is called when /sync returns a 'limited' response.
3725 * It is called with a room ID and returns a boolean. It should return 'true' if the SDK
3726 * can SAFELY remove events from this room. It may not be safe to remove events if there
3727 * are other references to the timelines for this room, e.g because the client is
3728 * actively viewing events in this room.
3729 * Default: returns false.
3730 * @param {Function} cb The callback which will be invoked.
3731 */
3732MatrixClient.prototype.setCanResetTimelineCallback = function (cb) {
3733 this._canResetTimelineCallback = cb;
3734};
3735
3736/**
3737 * Get the callback set via `setCanResetTimelineCallback`.
3738 * @return {?Function} The callback or null
3739 */
3740MatrixClient.prototype.getCanResetTimelineCallback = function () {
3741 return this._canResetTimelineCallback;
3742};
3743
3744function setupCallEventHandler(client) {
3745 var candidatesByCall = {
3746 // callId: [Candidate]
3747 };
3748
3749 // Maintain a buffer of events before the client has synced for the first time.
3750 // This buffer will be inspected to see if we should send incoming call
3751 // notifications. It needs to be buffered to correctly determine if an
3752 // incoming call has had a matching answer/hangup.
3753 var callEventBuffer = [];
3754 var isClientPrepared = false;
3755 client.on("sync", function (state) {
3756 if (state === "PREPARED") {
3757 isClientPrepared = true;
3758 var ignoreCallIds = {}; // Set<String>
3759 // inspect the buffer and mark all calls which have been answered
3760 // or hung up before passing them to the call event handler.
3761 for (var i = callEventBuffer.length - 1; i >= 0; i--) {
3762 var ev = callEventBuffer[i];
3763 if (ev.getType() === "m.call.answer" || ev.getType() === "m.call.hangup") {
3764 ignoreCallIds[ev.getContent().call_id] = "yep";
3765 }
3766 }
3767 // now loop through the buffer chronologically and inject them
3768 callEventBuffer.forEach(function (e) {
3769 if (ignoreCallIds[e.getContent().call_id]) {
3770 // This call has previously been ansered or hung up: ignore it
3771 return;
3772 }
3773 callEventHandler(e);
3774 });
3775 callEventBuffer = [];
3776 }
3777 });
3778
3779 client.on("event", onEvent);
3780
3781 function onEvent(event) {
3782 if (event.getType().indexOf("m.call.") !== 0) {
3783 // not a call event
3784 if (event.isBeingDecrypted() || event.isDecryptionFailure()) {
3785 // not *yet* a call event, but might become one...
3786 event.once("Event.decrypted", onEvent);
3787 }
3788 return;
3789 }
3790 if (!isClientPrepared) {
3791 callEventBuffer.push(event);
3792 return;
3793 }
3794 callEventHandler(event);
3795 }
3796
3797 function callEventHandler(event) {
3798 var content = event.getContent();
3799 var call = content.call_id ? client.callList[content.call_id] : undefined;
3800 var i = void 0;
3801 //console.log("RECV %s content=%s", event.getType(), JSON.stringify(content));
3802
3803 if (event.getType() === "m.call.invite") {
3804 if (event.getSender() === client.credentials.userId) {
3805 return; // ignore invites you send
3806 }
3807
3808 if (event.getAge() > content.lifetime) {
3809 return; // expired call
3810 }
3811
3812 if (call && call.state === "ended") {
3813 return; // stale/old invite event
3814 }
3815 if (call) {
3816 console.log("WARN: Already have a MatrixCall with id %s but got an " + "invite. Clobbering.", content.call_id);
3817 }
3818
3819 call = webRtcCall.createNewMatrixCall(client, event.getRoomId(), {
3820 forceTURN: client._forceTURN
3821 });
3822 if (!call) {
3823 console.log("Incoming call ID " + content.call_id + " but this client " + "doesn't support WebRTC");
3824 // don't hang up the call: there could be other clients
3825 // connected that do support WebRTC and declining the
3826 // the call on their behalf would be really annoying.
3827 return;
3828 }
3829
3830 call.callId = content.call_id;
3831 call._initWithInvite(event);
3832 client.callList[call.callId] = call;
3833
3834 // if we stashed candidate events for that call ID, play them back now
3835 if (candidatesByCall[call.callId]) {
3836 for (i = 0; i < candidatesByCall[call.callId].length; i++) {
3837 call._gotRemoteIceCandidate(candidatesByCall[call.callId][i]);
3838 }
3839 }
3840
3841 // Were we trying to call that user (room)?
3842 var existingCall = void 0;
3843 var existingCalls = utils.values(client.callList);
3844 for (i = 0; i < existingCalls.length; ++i) {
3845 var thisCall = existingCalls[i];
3846 if (call.roomId === thisCall.roomId && thisCall.direction === 'outbound' && ["wait_local_media", "create_offer", "invite_sent"].indexOf(thisCall.state) !== -1) {
3847 existingCall = thisCall;
3848 break;
3849 }
3850 }
3851
3852 if (existingCall) {
3853 // If we've only got to wait_local_media or create_offer and
3854 // we've got an invite, pick the incoming call because we know
3855 // we haven't sent our invite yet otherwise, pick whichever
3856 // call has the lowest call ID (by string comparison)
3857 if (existingCall.state === 'wait_local_media' || existingCall.state === 'create_offer' || existingCall.callId > call.callId) {
3858 console.log("Glare detected: answering incoming call " + call.callId + " and canceling outgoing call " + existingCall.callId);
3859 existingCall._replacedBy(call);
3860 call.answer();
3861 } else {
3862 console.log("Glare detected: rejecting incoming call " + call.callId + " and keeping outgoing call " + existingCall.callId);
3863 call.hangup();
3864 }
3865 } else {
3866 client.emit("Call.incoming", call);
3867 }
3868 } else if (event.getType() === 'm.call.answer') {
3869 if (!call) {
3870 return;
3871 }
3872 if (event.getSender() === client.credentials.userId) {
3873 if (call.state === 'ringing') {
3874 call._onAnsweredElsewhere(content);
3875 }
3876 } else {
3877 call._receivedAnswer(content);
3878 }
3879 } else if (event.getType() === 'm.call.candidates') {
3880 if (event.getSender() === client.credentials.userId) {
3881 return;
3882 }
3883 if (!call) {
3884 // store the candidates; we may get a call eventually.
3885 if (!candidatesByCall[content.call_id]) {
3886 candidatesByCall[content.call_id] = [];
3887 }
3888 candidatesByCall[content.call_id] = candidatesByCall[content.call_id].concat(content.candidates);
3889 } else {
3890 for (i = 0; i < content.candidates.length; i++) {
3891 call._gotRemoteIceCandidate(content.candidates[i]);
3892 }
3893 }
3894 } else if (event.getType() === 'm.call.hangup') {
3895 // Note that we also observe our own hangups here so we can see
3896 // if we've already rejected a call that would otherwise be valid
3897 if (!call) {
3898 // if not live, store the fact that the call has ended because
3899 // we're probably getting events backwards so
3900 // the hangup will come before the invite
3901 call = webRtcCall.createNewMatrixCall(client, event.getRoomId());
3902 if (call) {
3903 call.callId = content.call_id;
3904 call._initWithHangup(event);
3905 client.callList[content.call_id] = call;
3906 }
3907 } else {
3908 if (call.state !== 'ended') {
3909 call._onHangupReceived(content);
3910 delete client.callList[content.call_id];
3911 }
3912 }
3913 }
3914 }
3915}
3916
3917function checkTurnServers(client) {
3918 if (!client._supportsVoip) {
3919 return;
3920 }
3921 if (client.isGuest()) {
3922 return; // guests can't access TURN servers
3923 }
3924
3925 client.turnServer().done(function (res) {
3926 if (res.uris) {
3927 console.log("Got TURN URIs: " + res.uris + " refresh in " + res.ttl + " secs");
3928 // map the response to a format that can be fed to
3929 // RTCPeerConnection
3930 var servers = {
3931 urls: res.uris,
3932 username: res.username,
3933 credential: res.password
3934 };
3935 client._turnServers = [servers];
3936 // re-fetch when we're about to reach the TTL
3937 client._checkTurnServersTimeoutID = setTimeout(function () {
3938 checkTurnServers(client);
3939 }, (res.ttl || 60 * 60) * 1000 * 0.9);
3940 }
3941 }, function (err) {
3942 console.error("Failed to get TURN URIs");
3943 client._checkTurnServersTimeoutID = setTimeout(function () {
3944 checkTurnServers(client);
3945 }, 60000);
3946 });
3947}
3948
3949function _reject(callback, defer, err) {
3950 if (callback) {
3951 callback(err);
3952 }
3953 defer.reject(err);
3954}
3955
3956function _resolve(callback, defer, res) {
3957 if (callback) {
3958 callback(null, res);
3959 }
3960 defer.resolve(res);
3961}
3962
3963function _PojoToMatrixEventMapper(client) {
3964 function mapper(plainOldJsObject) {
3965 var event = new MatrixEvent(plainOldJsObject);
3966 if (event.isEncrypted()) {
3967 client.reEmitter.reEmit(event, ["Event.decrypted"]);
3968 event.attemptDecryption(client._crypto);
3969 }
3970 return event;
3971 }
3972 return mapper;
3973}
3974
3975/**
3976 * @return {Function}
3977 */
3978MatrixClient.prototype.getEventMapper = function () {
3979 return _PojoToMatrixEventMapper(this);
3980};
3981
3982// Identity Server Operations
3983// ==========================
3984
3985/**
3986 * Generates a random string suitable for use as a client secret. <strong>This
3987 * method is experimental and may change.</strong>
3988 * @return {string} A new client secret
3989 */
3990MatrixClient.prototype.generateClientSecret = function () {
3991 return (0, _randomstring.randomString)(32);
3992};
3993
3994/** */
3995module.exports.MatrixClient = MatrixClient;
3996/** */
3997module.exports.CRYPTO_ENABLED = CRYPTO_ENABLED;
3998
3999// MatrixClient Event JSDocs
4000
4001/**
4002 * Fires whenever the SDK receives a new event.
4003 * <p>
4004 * This is only fired for live events received via /sync - it is not fired for
4005 * events received over context, search, or pagination APIs.
4006 *
4007 * @event module:client~MatrixClient#"event"
4008 * @param {MatrixEvent} event The matrix event which caused this event to fire.
4009 * @example
4010 * matrixClient.on("event", function(event){
4011 * var sender = event.getSender();
4012 * });
4013 */
4014
4015/**
4016 * Fires whenever the SDK receives a new to-device event.
4017 * @event module:client~MatrixClient#"toDeviceEvent"
4018 * @param {MatrixEvent} event The matrix event which caused this event to fire.
4019 * @example
4020 * matrixClient.on("toDeviceEvent", function(event){
4021 * var sender = event.getSender();
4022 * });
4023 */
4024
4025/**
4026 * Fires whenever the SDK's syncing state is updated. The state can be one of:
4027 * <ul>
4028 *
4029 * <li>PREPARED: The client has synced with the server at least once and is
4030 * ready for methods to be called on it. This will be immediately followed by
4031 * a state of SYNCING. <i>This is the equivalent of "syncComplete" in the
4032 * previous API.</i></li>
4033 *
4034 * <li>CATCHUP: The client has detected the connection to the server might be
4035 * available again and will now try to do a sync again. As this sync might take
4036 * a long time (depending how long ago was last synced, and general server
4037 * performance) the client is put in this mode so the UI can reflect trying
4038 * to catch up with the server after losing connection.</li>
4039 *
4040 * <li>SYNCING : The client is currently polling for new events from the server.
4041 * This will be called <i>after</i> processing latest events from a sync.</li>
4042 *
4043 * <li>ERROR : The client has had a problem syncing with the server. If this is
4044 * called <i>before</i> PREPARED then there was a problem performing the initial
4045 * sync. If this is called <i>after</i> PREPARED then there was a problem polling
4046 * the server for updates. This may be called multiple times even if the state is
4047 * already ERROR. <i>This is the equivalent of "syncError" in the previous
4048 * API.</i></li>
4049 *
4050 * <li>RECONNECTING: The sync connection has dropped, but not (yet) in a way that
4051 * should be considered erroneous.
4052 * </li>
4053 *
4054 * <li>STOPPED: The client has stopped syncing with server due to stopClient
4055 * being called.
4056 * </li>
4057 * </ul>
4058 * State transition diagram:
4059 * <pre>
4060 * +---->STOPPED
4061 * |
4062 * +----->PREPARED -------> SYNCING <--+
4063 * | ^ | ^ |
4064 * | CATCHUP ----------+ | | |
4065 * | ^ V | |
4066 * null ------+ | +------- RECONNECTING |
4067 * | V V |
4068 * +------->ERROR ---------------------+
4069 *
4070 * NB: 'null' will never be emitted by this event.
4071 *
4072 * </pre>
4073 * Transitions:
4074 * <ul>
4075 *
4076 * <li><code>null -> PREPARED</code> : Occurs when the initial sync is completed
4077 * first time. This involves setting up filters and obtaining push rules.
4078 *
4079 * <li><code>null -> ERROR</code> : Occurs when the initial sync failed first time.
4080 *
4081 * <li><code>ERROR -> PREPARED</code> : Occurs when the initial sync succeeds
4082 * after previously failing.
4083 *
4084 * <li><code>PREPARED -> SYNCING</code> : Occurs immediately after transitioning
4085 * to PREPARED. Starts listening for live updates rather than catching up.
4086 *
4087 * <li><code>SYNCING -> RECONNECTING</code> : Occurs when the live update fails.
4088 *
4089 * <li><code>RECONNECTING -> RECONNECTING</code> : Can occur if the update calls
4090 * continue to fail, but the keepalive calls (to /versions) succeed.
4091 *
4092 * <li><code>RECONNECTING -> ERROR</code> : Occurs when the keepalive call also fails
4093 *
4094 * <li><code>ERROR -> SYNCING</code> : Occurs when the client has performed a
4095 * live update after having previously failed.
4096 *
4097 * <li><code>ERROR -> ERROR</code> : Occurs when the client has failed to keepalive
4098 * for a second time or more.</li>
4099 *
4100 * <li><code>SYNCING -> SYNCING</code> : Occurs when the client has performed a live
4101 * update. This is called <i>after</i> processing.</li>
4102 *
4103 * <li><code>* -> STOPPED</code> : Occurs once the client has stopped syncing or
4104 * trying to sync after stopClient has been called.</li>
4105 * </ul>
4106 *
4107 * @event module:client~MatrixClient#"sync"
4108 *
4109 * @param {string} state An enum representing the syncing state. One of "PREPARED",
4110 * "SYNCING", "ERROR", "STOPPED".
4111 *
4112 * @param {?string} prevState An enum representing the previous syncing state.
4113 * One of "PREPARED", "SYNCING", "ERROR", "STOPPED" <b>or null</b>.
4114 *
4115 * @param {?Object} data Data about this transition.
4116 *
4117 * @param {MatrixError} data.error The matrix error if <code>state=ERROR</code>.
4118 *
4119 * @param {String} data.oldSyncToken The 'since' token passed to /sync.
4120 * <code>null</code> for the first successful sync since this client was
4121 * started. Only present if <code>state=PREPARED</code> or
4122 * <code>state=SYNCING</code>.
4123 *
4124 * @param {String} data.nextSyncToken The 'next_batch' result from /sync, which
4125 * will become the 'since' token for the next call to /sync. Only present if
4126 * <code>state=PREPARED</code> or <code>state=SYNCING</code>.
4127 *
4128 * @param {boolean} data.catchingUp True if we are working our way through a
4129 * backlog of events after connecting. Only present if <code>state=SYNCING</code>.
4130 *
4131 * @example
4132 * matrixClient.on("sync", function(state, prevState, data) {
4133 * switch (state) {
4134 * case "ERROR":
4135 * // update UI to say "Connection Lost"
4136 * break;
4137 * case "SYNCING":
4138 * // update UI to remove any "Connection Lost" message
4139 * break;
4140 * case "PREPARED":
4141 * // the client instance is ready to be queried.
4142 * var rooms = matrixClient.getRooms();
4143 * break;
4144 * }
4145 * });
4146 */
4147
4148/**
4149* Fires whenever the sdk learns about a new group. <strong>This event
4150* is experimental and may change.</strong>
4151* @event module:client~MatrixClient#"Group"
4152* @param {Group} group The newly created, fully populated group.
4153* @example
4154* matrixClient.on("Group", function(group){
4155* var groupId = group.groupId;
4156* });
4157*/
4158
4159/**
4160* Fires whenever a new Room is added. This will fire when you are invited to a
4161* room, as well as when you join a room. <strong>This event is experimental and
4162* may change.</strong>
4163* @event module:client~MatrixClient#"Room"
4164* @param {Room} room The newly created, fully populated room.
4165* @example
4166* matrixClient.on("Room", function(room){
4167* var roomId = room.roomId;
4168* });
4169*/
4170
4171/**
4172* Fires whenever a Room is removed. This will fire when you forget a room.
4173* <strong>This event is experimental and may change.</strong>
4174* @event module:client~MatrixClient#"deleteRoom"
4175* @param {string} roomId The deleted room ID.
4176* @example
4177* matrixClient.on("deleteRoom", function(roomId){
4178* // update UI from getRooms()
4179* });
4180*/
4181
4182/**
4183 * Fires whenever an incoming call arrives.
4184 * @event module:client~MatrixClient#"Call.incoming"
4185 * @param {module:webrtc/call~MatrixCall} call The incoming call.
4186 * @example
4187 * matrixClient.on("Call.incoming", function(call){
4188 * call.answer(); // auto-answer
4189 * });
4190 */
4191
4192/**
4193 * Fires whenever the login session the JS SDK is using is no
4194 * longer valid and the user must log in again.
4195 * NB. This only fires when action is required from the user, not
4196 * when then login session can be renewed by using a refresh token.
4197 * @event module:client~MatrixClient#"Session.logged_out"
4198 * @example
4199 * matrixClient.on("Session.logged_out", function(call){
4200 * // show the login screen
4201 * });
4202 */
4203
4204/**
4205 * Fires when the JS SDK receives a M_CONSENT_NOT_GIVEN error in response
4206 * to a HTTP request.
4207 * @event module:client~MatrixClient#"no_consent"
4208 * @example
4209 * matrixClient.on("no_consent", function(message, contentUri) {
4210 * console.info(message + ' Go to ' + contentUri);
4211 * });
4212 */
4213
4214/**
4215 * Fires when a device is marked as verified/unverified/blocked/unblocked by
4216 * {@link module:client~MatrixClient#setDeviceVerified|MatrixClient.setDeviceVerified} or
4217 * {@link module:client~MatrixClient#setDeviceBlocked|MatrixClient.setDeviceBlocked}.
4218 *
4219 * @event module:client~MatrixClient#"deviceVerificationChanged"
4220 * @param {string} userId the owner of the verified device
4221 * @param {string} deviceId the id of the verified device
4222 * @param {module:crypto/deviceinfo} deviceInfo updated device information
4223 */
4224
4225/**
4226 * Fires whenever new user-scoped account_data is added.
4227 * @event module:client~MatrixClient#"accountData"
4228 * @param {MatrixEvent} event The event describing the account_data just added
4229 * @example
4230 * matrixClient.on("accountData", function(event){
4231 * myAccountData[event.type] = event.content;
4232 * });
4233 */
4234
4235/**
4236 * Fires whenever the status of e2e key backup changes, as returned by getKeyBackupEnabled()
4237 * @event module:client~MatrixClient#"crypto.keyBackupStatus"
4238 * @param {bool} enabled true if key backup has been enabled, otherwise false
4239 * @example
4240 * matrixClient.on("crypto.keyBackupStatus", function(enabled){
4241 * if (enabled) {
4242 * [...]
4243 * }
4244 * });
4245 */
4246
4247/**
4248 * Fires when we want to suggest to the user that they restore their megolm keys
4249 * from backup or by cross-signing the device.
4250 *
4251 * @event module:client~MatrixClient#"crypto.suggestKeyRestore"
4252 */
4253
4254// EventEmitter JSDocs
4255
4256/**
4257 * The {@link https://nodejs.org/api/events.html|EventEmitter} class.
4258 * @external EventEmitter
4259 * @see {@link https://nodejs.org/api/events.html}
4260 */
4261
4262/**
4263 * Adds a listener to the end of the listeners array for the specified event.
4264 * No checks are made to see if the listener has already been added. Multiple
4265 * calls passing the same combination of event and listener will result in the
4266 * listener being added multiple times.
4267 * @function external:EventEmitter#on
4268 * @param {string} event The event to listen for.
4269 * @param {Function} listener The function to invoke.
4270 * @return {EventEmitter} for call chaining.
4271 */
4272
4273/**
4274 * Alias for {@link external:EventEmitter#on}.
4275 * @function external:EventEmitter#addListener
4276 * @param {string} event The event to listen for.
4277 * @param {Function} listener The function to invoke.
4278 * @return {EventEmitter} for call chaining.
4279 */
4280
4281/**
4282 * Adds a <b>one time</b> listener for the event. This listener is invoked only
4283 * the next time the event is fired, after which it is removed.
4284 * @function external:EventEmitter#once
4285 * @param {string} event The event to listen for.
4286 * @param {Function} listener The function to invoke.
4287 * @return {EventEmitter} for call chaining.
4288 */
4289
4290/**
4291 * Remove a listener from the listener array for the specified event.
4292 * <b>Caution:</b> changes array indices in the listener array behind the
4293 * listener.
4294 * @function external:EventEmitter#removeListener
4295 * @param {string} event The event to listen for.
4296 * @param {Function} listener The function to invoke.
4297 * @return {EventEmitter} for call chaining.
4298 */
4299
4300/**
4301 * Removes all listeners, or those of the specified event. It's not a good idea
4302 * to remove listeners that were added elsewhere in the code, especially when
4303 * it's on an emitter that you didn't create (e.g. sockets or file streams).
4304 * @function external:EventEmitter#removeAllListeners
4305 * @param {string} event Optional. The event to remove listeners for.
4306 * @return {EventEmitter} for call chaining.
4307 */
4308
4309/**
4310 * Execute each of the listeners in order with the supplied arguments.
4311 * @function external:EventEmitter#emit
4312 * @param {string} event The event to emit.
4313 * @param {Function} listener The function to invoke.
4314 * @return {boolean} true if event had listeners, false otherwise.
4315 */
4316
4317/**
4318 * By default EventEmitters will print a warning if more than 10 listeners are
4319 * added for a particular event. This is a useful default which helps finding
4320 * memory leaks. Obviously not all Emitters should be limited to 10. This
4321 * function allows that to be increased. Set to zero for unlimited.
4322 * @function external:EventEmitter#setMaxListeners
4323 * @param {Number} n The max number of listeners.
4324 * @return {EventEmitter} for call chaining.
4325 */
4326
4327// MatrixClient Callback JSDocs
4328
4329/**
4330 * The standard MatrixClient callback interface. Functions which accept this
4331 * will specify 2 return arguments. These arguments map to the 2 parameters
4332 * specified in this callback.
4333 * @callback module:client.callback
4334 * @param {Object} err The error value, the "rejected" value or null.
4335 * @param {Object} data The data returned, the "resolved" value.
4336 */
4337
4338/**
4339 * {@link https://github.com/kriskowal/q|A promise implementation (Q)}. Functions
4340 * which return this will specify 2 return arguments. These arguments map to the
4341 * "onFulfilled" and "onRejected" values of the Promise.
4342 * @typedef {Object} Promise
4343 * @static
4344 * @property {Function} then promise.then(onFulfilled, onRejected, onProgress)
4345 * @property {Function} catch promise.catch(onRejected)
4346 * @property {Function} finally promise.finally(callback)
4347 * @property {Function} done promise.done(onFulfilled, onRejected, onProgress)
4348 */
4349//# sourceMappingURL=client.js.map
\No newline at end of file