You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-26 17:03:12 +03:00
Merge pull request #937 from matrix-org/bwindels/redactions-local-echo
Local echo for redactions
This commit is contained in:
@@ -887,32 +887,6 @@ MatrixBaseApis.prototype.sendStateEvent = function(roomId, eventType, content, s
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} roomId
|
|
||||||
* @param {string} eventId
|
|
||||||
* @param {string} [txnId] transaction id. One will be made up if not
|
|
||||||
* supplied.
|
|
||||||
* @param {module:client.callback} callback Optional.
|
|
||||||
* @return {module:client.Promise} Resolves: TODO
|
|
||||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
|
||||||
*/
|
|
||||||
MatrixBaseApis.prototype.redactEvent = function(
|
|
||||||
roomId, eventId, txnId, callback,
|
|
||||||
) {
|
|
||||||
if (arguments.length === 3) {
|
|
||||||
callback = txnId;
|
|
||||||
}
|
|
||||||
|
|
||||||
const path = utils.encodeUri("/rooms/$roomId/redact/$eventId/$txnId", {
|
|
||||||
$roomId: roomId,
|
|
||||||
$eventId: eventId,
|
|
||||||
$txnId: txnId ? txnId : this.makeTxnId(),
|
|
||||||
});
|
|
||||||
|
|
||||||
return this._http.authedRequest(callback, "PUT", path, undefined, {});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} roomId
|
* @param {string} roomId
|
||||||
* @param {Number} limit
|
* @param {Number} limit
|
||||||
|
|||||||
@@ -1696,6 +1696,21 @@ MatrixClient.prototype.setPowerLevel = function(roomId, userId, powerLevel,
|
|||||||
*/
|
*/
|
||||||
MatrixClient.prototype.sendEvent = function(roomId, eventType, content, txnId,
|
MatrixClient.prototype.sendEvent = function(roomId, eventType, content, txnId,
|
||||||
callback) {
|
callback) {
|
||||||
|
return this._sendCompleteEvent(roomId, {
|
||||||
|
type: eventType,
|
||||||
|
content: content,
|
||||||
|
}, txnId, callback);
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* @param {string} roomId
|
||||||
|
* @param {object} eventObject An object with the partial structure of an event, to which event_id, user_id, room_id and origin_server_ts will be added.
|
||||||
|
* @param {string} txnId the txnId.
|
||||||
|
* @param {module:client.callback} callback Optional.
|
||||||
|
* @return {module:client.Promise} Resolves: TODO
|
||||||
|
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||||
|
*/
|
||||||
|
MatrixClient.prototype._sendCompleteEvent = function(roomId, eventObject, txnId,
|
||||||
|
callback) {
|
||||||
if (utils.isFunction(txnId)) {
|
if (utils.isFunction(txnId)) {
|
||||||
callback = txnId; txnId = undefined;
|
callback = txnId; txnId = undefined;
|
||||||
}
|
}
|
||||||
@@ -1704,20 +1719,20 @@ MatrixClient.prototype.sendEvent = function(roomId, eventType, content, txnId,
|
|||||||
txnId = this.makeTxnId();
|
txnId = this.makeTxnId();
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.log(`sendEvent of type ${eventType} in ${roomId} with txnId ${txnId}`);
|
const localEvent = new MatrixEvent(Object.assign(eventObject, {
|
||||||
|
event_id: "~" + roomId + ":" + txnId,
|
||||||
|
user_id: this.credentials.userId,
|
||||||
|
room_id: roomId,
|
||||||
|
origin_server_ts: new Date().getTime(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const type = localEvent.getType();
|
||||||
|
logger.log(`sendEvent of type ${type} in ${roomId} with txnId ${txnId}`);
|
||||||
|
|
||||||
// we always construct a MatrixEvent when sending because the store and
|
// we always construct a MatrixEvent when sending because the store and
|
||||||
// scheduler use them. We'll extract the params back out if it turns out
|
// scheduler use them. We'll extract the params back out if it turns out
|
||||||
// the client has no scheduler or store.
|
// the client has no scheduler or store.
|
||||||
const room = this.getRoom(roomId);
|
const room = this.getRoom(roomId);
|
||||||
const localEvent = new MatrixEvent({
|
|
||||||
event_id: "~" + roomId + ":" + txnId,
|
|
||||||
user_id: this.credentials.userId,
|
|
||||||
room_id: roomId,
|
|
||||||
type: eventType,
|
|
||||||
origin_server_ts: new Date().getTime(),
|
|
||||||
content: content,
|
|
||||||
});
|
|
||||||
localEvent._txnId = txnId;
|
localEvent._txnId = txnId;
|
||||||
localEvent.setStatus(EventStatus.SENDING);
|
localEvent.setStatus(EventStatus.SENDING);
|
||||||
|
|
||||||
@@ -1871,6 +1886,9 @@ function _sendEventHttpRequest(client, event) {
|
|||||||
pathTemplate = "/rooms/$roomId/state/$eventType/$stateKey";
|
pathTemplate = "/rooms/$roomId/state/$eventType/$stateKey";
|
||||||
}
|
}
|
||||||
path = utils.encodeUri(pathTemplate, pathParams);
|
path = utils.encodeUri(pathTemplate, pathParams);
|
||||||
|
} else if (event.getType() === "m.room.redaction") {
|
||||||
|
const pathTemplate = `/rooms/$roomId/redact/${event.event.redacts}/$txnId`;
|
||||||
|
path = utils.encodeUri(pathTemplate, pathParams);
|
||||||
} else {
|
} else {
|
||||||
path = utils.encodeUri(
|
path = utils.encodeUri(
|
||||||
"/rooms/$roomId/send/$eventType/$txnId", pathParams,
|
"/rooms/$roomId/send/$eventType/$txnId", pathParams,
|
||||||
@@ -1887,6 +1905,23 @@ function _sendEventHttpRequest(client, event) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} roomId
|
||||||
|
* @param {string} eventId
|
||||||
|
* @param {string} [txnId] transaction id. One will be made up if not
|
||||||
|
* supplied.
|
||||||
|
* @param {module:client.callback} callback Optional.
|
||||||
|
* @return {module:client.Promise} Resolves: TODO
|
||||||
|
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||||
|
*/
|
||||||
|
MatrixClient.prototype.redactEvent = function(roomId, eventId, txnId, callback) {
|
||||||
|
return this._sendCompleteEvent(roomId, {
|
||||||
|
type: "m.room.redaction",
|
||||||
|
content: {},
|
||||||
|
redacts: eventId,
|
||||||
|
}, txnId, callback);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} roomId
|
* @param {string} roomId
|
||||||
* @param {Object} content
|
* @param {Object} content
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ module.exports.MatrixEvent = function MatrixEvent(
|
|||||||
this.forwardLooking = true;
|
this.forwardLooking = true;
|
||||||
this._pushActions = null;
|
this._pushActions = null;
|
||||||
this._replacingEvent = null;
|
this._replacingEvent = null;
|
||||||
|
this._locallyRedacted = false;
|
||||||
|
|
||||||
this._clearEvent = {};
|
this._clearEvent = {};
|
||||||
|
|
||||||
@@ -228,6 +229,9 @@ utils.extend(module.exports.MatrixEvent.prototype, {
|
|||||||
* @return {Object} The event content JSON, or an empty object.
|
* @return {Object} The event content JSON, or an empty object.
|
||||||
*/
|
*/
|
||||||
getOriginalContent: function() {
|
getOriginalContent: function() {
|
||||||
|
if (this._locallyRedacted) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
return this._clearEvent.content || this.event.content || {};
|
return this._clearEvent.content || this.event.content || {};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -239,7 +243,9 @@ utils.extend(module.exports.MatrixEvent.prototype, {
|
|||||||
* @return {Object} The event content JSON, or an empty object.
|
* @return {Object} The event content JSON, or an empty object.
|
||||||
*/
|
*/
|
||||||
getContent: function() {
|
getContent: function() {
|
||||||
if (this._replacingEvent) {
|
if (this._locallyRedacted) {
|
||||||
|
return {};
|
||||||
|
} else if (this._replacingEvent) {
|
||||||
return this._replacingEvent.getContent()["m.new_content"] || {};
|
return this._replacingEvent.getContent()["m.new_content"] || {};
|
||||||
} else {
|
} else {
|
||||||
return this.getOriginalContent();
|
return this.getOriginalContent();
|
||||||
@@ -666,6 +672,27 @@ utils.extend(module.exports.MatrixEvent.prototype, {
|
|||||||
return this.event.unsigned || {};
|
return this.event.unsigned || {};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
unmarkLocallyRedacted: function() {
|
||||||
|
const value = this._locallyRedacted;
|
||||||
|
this._locallyRedacted = false;
|
||||||
|
if (this.event.unsigned) {
|
||||||
|
this.event.unsigned.redacted_because = null;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
|
||||||
|
markLocallyRedacted: function(redactionEvent) {
|
||||||
|
if (this._locallyRedacted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.emit("Event.beforeRedaction", this, redactionEvent);
|
||||||
|
this._locallyRedacted = true;
|
||||||
|
if (!this.event.unsigned) {
|
||||||
|
this.event.unsigned = {};
|
||||||
|
}
|
||||||
|
this.event.unsigned.redacted_because = redactionEvent.event;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the content of an event in the same way it would be by the server
|
* Update the content of an event in the same way it would be by the server
|
||||||
* if it were redacted before it was sent to us
|
* if it were redacted before it was sent to us
|
||||||
@@ -679,6 +706,8 @@ utils.extend(module.exports.MatrixEvent.prototype, {
|
|||||||
throw new Error("invalid redaction_event in makeRedacted");
|
throw new Error("invalid redaction_event in makeRedacted");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._locallyRedacted = false;
|
||||||
|
|
||||||
this.emit("Event.beforeRedaction", this, redaction_event);
|
this.emit("Event.beforeRedaction", this, redaction_event);
|
||||||
|
|
||||||
this._replacingEvent = null;
|
this._replacingEvent = null;
|
||||||
|
|||||||
@@ -1138,17 +1138,15 @@ Room.prototype.addPendingEvent = function(event, txnId) {
|
|||||||
// For pending events, add them to the relations collection immediately.
|
// For pending events, add them to the relations collection immediately.
|
||||||
// (The alternate case below already covers this as part of adding to
|
// (The alternate case below already covers this as part of adding to
|
||||||
// the timeline set.)
|
// the timeline set.)
|
||||||
// TODO: We should consider whether this means it would be a better
|
this._aggregateNonLiveRelation(event);
|
||||||
// design to lift the relations handling up to the room instead.
|
}
|
||||||
for (let i = 0; i < this._timelineSets.length; i++) {
|
|
||||||
const timelineSet = this._timelineSets[i];
|
if (event.getType() === "m.room.redaction") {
|
||||||
if (timelineSet.getFilter()) {
|
const redactId = event.event.redacts;
|
||||||
if (this._filter.filterRoomTimeline([event]).length) {
|
const redactedEvent = this.getUnfilteredTimelineSet().findEventById(redactId);
|
||||||
timelineSet.aggregateRelations(event);
|
if (redactedEvent) {
|
||||||
}
|
redactedEvent.markLocallyRedacted(event);
|
||||||
} else {
|
this.emit("Room.redaction", event, this);
|
||||||
timelineSet.aggregateRelations(event);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1168,6 +1166,30 @@ Room.prototype.addPendingEvent = function(event, txnId) {
|
|||||||
|
|
||||||
this.emit("Room.localEchoUpdated", event, this, null, null);
|
this.emit("Room.localEchoUpdated", event, this, null, null);
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* Used to aggregate the local echo for a relation, and also
|
||||||
|
* for re-applying a relation after it's redaction has been cancelled,
|
||||||
|
* as the local echo for the redaction of the relation would have
|
||||||
|
* un-aggregated the relation. Note that this is different from regular messages,
|
||||||
|
* which are just kept detached for their local echo.
|
||||||
|
*
|
||||||
|
* Also note that live events are aggregated in the live EventTimelineSet.
|
||||||
|
* @param {module:models/event.MatrixEvent} event the relation event that needs to be aggregated.
|
||||||
|
*/
|
||||||
|
Room.prototype._aggregateNonLiveRelation = function(event) {
|
||||||
|
// TODO: We should consider whether this means it would be a better
|
||||||
|
// design to lift the relations handling up to the room instead.
|
||||||
|
for (let i = 0; i < this._timelineSets.length; i++) {
|
||||||
|
const timelineSet = this._timelineSets[i];
|
||||||
|
if (timelineSet.getFilter()) {
|
||||||
|
if (this._filter.filterRoomTimeline([event]).length) {
|
||||||
|
timelineSet.aggregateRelations(event);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
timelineSet.aggregateRelations(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deal with the echo of a message we sent.
|
* Deal with the echo of a message we sent.
|
||||||
@@ -1304,12 +1326,13 @@ Room.prototype.updatePendingEvent = function(event, newStatus, newEventId) {
|
|||||||
} else if (newStatus == EventStatus.CANCELLED) {
|
} else if (newStatus == EventStatus.CANCELLED) {
|
||||||
// remove it from the pending event list, or the timeline.
|
// remove it from the pending event list, or the timeline.
|
||||||
if (this._pendingEventList) {
|
if (this._pendingEventList) {
|
||||||
utils.removeElement(
|
const idx = this._pendingEventList.findIndex(ev => ev.getId() === oldEventId);
|
||||||
this._pendingEventList,
|
if (idx !== -1) {
|
||||||
function(ev) {
|
const [removedEvent] = this._pendingEventList.splice(idx, 1);
|
||||||
return ev.getId() == oldEventId;
|
if (removedEvent.getType() === "m.room.redaction") {
|
||||||
}, false,
|
this._revertRedactionLocalEcho(removedEvent);
|
||||||
);
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.removeEvent(oldEventId);
|
this.removeEvent(oldEventId);
|
||||||
}
|
}
|
||||||
@@ -1317,6 +1340,23 @@ Room.prototype.updatePendingEvent = function(event, newStatus, newEventId) {
|
|||||||
this.emit("Room.localEchoUpdated", event, this, oldEventId, oldStatus);
|
this.emit("Room.localEchoUpdated", event, this, oldEventId, oldStatus);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Room.prototype._revertRedactionLocalEcho = function(redactionEvent) {
|
||||||
|
const redactId = redactionEvent.event.redacts;
|
||||||
|
if (!redactId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const redactedEvent = this.getUnfilteredTimelineSet()
|
||||||
|
.findEventById(redactId);
|
||||||
|
if (redactedEvent) {
|
||||||
|
redactedEvent.unmarkLocallyRedacted();
|
||||||
|
// re-render after undoing redaction
|
||||||
|
this.emit("Room.redactionCancelled", redactionEvent, this);
|
||||||
|
// reapply relation now redaction failed
|
||||||
|
if (redactedEvent.isRelation()) {
|
||||||
|
this._aggregateNonLiveRelation(redactedEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add some events to this room. This can include state events, message
|
* Add some events to this room. This can include state events, message
|
||||||
@@ -1395,6 +1435,9 @@ Room.prototype.removeEvent = function(eventId) {
|
|||||||
for (let i = 0; i < this._timelineSets.length; i++) {
|
for (let i = 0; i < this._timelineSets.length; i++) {
|
||||||
const removed = this._timelineSets[i].removeEvent(eventId);
|
const removed = this._timelineSets[i].removeEvent(eventId);
|
||||||
if (removed) {
|
if (removed) {
|
||||||
|
if (removed.getType() === "m.room.redaction") {
|
||||||
|
this._revertRedactionLocalEcho(removed);
|
||||||
|
}
|
||||||
removedAny = true;
|
removedAny = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1818,10 +1861,21 @@ module.exports = Room;
|
|||||||
* event).
|
* event).
|
||||||
*
|
*
|
||||||
* @event module:client~MatrixClient#"Room.redaction"
|
* @event module:client~MatrixClient#"Room.redaction"
|
||||||
* @param {MatrixEvent} event The matrix event which was redacted
|
* @param {MatrixEvent} event The matrix redaction event
|
||||||
* @param {Room} room The room containing the redacted event
|
* @param {Room} room The room containing the redacted event
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fires when an event that was previously redacted isn't anymore.
|
||||||
|
* This happens when the redaction couldn't be sent and
|
||||||
|
* was subsequently cancelled by the user. Redactions have a local echo
|
||||||
|
* which is undone in this scenario.
|
||||||
|
*
|
||||||
|
* @event module:client~MatrixClient#"Room.redactionCancelled"
|
||||||
|
* @param {MatrixEvent} event The matrix redaction event that was cancelled.
|
||||||
|
* @param {Room} room The room containing the unredacted event
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires whenever the name of a room is updated.
|
* Fires whenever the name of a room is updated.
|
||||||
* @event module:client~MatrixClient#"Room.name"
|
* @event module:client~MatrixClient#"Room.name"
|
||||||
|
|||||||
@@ -127,7 +127,9 @@ SyncApi.prototype.createRoom = function(roomId) {
|
|||||||
timelineSupport,
|
timelineSupport,
|
||||||
unstableClientRelationAggregation,
|
unstableClientRelationAggregation,
|
||||||
});
|
});
|
||||||
client.reEmitter.reEmit(room, ["Room.name", "Room.timeline", "Room.redaction",
|
client.reEmitter.reEmit(room, ["Room.name", "Room.timeline",
|
||||||
|
"Room.redaction",
|
||||||
|
"Room.redactionCancelled",
|
||||||
"Room.receipt", "Room.tags",
|
"Room.receipt", "Room.tags",
|
||||||
"Room.timelineReset",
|
"Room.timelineReset",
|
||||||
"Room.localEchoUpdated",
|
"Room.localEchoUpdated",
|
||||||
|
|||||||
Reference in New Issue
Block a user