Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9d8973bf1f | |||
| 7f95237e02 | |||
| e1415d9829 | |||
| 19a12b3c79 | |||
| bec41e4f94 | |||
| 5f177aeec4 | |||
| b422916452 | |||
| 10378c0e7f | |||
| fba4d5fb0a | |||
| 77101823f5 | |||
| 15bc608368 | |||
| dfc4b34d09 | |||
| ad9daecbd4 | |||
| d29302716d | |||
| 6c7d13f8ce | |||
| e15a2d138c | |||
| 8bc9c19278 | |||
| dd86fade11 | |||
| ba1991aa8f | |||
| f4fd8d9ba6 | |||
| 02be0f659a | |||
| c7be310bdf | |||
| 55d8f56f98 | |||
| ab35fff9e8 | |||
| fdbc7a3112 | |||
| 3c6bd4774d | |||
| a2861c5781 | |||
| eaf3fe16eb | |||
| 963eaf7ec7 | |||
| e6e5b9b748 | |||
| 9ad031c857 | |||
| a0d465cb34 | |||
| 2dcf5227f0 | |||
| 518e92027c | |||
| ebc95667b8 |
+31
-2
@@ -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
|
||||
|
||||
Vendored
+15785
File diff suppressed because it is too large
Load Diff
+6
File diff suppressed because one or more lines are too long
+115
-59
@@ -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
@@ -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);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
@@ -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
@@ -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"
|
||||
*
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user