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