UNPKG

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