diff --git a/src/client.js b/src/client.js index 74b82a1256e..9ef9395d8a0 100644 --- a/src/client.js +++ b/src/client.js @@ -2,6 +2,7 @@ Copyright 2015, 2016 OpenMarket Ltd Copyright 2017 Vector Creations Ltd Copyright 2018-2019 New Vector Ltd +Copyright 2019 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -2190,7 +2191,12 @@ MatrixClient.prototype.sendReceipt = function(event, receiptType, callback) { * @return {module:client.Promise} Resolves: TODO * @return {module:http-api.MatrixError} Rejects: with an error response. */ -MatrixClient.prototype.sendReadReceipt = function(event, callback) { +MatrixClient.prototype.sendReadReceipt = async function(event, callback) { + const eventId = event.getId(); + const room = this.getRoom(event.getRoomId()); + if (room && room.hasPendingEvent(eventId)) { + throw new Error(`Cannot set read receipt to a pending event (${eventId})`); + } return this.sendReceipt(event, "m.read", callback); }; @@ -2200,20 +2206,25 @@ MatrixClient.prototype.sendReadReceipt = function(event, callback) { * and displayed as a horizontal line in the timeline that is visually distinct to the * position of the user's own read receipt. * @param {string} roomId ID of the room that has been read - * @param {string} eventId ID of the event that has been read + * @param {string} rmEventId ID of the event that has been read * @param {string} rrEvent the event tracked by the read receipt. This is here for * convenience because the RR and the RM are commonly updated at the same time as each * other. The local echo of this receipt will be done if set. Optional. * @return {module:client.Promise} Resolves: the empty object, {}. */ -MatrixClient.prototype.setRoomReadMarkers = function(roomId, eventId, rrEvent) { - const rmEventId = eventId; - let rrEventId; +MatrixClient.prototype.setRoomReadMarkers = async function(roomId, rmEventId, rrEvent) { + const room = this.getRoom(roomId); + if (room && room.hasPendingEvent(rmEventId)) { + throw new Error(`Cannot set read marker to a pending event (${rmEventId})`); + } // Add the optional RR update, do local echo like `sendReceipt` + let rrEventId; if (rrEvent) { rrEventId = rrEvent.getId(); - const room = this.getRoom(roomId); + if (room && room.hasPendingEvent(rrEventId)) { + throw new Error(`Cannot set read receipt to a pending event (${rrEventId})`); + } if (room) { room._addLocalEchoReceipt(this.credentials.userId, rrEvent, "m.read"); } diff --git a/src/models/room.js b/src/models/room.js index 3969ede4cf3..7f5c031a33f 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -1,6 +1,7 @@ /* Copyright 2015, 2016 OpenMarket Ltd Copyright 2018, 2019 New Vector Ltd +Copyright 2019 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -346,13 +347,30 @@ Room.prototype.userMayUpgradeRoom = function(userId) { Room.prototype.getPendingEvents = function() { if (this._opts.pendingEventOrdering !== "detached") { throw new Error( - "Cannot call getPendingEventList with pendingEventOrdering == " + + "Cannot call getPendingEvents with pendingEventOrdering == " + this._opts.pendingEventOrdering); } return this._pendingEventList; }; +/** + * Check whether the pending event list contains a given event by ID. + * + * @param {string} eventId The event ID to check for. + * @return {boolean} + * @throws If opts.pendingEventOrdering was not 'detached' + */ +Room.prototype.hasPendingEvent = function(eventId) { + if (this._opts.pendingEventOrdering !== "detached") { + throw new Error( + "Cannot call hasPendingEvent with pendingEventOrdering == " + + this._opts.pendingEventOrdering); + } + + return this._pendingEventList.some(event => event.getId() === eventId); +}; + /** * Get the live unfiltered timeline for this room. *