forked from mystiq/hydrogen-web
better handle remote echos and hookup in session and room
This commit is contained in:
parent
ccb722d766
commit
707988f806
5 changed files with 66 additions and 28 deletions
|
@ -3,9 +3,10 @@ import RoomSummary from "./summary.js";
|
||||||
import SyncWriter from "./timeline/persistence/SyncWriter.js";
|
import SyncWriter from "./timeline/persistence/SyncWriter.js";
|
||||||
import Timeline from "./timeline/Timeline.js";
|
import Timeline from "./timeline/Timeline.js";
|
||||||
import FragmentIdComparer from "./timeline/FragmentIdComparer.js";
|
import FragmentIdComparer from "./timeline/FragmentIdComparer.js";
|
||||||
|
import SendQueue from "./sending/SendQueue.js";
|
||||||
|
|
||||||
export default class Room extends EventEmitter {
|
export default class Room extends EventEmitter {
|
||||||
constructor({roomId, storage, hsApi, emitCollectionChange}) {
|
constructor({roomId, storage, hsApi, emitCollectionChange, sendScheduler, pendingEvents}) {
|
||||||
super();
|
super();
|
||||||
this._roomId = roomId;
|
this._roomId = roomId;
|
||||||
this._storage = storage;
|
this._storage = storage;
|
||||||
|
@ -14,16 +15,21 @@ export default class Room extends EventEmitter {
|
||||||
this._fragmentIdComparer = new FragmentIdComparer([]);
|
this._fragmentIdComparer = new FragmentIdComparer([]);
|
||||||
this._syncWriter = new SyncWriter({roomId, storage, fragmentIdComparer: this._fragmentIdComparer});
|
this._syncWriter = new SyncWriter({roomId, storage, fragmentIdComparer: this._fragmentIdComparer});
|
||||||
this._emitCollectionChange = emitCollectionChange;
|
this._emitCollectionChange = emitCollectionChange;
|
||||||
|
this._sendQueue = new SendQueue({roomId, storage, sendScheduler, pendingEvents});
|
||||||
this._timeline = null;
|
this._timeline = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async persistSync(roomResponse, membership, txn) {
|
async persistSync(roomResponse, membership, txn) {
|
||||||
const summaryChanged = this._summary.applySync(roomResponse, membership, txn);
|
const summaryChanged = this._summary.applySync(roomResponse, membership, txn);
|
||||||
const newTimelineEntries = await this._syncWriter.writeSync(roomResponse, txn);
|
const newTimelineEntries = await this._syncWriter.writeSync(roomResponse, txn);
|
||||||
return {summaryChanged, newTimelineEntries};
|
let removedPendingEvents;
|
||||||
|
if (roomResponse.timeline && roomResponse.timeline.events) {
|
||||||
|
removedPendingEvents = this._sendQueue.removeRemoteEchos(roomResponse.timeline.events, txn);
|
||||||
|
}
|
||||||
|
return {summaryChanged, newTimelineEntries, removedPendingEvents};
|
||||||
}
|
}
|
||||||
|
|
||||||
emitSync({summaryChanged, newTimelineEntries}) {
|
emitSync({summaryChanged, newTimelineEntries, removedPendingEvents}) {
|
||||||
if (summaryChanged) {
|
if (summaryChanged) {
|
||||||
this.emit("change");
|
this.emit("change");
|
||||||
this._emitCollectionChange(this);
|
this._emitCollectionChange(this);
|
||||||
|
@ -31,6 +37,9 @@ export default class Room extends EventEmitter {
|
||||||
if (this._timeline) {
|
if (this._timeline) {
|
||||||
this._timeline.appendLiveEntries(newTimelineEntries);
|
this._timeline.appendLiveEntries(newTimelineEntries);
|
||||||
}
|
}
|
||||||
|
if (removedPendingEvents) {
|
||||||
|
this._sendQueue.emitRemovals(removedPendingEvents);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
load(summary, txn) {
|
load(summary, txn) {
|
||||||
|
@ -38,6 +47,10 @@ export default class Room extends EventEmitter {
|
||||||
return this._syncWriter.load(txn);
|
return this._syncWriter.load(txn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sendEvent(eventType, content) {
|
||||||
|
this._sendQueue.enqueueEvent(eventType, content);
|
||||||
|
}
|
||||||
|
|
||||||
get name() {
|
get name() {
|
||||||
return this._summary.name;
|
return this._summary.name;
|
||||||
}
|
}
|
||||||
|
@ -55,6 +68,7 @@ export default class Room extends EventEmitter {
|
||||||
storage: this._storage,
|
storage: this._storage,
|
||||||
hsApi: this._hsApi,
|
hsApi: this._hsApi,
|
||||||
fragmentIdComparer: this._fragmentIdComparer,
|
fragmentIdComparer: this._fragmentIdComparer,
|
||||||
|
pendingEvents: this._sendQueue.pendingEvents,
|
||||||
closeCallback: () => this._timeline = null,
|
closeCallback: () => this._timeline = null,
|
||||||
});
|
});
|
||||||
await this._timeline.load();
|
await this._timeline.load();
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import SortedArray from "../../../observable/list/SortedArray.js";
|
import SortedArray from "../../../observable/list/SortedArray.js";
|
||||||
import {NetworkError} from "../../error.js";
|
import {NetworkError} from "../../error.js";
|
||||||
import {StorageError} from "../../storage/common.js";
|
|
||||||
import PendingEvent from "./PendingEvent.js";
|
import PendingEvent from "./PendingEvent.js";
|
||||||
|
|
||||||
function makeTxnId() {
|
function makeTxnId() {
|
||||||
|
@ -10,10 +9,11 @@ function makeTxnId() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class SendQueue {
|
export default class SendQueue {
|
||||||
constructor({roomId, storage, scheduler, pendingEvents}) {
|
constructor({roomId, storage, sendScheduler, pendingEvents}) {
|
||||||
|
pendingEvents = pendingEvents || [];
|
||||||
this._roomId = roomId;
|
this._roomId = roomId;
|
||||||
this._storage = storage;
|
this._storage = storage;
|
||||||
this._scheduler = scheduler;
|
this._sendScheduler = sendScheduler;
|
||||||
this._pendingEvents = new SortedArray((a, b) => a.queueIndex - b.queueIndex);
|
this._pendingEvents = new SortedArray((a, b) => a.queueIndex - b.queueIndex);
|
||||||
this._pendingEvents.setManySorted(pendingEvents.map(data => new PendingEvent(data)));
|
this._pendingEvents.setManySorted(pendingEvents.map(data => new PendingEvent(data)));
|
||||||
this._isSending = false;
|
this._isSending = false;
|
||||||
|
@ -30,7 +30,7 @@ export default class SendQueue {
|
||||||
if (pendingEvent.remoteId) {
|
if (pendingEvent.remoteId) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const response = await this._scheduler.request(hsApi => {
|
const response = await this._sendScheduler.request(hsApi => {
|
||||||
return hsApi.send(
|
return hsApi.send(
|
||||||
pendingEvent.roomId,
|
pendingEvent.roomId,
|
||||||
pendingEvent.eventType,
|
pendingEvent.eventType,
|
||||||
|
@ -50,14 +50,29 @@ export default class SendQueue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removeRemoteEchos(events, txn) {
|
||||||
async receiveRemoteEcho(txnId) {
|
const removed = [];
|
||||||
|
for (const event of events) {
|
||||||
|
const txnId = event.unsigned && event.unsigned.transaction_id;
|
||||||
|
if (txnId) {
|
||||||
const idx = this._pendingEvents.array.findIndex(pe => pe.txnId === txnId);
|
const idx = this._pendingEvents.array.findIndex(pe => pe.txnId === txnId);
|
||||||
if (idx !== 0) {
|
if (idx !== -1) {
|
||||||
const pendingEvent = this._pendingEvents.get(idx);
|
const pendingEvent = this._pendingEvents.get(idx);
|
||||||
|
txn.pendingEvents.remove(pendingEvent.roomId, pendingEvent.queueIndex);
|
||||||
|
removed.push(pendingEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
emitRemovals(pendingEvents) {
|
||||||
|
for (const pendingEvent of pendingEvents) {
|
||||||
|
const idx = this._pendingEvents.array.indexOf(pendingEvent);
|
||||||
|
if (idx !== -1) {
|
||||||
this._amountSent -= 1;
|
this._amountSent -= 1;
|
||||||
this._pendingEvents.remove(idx);
|
this._pendingEvents.remove(idx);
|
||||||
await this._removeEvent(pendingEvent);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,17 +110,6 @@ export default class SendQueue {
|
||||||
await txn.complete();
|
await txn.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
async _removeEvent(pendingEvent) {
|
|
||||||
const txn = await this._storage.readWriteTxn([this._storage.storeNames.pendingEvents]);
|
|
||||||
try {
|
|
||||||
txn.pendingEvents.remove(pendingEvent.roomId, pendingEvent.queueIndex);
|
|
||||||
} catch (err) {
|
|
||||||
txn.abort();
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
await txn.complete();
|
|
||||||
}
|
|
||||||
|
|
||||||
async _createAndStoreEvent(eventType, content) {
|
async _createAndStoreEvent(eventType, content) {
|
||||||
const txn = await this._storage.readWriteTxn([this._storage.storeNames.pendingEvents]);
|
const txn = await this._storage.readWriteTxn([this._storage.storeNames.pendingEvents]);
|
||||||
let pendingEvent;
|
let pendingEvent;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import Room from "./room/room.js";
|
import Room from "./room/room.js";
|
||||||
import { ObservableMap } from "../observable/index.js";
|
import { ObservableMap } from "../observable/index.js";
|
||||||
|
import { SendScheduler, RateLimitingBackoff } from "./SendScheduler.js";
|
||||||
|
|
||||||
export default class Session {
|
export default class Session {
|
||||||
// sessionInfo contains deviceId, userId and homeServer
|
// sessionInfo contains deviceId, userId and homeServer
|
||||||
|
@ -9,6 +10,7 @@ export default class Session {
|
||||||
this._session = null;
|
this._session = null;
|
||||||
this._sessionInfo = sessionInfo;
|
this._sessionInfo = sessionInfo;
|
||||||
this._rooms = new ObservableMap();
|
this._rooms = new ObservableMap();
|
||||||
|
this._sendScheduler = new SendScheduler({hsApi, backoff: new RateLimitingBackoff()});
|
||||||
this._roomUpdateCallback = (room, params) => this._rooms.update(room.id, params);
|
this._roomUpdateCallback = (room, params) => this._rooms.update(room.id, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +21,7 @@ export default class Session {
|
||||||
this._storage.storeNames.roomState,
|
this._storage.storeNames.roomState,
|
||||||
this._storage.storeNames.timelineEvents,
|
this._storage.storeNames.timelineEvents,
|
||||||
this._storage.storeNames.timelineFragments,
|
this._storage.storeNames.timelineFragments,
|
||||||
|
this._storage.storeNames.pendingEvents,
|
||||||
]);
|
]);
|
||||||
// restore session object
|
// restore session object
|
||||||
this._session = await txn.session.get();
|
this._session = await txn.session.get();
|
||||||
|
@ -26,24 +29,40 @@ export default class Session {
|
||||||
this._session = {};
|
this._session = {};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const pendingEventsByRoomId = await this._getPendingEventsByRoom(txn);
|
||||||
// load rooms
|
// load rooms
|
||||||
const rooms = await txn.roomSummary.getAll();
|
const rooms = await txn.roomSummary.getAll();
|
||||||
await Promise.all(rooms.map(summary => {
|
await Promise.all(rooms.map(summary => {
|
||||||
const room = this.createRoom(summary.roomId);
|
const room = this.createRoom(summary.roomId, pendingEventsByRoomId[summary.roomId]);
|
||||||
return room.load(summary, txn);
|
return room.load(summary, txn);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _getPendingEventsByRoom(txn) {
|
||||||
|
const pendingEvents = await txn.pendingEvents.getAll();
|
||||||
|
return pendingEvents.reduce((groups, pe) => {
|
||||||
|
const group = groups.get(pe.roomId);
|
||||||
|
if (group) {
|
||||||
|
group.push(pe);
|
||||||
|
} else {
|
||||||
|
groups.set(pe.roomId, [pe]);
|
||||||
|
}
|
||||||
|
return groups;
|
||||||
|
}, new Map());
|
||||||
|
}
|
||||||
|
|
||||||
get rooms() {
|
get rooms() {
|
||||||
return this._rooms;
|
return this._rooms;
|
||||||
}
|
}
|
||||||
|
|
||||||
createRoom(roomId) {
|
createRoom(roomId, pendingEvents) {
|
||||||
const room = new Room({
|
const room = new Room({
|
||||||
roomId,
|
roomId,
|
||||||
storage: this._storage,
|
storage: this._storage,
|
||||||
emitCollectionChange: this._roomUpdateCallback,
|
emitCollectionChange: this._roomUpdateCallback,
|
||||||
hsApi: this._hsApi,
|
hsApi: this._hsApi,
|
||||||
|
sendScheduler: this._sendScheduler,
|
||||||
|
pendingEvents,
|
||||||
});
|
});
|
||||||
this._rooms.add(roomId, room);
|
this._rooms.add(roomId, room);
|
||||||
return room;
|
return room;
|
||||||
|
|
|
@ -49,7 +49,7 @@ export default class PendingEventStore {
|
||||||
return this._eventStore.put(pendingEvent);
|
return this._eventStore.put(pendingEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
getAllEvents() {
|
getAll() {
|
||||||
return this._eventStore.selectAll();
|
return this._eventStore.selectAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,6 +77,7 @@ export default class Sync extends EventEmitter {
|
||||||
storeNames.roomState,
|
storeNames.roomState,
|
||||||
storeNames.timelineEvents,
|
storeNames.timelineEvents,
|
||||||
storeNames.timelineFragments,
|
storeNames.timelineFragments,
|
||||||
|
storeNames.pendingEvents,
|
||||||
]);
|
]);
|
||||||
const roomChanges = [];
|
const roomChanges = [];
|
||||||
try {
|
try {
|
||||||
|
|
Loading…
Reference in a new issue