2016-09-06 15:39:21 +00:00
|
|
|
/*
|
|
|
|
Copyright 2016 OpenMarket Ltd
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2016-09-26 17:02:14 +00:00
|
|
|
import MatrixClientPeg from '../MatrixClientPeg';
|
2018-09-04 11:07:24 +00:00
|
|
|
import {unique} from '../ArrayUtils';
|
2016-09-26 17:02:14 +00:00
|
|
|
|
2016-09-06 15:39:21 +00:00
|
|
|
/**
|
|
|
|
* Class that takes a Matrix Client and flips the m.direct map
|
|
|
|
* so the operation of mapping a room ID to which user it's a DM
|
|
|
|
* with can be performed efficiently.
|
2016-09-26 17:02:14 +00:00
|
|
|
*
|
|
|
|
* With 'start', this can also keep itself up to date over time.
|
2016-09-06 15:39:21 +00:00
|
|
|
*/
|
|
|
|
export default class DMRoomMap {
|
|
|
|
constructor(matrixClient) {
|
2016-09-12 17:32:44 +00:00
|
|
|
this.matrixClient = matrixClient;
|
2016-09-09 15:15:01 +00:00
|
|
|
this.roomToUser = null;
|
2018-08-30 10:01:13 +00:00
|
|
|
// see _onAccountData
|
|
|
|
this._hasSentOutPatchDirectAccountDataPatch = false;
|
2016-09-09 15:15:01 +00:00
|
|
|
|
2016-09-26 17:02:14 +00:00
|
|
|
// XXX: Force-bind the event handler method because it
|
|
|
|
// doesn't call it with our object as the 'this'
|
|
|
|
// (use a static property arrow function for this when we can)
|
|
|
|
this._onAccountData = this._onAccountData.bind(this);
|
|
|
|
|
2016-09-06 15:39:21 +00:00
|
|
|
const mDirectEvent = matrixClient.getAccountData('m.direct');
|
|
|
|
if (!mDirectEvent) {
|
|
|
|
this.userToRooms = {};
|
|
|
|
} else {
|
|
|
|
this.userToRooms = mDirectEvent.getContent();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-27 08:56:31 +00:00
|
|
|
/**
|
|
|
|
* Makes and returns a new shared instance that can then be accessed
|
|
|
|
* with shared(). This returned instance is not automatically started.
|
|
|
|
*/
|
|
|
|
static makeShared() {
|
|
|
|
DMRoomMap._sharedInstance = new DMRoomMap(MatrixClientPeg.get());
|
|
|
|
return DMRoomMap._sharedInstance;
|
|
|
|
}
|
|
|
|
|
2016-09-26 17:02:14 +00:00
|
|
|
/**
|
|
|
|
* Returns a shared instance of the class
|
|
|
|
* that uses the singleton matrix client
|
|
|
|
* The shared instance must be started before use.
|
|
|
|
*/
|
|
|
|
static shared() {
|
|
|
|
return DMRoomMap._sharedInstance;
|
|
|
|
}
|
|
|
|
|
|
|
|
start() {
|
|
|
|
this._populateRoomToUser();
|
|
|
|
this.matrixClient.on("accountData", this._onAccountData);
|
|
|
|
}
|
|
|
|
|
|
|
|
stop() {
|
|
|
|
this.matrixClient.removeListener("accountData", this._onAccountData);
|
|
|
|
}
|
|
|
|
|
2018-09-04 11:03:55 +00:00
|
|
|
async _onAccountData(ev) {
|
2016-09-26 17:02:14 +00:00
|
|
|
if (ev.getType() == 'm.direct') {
|
2018-08-30 10:36:53 +00:00
|
|
|
const userToRooms = this.matrixClient.getAccountData('m.direct').getContent() || {};
|
2018-08-30 08:53:25 +00:00
|
|
|
const myUserId = this.matrixClient.getUserId();
|
2018-08-30 09:50:57 +00:00
|
|
|
const selfDMs = userToRooms[myUserId];
|
|
|
|
if (selfDMs && selfDMs.length) {
|
2018-09-04 11:03:55 +00:00
|
|
|
const neededPatching = await this._patchUpSelfDMs(userToRooms);
|
2018-08-30 10:01:13 +00:00
|
|
|
// to avoid multiple devices fighting to correct
|
|
|
|
// the account data, only try to send the corrected
|
|
|
|
// version once.
|
2018-08-30 10:36:53 +00:00
|
|
|
if (neededPatching && !this._hasSentOutPatchDirectAccountDataPatch) {
|
2018-08-30 10:01:13 +00:00
|
|
|
this._hasSentOutPatchDirectAccountDataPatch = true;
|
|
|
|
this.matrixClient.setAccountData('m.direct', userToRooms);
|
|
|
|
}
|
2018-08-30 08:53:25 +00:00
|
|
|
}
|
|
|
|
this.userToRooms = userToRooms;
|
2016-09-26 17:02:14 +00:00
|
|
|
this._populateRoomToUser();
|
|
|
|
}
|
|
|
|
}
|
2018-08-30 08:53:25 +00:00
|
|
|
/**
|
|
|
|
* some client bug somewhere is causing some DMs to be marked
|
|
|
|
* with ourself, not the other user. Fix it by guessing the other user and
|
|
|
|
* modifying userToRooms
|
|
|
|
*/
|
2018-09-04 11:03:55 +00:00
|
|
|
async _patchUpSelfDMs(userToRooms) {
|
2018-08-30 08:53:25 +00:00
|
|
|
const myUserId = this.matrixClient.getUserId();
|
|
|
|
const selfRoomIds = userToRooms[myUserId];
|
|
|
|
if (selfRoomIds) {
|
2018-09-04 11:03:55 +00:00
|
|
|
// account data gets emitted before the rooms are available
|
|
|
|
// so wait for the sync to be ready and then read the rooms.
|
|
|
|
await this._waitForSyncReady();
|
2018-08-30 10:36:53 +00:00
|
|
|
// any self-chats that should not be self-chats?
|
|
|
|
const guessedUserIdsThatChanged = selfRoomIds.map((roomId) => {
|
2018-08-30 08:53:25 +00:00
|
|
|
const room = this.matrixClient.getRoom(roomId);
|
2018-08-30 10:36:53 +00:00
|
|
|
if (room) {
|
|
|
|
const userId = room.guessDMUserId();
|
|
|
|
if (userId && userId !== myUserId) {
|
|
|
|
return {userId, roomId};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}).filter((ids) => !!ids); //filter out
|
|
|
|
// these are actually all legit self-chats
|
|
|
|
// bail out
|
|
|
|
if (!guessedUserIdsThatChanged.length) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
userToRooms[myUserId] = selfRoomIds.filter((roomId) => {
|
2018-09-04 11:06:27 +00:00
|
|
|
return !guessedUserIdsThatChanged
|
2018-08-30 10:36:53 +00:00
|
|
|
.some((ids) => ids.roomId === roomId);
|
2018-08-30 08:53:25 +00:00
|
|
|
});
|
2018-08-30 10:36:53 +00:00
|
|
|
guessedUserIdsThatChanged.forEach(({userId, roomId}) => {
|
2018-08-30 09:37:13 +00:00
|
|
|
let roomIds = userToRooms[userId];
|
2018-08-30 08:53:25 +00:00
|
|
|
if (!roomIds) {
|
2018-09-04 11:07:24 +00:00
|
|
|
userToRooms[userId] = [roomId];
|
|
|
|
} else {
|
|
|
|
roomIds.push(roomId);
|
|
|
|
userToRooms[userId] = unique(roomIds);
|
2018-08-30 08:53:25 +00:00
|
|
|
}
|
|
|
|
});
|
2018-08-30 10:36:53 +00:00
|
|
|
return true;
|
2018-08-30 08:53:25 +00:00
|
|
|
}
|
|
|
|
}
|
2016-09-26 17:02:14 +00:00
|
|
|
|
2018-09-04 11:03:55 +00:00
|
|
|
_waitForSyncReady() {
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
const syncState = this.matrixClient.getSyncState();
|
|
|
|
if (syncState === 'PREPARED' || syncState === 'SYNCING') {
|
|
|
|
resolve();
|
|
|
|
} else {
|
|
|
|
// if we already got an accountData event,
|
|
|
|
// next sync should not be ERROR, so just resolve
|
|
|
|
this.matrixClient.once('sync', () => resolve());
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-09-06 15:39:21 +00:00
|
|
|
getDMRoomsForUserId(userId) {
|
2016-09-09 16:35:35 +00:00
|
|
|
// Here, we return the empty list if there are no rooms,
|
|
|
|
// since the number of conversations you have with this user is zero.
|
|
|
|
return this.userToRooms[userId] || [];
|
2016-09-06 15:39:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
getUserIdForRoomId(roomId) {
|
2016-09-09 15:15:01 +00:00
|
|
|
if (this.roomToUser == null) {
|
|
|
|
// we lazily populate roomToUser so you can use
|
|
|
|
// this class just to call getDMRoomsForUserId
|
|
|
|
// which doesn't do very much, but is a fairly
|
|
|
|
// convenient wrapper and there's no point
|
|
|
|
// iterating through the map if getUserIdForRoomId()
|
|
|
|
// is never called.
|
|
|
|
this._populateRoomToUser();
|
|
|
|
}
|
2016-09-09 16:35:35 +00:00
|
|
|
// Here, we return undefined if the room is not in the map:
|
|
|
|
// the room ID you gave is not a DM room for any user.
|
2016-09-12 17:32:44 +00:00
|
|
|
if (this.roomToUser[roomId] === undefined) {
|
|
|
|
// no entry? if the room is an invite, look for the is_direct hint.
|
|
|
|
const room = this.matrixClient.getRoom(roomId);
|
|
|
|
if (room) {
|
2018-08-14 09:43:03 +00:00
|
|
|
return room.getDMInviter();
|
2016-09-12 17:32:44 +00:00
|
|
|
}
|
|
|
|
}
|
2016-09-06 15:39:21 +00:00
|
|
|
return this.roomToUser[roomId];
|
|
|
|
}
|
2016-09-09 15:15:01 +00:00
|
|
|
|
|
|
|
_populateRoomToUser() {
|
|
|
|
this.roomToUser = {};
|
|
|
|
for (const user of Object.keys(this.userToRooms)) {
|
|
|
|
for (const roomId of this.userToRooms[user]) {
|
|
|
|
this.roomToUser[roomId] = user;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-09-06 15:39:21 +00:00
|
|
|
}
|