1
0
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:
Kegan Dougal
2015-06-17 17:42:12 +01:00
parent ee4d66024a
commit 8e6eb35dfe
6 changed files with 144 additions and 5 deletions

View File

@@ -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.

View File

@@ -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 = "";

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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]]);

View File

@@ -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.