UNPKG

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