Compare commits

..

35 Commits

Author SHA1 Message Date
Richard van der Hoff 9d8973bf1f 0.5.0 2016-03-22 17:56:32 +00:00
Richard van der Hoff 7f95237e02 Prepare changelog for v0.5.0 2016-03-22 17:56:05 +00:00
Matthew Hodgson e1415d9829 Merge pull request #117 from matrix-org/matthew/roomlist
get/setRoomVisibility API
2016-03-22 12:30:18 +00:00
Richard van der Hoff 19a12b3c79 Merge pull request #115 from matrix-org/rav/txnid_clashes
Include a counter in generated transaction IDs
2016-03-22 12:20:02 +00:00
Matthew Hodgson bec41e4f94 incorporate review 2016-03-22 10:20:02 +00:00
Matthew Hodgson 5f177aeec4 get/setRoomVisibility API 2016-03-22 00:55:53 +00:00
Matthew Hodgson b422916452 add to existing users if present, to avoid destroying presence data 2016-03-21 18:15:55 +00:00
Matthew Hodgson 10378c0e7f Merge pull request #116 from matrix-org/matthew/fix-invite-picker-info
update store user metadata based on membership events rather than presence
2016-03-21 16:12:42 +00:00
Matthew Hodgson fba4d5fb0a Merge pull request #114 from matrix-org/matthew/stop-peeking
API to stop peeking
2016-03-21 16:12:29 +00:00
Matthew Hodgson 77101823f5 track kicked rooms correctly 2016-03-19 02:18:37 +00:00
Matthew Hodgson 15bc608368 presence no longer returns profile data, so we have to update our store's users based on membership events instead 2016-03-19 01:45:10 +00:00
Richard van der Hoff dfc4b34d09 Include a counter in generated transaction IDs
Fixes a flaky test which sometimes failed due to sending two events in the same
millisecond.
2016-03-18 21:32:15 +00:00
Richard van der Hoff ad9daecbd4 Pass the right options into SyncApi when peeking
When we peek into a room, we create its Room object. We need to make sure it is
created with the same options as we would if it were created via the /sync
calls.

Save the options passed in when startClient is called, and then pass them into
the SyncApi each time we create it.
2016-03-18 20:59:23 +00:00
Matthew Hodgson d29302716d oops 2016-03-18 19:25:22 +00:00
Matthew Hodgson 6c7d13f8ce API to stop peeking 2016-03-18 19:22:34 +00:00
Richard van der Hoff e15a2d138c Merge pull request #112 from matrix-org/rav/cancel_send
Support for cancelling pending events
2016-03-18 16:17:44 +00:00
Richard van der Hoff 8bc9c19278 Merge pull request #111 from matrix-org/rav/pending_event_list
Implement 'pendingEventList'
2016-03-18 16:17:30 +00:00
David Baker dd86fade11 Merge pull request #113 from matrix-org/dbkr/threepid_lookup
Add a method to the js sdk to look up 3pids on the ID server.
2016-03-18 15:54:43 +00:00
David Baker ba1991aa8f Add more docs :) 2016-03-18 15:54:19 +00:00
David Baker f4fd8d9ba6 Add a method to the js sdk to look up 3pids on the ID server. 2016-03-18 15:15:10 +00:00
Richard van der Hoff 02be0f659a Support for cancelling pending events
Implement client.cancelPendingEvent which will cancel queued or not_sent events
2016-03-17 22:15:46 +00:00
Richard van der Hoff c7be310bdf Fix addPendingEvent invocation in unit test 2016-03-17 22:10:40 +00:00
Richard van der Hoff 55d8f56f98 update docs 2016-03-17 17:53:20 +00:00
Richard van der Hoff ab35fff9e8 Implement 'pendingEventList'
The existing 'pendingEventOrdering'=='end' semantics had been substantially
broken by the introduction of timelines and gappy syncs: after a gappy
sync, pending events would get stuck in the old timeline section. (Part of
https://github.com/vector-im/vector-web/issues/1120).
2016-03-17 17:05:23 +00:00
Richard van der Hoff fdbc7a3112 Merge pull request #110 from matrix-org/rav/refactor_remote_echo
Refactor transmitted-messages code
2016-03-17 16:40:47 +00:00
Richard van der Hoff 3c6bd4774d Refactor transmitted-messages code
This is some preparatory work for fixing up the problems with te timeline
ordering of unsent messages
(https://github.com/vector-im/vector-web/issues/1120). The functional changes
here should be minimal (bar an extra `Room.localEchoUpdated` when the local
echo is first added to the timeline).

Give `MatrixClient.sendEvent` its own entry point `Room.addPendingMessage`
instead of pushing it through `addEventsToTimeline`; this considerably
simplifies the implementation of the latter and also means that we can contain
the `_txnId` ming to MatrixClient.

Move the code which deals with a successful `/send` response from
`MatrixClient` into `Room.updatePendingEvent`, since it involves fiddling with
the innards of the Room.

Also adds a new EventStatus 'SENT' for events which have been successfully sent
but whose remote echo we still haven't received.
2016-03-17 14:26:36 +00:00
Richard van der Hoff a2861c5781 Merge pull request #109 from matrix-org/rav/log_sync_error_stack
Log the stack when we get a sync error
2016-03-17 14:24:07 +00:00
Richard van der Hoff eaf3fe16eb sync error: Don't log the exception twice
If we have e.stack, then it will include the description of the exception.
2016-03-17 12:05:01 +00:00
Richard van der Hoff 963eaf7ec7 Log the stack when we get a sync error
If we have the stack for an exception in the /sync loop, we should log it.
2016-03-17 11:54:43 +00:00
Richard van der Hoff e6e5b9b748 release.sh: fix -z option 2016-03-17 01:33:51 +00:00
Richard van der Hoff 9ad031c857 Make changelog file and jsdoc generation switchable 2016-03-17 01:27:48 +00:00
Richard van der Hoff a0d465cb34 Merge master to develop after release 2016-03-17 01:12:39 +00:00
Richard van der Hoff 2dcf5227f0 Merge remote-tracking branch 'origin/master' into develop 2016-03-17 01:12:18 +00:00
Richard van der Hoff 518e92027c Add missing "Changes in" to changelog 2016-03-17 01:07:34 +00:00
Matthew Hodgson ebc95667b8 workaround for unicode regexp matches - https://github.com/vector-im/vector-web/issues/568 2016-03-17 01:02:50 +00:00
15 changed files with 16399 additions and 247 deletions
+31 -2
View File
@@ -1,5 +1,34 @@
[0.4.2](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.4.2) (2016-03-17)
=====================================================================================
Changes in [0.5.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.5.0) (2016-03-22)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.4.2...v0.5.0)
**BREAKING CHANGES**:
* `opts.pendingEventOrdering`==`end` is no longer supported in the arguments to
`MatrixClient.startClient()`. Instead we provide a `detached` option, which
puts pending events into a completely separate list in the Room, accessible
via `Room.getPendingEvents()`.
[\#111](https://github.com/matrix-org/matrix-js-sdk/pull/111)
Other improvements:
* Log the stack when we get a sync error
[\#109](https://github.com/matrix-org/matrix-js-sdk/pull/109)
* Refactor transmitted-messages code
[\#110](https://github.com/matrix-org/matrix-js-sdk/pull/110)
* Add a method to the js sdk to look up 3pids on the ID server.
[\#113](https://github.com/matrix-org/matrix-js-sdk/pull/113)
* Support for cancelling pending events
[\#112](https://github.com/matrix-org/matrix-js-sdk/pull/112)
* API to stop peeking
[\#114](https://github.com/matrix-org/matrix-js-sdk/pull/114)
* update store user metadata based on membership events rather than presence
[\#116](https://github.com/matrix-org/matrix-js-sdk/pull/116)
* Include a counter in generated transaction IDs
[\#115](https://github.com/matrix-org/matrix-js-sdk/pull/115)
* get/setRoomVisibility API
[\#117](https://github.com/matrix-org/matrix-js-sdk/pull/117)
Changes in [0.4.2](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.4.2) (2016-03-17)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.4.1...v0.4.2)
* Try again if a pagination request gives us no new messages
+15785
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+115 -59
View File
@@ -144,7 +144,10 @@ function MatrixClient(opts) {
var self = this;
this.scheduler.setProcessFunction(function(eventToSend) {
var room = self.getRoom(eventToSend.getRoomId());
_updateLocalEchoStatus(room, eventToSend, EventStatus.SENDING);
if (eventToSend.status !== EventStatus.SENDING) {
_updatePendingEventStatus(room, eventToSend,
EventStatus.SENDING);
}
return _sendEventHttpRequest(self, eventToSend);
});
}
@@ -180,7 +183,7 @@ function MatrixClient(opts) {
this._peekSync = null;
this._isGuest = false;
this._ongoingScrollbacks = {};
this._txnCtr = 0;
this.timelineSupport = Boolean(opts.timelineSupport);
}
utils.inherits(MatrixClient, EventEmitter);
@@ -694,7 +697,7 @@ MatrixClient.prototype.joinRoom = function(roomIdOrAlias, opts, callback) {
return self._http.authedRequest(undefined, "POST", path, undefined, data);
}).then(function(res) {
var roomId = res.room_id;
var syncApi = new SyncApi(self);
var syncApi = new SyncApi(self, self._clientOpts);
var room = syncApi.createRoom(roomId);
if (opts.syncRoom) {
// v2 will do this for us
@@ -718,10 +721,32 @@ MatrixClient.prototype.joinRoom = function(roomIdOrAlias, opts, callback) {
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
MatrixClient.prototype.resendEvent = function(event, room) {
_updateLocalEchoStatus(room, event, EventStatus.SENDING);
_updatePendingEventStatus(room, event, EventStatus.SENDING);
return _sendEvent(this, room, event);
};
/**
* Cancel a queued or unsent event.
*
* @param {MatrixEvent} event Event to cancel
* @throws Error if the event is not in QUEUED or NOT_SENT state
*/
MatrixClient.prototype.cancelPendingEvent = function(event) {
if ([EventStatus.QUEUED, EventStatus.NOT_SENT].indexOf(event.status) < 0) {
throw new Error("cannot cancel an event with status " + event.status);
}
// first tell the scheduler to forget about it, if it's queued
if (this.scheduler) {
this.scheduler.removeEventFromQueue(event);
}
// then tell the room about the change of state, which will remove it
// from the room's list of pending events.
var room = this.getRoom(event.getRoomId());
_updatePendingEventStatus(room, event, EventStatus.CANCELLED);
};
/**
* @param {string} roomId
* @param {string} name
@@ -912,7 +937,7 @@ MatrixClient.prototype.sendEvent = function(roomId, eventType, content, txnId,
if (utils.isFunction(txnId)) { callback = txnId; txnId = undefined; }
if (!txnId) {
txnId = "m" + new Date().getTime();
txnId = "m" + new Date().getTime() + "." + (this._txnCtr++);
}
// we always construct a MatrixEvent when sending because the store and
@@ -928,11 +953,11 @@ MatrixClient.prototype.sendEvent = function(roomId, eventType, content, txnId,
content: content
});
localEvent._txnId = txnId;
localEvent.status = EventStatus.SENDING;
// add this event immediately to the local store as 'sending'.
if (room) {
localEvent.status = EventStatus.SENDING;
room.addEventsToTimeline([localEvent]);
room.addPendingEvent(localEvent, txnId);
}
if (eventType === "m.room.message" && this.sessionStore && CRYPTO_ENABLED) {
@@ -1140,11 +1165,6 @@ function _badEncryptedMessage(event, reason) {
}
function _sendEvent(client, room, event, callback) {
// cache the local event ID here because if /sync returns before /send then
// event.getId() will return a REAL event ID which we will then incorrectly
// remove!
var localEventId = event.getId();
var defer = q.defer();
var promise;
// this event may be queued
@@ -1157,7 +1177,7 @@ function _sendEvent(client, room, event, callback) {
if (promise && client.scheduler.getQueueForEvent(event).length > 1) {
// event is processed FIFO so if the length is 2 or more we know
// this event is stuck behind an earlier event.
_updateLocalEchoStatus(room, event, EventStatus.QUEUED);
_updatePendingEventStatus(room, event, EventStatus.QUEUED);
}
}
@@ -1167,42 +1187,13 @@ function _sendEvent(client, room, event, callback) {
promise.done(function(res) { // the request was sent OK
if (room) {
var eventId = res.event_id;
// FIXME: This manipulation of the room should probably be done
// inside the room class, not by the client.
var timeline = room.getTimelineForEvent(eventId);
if (!timeline) {
// we haven't yet received the event from the stream; we
// need to update the fake event with the right event id.
//
// best way to make sure the room timeline structures are updated
// correctly is to remove the event and add it again with the right
// ID.
//
// This will also make us synthesize our own read receipt for the
// sent message.
var oldStatus = event.status;
room.removeEvents([localEventId]);
event.event.event_id = res.event_id;
// TODO: at this point, we're still expecting the remote echo
// to come back and update the server-generated fields for
// us. We should probably set the status to some distinct value
// so that the client app can figure out what is going on.
event.status = null;
room.addEventsToTimeline([event]);
// FIXME: doing this here is a horrible fudge, but this all
// needs unpicking, which will touch the crypto code.
room.emit("Room.localEchoUpdated", event, room, localEventId,
oldStatus);
}
room.updatePendingEvent(event, EventStatus.SENT, res.event_id);
}
_resolve(callback, defer, res);
}, function(err) {
// the request failed to send.
_updateLocalEchoStatus(room, event, EventStatus.NOT_SENT);
_updatePendingEventStatus(room, event, EventStatus.NOT_SENT);
_reject(callback, defer, err);
});
@@ -1210,9 +1201,9 @@ function _sendEvent(client, room, event, callback) {
return defer.promise;
}
function _updateLocalEchoStatus(room, event, newStatus) {
function _updatePendingEventStatus(room, event, newStatus) {
if (room) {
room.updateLocalEchoStatus(event, newStatus);
room.updatePendingEvent(event, newStatus);
} else {
event.status = newStatus;
}
@@ -1945,6 +1936,39 @@ MatrixClient.prototype.resolveRoomAlias = function(roomAlias, callback) {
return this._http.request(callback, "GET", path);
};
/**
* Get the visibility of a room in the current HS's room directory
* @param {string} roomId
* @param {module:client.callback} callback Optional.
* @return {module:client.Promise} Resolves: TODO
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
MatrixClient.prototype.getRoomDirectoryVisibility =
function(roomId, callback) {
var path = utils.encodeUri("/directory/list/room/$roomId", {
$roomId: roomId
});
return this._http.authedRequest(callback, "GET", path);
};
/**
* Set the visbility of a room in the current HS's room directory
* @param {string} roomId
* @param {string} visibility
* @param {module:client.callback} callback Optional.
* @return {module:client.Promise} Resolves: result object
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
MatrixClient.prototype.setRoomDirectoryVisibility =
function(roomId, visibility, callback) {
var path = utils.encodeUri("/directory/list/room/$roomId", {
$roomId: roomId
});
return this._http.authedRequest(
callback, "PUT", path, undefined, { "visibility": visibility }
);
};
/**
* @param {string} roomId
* @param {Number} limit
@@ -2322,10 +2346,20 @@ MatrixClient.prototype.peekInRoom = function(roomId) {
if (this._peekSync) {
this._peekSync.stopPeeking();
}
this._peekSync = new SyncApi(this);
this._peekSync = new SyncApi(this, this._clientOpts);
return this._peekSync.peek(roomId);
};
/**
* Stop any ongoing room peeking.
*/
MatrixClient.prototype.stopPeeking = function() {
if (this._peekSync) {
this._peekSync.stopPeeking();
this._peekSync = null;
}
};
/**
* Set r/w flags for guest access in a room.
* @param {string} roomId The room to configure guest access in.
@@ -2801,7 +2835,7 @@ MatrixClient.prototype.syncLeftRooms = function() {
return this._syncLeftRoomsPromise; // return the ongoing request
}
var self = this;
var syncApi = new SyncApi(this);
var syncApi = new SyncApi(this, this._clientOpts);
this._syncLeftRoomsPromise = syncApi.syncLeftRooms();
// cleanup locks
@@ -2931,11 +2965,14 @@ MatrixClient.prototype.isLoggedIn = function() {
* @param {Boolean=} opts.resolveInvitesToProfiles True to do /profile requests
* on every invite event if the displayname/avatar_url is not known for this user ID.
* Default: false.
* @param {String=} opts.pendingEventOrdering Controls where pending messages appear
* in a room's timeline. If "<b>chronological</b>", messages will appear in the timeline
* when the call to <code>sendEvent</code> was made. If "<b>end</b>", pending messages
* will always appear at the end of the timeline (multiple pending messages will be sorted
* chronologically). Default: "chronological".
*
* @param {String=} opts.pendingEventOrdering Controls where pending messages
* appear in a room's timeline. If "<b>chronological</b>", messages will appear
* in the timeline when the call to <code>sendEvent</code> was made. If
* "<b>detached</b>", pending messages will appear in a separate list,
* accessbile via {@link module:models/room~Room#getPendingEvents}. Default:
* "chronological".
*
* @param {Number=} opts.pollTimeout The number of milliseconds to wait on /events.
* Default: 30000 (30 seconds).
*/
@@ -2952,6 +2989,8 @@ MatrixClient.prototype.startClient = function(opts) {
};
}
this._clientOpts = opts;
if (CRYPTO_ENABLED && this.sessionStore !== null) {
this.uploadKeys(5);
}
@@ -3264,12 +3303,29 @@ MatrixClient.prototype.requestEmailToken = function(email, clientSecret,
return this._http.idServerRequest(
callback, "POST", "/validate/email/requestToken",
params, httpApi.PREFIX_IDENTITY_V1
).then(function(res) {
if (typeof res === "string") {
return JSON.parse(res);
}
return res;
});
);
};
/**
* Looks up the public Matrix ID mapping for a given 3rd party
* identifier from the Identity Server
* @param {string} medium The medium of the threepid, eg. 'email'
* @param {string} address The textual address of the threepid
* @param {module:client.callback} callback Optional.
* @return {module:client.Promise} Resolves: A threepid mapping
* object or the empty object if no mapping
* exists
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
MatrixClient.prototype.lookupThreePid = function(medium, address, callback) {
var params = {
medium: medium,
address: address,
};
return this._http.idServerRequest(
callback, "GET", "/lookup",
params, httpApi.PREFIX_IDENTITY_V1
);
};
/**
+6 -1
View File
@@ -245,7 +245,12 @@ module.exports.MatrixHttpApi.prototype = {
opts,
requestCallback(defer, callback, this.opts.onlyData)
);
return defer.promise;
// ID server does not always take JSON, so we can't use requests' 'json'
// option as we do with the home server, but it does return JSON, so
// parse it manually
return defer.promise.then(function(response) {
return JSON.parse(response);
});
},
/**
+1 -14
View File
@@ -211,10 +211,8 @@ EventTimeline.prototype.setNeighbouringTimeline = function(neighbour, direction)
*
* @param {MatrixEvent} event new event
* @param {boolean} atStart true to insert new event at the start
* @param {boolean} [spliceBeforeLocalEcho = false] insert this event before any
* localecho events at the end of the timeline. Ignored if atStart == true
*/
EventTimeline.prototype.addEvent = function(event, atStart, spliceBeforeLocalEcho) {
EventTimeline.prototype.addEvent = function(event, atStart) {
var stateContext = atStart ? this._startState : this._endState;
setEventMetadata(event, stateContext, atStart);
@@ -243,17 +241,6 @@ EventTimeline.prototype.addEvent = function(event, atStart, spliceBeforeLocalEch
insertIndex = 0;
} else {
insertIndex = this._events.length;
// if this is a real event, we might need to splice it in before any pending
// local echo events.
if (spliceBeforeLocalEcho) {
for (var j = this._events.length - 1; j >= 0; j--) {
if (!this._events[j].status) { // real events don't have a status
insertIndex = j + 1;
break;
}
}
}
}
this._events.splice(insertIndex, 0, event); // insert element
+7 -1
View File
@@ -32,7 +32,13 @@ module.exports.EventStatus = {
/** The event is in the process of being sent. */
SENDING: "sending",
/** The event is in a queue waiting to be sent. */
QUEUED: "queued"
QUEUED: "queued",
/** The event has been sent to the server, but we have not yet received the
* echo. */
SENT: "sent",
/** The event was cancelled before it was successfully sent. */
CANCELLED: "cancelled",
};
/**
+230 -86
View File
@@ -68,11 +68,14 @@ function synthesizeReceipt(userId, event, receiptType) {
* @param {*} opts.storageToken Optional. The token which a data store can use
* to remember the state of the room. What this means is dependent on the store
* implementation.
* @param {String=} opts.pendingEventOrdering Controls where pending messages appear
* in a room's timeline. If "<b>chronological</b>", messages will appear in the timeline
* when the call to <code>sendEvent</code> was made. If "<b>end</b>", pending messages
* will always appear at the end of the timeline (multiple pending messages will be sorted
* chronologically). Default: "chronological".
*
* @param {String=} opts.pendingEventOrdering Controls where pending messages
* appear in a room's timeline. If "<b>chronological</b>", messages will appear
* in the timeline when the call to <code>sendEvent</code> was made. If
* "<b>detached</b>", pending messages will appear in a separate list,
* accessbile via {@link module:models/room~Room#getPendingEvents}. Default:
* "chronological".
*
* @param {boolean} [opts.timelineSupport = false] Set to true to enable improved
* timeline support.
*
@@ -99,10 +102,10 @@ function Room(roomId, opts) {
opts = opts || {};
opts.pendingEventOrdering = opts.pendingEventOrdering || "chronological";
if (["chronological", "end"].indexOf(opts.pendingEventOrdering) === -1) {
if (["chronological", "detached"].indexOf(opts.pendingEventOrdering) === -1) {
throw new Error(
"opts.pendingEventOrdering MUST be either 'chronological' or " +
"'end'. Got: '" + opts.pendingEventOrdering + "'"
"'detached'. Got: '" + opts.pendingEventOrdering + "'"
);
}
@@ -151,9 +154,31 @@ function Room(roomId, opts) {
this._eventIdToTimeline = {};
this._timelineSupport = Boolean(opts.timelineSupport);
if (this._opts.pendingEventOrdering == "detached") {
this._pendingEventList = [];
}
}
utils.inherits(Room, EventEmitter);
/**
* Get the list of pending sent events for this room
*
* @return {module:models/event.MatrixEvent[]} A list of the sent events
* waiting for remote echo.
*
* @throws If <code>opts.pendingEventOrdering</code> was not 'detached'
*/
Room.prototype.getPendingEvents = function() {
if (this._opts.pendingEventOrdering !== "detached") {
throw new Error(
"Cannot call getPendingEventList with pendingEventOrdering == " +
this._opts.pendingEventOrdering);
}
return this._pendingEventList;
};
/**
* Get the live timeline for this room.
*
@@ -369,9 +394,9 @@ Room.prototype.getAvatarUrl = function(baseUrl, width, height, resizeMethod,
*/
Room.prototype.addTimeline = function() {
if (!this._timelineSupport) {
throw Error("timeline support is disabled. Set the 'timelineSupport'" +
" parameter to true when creating MatrixClient to enable" +
" it.");
throw new Error("timeline support is disabled. Set the 'timelineSupport'" +
" parameter to true when creating MatrixClient to enable" +
" it.");
}
var timeline = new EventTimeline(this.roomId);
@@ -549,7 +574,7 @@ Room.prototype.addEventsToTimeline = function(events, toStartOfTimeline,
};
/**
* Check for redactions, and otherwise add event to the given timeline. Assumes
* Add event to the given timeline, and emit Room.timeline. Assumes
* we have already checked we don't know about this event.
*
* Will fire "Room.timeline" for each event added.
@@ -558,18 +583,13 @@ Room.prototype.addEventsToTimeline = function(events, toStartOfTimeline,
* @param {EventTimeline} timeline
* @param {boolean} toStartOfTimeline
*
* @param {boolean} spliceBeforeLocalEcho if true, insert this event before
* any localecho events at the end of the timeline. Ignored if
* toStartOfTimeline == true.
*
* @fires module:client~MatrixClient#event:"Room.timeline"
*
* @private
*/
Room.prototype._addEventToTimeline = function(event, timeline, toStartOfTimeline,
spliceBeforeLocalEcho) {
Room.prototype._addEventToTimeline = function(event, timeline, toStartOfTimeline) {
var eventId = event.getId();
timeline.addEvent(event, toStartOfTimeline, spliceBeforeLocalEcho);
timeline.addEvent(event, toStartOfTimeline);
this._eventIdToTimeline[eventId] = timeline;
var data = {
@@ -589,56 +609,7 @@ Room.prototype._addEventToTimeline = function(event, timeline, toStartOfTimeline
* @private
*/
Room.prototype._addLiveEvents = function(events) {
var addLocalEchoToEnd = this._opts.pendingEventOrdering === "end";
for (var i = 0; i < events.length; i++) {
var isLocalEcho = (
events[i].status === EventStatus.SENDING ||
events[i].status === EventStatus.QUEUED
);
// FIXME: HORRIBLE ASSUMPTION THAT THIS PROP EXISTS
// Exists due to client.js:815 (MatrixClient.sendEvent)
// We should make txnId a first class citizen.
if (events[i]._txnId) {
// this is the outgoing copy of the event (ie, the local echo).
this._txnToEvent[events[i]._txnId] = events[i];
}
else if (events[i].getUnsigned().transaction_id) {
// remote echo of an event we sent earlier
var existingEvent = this._txnToEvent[events[i].getUnsigned().transaction_id];
if (existingEvent) {
var oldEventId = existingEvent.getId();
var oldStatus = existingEvent.status;
// no longer pending
delete this._txnToEvent[events[i].getUnsigned().transaction_id];
// update the timeline map, because the event id has changed
var existingTimeline = this._eventIdToTimeline[oldEventId];
if (existingTimeline) {
delete this._eventIdToTimeline[oldEventId];
this._eventIdToTimeline[events[i].getId()] = existingTimeline;
}
// replace the event source, but preserve the original content
// and type in case it was encrypted (we won't be able to
// decrypt it, even though we sent it.)
var existingSource = existingEvent.event;
existingEvent.event = events[i].event;
existingEvent.event.content = existingSource.content;
existingEvent.event.type = existingSource.type;
// successfully sent.
existingEvent.status = null;
this.emit("Room.localEchoUpdated", existingEvent, this, oldEventId,
oldStatus);
continue;
}
}
if (events[i].getType() === "m.room.redaction") {
var redactId = events[i].event.redacts;
@@ -660,21 +631,24 @@ Room.prototype._addLiveEvents = function(events) {
// this may be needed to trigger an update.
}
var spliceBeforeLocalEcho = !isLocalEcho && addLocalEchoToEnd;
if (events[i].getUnsigned().transaction_id) {
var existingEvent = this._txnToEvent[events[i].getUnsigned().transaction_id];
if (existingEvent) {
// remote echo of an event we sent earlier
this._handleRemoteEcho(events[i], existingEvent);
continue;
}
}
if (!this._eventIdToTimeline[events[i].getId()]) {
// TODO: pass through filter to see if this should be added to the timeline.
this._addEventToTimeline(events[i], this._liveTimeline, false,
spliceBeforeLocalEcho);
this._addEventToTimeline(events[i], this._liveTimeline, false);
}
// synthesize and inject implicit read receipts
// Done after adding the event because otherwise the app would get a read receipt
// pointing to an event that wasn't yet in the timeline
//
// (we don't do this for local echoes, as they have temporary event
// ids, which don't make much sense as RRs).
if (events[i].sender && !isLocalEcho) {
if (events[i].sender) {
this.addReceipt(synthesizeReceipt(
events[i].sender.userId, events[i], "m.read"
), true);
@@ -684,29 +658,199 @@ Room.prototype._addLiveEvents = function(events) {
/**
* Update the status field on a local echo, to reflect its transmission
* Add a pending outgoing event to this room.
*
* <p>The event is added to either the pendingEventList, or the live timeline,
* depending on the setting of opts.pendingEventOrdering.
*
* <p>This is an internal method, intended for use by MatrixClient.
*
* @param {module:models/event.MatrixEvent} event The event to add.
*
* @param {string} txnId Transaction id for this outgoing event
*
* @fires module:client~MatrixClient#event:"Room.localEchoUpdated"
*
* @throws if the event doesn't have status SENDING, or we aren't given a
* unique transaction id.
*/
Room.prototype.addPendingEvent = function(event, txnId) {
if (event.status !== EventStatus.SENDING) {
throw new Error("addPendingEvent called on an event with status " +
event.status);
}
if (this._txnToEvent[txnId]) {
throw new Error("addPendingEvent called on an event with known txnId " +
txnId);
}
// call setEventMetadata to set up event.sender etc
setEventMetadata(
event,
this._liveTimeline.getState(EventTimeline.FORWARDS),
false
);
this._txnToEvent[txnId] = event;
if (this._opts.pendingEventOrdering == "detached") {
this._pendingEventList.push(event);
} else {
this._addEventToTimeline(event, this._liveTimeline, false);
}
this.emit("Room.localEchoUpdated", event, this, null, null);
};
/**
* Deal with the echo of a message we sent.
*
* <p>We move the event to the live timeline if it isn't there already, and
* update it.
*
* @param {module:models/event~MatrixEvent} remoteEvent The event received from
* /sync
* @param {module:models/event~MatrixEvent} localEvent The local echo, which
* should be either in the _pendingEventList or the timeline.
*
* @fires module:client~MatrixClient#event:"Room.localEchoUpdated"
* @private
*/
Room.prototype._handleRemoteEcho = function(remoteEvent, localEvent) {
var oldEventId = localEvent.getId();
var newEventId = remoteEvent.getId();
var oldStatus = localEvent.status;
// no longer pending
delete this._txnToEvent[remoteEvent.transaction_id];
// if it's in the pending list, remove it
if (this._pendingEventList) {
utils.removeElement(
this._pendingEventList,
function(ev) { return ev.getId() == oldEventId; },
false
);
}
// replace the event source, but preserve the original content
// and type in case it was encrypted (we won't be able to
// decrypt it, even though we sent it.)
var existingSource = localEvent.event;
localEvent.event = remoteEvent.event;
localEvent.event.content = existingSource.content;
localEvent.event.type = existingSource.type;
// successfully sent.
localEvent.status = null;
// if it's already in the timeline, update the timeline map. If it's not, add it.
var existingTimeline = this._eventIdToTimeline[oldEventId];
if (existingTimeline) {
delete this._eventIdToTimeline[oldEventId];
this._eventIdToTimeline[newEventId] = existingTimeline;
} else {
this._addEventToTimeline(localEvent, this._liveTimeline, false);
}
this.emit("Room.localEchoUpdated", localEvent, this,
oldEventId, oldStatus);
};
/* a map from current event status to a list of allowed next statuses
*/
var ALLOWED_TRANSITIONS = {};
ALLOWED_TRANSITIONS[EventStatus.SENDING] =
[EventStatus.QUEUED, EventStatus.NOT_SENT, EventStatus.SENT];
ALLOWED_TRANSITIONS[EventStatus.QUEUED] =
[EventStatus.SENDING, EventStatus.CANCELLED];
ALLOWED_TRANSITIONS[EventStatus.SENT] =
[];
ALLOWED_TRANSITIONS[EventStatus.NOT_SENT] =
[EventStatus.SENDING, EventStatus.QUEUED, EventStatus.CANCELLED];
ALLOWED_TRANSITIONS[EventStatus.CANCELLED] =
[];
/**
* Update the status / event id on a pending event, to reflect its transmission
* progress.
*
* <p>This is an internal method.
*
* @param {MatrixEvent} event local echo event
* @param {EventStatus} newStatus status to assign
* @param {string} newEventId new event id to assign. Ignored unless
* newStatus == EventStatus.SENT.
* @fires module:client~MatrixClient#event:"Room.localEchoUpdated"
*/
Room.prototype.updateLocalEchoStatus = function(event, newStatus) {
if (!event.status) {
throw new Error("updateLocalEchoStatus called on an event which is " +
"not a local echo.");
Room.prototype.updatePendingEvent = function(event, newStatus, newEventId) {
// if the message was sent, we expect an event id
if (newStatus == EventStatus.SENT && !newEventId) {
throw new Error("updatePendingEvent called with status=SENT, " +
"but no new event id");
}
if (!this.getTimelineForEvent(event.getId())) {
throw new Error("updateLocalEchoStatus called on an unknown event.");
// SENT races against /sync, so we have to special-case it.
if (newStatus == EventStatus.SENT) {
var timeline = this._eventIdToTimeline[newEventId];
if (timeline) {
// we've already received the event via the event stream.
// nothing more to do here.
return;
}
}
var oldStatus = event.status;
var oldEventId = event.getId();
if (!oldStatus) {
throw new Error("updatePendingEventStatus called on an event which is " +
"not a local echo.");
}
var allowed = ALLOWED_TRANSITIONS[oldStatus];
if (!allowed || allowed.indexOf(newStatus) < 0) {
throw new Error("Invalid EventStatus transition " + oldStatus + "->" +
newStatus);
}
event.status = newStatus;
if (newStatus == EventStatus.SENT) {
// update the event id
event.event.event_id = newEventId;
// if the event was already in the timeline (which will be the case if
// opts.pendingEventOrdering==chronological), we need to update the
// timeline map.
var existingTimeline = this._eventIdToTimeline[oldEventId];
if (existingTimeline) {
delete this._eventIdToTimeline[oldEventId];
this._eventIdToTimeline[newEventId] = existingTimeline;
}
}
else if (newStatus == EventStatus.CANCELLED) {
// remove it from the pending event list, or the timeline.
if (this._pendingEventList) {
utils.removeElement(
this._pendingEventList,
function(ev) { return ev.getId() == oldEventId; },
false
);
}
this.removeEvent(oldEventId);
}
this.emit("Room.localEchoUpdated", event, this, event.getId(), oldStatus);
};
/**
* Add some events to this room. This can include state events, message
* events and typing notifications. These events are treated as "live" so
@@ -1382,14 +1526,14 @@ module.exports = Room;
*
* <p>Once the /send request completes, if the remote echo has not already
* arrived, the event is updated with a new event id and the status is set to
* null. The server-generated fields are of course not updated yet.
* 'SENT'. The server-generated fields are of course not updated yet.
*
* <p>Finally, the /send might fail. In this case, the event's status is set to
* <p>If the /send fails, In this case, the event's status is set to
* 'NOT_SENT'. If it is later resent, the process starts again, setting the
* status to 'SENDING'.
* status to 'SENDING'. Alternatively, the message may be cancelled, which
* removes the event from the room, and sets the status to 'CANCELLED'.
*
* <p>This event is raised to reflect each of the transitions above (except the
* first send attempt).
* <p>This event is raised to reflect each of the transitions above.
*
* @event module:client~MatrixClient#"Room.localEchoUpdated"
*
+4 -2
View File
@@ -160,7 +160,9 @@ function PushProcessor(client) {
var displayName = room.currentState.getMember(client.credentials.userId).name;
var pat = new RegExp("\\b" + escapeRegExp(displayName) + "\\b", 'i');
// N.B. we can't use \b as it chokes on unicode. however \W seems to be okay
// as shorthand for [^0-9A-Za-z_].
var pat = new RegExp("(^|\\W)" + escapeRegExp(displayName) + "(\\W|$)", 'i');
return ev.content.body.search(pat) > -1;
};
@@ -174,7 +176,7 @@ function PushProcessor(client) {
var pat;
if (cond.key == 'content.body') {
pat = '\\b' + globToRegexp(cond.pattern) + '\\b';
pat = '(^|\\W)' + globToRegexp(cond.pattern) + '(\\W|$)';
} else {
pat = '^' + globToRegexp(cond.pattern) + '$';
}
+1 -8
View File
@@ -94,15 +94,8 @@ module.exports.MatrixInMemoryStore.prototype = {
// which would then show up in these lists (!)
return;
}
// We don't clobber any existing entry in the user map which has presence
// so user entries with presence info are preferred. This does mean we will
// clobber room member entries constantly, which is desirable to keep things
// like display names and avatar URLs up-to-date.
if (this.users[member.userId] && this.users[member.userId].events.presence) {
return;
}
var user = new User(member.userId);
var user = this.users[member.userId] || new User(member.userId);
user.setDisplayName(member.name);
var rawUrl = (
member.events.member ? member.events.member.getContent().avatar_url : null
+20 -5
View File
@@ -519,8 +519,9 @@ SyncApi.prototype._sync = function(syncOptions) {
self._processSyncResponse(syncToken, data);
}
catch (e) {
console.error("Caught /sync error:");
console.error(e);
// log the exception with stack if we have it, else fall back
// to the plain description
console.error("Caught /sync error", e.stack || e);
}
// emit synced events
@@ -718,14 +719,28 @@ SyncApi.prototype._processSyncResponse = function(syncToken, data) {
accountDataEvents.forEach(function(e) { client.emit("event", e); });
});
// Handle leaves
// Handle leaves (e.g. kicked rooms)
leaveRooms.forEach(function(leaveObj) {
// Do the bear minimum to register rejected invites / you leaving rooms
var room = leaveObj.room;
var stateEvents =
self._mapSyncEventsFormat(leaveObj.state, room);
var timelineEvents =
self._mapSyncEventsFormat(leaveObj.timeline, room);
room.addEvents(timelineEvents);
var accountDataEvents =
self._mapSyncEventsFormat(leaveObj.account_data);
self._processRoomEvents(room, stateEvents, timelineEvents);
room.addAccountData(accountDataEvents);
room.recalculate(client.credentials.userId);
if (leaveObj.isBrandNewRoom) {
client.store.storeRoom(room);
client.emit("Room", room);
}
stateEvents.forEach(function(e) { client.emit("event", e); });
timelineEvents.forEach(function(e) { client.emit("event", e); });
accountDataEvents.forEach(function(e) { client.emit("event", e); });
});
};
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "matrix-js-sdk",
"version": "0.4.2",
"version": "0.5.0",
"description": "Matrix Client-Server SDK for Javascript",
"main": "index.js",
"scripts": {
+40 -22
View File
@@ -8,26 +8,36 @@
set -e
USAGE="$0 [-x] vX.Y.Z"
USAGE="$0 [-xz] [-c changelog_file] vX.Y.Z"
help() {
cat <<EOF
$USAGE
-x: skip updating the changelog
-c changelog_file: specify name of file containing changelog
-x: skip updating the changelog
-z: skip generating the jsdoc
EOF
}
skip_changelog=
while getopts hx f; do
skip_jsdoc=
changelog_file="CHANGELOG.md"
while getopts hc:xz f; do
case $f in
h)
help
exit 0
;;
c)
changelog_file="$OPTARG"
;;
x)
skip_changelog=1
;;
z)
skip_jsdoc=1
;;
esac
done
shift `expr $OPTIND - 1`
@@ -52,8 +62,6 @@ esac
release="${tag#v}"
rel_branch="release-$tag"
cd `dirname $0`
# we might already be on the release branch, in which case, yay
if [ $(git symbolic-ref --short HEAD) != "$rel_branch" ]; then
echo "Creating release branch"
@@ -62,12 +70,12 @@ fi
if [ -z "$skip_changelog" ]; then
echo "Generating changelog"
update_changelog "$release"
read -p "Edit CHANGELOG.md manually, or press enter to continue " REPLY
update_changelog -f "$changelog_file" "$release"
read -p "Edit $changelog_file manually, or press enter to continue " REPLY
if [ -n "$(git ls-files --modified CHANGELOG.md)" ]; then
if [ -n "$(git ls-files --modified $changelog_file)" ]; then
echo "Committing updated changelog"
git commit "CHANGELOG.md" -m "Prepare changelog for $tag"
git commit "$changelog_file" -m "Prepare changelog for $tag"
fi
fi
@@ -75,19 +83,20 @@ fi
echo "npm version"
npm version "$release"
# generate the docs
echo "generating jsdocs"
npm run gendoc
if [ -z "$skip_jsdoc" ]; then
echo "generating jsdocs"
npm run gendoc
echo "copying jsdocs to gh-pages branch"
git checkout gh-pages
git pull
cp -ar ".jsdoc/matrix-js-sdk/$release" .
perl -i -pe 'BEGIN {$rel=shift} $_ =~ /^<\/ul>/ && print
"<li><a href=\"${rel}/index.html\">Version ${rel}</a></li>\n"' \
$release index.html
git add "$release"
git commit --no-verify -m "Add jsdoc for $release" index.html "$release"
echo "copying jsdocs to gh-pages branch"
git checkout gh-pages
git pull
cp -ar ".jsdoc/matrix-js-sdk/$release" .
perl -i -pe 'BEGIN {$rel=shift} $_ =~ /^<\/ul>/ && print
"<li><a href=\"${rel}/index.html\">Version ${rel}</a></li>\n"' \
$release index.html
git add "$release"
git commit --no-verify -m "Add jsdoc for $release" index.html "$release"
fi
# merge release branch to master
echo "updating master branch"
@@ -96,7 +105,16 @@ git pull
git merge --ff-only "$rel_branch"
# push everything to github
git push origin master "$rel_branch" "$tag" "gh-pages"
git push origin master "$rel_branch" "$tag"
if [ -z "$skip_jsdoc" ]; then
git push origin gh-pages
fi
# publish to npmjs
npm publish
# finally, merge master back onto develop
git checkout develop
git pull
git merge master
git push origin develop
+52 -1
View File
@@ -2,22 +2,30 @@
var sdk = require("../..");
var HttpBackend = require("../mock-request");
var utils = require("../test-utils");
var EventStatus = sdk.EventStatus;
describe("MatrixClient retrying", function() {
var baseUrl = "http://localhost.or.something";
var client, httpBackend;
var scheduler;
var userId = "@alice:localhost";
var accessToken = "aseukfgwef";
var roomId = "!room:here";
var room;
beforeEach(function() {
utils.beforeEach(this);
httpBackend = new HttpBackend();
sdk.request(httpBackend.requestFn);
scheduler = new sdk.MatrixScheduler();
client = sdk.createClient({
baseUrl: baseUrl,
userId: userId,
accessToken: accessToken
accessToken: accessToken,
scheduler: scheduler,
});
room = new sdk.Room(roomId);
client.store.storeRoom(room);
});
afterEach(function() {
@@ -40,6 +48,49 @@ describe("MatrixClient retrying", function() {
});
it("should mark events as EventStatus.CANCELLED when cancelled", function(done) {
// send a couple of events; the second will be queued
var ev1, ev2;
client.sendMessage(roomId, "m1").then(function(ev) {
expect(ev).toEqual(ev1);
});
client.sendMessage(roomId, "m2").then(function(ev) {
expect(ev).toEqual(ev2);
});
// both events should be in the timeline at this point
var tl = room.getLiveTimeline().getEvents();
expect(tl.length).toEqual(2);
ev1 = tl[0];
ev2 = tl[1];
expect(ev1.status).toEqual(EventStatus.SENDING);
expect(ev2.status).toEqual(EventStatus.QUEUED);
// now we can cancel the second and check everything looks sane
client.cancelPendingEvent(ev2);
expect(ev2.status).toEqual(EventStatus.CANCELLED);
expect(tl.length).toEqual(1);
// shouldn't be able to cancel the first message yet
expect(function() { client.cancelPendingEvent(ev1); })
.toThrow();
// fail the first send
httpBackend.when("PUT", "/send/m.room.message/")
.respond(400);
httpBackend.flush().then(function() {
expect(ev1.status).toEqual(EventStatus.NOT_SENT);
expect(tl.length).toEqual(1);
// cancel the first message
client.cancelPendingEvent(ev1);
expect(ev1.status).toEqual(EventStatus.CANCELLED);
expect(tl.length).toEqual(0);
}).catch(utils.failTest).done(done);
});
describe("resending", function() {
xit("should be able to resend a NOT_SENT event", function() {
+100 -45
View File
@@ -333,7 +333,6 @@ describe("Room", function() {
var localEvent = utils.mkMessage({
room: roomId, user: userA, event: true,
});
localEvent._txnId = "TXN_ID";
localEvent.status = EventStatus.SENDING;
var localEventId = localEvent.getId();
@@ -344,22 +343,37 @@ describe("Room", function() {
var remoteEventId = remoteEvent.getId();
var callCount = 0;
room.on("Room.localEchoUpdated", function(event, emitRoom, oldEventId) {
callCount += 1;
expect(event.getId()).toEqual(remoteEventId);
expect(emitRoom).toEqual(room);
expect(oldEventId).toEqual(localEventId);
});
room.on("Room.localEchoUpdated",
function(event, emitRoom, oldEventId, oldStatus) {
switch (callCount) {
case 0:
expect(event.getId()).toEqual(localEventId);
expect(event.status).toEqual(EventStatus.SENDING);
expect(emitRoom).toEqual(room);
expect(oldEventId).toBe(null);
expect(oldStatus).toBe(null);
break;
case 1:
expect(event.getId()).toEqual(remoteEventId);
expect(event.status).toBe(null);
expect(emitRoom).toEqual(room);
expect(oldEventId).toEqual(localEventId);
expect(oldStatus).toBe(EventStatus.SENDING);
break;
}
callCount += 1;
}
);
// first add the local echo to the timeline
room.addEventsToTimeline([localEvent]);
// first add the local echo
room.addPendingEvent(localEvent, "TXN_ID");
expect(room.timeline.length).toEqual(1);
// then the remoteEvent
room.addEventsToTimeline([remoteEvent]);
expect(room.timeline.length).toEqual(1);
expect(callCount).toEqual(1);
expect(callCount).toEqual(2);
});
});
@@ -1129,10 +1143,11 @@ describe("Room", function() {
});
});
describe("pendingEventOrdering", function() {
it("should sort pending events to the end of the timeline if 'end'", function() {
describe("addPendingEvent", function() {
it("should add pending events to the pendingEventList if " +
"pendingEventOrdering == 'detached'", function() {
var room = new Room(roomId, {
pendingEventOrdering: "end"
pendingEventOrdering: "detached"
});
var eventA = utils.mkMessage({
room: roomId, user: userA, msg: "remote 1", event: true
@@ -1144,13 +1159,19 @@ describe("Room", function() {
var eventC = utils.mkMessage({
room: roomId, user: userA, msg: "remote 2", event: true
});
room.addEvents([eventA, eventB, eventC]);
room.addEvents([eventA]);
room.addPendingEvent(eventB, "TXN1");
room.addEvents([eventC]);
expect(room.timeline).toEqual(
[eventA, eventC, eventB]
[eventA, eventC]
);
expect(room.getPendingEvents()).toEqual(
[eventB]
);
});
it("should sort pending events chronologically if 'chronological'", function() {
it("should add pending events to the timeline if " +
"pendingEventOrdering == 'chronological'", function() {
room = new Room(roomId, {
pendingEventOrdering: "chronological"
});
@@ -1164,50 +1185,84 @@ describe("Room", function() {
var eventC = utils.mkMessage({
room: roomId, user: userA, msg: "remote 2", event: true
});
room.addEvents([eventA, eventB, eventC]);
room.addEvents([eventA]);
room.addPendingEvent(eventB, "TXN1");
room.addEvents([eventC]);
expect(room.timeline).toEqual(
[eventA, eventB, eventC]
);
});
});
it("should treat NOT_SENT events as local echo", function() {
describe("updatePendingEvent", function() {
it("should remove cancelled events from the pending list", function() {
var room = new Room(roomId, {
pendingEventOrdering: "end"
pendingEventOrdering: "detached"
});
var eventA = utils.mkMessage({
room: roomId, user: userA, msg: "remote 1", event: true
room: roomId, user: userA, event: true
});
var eventB = utils.mkMessage({
room: roomId, user: userA, msg: "local 1", event: true
});
eventB.status = EventStatus.NOT_SENT;
var eventC = utils.mkMessage({
room: roomId, user: userA, msg: "remote 2", event: true
});
room.addEvents([eventA, eventB, eventC]);
expect(room.timeline).toEqual(
[eventA, eventC, eventB]
eventA.status = EventStatus.SENDING;
var eventId = eventA.getId();
room.addPendingEvent(eventA, "TXN1");
expect(room.getPendingEvents()).toEqual(
[eventA]
);
// the event has to have been failed or queued before it can be
// cancelled
room.updatePendingEvent(eventA, EventStatus.NOT_SENT);
var callCount = 0;
room.on("Room.localEchoUpdated",
function(event, emitRoom, oldEventId, oldStatus) {
expect(event).toEqual(eventA);
expect(event.status).toEqual(EventStatus.CANCELLED);
expect(emitRoom).toEqual(room);
expect(oldEventId).toEqual(eventId);
expect(oldStatus).toEqual(EventStatus.NOT_SENT);
callCount++;
});
room.updatePendingEvent(eventA, EventStatus.CANCELLED);
expect(room.getPendingEvents()).toEqual([]);
expect(callCount).toEqual(1);
});
it("should treat QUEUED events as local echo", function() {
var room = new Room(roomId, {
pendingEventOrdering: "end"
});
it("should remove cancelled events from the timeline", function() {
var room = new Room(roomId);
var eventA = utils.mkMessage({
room: roomId, user: userA, msg: "remote 1", event: true
room: roomId, user: userA, event: true
});
var eventB = utils.mkMessage({
room: roomId, user: userA, msg: "local 1", event: true
});
eventB.status = EventStatus.QUEUED;
var eventC = utils.mkMessage({
room: roomId, user: userA, msg: "remote 2", event: true
});
room.addEvents([eventA, eventB, eventC]);
expect(room.timeline).toEqual(
[eventA, eventC, eventB]
eventA.status = EventStatus.SENDING;
var eventId = eventA.getId();
room.addPendingEvent(eventA, "TXN1");
expect(room.getLiveTimeline().getEvents()).toEqual(
[eventA]
);
// the event has to have been failed or queued before it can be
// cancelled
room.updatePendingEvent(eventA, EventStatus.NOT_SENT);
var callCount = 0;
room.on("Room.localEchoUpdated",
function(event, emitRoom, oldEventId, oldStatus) {
expect(event).toEqual(eventA);
expect(event.status).toEqual(EventStatus.CANCELLED);
expect(emitRoom).toEqual(room);
expect(oldEventId).toEqual(eventId);
expect(oldStatus).toEqual(EventStatus.NOT_SENT);
callCount++;
});
room.updatePendingEvent(eventA, EventStatus.CANCELLED);
expect(room.getLiveTimeline().getEvents()).toEqual([]);
expect(callCount).toEqual(1);
});
});
});