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