You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-08-09 10:22:46 +03:00
Implement local echo.
Mark events being sent via the status property. Update CHANGELOG.
This commit is contained in:
@@ -8,3 +8,8 @@ Breaking changes:
|
||||
New properties:
|
||||
* `User.events`
|
||||
* `RoomMember.events`
|
||||
|
||||
New features:
|
||||
* Local echo. When you send an event using the SDK it will immediately be
|
||||
added to the timeline with the event.status of `'sending'`. When the event is
|
||||
finally sent, this status will be removed.
|
||||
|
@@ -15,6 +15,7 @@ var viewingRoom = null;
|
||||
var numMessagesToShow = 20;
|
||||
|
||||
// Reading from stdin
|
||||
var CLEAR_CONSOLE = '\x1B[2J';
|
||||
var readline = require("readline");
|
||||
var rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
@@ -49,11 +50,14 @@ rl.on('line', function(line) {
|
||||
}
|
||||
else if (viewingRoom) {
|
||||
matrixClient.sendTextMessage(viewingRoom.roomId, line).done(function() {
|
||||
console.log('\x1B[2J'); // clear console
|
||||
console.log(CLEAR_CONSOLE);
|
||||
printMessages();
|
||||
}, function(err) {
|
||||
console.log("Error: %s", err);
|
||||
});
|
||||
// print local echo immediately
|
||||
console.log(CLEAR_CONSOLE);
|
||||
printMessages();
|
||||
}
|
||||
});
|
||||
// ==== END User input
|
||||
@@ -101,7 +105,7 @@ function printMessages() {
|
||||
printRoomList();
|
||||
return;
|
||||
}
|
||||
console.log('\x1B[2J'); // clear console
|
||||
console.log(CLEAR_CONSOLE);
|
||||
var mostRecentMessages = viewingRoom.timeline.slice(numMessagesToShow * -1);
|
||||
for (var i = 0; i < mostRecentMessages.length; i++) {
|
||||
printLine(mostRecentMessages[i]);
|
||||
@@ -149,6 +153,9 @@ function printLine(event) {
|
||||
if (event.getSender() === myUserId) {
|
||||
name = "Me";
|
||||
separator = ">>>";
|
||||
if (event.status === "sending") {
|
||||
separator = "...";
|
||||
}
|
||||
}
|
||||
var body = "";
|
||||
|
||||
|
@@ -7,6 +7,7 @@ var EventEmitter = require("events").EventEmitter;
|
||||
|
||||
var httpApi = require("./http-api");
|
||||
var MatrixEvent = require("./models/event").MatrixEvent;
|
||||
var EventStatus = require("./models/event").EventStatus;
|
||||
var Room = require("./models/room");
|
||||
var User = require("./models/user");
|
||||
var MatrixInMemoryStore = require("./store/memory").MatrixInMemoryStore;
|
||||
@@ -233,9 +234,47 @@ MatrixClient.prototype.sendEvent = function(roomId, eventType, content, txnId,
|
||||
$eventType: eventType,
|
||||
$txnId: txnId
|
||||
});
|
||||
|
||||
// add this event immediately to the local store as 'sending'.
|
||||
// NB: Don't need to check for this.store since getRoom does.
|
||||
var room = this.getRoom(roomId);
|
||||
var localEvent = null;
|
||||
if (room) {
|
||||
localEvent = new MatrixEvent({
|
||||
event_id: "~" + roomId + ":" + txnId,
|
||||
user_id: this.credentials.userId,
|
||||
room_id: roomId,
|
||||
type: "m.room.message",
|
||||
origin_server_ts: new Date().getTime(),
|
||||
content: content
|
||||
});
|
||||
localEvent.status = EventStatus.SENDING;
|
||||
room.addEventsToTimeline([localEvent]);
|
||||
}
|
||||
return this._http.authedRequest(
|
||||
callback, "PUT", path, undefined, content
|
||||
);
|
||||
).then(function (res) {
|
||||
if (room && localEvent) {
|
||||
var eventId = res.event_id;
|
||||
// try to find an event with this event_id. If we find it, this is
|
||||
// the echo of this event *from the event stream* so we can remove
|
||||
// the fake event we made above. If we don't find it, we're still
|
||||
// waiting on the fake event and so should assign the fake event
|
||||
// with the real event_id for matching later.
|
||||
var matchingEvent = utils.findElement(room.timeline, function(ev) {
|
||||
return ev.getId() === eventId;
|
||||
}, true);
|
||||
if (matchingEvent) {
|
||||
utils.removeElement(room.timeline, function(ev) {
|
||||
return ev.getId() === localEvent.getId();
|
||||
}, true);
|
||||
}
|
||||
else {
|
||||
localEvent.event.event_id = res.event_id;
|
||||
localEvent.status = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -982,7 +1021,7 @@ function _pollForEvents(client) {
|
||||
// roomInitialSync at this point to pull in state).
|
||||
room = createNewRoom(self, roomIds[i]);
|
||||
}
|
||||
room.addEvents(roomIdToEvents[roomIds[i]]);
|
||||
room.addEvents(roomIdToEvents[roomIds[i]], "replace");
|
||||
room.recalculate(self.credentials.userId);
|
||||
}
|
||||
}
|
||||
|
@@ -44,6 +44,7 @@ module.exports.MatrixEvent = function MatrixEvent(event) {
|
||||
this.forwardLooking = true;
|
||||
};
|
||||
module.exports.MatrixEvent.prototype = {
|
||||
|
||||
/**
|
||||
* Get the event_id for this event.
|
||||
* @return {string} The event ID, e.g. <code>$143350589368169JsLZx:localhost
|
||||
|
@@ -104,13 +104,45 @@ Room.prototype.addEventsToTimeline = function(events, toStartOfTimeline) {
|
||||
* events and typing notifications. These events are treated as "live" so
|
||||
* they will go to the end of the timeline.
|
||||
* @param {MatrixEvent[]} events A list of events to add.
|
||||
* @param {string} duplicateStrategy Optional. Applies to events in the
|
||||
* timeline only. If this is not specified, no duplicate suppression is
|
||||
* performed (this improves performance). If this is 'replace' then if a
|
||||
* duplicate is encountered, the event passed to this function will replace the
|
||||
* existing event in the timeline. If this is 'ignore', then the event passed to
|
||||
* this function will be ignored entirely, preserving the existing event in the
|
||||
* timeline. Events are identical based on their event ID <b>only</b>.
|
||||
* @throws If <code>duplicateStrategy</code> is not falsey, 'replace' or 'ignore'.
|
||||
*/
|
||||
Room.prototype.addEvents = function(events) {
|
||||
Room.prototype.addEvents = function(events, duplicateStrategy) {
|
||||
if (duplicateStrategy && ["replace", "ignore"].indexOf(duplicateStrategy) === -1) {
|
||||
throw new Error("duplicateStrategy MUST be either 'replace' or 'ignore'");
|
||||
}
|
||||
for (var i = 0; i < events.length; i++) {
|
||||
if (events[i].getType() === "m.typing") {
|
||||
this.currentState.setTypingEvent(events[i]);
|
||||
}
|
||||
else {
|
||||
if (duplicateStrategy) {
|
||||
// is there a duplicate?
|
||||
var shouldIgnore = false;
|
||||
for (var j = 0; j < this.timeline.length; j++) {
|
||||
if (this.timeline[j].getId() === events[i].getId()) {
|
||||
if (duplicateStrategy === "replace") {
|
||||
this.timeline[j] = events[i];
|
||||
// skip the insert so we don't add this event twice.
|
||||
// Don't break in case we replace multiple events.
|
||||
shouldIgnore = true;
|
||||
}
|
||||
else if (duplicateStrategy === "ignore") {
|
||||
shouldIgnore = true;
|
||||
break; // stop searching, we're skipping the insert
|
||||
}
|
||||
}
|
||||
}
|
||||
if (shouldIgnore) {
|
||||
continue; // skip the insertion of this event.
|
||||
}
|
||||
}
|
||||
// TODO: We should have a filter to say "only add state event
|
||||
// types X Y Z to the timeline".
|
||||
this.addEventsToTimeline([events[i]]);
|
||||
|
55
lib/utils.js
55
lib/utils.js
@@ -111,6 +111,61 @@ module.exports.forEach = function(array, fn) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The findElement() method returns a value in the array, if an element in the array
|
||||
* satisfies (returns true) the provided testing function. Otherwise undefined
|
||||
* is returned.
|
||||
* @param {Array} array The array.
|
||||
* @param {Function} fn Function to execute on each value in the array, with the
|
||||
* function signature <code>fn(element, index, array)</code>
|
||||
* @param {boolean} reverse True to search in reverse order.
|
||||
* @return {*} The first value in the array which returns <code>true</code> for
|
||||
* the given function.
|
||||
*/
|
||||
module.exports.findElement = function(array, fn, reverse) {
|
||||
if (reverse) {
|
||||
for (var i = array.length-1; i >= 0; i--) {
|
||||
if (fn(array[i], i, array)) {
|
||||
return array[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (var i = 0; i < array.length; i++) {
|
||||
if (fn(array[i], i, array)) {
|
||||
return array[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The removeElement() method removes the first element in the array that
|
||||
* satisfies (returns true) the provided testing function.
|
||||
* @param {Array} array The array.
|
||||
* @param {Function} fn Function to execute on each value in the array, with the
|
||||
* function signature <code>fn(element, index, array)</code>. Return true to
|
||||
* remove this element and break.
|
||||
* @param {boolean} reverse True to search in reverse order.
|
||||
*/
|
||||
module.exports.removeElement = function(array, fn, reverse) {
|
||||
if (reverse) {
|
||||
for (var i = array.length-1; i >= 0; i--) {
|
||||
if (fn(array[i], i, array)) {
|
||||
array.splice(i, 1);
|
||||
return; }
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (var i = 0; i < array.length; i++) {
|
||||
if (fn(array[i], i, array)) {
|
||||
array.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the given thing is a function.
|
||||
* @param {*} value The thing to check.
|
||||
|
Reference in New Issue
Block a user