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