You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-08-07 23:02:56 +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:
|
New properties:
|
||||||
* `User.events`
|
* `User.events`
|
||||||
* `RoomMember.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;
|
var numMessagesToShow = 20;
|
||||||
|
|
||||||
// Reading from stdin
|
// Reading from stdin
|
||||||
|
var CLEAR_CONSOLE = '\x1B[2J';
|
||||||
var readline = require("readline");
|
var readline = require("readline");
|
||||||
var rl = readline.createInterface({
|
var rl = readline.createInterface({
|
||||||
input: process.stdin,
|
input: process.stdin,
|
||||||
@@ -49,11 +50,14 @@ rl.on('line', function(line) {
|
|||||||
}
|
}
|
||||||
else if (viewingRoom) {
|
else if (viewingRoom) {
|
||||||
matrixClient.sendTextMessage(viewingRoom.roomId, line).done(function() {
|
matrixClient.sendTextMessage(viewingRoom.roomId, line).done(function() {
|
||||||
console.log('\x1B[2J'); // clear console
|
console.log(CLEAR_CONSOLE);
|
||||||
printMessages();
|
printMessages();
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
console.log("Error: %s", err);
|
console.log("Error: %s", err);
|
||||||
});
|
});
|
||||||
|
// print local echo immediately
|
||||||
|
console.log(CLEAR_CONSOLE);
|
||||||
|
printMessages();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// ==== END User input
|
// ==== END User input
|
||||||
@@ -101,7 +105,7 @@ function printMessages() {
|
|||||||
printRoomList();
|
printRoomList();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log('\x1B[2J'); // clear console
|
console.log(CLEAR_CONSOLE);
|
||||||
var mostRecentMessages = viewingRoom.timeline.slice(numMessagesToShow * -1);
|
var mostRecentMessages = viewingRoom.timeline.slice(numMessagesToShow * -1);
|
||||||
for (var i = 0; i < mostRecentMessages.length; i++) {
|
for (var i = 0; i < mostRecentMessages.length; i++) {
|
||||||
printLine(mostRecentMessages[i]);
|
printLine(mostRecentMessages[i]);
|
||||||
@@ -149,6 +153,9 @@ function printLine(event) {
|
|||||||
if (event.getSender() === myUserId) {
|
if (event.getSender() === myUserId) {
|
||||||
name = "Me";
|
name = "Me";
|
||||||
separator = ">>>";
|
separator = ">>>";
|
||||||
|
if (event.status === "sending") {
|
||||||
|
separator = "...";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var body = "";
|
var body = "";
|
||||||
|
|
||||||
|
@@ -7,6 +7,7 @@ var EventEmitter = require("events").EventEmitter;
|
|||||||
|
|
||||||
var httpApi = require("./http-api");
|
var httpApi = require("./http-api");
|
||||||
var MatrixEvent = require("./models/event").MatrixEvent;
|
var MatrixEvent = require("./models/event").MatrixEvent;
|
||||||
|
var EventStatus = require("./models/event").EventStatus;
|
||||||
var Room = require("./models/room");
|
var Room = require("./models/room");
|
||||||
var User = require("./models/user");
|
var User = require("./models/user");
|
||||||
var MatrixInMemoryStore = require("./store/memory").MatrixInMemoryStore;
|
var MatrixInMemoryStore = require("./store/memory").MatrixInMemoryStore;
|
||||||
@@ -233,9 +234,47 @@ MatrixClient.prototype.sendEvent = function(roomId, eventType, content, txnId,
|
|||||||
$eventType: eventType,
|
$eventType: eventType,
|
||||||
$txnId: txnId
|
$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(
|
return this._http.authedRequest(
|
||||||
callback, "PUT", path, undefined, content
|
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).
|
// roomInitialSync at this point to pull in state).
|
||||||
room = createNewRoom(self, roomIds[i]);
|
room = createNewRoom(self, roomIds[i]);
|
||||||
}
|
}
|
||||||
room.addEvents(roomIdToEvents[roomIds[i]]);
|
room.addEvents(roomIdToEvents[roomIds[i]], "replace");
|
||||||
room.recalculate(self.credentials.userId);
|
room.recalculate(self.credentials.userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -44,6 +44,7 @@ module.exports.MatrixEvent = function MatrixEvent(event) {
|
|||||||
this.forwardLooking = true;
|
this.forwardLooking = true;
|
||||||
};
|
};
|
||||||
module.exports.MatrixEvent.prototype = {
|
module.exports.MatrixEvent.prototype = {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the event_id for this event.
|
* Get the event_id for this event.
|
||||||
* @return {string} The event ID, e.g. <code>$143350589368169JsLZx:localhost
|
* @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
|
* events and typing notifications. These events are treated as "live" so
|
||||||
* they will go to the end of the timeline.
|
* they will go to the end of the timeline.
|
||||||
* @param {MatrixEvent[]} events A list of events to add.
|
* @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++) {
|
for (var i = 0; i < events.length; i++) {
|
||||||
if (events[i].getType() === "m.typing") {
|
if (events[i].getType() === "m.typing") {
|
||||||
this.currentState.setTypingEvent(events[i]);
|
this.currentState.setTypingEvent(events[i]);
|
||||||
}
|
}
|
||||||
else {
|
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
|
// TODO: We should have a filter to say "only add state event
|
||||||
// types X Y Z to the timeline".
|
// types X Y Z to the timeline".
|
||||||
this.addEventsToTimeline([events[i]]);
|
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.
|
* Checks if the given thing is a function.
|
||||||
* @param {*} value The thing to check.
|
* @param {*} value The thing to check.
|
||||||
|
Reference in New Issue
Block a user