2020-08-05 22:08:55 +05:30
|
|
|
/*
|
|
|
|
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2021-05-07 19:43:49 +05:30
|
|
|
import {BaseRoom} from "./BaseRoom.js";
|
2020-04-21 00:56:39 +05:30
|
|
|
import {SyncWriter} from "./timeline/persistence/SyncWriter.js";
|
2021-06-03 20:12:09 +05:30
|
|
|
import {MemberWriter} from "./timeline/persistence/MemberWriter.js";
|
|
|
|
import {RelationWriter} from "./timeline/persistence/RelationWriter.js";
|
2020-04-21 00:56:39 +05:30
|
|
|
import {SendQueue} from "./sending/SendQueue.js";
|
2020-08-17 14:18:00 +05:30
|
|
|
import {WrappedError} from "../error.js"
|
2020-08-21 21:41:07 +05:30
|
|
|
import {Heroes} from "./members/Heroes.js";
|
2020-11-11 15:17:55 +05:30
|
|
|
import {AttachmentUpload} from "./AttachmentUpload.js";
|
2020-09-10 15:39:17 +05:30
|
|
|
import {DecryptionSource} from "../e2ee/common.js";
|
2021-08-04 13:35:33 +05:30
|
|
|
import {PowerLevels, EVENT_TYPE as POWERLEVELS_EVENT_TYPE } from "./timeline/PowerLevels.js";
|
2020-11-11 15:17:55 +05:30
|
|
|
|
2020-09-10 15:39:17 +05:30
|
|
|
const EVENT_ENCRYPTED_TYPE = "m.room.encrypted";
|
2018-12-21 19:05:24 +05:30
|
|
|
|
2021-05-07 19:43:49 +05:30
|
|
|
export class Room extends BaseRoom {
|
|
|
|
constructor(options) {
|
|
|
|
super(options);
|
|
|
|
const {pendingEvents} = options;
|
2021-06-03 20:12:09 +05:30
|
|
|
const relationWriter = new RelationWriter({
|
|
|
|
roomId: this.id,
|
|
|
|
fragmentIdComparer: this._fragmentIdComparer,
|
|
|
|
ownUserId: this._user.id
|
|
|
|
});
|
|
|
|
this._syncWriter = new SyncWriter({
|
|
|
|
roomId: this.id,
|
|
|
|
fragmentIdComparer: this._fragmentIdComparer,
|
|
|
|
relationWriter,
|
|
|
|
memberWriter: new MemberWriter(this.id)
|
|
|
|
});
|
2021-05-07 19:43:49 +05:30
|
|
|
this._sendQueue = new SendQueue({roomId: this.id, storage: this._storage, hsApi: this._hsApi, pendingEvents});
|
2020-09-04 15:40:12 +05:30
|
|
|
}
|
|
|
|
|
2020-09-23 17:56:14 +05:30
|
|
|
_setEncryption(roomEncryption) {
|
2021-05-07 19:43:49 +05:30
|
|
|
if (super._setEncryption(roomEncryption)) {
|
2020-09-04 18:58:22 +05:30
|
|
|
this._sendQueue.enableEncryption(this._roomEncryption);
|
2021-05-07 19:43:49 +05:30
|
|
|
return true;
|
2020-09-04 18:58:22 +05:30
|
|
|
}
|
2021-05-07 19:43:49 +05:30
|
|
|
return false;
|
2021-03-03 15:57:55 +05:30
|
|
|
}
|
|
|
|
|
2021-04-20 21:09:46 +05:30
|
|
|
async prepareSync(roomResponse, membership, invite, newKeys, txn, log) {
|
2021-02-17 23:15:04 +05:30
|
|
|
log.set("id", this.id);
|
2021-03-01 19:34:45 +05:30
|
|
|
if (newKeys) {
|
|
|
|
log.set("newKeys", newKeys.length);
|
|
|
|
}
|
2021-05-10 22:12:30 +05:30
|
|
|
let summaryChanges = this._summary.data.applySyncResponse(roomResponse, membership);
|
2021-04-23 21:35:46 +05:30
|
|
|
if (membership === "join" && invite) {
|
2021-04-20 21:09:46 +05:30
|
|
|
summaryChanges = summaryChanges.applyInvite(invite);
|
|
|
|
}
|
2020-09-23 17:56:14 +05:30
|
|
|
let roomEncryption = this._roomEncryption;
|
|
|
|
// encryption is enabled in this sync
|
|
|
|
if (!roomEncryption && summaryChanges.encryption) {
|
2021-02-17 23:15:04 +05:30
|
|
|
log.set("enableEncryption", true);
|
2020-09-23 17:56:14 +05:30
|
|
|
roomEncryption = this._createRoomEncryption(this, summaryChanges.encryption);
|
|
|
|
}
|
2020-09-10 15:41:43 +05:30
|
|
|
|
2021-03-02 03:00:33 +05:30
|
|
|
let retryEntries;
|
2020-09-23 17:56:14 +05:30
|
|
|
let decryptPreparation;
|
|
|
|
if (roomEncryption) {
|
2021-03-03 18:23:52 +05:30
|
|
|
let eventsToDecrypt = roomResponse?.timeline?.events || [];
|
|
|
|
// when new keys arrive, also see if any older events can now be retried to decrypt
|
|
|
|
if (newKeys) {
|
2021-03-05 16:20:54 +05:30
|
|
|
retryEntries = await this._getSyncRetryDecryptEntries(newKeys, roomEncryption, txn);
|
2021-03-03 18:23:52 +05:30
|
|
|
if (retryEntries.length) {
|
|
|
|
log.set("retry", retryEntries.length);
|
|
|
|
eventsToDecrypt = eventsToDecrypt.concat(retryEntries.map(entry => entry.event));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
eventsToDecrypt = eventsToDecrypt.filter(event => {
|
|
|
|
return event?.type === EVENT_ENCRYPTED_TYPE;
|
|
|
|
});
|
|
|
|
if (eventsToDecrypt.length) {
|
|
|
|
decryptPreparation = await roomEncryption.prepareDecryptAll(
|
|
|
|
eventsToDecrypt, newKeys, DecryptionSource.Sync, txn);
|
|
|
|
}
|
2020-09-04 18:58:22 +05:30
|
|
|
}
|
2020-09-23 17:56:14 +05:30
|
|
|
|
|
|
|
return {
|
|
|
|
roomEncryption,
|
|
|
|
summaryChanges,
|
|
|
|
decryptPreparation,
|
|
|
|
decryptChanges: null,
|
2021-03-02 03:00:33 +05:30
|
|
|
retryEntries
|
2020-09-23 17:56:14 +05:30
|
|
|
};
|
2020-09-04 18:58:22 +05:30
|
|
|
}
|
|
|
|
|
2021-02-17 23:15:04 +05:30
|
|
|
async afterPrepareSync(preparation, parentLog) {
|
2020-09-23 17:56:14 +05:30
|
|
|
if (preparation.decryptPreparation) {
|
2021-03-02 19:59:35 +05:30
|
|
|
await parentLog.wrap("decrypt", async log => {
|
2021-02-17 23:15:04 +05:30
|
|
|
log.set("id", this.id);
|
|
|
|
preparation.decryptChanges = await preparation.decryptPreparation.decrypt();
|
|
|
|
preparation.decryptPreparation = null;
|
2021-02-18 17:09:29 +05:30
|
|
|
}, parentLog.level.Detail);
|
2020-09-10 15:41:43 +05:30
|
|
|
}
|
2020-09-04 15:39:19 +05:30
|
|
|
}
|
|
|
|
|
2020-08-19 20:28:28 +05:30
|
|
|
/** @package */
|
2021-03-02 03:00:33 +05:30
|
|
|
async writeSync(roomResponse, isInitialSync, {summaryChanges, decryptChanges, roomEncryption, retryEntries}, txn, log) {
|
2021-02-17 23:15:04 +05:30
|
|
|
log.set("id", this.id);
|
2021-05-06 18:56:48 +05:30
|
|
|
const isRejoin = summaryChanges.isNewJoin(this._summary.data);
|
2021-05-05 20:33:22 +05:30
|
|
|
if (isRejoin) {
|
2021-05-05 22:34:26 +05:30
|
|
|
// remove all room state before calling syncWriter,
|
|
|
|
// so no old state sticks around
|
|
|
|
txn.roomState.removeAllForRoom(this.id);
|
2021-05-06 18:57:10 +05:30
|
|
|
txn.roomMembers.removeAllForRoom(this.id);
|
2021-05-05 20:33:22 +05:30
|
|
|
}
|
2021-05-20 13:31:30 +05:30
|
|
|
const {entries: newEntries, updatedEntries, newLiveKey, memberChanges} =
|
2021-04-22 20:51:29 +05:30
|
|
|
await log.wrap("syncWriter", log => this._syncWriter.writeSync(roomResponse, isRejoin, txn, log), log.level.Detail);
|
2020-09-23 17:56:14 +05:30
|
|
|
if (decryptChanges) {
|
2021-03-15 17:26:40 +05:30
|
|
|
const decryption = await log.wrap("decryptChanges", log => decryptChanges.write(txn, log));
|
2021-03-03 01:59:32 +05:30
|
|
|
log.set("decryptionResults", decryption.results.size);
|
|
|
|
log.set("decryptionErrors", decryption.errors.size);
|
2021-03-02 03:44:14 +05:30
|
|
|
if (this._isTimelineOpen) {
|
|
|
|
await decryption.verifySenders(txn);
|
|
|
|
}
|
2021-03-03 15:57:18 +05:30
|
|
|
decryption.applyToEntries(newEntries);
|
2021-03-02 03:00:33 +05:30
|
|
|
if (retryEntries?.length) {
|
2021-03-03 15:57:18 +05:30
|
|
|
decryption.applyToEntries(retryEntries);
|
2021-05-20 13:31:30 +05:30
|
|
|
updatedEntries.push(...retryEntries);
|
2021-03-02 03:00:33 +05:30
|
|
|
}
|
2020-09-10 15:41:43 +05:30
|
|
|
}
|
2021-05-20 13:31:30 +05:30
|
|
|
log.set("newEntries", newEntries.length);
|
|
|
|
log.set("updatedEntries", updatedEntries.length);
|
2021-03-02 23:44:29 +05:30
|
|
|
let shouldFlushKeyShares = false;
|
2020-09-10 15:41:43 +05:30
|
|
|
// pass member changes to device tracker
|
2020-09-23 17:56:14 +05:30
|
|
|
if (roomEncryption && this.isTrackingMembers && memberChanges?.size) {
|
2021-03-03 16:21:50 +05:30
|
|
|
shouldFlushKeyShares = await roomEncryption.writeMemberChanges(memberChanges, txn, log);
|
2021-03-02 23:44:29 +05:30
|
|
|
log.set("shouldFlushKeyShares", shouldFlushKeyShares);
|
2020-09-10 15:41:43 +05:30
|
|
|
}
|
2021-05-20 13:31:30 +05:30
|
|
|
const allEntries = newEntries.concat(updatedEntries);
|
2020-09-23 17:56:14 +05:30
|
|
|
// also apply (decrypted) timeline entries to the summary changes
|
|
|
|
summaryChanges = summaryChanges.applyTimelineEntries(
|
2021-03-03 15:57:18 +05:30
|
|
|
allEntries, isInitialSync, !this._isTimelineOpen, this._user.id);
|
2021-05-06 18:56:48 +05:30
|
|
|
|
2021-05-10 22:12:30 +05:30
|
|
|
// if we've have left the room, remove the summary
|
|
|
|
if (summaryChanges.membership !== "join") {
|
2021-05-06 18:56:48 +05:30
|
|
|
txn.roomSummary.remove(this.id);
|
2021-05-05 20:33:22 +05:30
|
|
|
} else {
|
|
|
|
// write summary changes, and unset if nothing was actually changed
|
|
|
|
summaryChanges = this._summary.writeData(summaryChanges, txn);
|
|
|
|
}
|
2021-03-03 01:59:32 +05:30
|
|
|
if (summaryChanges) {
|
|
|
|
log.set("summaryChanges", summaryChanges.diff(this._summary.data));
|
|
|
|
}
|
2020-08-21 21:41:07 +05:30
|
|
|
// fetch new members while we have txn open,
|
|
|
|
// but don't make any in-memory changes yet
|
|
|
|
let heroChanges;
|
2021-03-05 21:34:18 +05:30
|
|
|
// if any hero changes their display name, the summary in the room response
|
|
|
|
// is also updated, which will trigger a RoomSummary update
|
|
|
|
// and make summaryChanges non-falsy here
|
2020-09-23 17:56:14 +05:30
|
|
|
if (summaryChanges?.needsHeroes) {
|
2020-08-21 22:33:21 +05:30
|
|
|
// room name disappeared, open heroes
|
|
|
|
if (!this._heroes) {
|
|
|
|
this._heroes = new Heroes(this._roomId);
|
|
|
|
}
|
2020-08-31 13:20:57 +05:30
|
|
|
heroChanges = await this._heroes.calculateChanges(summaryChanges.heroes, memberChanges, txn);
|
2020-08-21 21:41:07 +05:30
|
|
|
}
|
2019-07-27 02:03:33 +05:30
|
|
|
let removedPendingEvents;
|
2020-09-23 21:04:25 +05:30
|
|
|
if (Array.isArray(roomResponse.timeline?.events)) {
|
2021-05-21 14:17:48 +05:30
|
|
|
removedPendingEvents = await this._sendQueue.removeRemoteEchos(roomResponse.timeline.events, txn, log);
|
2019-07-27 02:03:33 +05:30
|
|
|
}
|
2021-08-05 12:41:54 +05:30
|
|
|
const powerLevelsEvent = this._getPowerLevelsEvent(roomResponse);
|
2020-08-21 21:41:07 +05:30
|
|
|
return {
|
|
|
|
summaryChanges,
|
2020-09-23 17:56:14 +05:30
|
|
|
roomEncryption,
|
2021-03-03 15:57:18 +05:30
|
|
|
newEntries,
|
2021-05-20 13:31:30 +05:30
|
|
|
updatedEntries,
|
2020-08-21 21:41:07 +05:30
|
|
|
newLiveKey,
|
|
|
|
removedPendingEvents,
|
2020-08-31 13:20:57 +05:30
|
|
|
memberChanges,
|
2020-09-08 18:08:27 +05:30
|
|
|
heroChanges,
|
2021-08-05 12:41:54 +05:30
|
|
|
powerLevelsEvent,
|
2021-03-02 23:44:29 +05:30
|
|
|
shouldFlushKeyShares,
|
2020-08-21 21:41:07 +05:30
|
|
|
};
|
2019-02-27 23:57:45 +05:30
|
|
|
}
|
|
|
|
|
2020-09-08 18:09:33 +05:30
|
|
|
/**
|
|
|
|
* @package
|
|
|
|
* Called with the changes returned from `writeSync` to apply them and emit changes.
|
|
|
|
* No storage or network operations should be done here.
|
|
|
|
*/
|
2021-03-03 15:57:18 +05:30
|
|
|
afterSync(changes, log) {
|
|
|
|
const {
|
|
|
|
summaryChanges, newEntries, updatedEntries, newLiveKey,
|
2021-08-05 12:41:54 +05:30
|
|
|
removedPendingEvents, memberChanges, powerLevelsEvent,
|
2021-03-03 15:57:18 +05:30
|
|
|
heroChanges, roomEncryption
|
|
|
|
} = changes;
|
2021-02-17 23:15:04 +05:30
|
|
|
log.set("id", this.id);
|
2020-03-15 01:19:15 +05:30
|
|
|
this._syncWriter.afterSync(newLiveKey);
|
2020-09-23 17:56:14 +05:30
|
|
|
this._setEncryption(roomEncryption);
|
2020-08-31 13:20:57 +05:30
|
|
|
if (memberChanges.size) {
|
2020-08-19 19:42:49 +05:30
|
|
|
if (this._changedMembersDuringSync) {
|
2020-08-31 13:20:57 +05:30
|
|
|
for (const [userId, memberChange] of memberChanges.entries()) {
|
|
|
|
this._changedMembersDuringSync.set(userId, memberChange.member);
|
2020-08-19 19:42:49 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
if (this._memberList) {
|
2020-08-31 13:20:57 +05:30
|
|
|
this._memberList.afterSync(memberChanges);
|
2020-08-19 19:42:49 +05:30
|
|
|
}
|
2021-07-19 19:36:09 +05:30
|
|
|
if (this._observedMembers) {
|
|
|
|
this._updateObservedMembers(memberChanges);
|
|
|
|
}
|
2021-03-03 19:23:22 +05:30
|
|
|
if (this._timeline) {
|
|
|
|
for (const [userId, memberChange] of memberChanges.entries()) {
|
|
|
|
if (userId === this._user.id) {
|
|
|
|
this._timeline.updateOwnMember(memberChange.member);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-08-19 19:42:49 +05:30
|
|
|
}
|
2020-08-21 21:41:07 +05:30
|
|
|
let emitChange = false;
|
2020-03-15 01:16:49 +05:30
|
|
|
if (summaryChanges) {
|
2020-06-27 02:56:24 +05:30
|
|
|
this._summary.applyChanges(summaryChanges);
|
2020-09-23 17:56:14 +05:30
|
|
|
if (!this._summary.data.needsHeroes) {
|
2020-08-21 21:41:07 +05:30
|
|
|
this._heroes = null;
|
|
|
|
}
|
|
|
|
emitChange = true;
|
|
|
|
}
|
|
|
|
if (this._heroes && heroChanges) {
|
|
|
|
const oldName = this.name;
|
2020-09-23 17:56:14 +05:30
|
|
|
this._heroes.applyChanges(heroChanges, this._summary.data);
|
2020-08-21 21:41:07 +05:30
|
|
|
if (oldName !== this.name) {
|
|
|
|
emitChange = true;
|
|
|
|
}
|
|
|
|
}
|
2021-08-05 12:41:54 +05:30
|
|
|
if (powerLevelsEvent) {
|
|
|
|
this._updatePowerLevels(powerLevelsEvent);
|
|
|
|
}
|
2020-08-21 21:41:07 +05:30
|
|
|
if (emitChange) {
|
2020-09-14 20:03:43 +05:30
|
|
|
this._emitUpdate();
|
2019-02-21 04:18:16 +05:30
|
|
|
}
|
2019-02-28 03:20:08 +05:30
|
|
|
if (this._timeline) {
|
2021-03-03 15:57:18 +05:30
|
|
|
// these should not be added if not already there
|
|
|
|
this._timeline.replaceEntries(updatedEntries);
|
2021-06-23 21:08:52 +05:30
|
|
|
this._timeline.addEntries(newEntries);
|
2019-02-28 03:20:08 +05:30
|
|
|
}
|
2020-10-30 19:49:51 +05:30
|
|
|
if (this._observedEvents) {
|
2021-03-03 15:57:18 +05:30
|
|
|
this._observedEvents.updateEvents(updatedEntries);
|
|
|
|
this._observedEvents.updateEvents(newEntries);
|
2020-10-30 19:49:51 +05:30
|
|
|
}
|
2019-07-27 02:03:33 +05:30
|
|
|
if (removedPendingEvents) {
|
|
|
|
this._sendQueue.emitRemovals(removedPendingEvents);
|
|
|
|
}
|
2020-09-23 21:52:51 +05:30
|
|
|
}
|
2018-12-21 19:05:24 +05:30
|
|
|
|
2021-07-19 19:36:09 +05:30
|
|
|
_updateObservedMembers(memberChanges) {
|
|
|
|
for (const [userId, memberChange] of memberChanges) {
|
|
|
|
const observableMember = this._observedMembers.get(userId);
|
|
|
|
if (observableMember) {
|
|
|
|
observableMember.set(memberChange.member);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-05 12:41:54 +05:30
|
|
|
_getPowerLevelsEvent(roomResponse) {
|
|
|
|
const isPowerlevelEvent = event => event.type === POWERLEVELS_EVENT_TYPE;
|
|
|
|
const powerLevelEvent = roomResponse.state?.events.find(isPowerlevelEvent) ?? roomResponse.timeline?.events.find(isPowerlevelEvent);
|
|
|
|
return powerLevelEvent;
|
|
|
|
}
|
|
|
|
|
|
|
|
_updatePowerLevels(powerLevelEvent) {
|
|
|
|
if (this._powerLevels) {
|
2021-08-04 13:35:33 +05:30
|
|
|
const newPowerLevels = new PowerLevels({
|
|
|
|
powerLevelEvent,
|
|
|
|
ownUserId: this._user.id,
|
|
|
|
membership: this.membership,
|
|
|
|
});
|
|
|
|
this._powerLevels.set(newPowerLevels);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-02 23:44:29 +05:30
|
|
|
needsAfterSyncCompleted({shouldFlushKeyShares}) {
|
|
|
|
return shouldFlushKeyShares;
|
2020-09-10 15:41:43 +05:30
|
|
|
}
|
|
|
|
|
2020-09-08 18:08:27 +05:30
|
|
|
/**
|
|
|
|
* Only called if the result of writeSync had `needsAfterSyncCompleted` set.
|
|
|
|
* Can be used to do longer running operations that resulted from the last sync,
|
|
|
|
* like network operations.
|
|
|
|
*/
|
2021-04-20 21:33:27 +05:30
|
|
|
async afterSyncCompleted(changes, log) {
|
2021-02-17 23:15:04 +05:30
|
|
|
log.set("id", this.id);
|
2020-09-08 18:08:27 +05:30
|
|
|
if (this._roomEncryption) {
|
2021-02-23 23:52:25 +05:30
|
|
|
await this._roomEncryption.flushPendingRoomKeyShares(this._hsApi, null, log);
|
2020-09-08 18:08:27 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-19 20:28:28 +05:30
|
|
|
/** @package */
|
2021-02-23 23:52:25 +05:30
|
|
|
start(pendingOperations, parentLog) {
|
2020-09-08 18:09:07 +05:30
|
|
|
if (this._roomEncryption) {
|
2021-02-23 23:52:25 +05:30
|
|
|
const roomKeyShares = pendingOperations?.get("share_room_key");
|
|
|
|
if (roomKeyShares) {
|
|
|
|
// if we got interrupted last time sending keys to newly joined members
|
|
|
|
parentLog.wrapDetached("flush room keys", log => {
|
|
|
|
log.set("id", this.id);
|
|
|
|
return this._roomEncryption.flushPendingRoomKeyShares(this._hsApi, roomKeyShares, log);
|
|
|
|
});
|
2020-09-08 18:09:07 +05:30
|
|
|
}
|
|
|
|
}
|
2021-02-23 23:52:25 +05:30
|
|
|
|
|
|
|
this._sendQueue.resumeSending(parentLog);
|
2019-07-27 02:10:39 +05:30
|
|
|
}
|
|
|
|
|
2020-08-19 20:28:28 +05:30
|
|
|
/** @package */
|
2021-02-23 23:52:25 +05:30
|
|
|
async load(summary, txn, log) {
|
2020-08-17 14:18:00 +05:30
|
|
|
try {
|
2021-05-13 02:37:25 +05:30
|
|
|
await super.load(summary, txn, log);
|
|
|
|
await this._syncWriter.load(txn, log);
|
2020-08-17 14:18:00 +05:30
|
|
|
} catch (err) {
|
|
|
|
throw new WrappedError(`Could not load room ${this._roomId}`, err);
|
|
|
|
}
|
2020-09-23 21:52:51 +05:30
|
|
|
}
|
2019-02-27 03:15:58 +05:30
|
|
|
|
2021-05-21 14:17:48 +05:30
|
|
|
async _writeGapFill(gapChunk, txn, log) {
|
|
|
|
const removedPendingEvents = await this._sendQueue.removeRemoteEchos(gapChunk, txn, log);
|
2021-05-07 19:43:49 +05:30
|
|
|
return removedPendingEvents;
|
|
|
|
}
|
|
|
|
|
|
|
|
_applyGapFill(removedPendingEvents) {
|
|
|
|
this._sendQueue.emitRemovals(removedPendingEvents);
|
|
|
|
}
|
|
|
|
|
2020-08-19 20:28:28 +05:30
|
|
|
/** @public */
|
2021-02-23 23:52:59 +05:30
|
|
|
sendEvent(eventType, content, attachments, log = null) {
|
2021-06-08 20:26:17 +05:30
|
|
|
return this._platform.logger.wrapOrRun(log, "send", log => {
|
2021-02-23 23:52:59 +05:30
|
|
|
log.set("id", this.id);
|
|
|
|
return this._sendQueue.enqueueEvent(eventType, content, attachments, log);
|
|
|
|
});
|
2019-07-27 02:03:33 +05:30
|
|
|
}
|
|
|
|
|
2021-05-20 18:23:17 +05:30
|
|
|
/** @public */
|
|
|
|
sendRedaction(eventIdOrTxnId, reason, log = null) {
|
2021-06-08 20:26:17 +05:30
|
|
|
return this._platform.logger.wrapOrRun(log, "redact", log => {
|
2021-05-20 18:23:17 +05:30
|
|
|
log.set("id", this.id);
|
|
|
|
return this._sendQueue.enqueueRedaction(eventIdOrTxnId, reason, log);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-11-11 15:17:55 +05:30
|
|
|
/** @public */
|
2021-02-23 23:52:59 +05:30
|
|
|
async ensureMessageKeyIsShared(log = null) {
|
|
|
|
if (!this._roomEncryption) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
return this._platform.logger.wrapOrRun(log, "ensureMessageKeyIsShared", log => {
|
|
|
|
log.set("id", this.id);
|
|
|
|
return this._roomEncryption.ensureMessageKeyIsShared(this._hsApi, log);
|
|
|
|
});
|
2020-11-07 04:13:02 +05:30
|
|
|
}
|
|
|
|
|
2021-07-01 03:37:27 +05:30
|
|
|
get avatarColorId() {
|
|
|
|
return this._heroes?.roomAvatarColorId || this._roomId;
|
|
|
|
}
|
|
|
|
|
2020-08-21 15:26:45 +05:30
|
|
|
get isUnread() {
|
2020-09-23 17:56:14 +05:30
|
|
|
return this._summary.data.isUnread;
|
2020-08-21 15:26:45 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
get notificationCount() {
|
2020-09-23 17:56:14 +05:30
|
|
|
return this._summary.data.notificationCount;
|
2020-08-21 15:26:45 +05:30
|
|
|
}
|
2020-08-21 19:20:32 +05:30
|
|
|
|
|
|
|
get highlightCount() {
|
2020-09-23 17:56:14 +05:30
|
|
|
return this._summary.data.highlightCount;
|
2020-08-21 19:20:32 +05:30
|
|
|
}
|
2020-08-21 15:26:45 +05:30
|
|
|
|
2020-08-31 12:23:47 +05:30
|
|
|
get isTrackingMembers() {
|
2020-09-23 17:56:14 +05:30
|
|
|
return this._summary.data.isTrackingMembers;
|
2020-08-31 12:23:47 +05:30
|
|
|
}
|
|
|
|
|
2020-08-21 18:46:57 +05:30
|
|
|
async _getLastEventId() {
|
|
|
|
const lastKey = this._syncWriter.lastMessageKey;
|
|
|
|
if (lastKey) {
|
2021-03-05 00:17:02 +05:30
|
|
|
const txn = await this._storage.readTxn([
|
2020-08-21 18:46:57 +05:30
|
|
|
this._storage.storeNames.timelineEvents,
|
|
|
|
]);
|
|
|
|
const eventEntry = await txn.timelineEvents.get(this._roomId, lastKey);
|
|
|
|
return eventEntry?.event?.event_id;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-24 15:52:19 +05:30
|
|
|
async clearUnread(log = null) {
|
2020-08-21 18:46:57 +05:30
|
|
|
if (this.isUnread || this.notificationCount) {
|
2021-02-24 15:52:19 +05:30
|
|
|
return await this._platform.logger.wrapOrRun(log, "clearUnread", async log => {
|
|
|
|
log.set("id", this.id);
|
2021-03-05 00:17:02 +05:30
|
|
|
const txn = await this._storage.readWriteTxn([
|
2021-02-24 15:52:19 +05:30
|
|
|
this._storage.storeNames.roomSummary,
|
|
|
|
]);
|
|
|
|
let data;
|
|
|
|
try {
|
|
|
|
data = this._summary.writeClearUnread(txn);
|
|
|
|
} catch (err) {
|
|
|
|
txn.abort();
|
2020-08-21 18:53:25 +05:30
|
|
|
throw err;
|
|
|
|
}
|
2021-02-24 15:52:19 +05:30
|
|
|
await txn.complete();
|
|
|
|
this._summary.applyChanges(data);
|
|
|
|
this._emitUpdate();
|
|
|
|
|
|
|
|
try {
|
|
|
|
const lastEventId = await this._getLastEventId();
|
|
|
|
if (lastEventId) {
|
|
|
|
await this._hsApi.receipt(this._roomId, "m.read", lastEventId);
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
// ignore ConnectionError
|
|
|
|
if (err.name !== "ConnectionError") {
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2020-08-21 15:26:10 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-12 17:29:08 +05:30
|
|
|
leave(log = null) {
|
|
|
|
return this._platform.logger.wrapOrRun(log, "leave room", async log => {
|
|
|
|
log.set("id", this.id);
|
|
|
|
await this._hsApi.leave(this.id, {log}).response();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-05-07 19:43:49 +05:30
|
|
|
/* called by BaseRoom to pass pendingEvents when opening the timeline */
|
|
|
|
_getPendingEvents() {
|
|
|
|
return this._sendQueue.pendingEvents;
|
2020-05-09 23:32:08 +05:30
|
|
|
}
|
2020-08-31 17:41:08 +05:30
|
|
|
|
|
|
|
/** @package */
|
|
|
|
writeIsTrackingMembers(value, txn) {
|
|
|
|
return this._summary.writeIsTrackingMembers(value, txn);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @package */
|
|
|
|
applyIsTrackingMembersChanges(changes) {
|
|
|
|
this._summary.applyChanges(changes);
|
|
|
|
}
|
2020-09-18 16:38:18 +05:30
|
|
|
|
2020-11-13 21:49:19 +05:30
|
|
|
createAttachment(blob, filename) {
|
2020-11-18 17:32:38 +05:30
|
|
|
return new AttachmentUpload({blob, filename, platform: this._platform});
|
2020-11-11 15:17:55 +05:30
|
|
|
}
|
|
|
|
|
2020-09-18 16:38:18 +05:30
|
|
|
dispose() {
|
2021-05-07 19:43:49 +05:30
|
|
|
super.dispose();
|
2020-11-19 00:38:42 +05:30
|
|
|
this._sendQueue.dispose();
|
2020-09-18 16:38:18 +05:30
|
|
|
}
|
2019-02-21 04:18:16 +05:30
|
|
|
}
|