Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 09d08a5092 | |||
| 8c7d041628 | |||
| 0421e69f14 | |||
| 8b35ddae0a | |||
| 7243367a64 | |||
| fb388f5d2d | |||
| 0757a1dd1c | |||
| 06486f7ad0 | |||
| a9d8c58ea0 | |||
| beec36d484 | |||
| 37ea7edaa0 | |||
| 2c40932080 | |||
| 3777b3e211 | |||
| 2f7d7308a1 | |||
| 0e606c6fe2 | |||
| 3af35c8209 | |||
| a2aed99f56 | |||
| 523a684d3f | |||
| dc386bab46 | |||
| 69079a2f9a | |||
| df33f7aceb | |||
| d87e5471fa | |||
| 90101c0340 | |||
| 950fce80c8 | |||
| 9135c50b83 | |||
| 83bad6ee0d | |||
| 3404751eb9 | |||
| 0282021e09 | |||
| 526e1d59e9 | |||
| dbc3a9a500 | |||
| cff7c8a59f | |||
| 64b8047f01 | |||
| 11e4760935 | |||
| c21d5634bb | |||
| dc56d7f784 | |||
| 1ff1064295 | |||
| a493a0ddb3 | |||
| 7573171d05 | |||
| 989e7cf78b | |||
| 384c474800 | |||
| 1d2c705e13 | |||
| c469ff4c8d | |||
| c7575f3f16 | |||
| 415251dd70 | |||
| 458cc55dec | |||
| d2adb30ded | |||
| bbe41aa7b4 | |||
| ece9391878 | |||
| 64640287cf | |||
| 57f88b00ba | |||
| e618ad9589 | |||
| 69c4d7b66e | |||
| d7b3b91eec | |||
| 88cc63e2a2 | |||
| cdea96fa0a | |||
| cfc10fa82d |
+41
-1
@@ -1,3 +1,43 @@
|
||||
Changes in [0.5.3](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.5.3) (2016-06-02)
|
||||
================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.5.2...v0.5.3)
|
||||
|
||||
* Add support for the openid interface
|
||||
[\#133](https://github.com/matrix-org/matrix-js-sdk/pull/133)
|
||||
* Bugfix for HTTP upload content when running on node
|
||||
[\#129](https://github.com/matrix-org/matrix-js-sdk/pull/129)
|
||||
|
||||
Changes in [0.5.2](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.5.2) (2016-04-19)
|
||||
================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.5.1...v0.5.2)
|
||||
|
||||
* Track the absolute time that presence events are received, so that the
|
||||
relative lastActiveAgo value is meaningful.
|
||||
[\#128](https://github.com/matrix-org/matrix-js-sdk/pull/128)
|
||||
* Refactor the addition of events to rooms
|
||||
[\#127](https://github.com/matrix-org/matrix-js-sdk/pull/127)
|
||||
* Clean up test shutdown
|
||||
[\#126](https://github.com/matrix-org/matrix-js-sdk/pull/126)
|
||||
* Add methods to get (and set) pushers
|
||||
[\#125](https://github.com/matrix-org/matrix-js-sdk/pull/125)
|
||||
* URL previewing support
|
||||
[\#122](https://github.com/matrix-org/matrix-js-sdk/pull/122)
|
||||
* Avoid paginating forever in private rooms
|
||||
[\#124](https://github.com/matrix-org/matrix-js-sdk/pull/124)
|
||||
* Fix a bug where we recreated sync filters
|
||||
[\#123](https://github.com/matrix-org/matrix-js-sdk/pull/123)
|
||||
* Implement HTTP timeouts in realtime
|
||||
[\#121](https://github.com/matrix-org/matrix-js-sdk/pull/121)
|
||||
|
||||
Changes in [0.5.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.5.1) (2016-03-30)
|
||||
================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.5.0...v0.5.1)
|
||||
|
||||
* Only count joined members for the member count in notifications.
|
||||
[\#119](https://github.com/matrix-org/matrix-js-sdk/pull/119)
|
||||
* Add maySendEvent to match maySendStateEvent
|
||||
[\#118](https://github.com/matrix-org/matrix-js-sdk/pull/118)
|
||||
|
||||
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)
|
||||
@@ -70,7 +110,7 @@ Improvements:
|
||||
* Avoid getting stuck in a pagination loop when the server sends us only
|
||||
messages we've already seen
|
||||
(https://github.com/matrix-org/matrix-js-sdk/pull/96).
|
||||
|
||||
|
||||
New methods:
|
||||
* Add `MatrixClient.setPushRuleActions` to set the actions for a push
|
||||
notification rule (https://github.com/matrix-org/matrix-js-sdk/pull/90)
|
||||
|
||||
Vendored
+15832
File diff suppressed because it is too large
Load Diff
+7
File diff suppressed because one or more lines are too long
Vendored
+16305
File diff suppressed because it is too large
Load Diff
+8
File diff suppressed because one or more lines are too long
Vendored
+16327
File diff suppressed because it is too large
Load Diff
+8
File diff suppressed because one or more lines are too long
Executable
+24
@@ -0,0 +1,24 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# pre-commit: script to run checks on a working copy before commit
|
||||
#
|
||||
# To use, symlink it into .git/hooks:
|
||||
# ln -s ../../git-hooks/pre-commit .git/hooks
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
# create a temp dir
|
||||
tmpdir=`mktemp -d`
|
||||
trap 'rm -rf "$tmpdir"' EXIT
|
||||
|
||||
# get a copy of the index
|
||||
git checkout-index --prefix="$tmpdir/" -a
|
||||
|
||||
# keep node_modules/.bin on the path
|
||||
rootdir=`git rev-parse --show-toplevel`
|
||||
export PATH="$rootdir/node_modules/.bin:$PATH"
|
||||
|
||||
# now run our checks
|
||||
cd "$tmpdir"
|
||||
npm run lint
|
||||
+88
-3
@@ -185,6 +185,7 @@ function MatrixClient(opts) {
|
||||
this._ongoingScrollbacks = {};
|
||||
this._txnCtr = 0;
|
||||
this.timelineSupport = Boolean(opts.timelineSupport);
|
||||
this.urlPreviewCache = {};
|
||||
}
|
||||
utils.inherits(MatrixClient, EventEmitter);
|
||||
|
||||
@@ -907,7 +908,7 @@ MatrixClient.prototype.getStateEvent = function(roomId, eventType, stateKey, cal
|
||||
* @return {module:client.Promise} Resolves: TODO
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
MatrixClient.prototype.sendStateEvent = function(roomId, eventType, content, stateKey,
|
||||
MatrixClient.prototype.sendStateEvent = function(roomId, eventType, content, stateKey,
|
||||
callback) {
|
||||
var pathParams = {
|
||||
$roomId: roomId,
|
||||
@@ -1448,6 +1449,41 @@ MatrixClient.prototype.getCurrentUploads = function() {
|
||||
return this._http.getCurrentUploads();
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a preview of the given URL as of (roughly) the given point in time,
|
||||
* described as an object with OpenGraph keys and associated values.
|
||||
* Attributes may be synthesized where actual OG metadata is lacking.
|
||||
* Caches results to prevent hammering the server.
|
||||
* @param {string} url The URL to get preview data for
|
||||
* @param {Number} ts The preferred point in time that the preview should
|
||||
* describe (ms since epoch). The preview returned will either be the most
|
||||
* recent one preceding this timestamp if available, or failing that the next
|
||||
* most recent available preview.
|
||||
* @param {module:client.callback} callback Optional.
|
||||
* @return {module:client.Promise} Resolves: Object of OG metadata.
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
* May return synthesized attributes if the URL lacked OG meta.
|
||||
*/
|
||||
MatrixClient.prototype.getUrlPreview = function(url, ts, callback) {
|
||||
var key = ts + "_" + url;
|
||||
var og = this.urlPreviewCache[key];
|
||||
if (og) {
|
||||
return q(og);
|
||||
}
|
||||
|
||||
var self = this;
|
||||
return this._http.authedRequestWithPrefix(
|
||||
callback, "GET", "/preview_url", {
|
||||
url: url,
|
||||
ts: ts,
|
||||
}, undefined, httpApi.PREFIX_MEDIA_R0
|
||||
).then(function(response) {
|
||||
// TODO: expire cache occasionally
|
||||
self.urlPreviewCache[key] = response;
|
||||
return response;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} roomId
|
||||
* @param {boolean} isTyping
|
||||
@@ -1693,7 +1729,7 @@ MatrixClient.prototype.kick = function(roomId, userId, reason, callback) {
|
||||
* @return {module:client.Promise} Resolves: TODO
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
function _setMembershipState(client, roomId, userId, membershipValue, reason,
|
||||
function _setMembershipState(client, roomId, userId, membershipValue, reason,
|
||||
callback) {
|
||||
if (utils.isFunction(reason)) { callback = reason; reason = undefined; }
|
||||
|
||||
@@ -1904,6 +1940,38 @@ MatrixClient.prototype.setPresence = function(presence, callback) {
|
||||
);
|
||||
};
|
||||
|
||||
// Pushers
|
||||
// =======
|
||||
|
||||
/**
|
||||
* Gets all pushers registered for the logged-in user
|
||||
*
|
||||
* @param {module:client.callback} callback Optional.
|
||||
* @return {module:client.Promise} Resolves: Array of objects representing pushers
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
MatrixClient.prototype.getPushers = function(callback) {
|
||||
var path = "/pushers";
|
||||
return this._http.authedRequest(
|
||||
callback, "GET", path, undefined, undefined
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a new pusher or updates an existing pusher
|
||||
*
|
||||
* @param {Object} pusher Object representing a pusher
|
||||
* @param {module:client.callback} callback Optional.
|
||||
* @return {module:client.Promise} Resolves: Empty json object on success
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
MatrixClient.prototype.setPusher = function(pusher, callback) {
|
||||
var path = "/pushers/set";
|
||||
return this._http.authedRequest(
|
||||
callback, "POST", path, null, pusher
|
||||
);
|
||||
};
|
||||
|
||||
// Public (non-authed) operations
|
||||
// ==============================
|
||||
|
||||
@@ -2063,7 +2131,7 @@ MatrixClient.prototype.scrollback = function(room, limit, callback) {
|
||||
return self._http.authedRequest(callback, "GET", path, params);
|
||||
}).done(function(res) {
|
||||
var matrixEvents = utils.map(res.chunk, _PojoToMatrixEventMapper(self));
|
||||
room.addEventsToTimeline(matrixEvents, true);
|
||||
room.addEventsToTimeline(matrixEvents, true, room.getLiveTimeline());
|
||||
room.oldState.paginationToken = res.end;
|
||||
if (res.chunk.length === 0) {
|
||||
room.oldState.paginationToken = null;
|
||||
@@ -2908,6 +2976,23 @@ MatrixClient.prototype.getFilter = function(userId, filterId, allowCached) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a bearer token from the Home Server that the user can
|
||||
* present to a third party in order to prove their ownership
|
||||
* of the Matrix account they are logged into.
|
||||
* @return {module:client.Promise} Resolves: Token object
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
MatrixClient.prototype.getOpenIdToken = function() {
|
||||
var path = utils.encodeUri("/user/$userId/openid/request_token", {
|
||||
$userId: this.credentials.userId,
|
||||
});
|
||||
|
||||
return this._http.authedRequest(
|
||||
undefined, "POST", path, undefined, {}
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
// VoIP operations
|
||||
// ===============
|
||||
|
||||
+24
-9
@@ -21,6 +21,11 @@ limitations under the License.
|
||||
var q = require("q");
|
||||
var utils = require("./utils");
|
||||
|
||||
// we use our own implementation of setTimeout, so that if we get suspended in
|
||||
// the middle of a /sync, we cancel the sync as soon as we awake, rather than
|
||||
// waiting for the delay to elapse.
|
||||
var callbacks = require("./realtime-callbacks");
|
||||
|
||||
/*
|
||||
TODO:
|
||||
- CS: complete register function (doing stages)
|
||||
@@ -42,6 +47,11 @@ module.exports.PREFIX_UNSTABLE = "/_matrix/client/unstable";
|
||||
*/
|
||||
module.exports.PREFIX_IDENTITY_V1 = "/_matrix/identity/api/v1";
|
||||
|
||||
/**
|
||||
* URI path for the media repo API
|
||||
*/
|
||||
module.exports.PREFIX_MEDIA_R0 = "/_matrix/media/r0";
|
||||
|
||||
/**
|
||||
* Construct a MatrixHttpApi.
|
||||
* @constructor
|
||||
@@ -125,12 +135,12 @@ module.exports.MatrixHttpApi.prototype = {
|
||||
cb(new Error('Timeout'));
|
||||
};
|
||||
|
||||
xhr.timeout_timer = setTimeout(timeout_fn, 30000);
|
||||
xhr.timeout_timer = callbacks.setTimeout(timeout_fn, 30000);
|
||||
|
||||
xhr.onreadystatechange = function() {
|
||||
switch (xhr.readyState) {
|
||||
case global.XMLHttpRequest.DONE:
|
||||
clearTimeout(xhr.timeout_timer);
|
||||
callbacks.clearTimeout(xhr.timeout_timer);
|
||||
var err;
|
||||
if (!xhr.responseText) {
|
||||
err = new Error('No response body.');
|
||||
@@ -152,10 +162,10 @@ module.exports.MatrixHttpApi.prototype = {
|
||||
}
|
||||
};
|
||||
xhr.upload.addEventListener("progress", function(ev) {
|
||||
clearTimeout(xhr.timeout_timer);
|
||||
callbacks.clearTimeout(xhr.timeout_timer);
|
||||
upload.loaded = ev.loaded;
|
||||
upload.total = ev.total;
|
||||
xhr.timeout_timer = setTimeout(timeout_fn, 30000);
|
||||
xhr.timeout_timer = callbacks.setTimeout(timeout_fn, 30000);
|
||||
defer.notify(ev);
|
||||
});
|
||||
url += "?access_token=" + encodeURIComponent(this.opts.accessToken);
|
||||
@@ -178,9 +188,10 @@ module.exports.MatrixHttpApi.prototype = {
|
||||
upload.request = this.opts.request({
|
||||
uri: url,
|
||||
qs: queryParams,
|
||||
method: "POST"
|
||||
method: "POST",
|
||||
headers: {"Content-Type": file.type},
|
||||
body: file.stream
|
||||
}, requestCallback(defer, callback, this.opts.onlyData));
|
||||
file.stream.pipe(this.opts.request);
|
||||
}
|
||||
|
||||
this.uploads.push(upload);
|
||||
@@ -450,9 +461,13 @@ module.exports.MatrixHttpApi.prototype = {
|
||||
|
||||
var timeoutId;
|
||||
var timedOut = false;
|
||||
var req;
|
||||
if (localTimeoutMs) {
|
||||
timeoutId = setTimeout(function() {
|
||||
timeoutId = callbacks.setTimeout(function() {
|
||||
timedOut = true;
|
||||
if (req && req.abort) {
|
||||
req.abort();
|
||||
}
|
||||
defer.reject(new module.exports.MatrixError({
|
||||
error: "Locally timed out waiting for a response",
|
||||
errcode: "ORG.MATRIX.JSSDK_TIMEOUT",
|
||||
@@ -464,7 +479,7 @@ module.exports.MatrixHttpApi.prototype = {
|
||||
var reqPromise = defer.promise;
|
||||
|
||||
try {
|
||||
var req = this.opts.request(
|
||||
req = this.opts.request(
|
||||
{
|
||||
uri: uri,
|
||||
method: method,
|
||||
@@ -477,7 +492,7 @@ module.exports.MatrixHttpApi.prototype = {
|
||||
},
|
||||
function(err, response, body) {
|
||||
if (localTimeoutMs) {
|
||||
clearTimeout(timeoutId);
|
||||
callbacks.clearTimeout(timeoutId);
|
||||
if (timedOut) {
|
||||
return; // already rejected promise
|
||||
}
|
||||
|
||||
@@ -42,6 +42,8 @@ function EventTimeline(roomId) {
|
||||
|
||||
// this is used by client.js
|
||||
this._paginationRequests = {'b': null, 'f': null};
|
||||
|
||||
this._name = roomId + ":" + new Date().toISOString();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -290,6 +292,15 @@ EventTimeline.prototype.removeEvent = function(eventId) {
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a string to identify this timeline, for debugging
|
||||
*
|
||||
* @return {string} name for this timeline
|
||||
*/
|
||||
EventTimeline.prototype.toString = function() {
|
||||
return this._name;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The EventTimeline class
|
||||
|
||||
@@ -239,6 +239,30 @@ RoomState.prototype.getUserIdsWithDisplayName = function(displayName) {
|
||||
return this._displayNameToUserIds[displayName] || [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Short-form for maySendEvent('m.room.message', userId)
|
||||
* @param {string} userId The user ID of the user to test permission for
|
||||
* @return {boolean} true if the given user ID should be permitted to send
|
||||
* message events into the given room.
|
||||
*/
|
||||
RoomState.prototype.maySendMessage = function(userId) {
|
||||
return this._maySendEventOfType('m.room.message', userId, false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if the given user ID has permission to send a normal
|
||||
* event of type `eventType` into this room.
|
||||
* @param {string} type The type of event to test
|
||||
* @param {string} userId The user ID of the user to test permission for
|
||||
* @return {boolean} true if the given user ID should be permitted to send
|
||||
* the given type of event into this room,
|
||||
* according to the room's state.
|
||||
*/
|
||||
RoomState.prototype.maySendEvent = function(eventType, userId) {
|
||||
return this._maySendEventOfType(eventType, userId, false);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if the given MatrixClient has permission to send a state
|
||||
* event of type `stateEventType` into this room.
|
||||
@@ -265,6 +289,22 @@ RoomState.prototype.mayClientSendStateEvent = function(stateEventType, cli) {
|
||||
* according to the room's state.
|
||||
*/
|
||||
RoomState.prototype.maySendStateEvent = function(stateEventType, userId) {
|
||||
return this._maySendEventOfType(stateEventType, userId, true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if the given user ID has permission to send a normal or state
|
||||
* event of type `eventType` into this room.
|
||||
* @param {string} type The type of event to test
|
||||
* @param {string} userId The user ID of the user to test permission for
|
||||
* @param {boolean} state If true, tests if the user may send a state
|
||||
event of this type. Otherwise tests whether
|
||||
they may send a regular event.
|
||||
* @return {boolean} true if the given user ID should be permitted to send
|
||||
* the given type of event into this room,
|
||||
* according to the room's state.
|
||||
*/
|
||||
RoomState.prototype._maySendEventOfType = function(eventType, userId, state) {
|
||||
var member = this.getMember(userId);
|
||||
if (!member || member.membership == 'leave') { return false; }
|
||||
|
||||
@@ -277,6 +317,7 @@ RoomState.prototype.maySendStateEvent = function(stateEventType, userId) {
|
||||
var user_levels = [];
|
||||
|
||||
var state_default = 0;
|
||||
var events_default = 0;
|
||||
if (power_levels_event) {
|
||||
power_levels = power_levels_event.getContent();
|
||||
events_levels = power_levels.events || {};
|
||||
@@ -289,13 +330,16 @@ RoomState.prototype.maySendStateEvent = function(stateEventType, userId) {
|
||||
} else {
|
||||
state_default = 50;
|
||||
}
|
||||
if (power_levels.events_default !== undefined) {
|
||||
events_default = power_levels.events_default;
|
||||
}
|
||||
}
|
||||
|
||||
var state_event_level = state_default;
|
||||
if (events_levels[stateEventType] !== undefined) {
|
||||
state_event_level = events_levels[stateEventType];
|
||||
var required_level = state ? state_default : events_default;
|
||||
if (events_levels[eventType] !== undefined) {
|
||||
required_level = events_levels[eventType];
|
||||
}
|
||||
return member.powerLevel >= state_event_level;
|
||||
return member.powerLevel >= required_level;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
+123
-90
@@ -26,6 +26,18 @@ var utils = require("../utils");
|
||||
var ContentRepo = require("../content-repo");
|
||||
var EventTimeline = require("./event-timeline");
|
||||
|
||||
|
||||
// var DEBUG = false;
|
||||
var DEBUG = true;
|
||||
|
||||
if (DEBUG) {
|
||||
// using bind means that we get to keep useful line numbers in the console
|
||||
var debuglog = console.log.bind(console);
|
||||
} else {
|
||||
var debuglog = function() {};
|
||||
}
|
||||
|
||||
|
||||
function synthesizeReceipt(userId, event, receiptType) {
|
||||
// console.log("synthesizing receipt for "+event.getId());
|
||||
// This is really ugly because JS has no way to express an object literal
|
||||
@@ -416,8 +428,8 @@ Room.prototype.addTimeline = function() {
|
||||
* (oldest) instead of the end (newest) of the timeline. If true, the oldest
|
||||
* event will be the <b>last</b> element of 'events'.
|
||||
*
|
||||
* @param {module:models/event-timeline~EventTimeline=} timeline timeline to
|
||||
* add events to. If not given, events will be added to the live timeline
|
||||
* @param {module:models/event-timeline~EventTimeline} timeline timeline to
|
||||
* add events to.
|
||||
*
|
||||
* @param {string=} paginationToken token for the next batch of events
|
||||
*
|
||||
@@ -427,13 +439,16 @@ Room.prototype.addTimeline = function() {
|
||||
Room.prototype.addEventsToTimeline = function(events, toStartOfTimeline,
|
||||
timeline, paginationToken) {
|
||||
if (!timeline) {
|
||||
timeline = this._liveTimeline;
|
||||
throw new Error(
|
||||
"'timeline' not specified for Room.addEventsToTimeline"
|
||||
);
|
||||
}
|
||||
|
||||
if (!toStartOfTimeline && timeline == this._liveTimeline) {
|
||||
// special treatment for live events
|
||||
this._addLiveEvents(events);
|
||||
return;
|
||||
throw new Error(
|
||||
"Room.addEventsToTimeline cannot be used for adding events to " +
|
||||
"the live timeline - use Room.addLiveEvents instead"
|
||||
);
|
||||
}
|
||||
|
||||
var direction = toStartOfTimeline ? EventTimeline.BACKWARDS :
|
||||
@@ -529,7 +544,7 @@ Room.prototype.addEventsToTimeline = function(events, toStartOfTimeline,
|
||||
lastEventWasNew = false;
|
||||
|
||||
if (existingTimeline == timeline) {
|
||||
console.log("Event " + eventId + " already in timeline " + timeline);
|
||||
debuglog("Event " + eventId + " already in timeline " + timeline);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -545,10 +560,10 @@ Room.prototype.addEventsToTimeline = function(events, toStartOfTimeline,
|
||||
// that would happen, so I'm going to ignore it for now.
|
||||
//
|
||||
if (existingTimeline == neighbour) {
|
||||
console.log("Event " + eventId + " in neighbouring timeline - " +
|
||||
debuglog("Event " + eventId + " in neighbouring timeline - " +
|
||||
"switching to " + existingTimeline);
|
||||
} else {
|
||||
console.log("Event " + eventId + " already in a different " +
|
||||
debuglog("Event " + eventId + " already in a different " +
|
||||
"timeline " + existingTimeline);
|
||||
}
|
||||
timeline = existingTimeline;
|
||||
@@ -601,58 +616,92 @@ Room.prototype._addEventToTimeline = function(event, timeline, toStartOfTimeline
|
||||
|
||||
|
||||
/**
|
||||
* Add some events to the end of this room's live timeline. Will fire
|
||||
* "Room.timeline" for each event added.
|
||||
* Add an event to the end of this room's live timeline. Will fire
|
||||
* "Room.timeline"..
|
||||
*
|
||||
* @param {MatrixEvent[]} events A list of events to add.
|
||||
* @param {MatrixEvent} event Event to be added
|
||||
* @param {string?} duplicateStrategy 'ignore' or 'replace'
|
||||
* @fires module:client~MatrixClient#event:"Room.timeline"
|
||||
* @private
|
||||
*/
|
||||
Room.prototype._addLiveEvents = function(events) {
|
||||
for (var i = 0; i < events.length; i++) {
|
||||
if (events[i].getType() === "m.room.redaction") {
|
||||
var redactId = events[i].event.redacts;
|
||||
Room.prototype._addLiveEvent = function(event, duplicateStrategy) {
|
||||
if (event.getType() === "m.room.redaction") {
|
||||
var redactId = event.event.redacts;
|
||||
|
||||
// if we know about this event, redact its contents now.
|
||||
var redactedEvent = this.findEventById(redactId);
|
||||
if (redactedEvent) {
|
||||
redactedEvent.makeRedacted(events[i]);
|
||||
this.emit("Room.redaction", events[i], this);
|
||||
// if we know about this event, redact its contents now.
|
||||
var redactedEvent = this.findEventById(redactId);
|
||||
if (redactedEvent) {
|
||||
redactedEvent.makeRedacted(event);
|
||||
this.emit("Room.redaction", event, this);
|
||||
|
||||
// TODO: we stash user displaynames (among other things) in
|
||||
// RoomMember objects which are then attached to other events
|
||||
// (in the sender and target fields). We should get those
|
||||
// RoomMember objects to update themselves when the events that
|
||||
// they are based on are changed.
|
||||
// TODO: we stash user displaynames (among other things) in
|
||||
// RoomMember objects which are then attached to other events
|
||||
// (in the sender and target fields). We should get those
|
||||
// RoomMember objects to update themselves when the events that
|
||||
// they are based on are changed.
|
||||
}
|
||||
|
||||
// NB: We continue to add the redaction event to the timeline so
|
||||
// clients can say "so and so redacted an event" if they wish to. Also
|
||||
// this may be needed to trigger an update.
|
||||
}
|
||||
|
||||
if (event.getUnsigned().transaction_id) {
|
||||
var existingEvent = this._txnToEvent[event.getUnsigned().transaction_id];
|
||||
if (existingEvent) {
|
||||
// remote echo of an event we sent earlier
|
||||
this._handleRemoteEcho(event, existingEvent);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var timeline = this._eventIdToTimeline[event.getId()];
|
||||
if (timeline) {
|
||||
if (duplicateStrategy === "replace") {
|
||||
debuglog("Room._addLiveEvent: replacing duplicate event " +
|
||||
event.getId());
|
||||
var tlEvents = timeline.getEvents();
|
||||
for (var j = 0; j < tlEvents.length; j++) {
|
||||
if (tlEvents[j].getId() === event.getId()) {
|
||||
// still need to set the right metadata on this event
|
||||
setEventMetadata(
|
||||
event,
|
||||
timeline.getState(EventTimeline.FORWARDS),
|
||||
false
|
||||
);
|
||||
|
||||
if (!tlEvents[j].encryptedType) {
|
||||
tlEvents[j] = event;
|
||||
}
|
||||
|
||||
// XXX: we need to fire an event when this happens.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// NB: We continue to add the redaction event to the timeline so
|
||||
// clients can say "so and so redacted an event" if they wish to. Also
|
||||
// this may be needed to trigger an update.
|
||||
} else {
|
||||
debuglog("Room._addLiveEvent: ignoring duplicate event " +
|
||||
event.getId());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
// TODO: pass through filter to see if this should be added to the timeline.
|
||||
this._addEventToTimeline(event, this._liveTimeline, false);
|
||||
|
||||
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);
|
||||
}
|
||||
// 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
|
||||
if (event.sender) {
|
||||
this.addReceipt(synthesizeReceipt(
|
||||
event.sender.userId, event, "m.read"
|
||||
), true);
|
||||
|
||||
// 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
|
||||
if (events[i].sender) {
|
||||
this.addReceipt(synthesizeReceipt(
|
||||
events[i].sender.userId, events[i], "m.read"
|
||||
), true);
|
||||
}
|
||||
// Any live events from a user could be taken as implicit
|
||||
// presence information: evidence that they are currently active.
|
||||
// ...except in a world where we use 'user.currentlyActive' to reduce
|
||||
// presence spam, this isn't very useful - we'll get a transition when
|
||||
// they are no longer currently active anyway. So don't bother to
|
||||
// reset the lastActiveAgo and lastPresenceTs from the RoomState's user.
|
||||
}
|
||||
};
|
||||
|
||||
@@ -855,20 +904,36 @@ Room.prototype.updatePendingEvent = function(event, newStatus, newEventId) {
|
||||
* Add some events to this room. This can include state events, message
|
||||
* events and typing notifications. These events are treated as "live" so
|
||||
* they will go to the end of the timeline.
|
||||
*
|
||||
* @param {MatrixEvent[]} events A list of events to add.
|
||||
*
|
||||
* @param {string} duplicateStrategy Optional. Applies to events in the
|
||||
* timeline only. If this is not specified, no duplicate suppression is
|
||||
* performed (this improves performance). If this is 'replace' then if a
|
||||
* duplicate is encountered, the event passed to this function will replace the
|
||||
* existing event in the timeline. If this is 'ignore', then the event passed to
|
||||
* timeline only. If this is 'replace' then if a duplicate is encountered, the
|
||||
* event passed to this function will replace the existing event in the
|
||||
* timeline. If this is not specified, or is 'ignore', then the event passed to
|
||||
* this function will be ignored entirely, preserving the existing event in the
|
||||
* timeline. Events are identical based on their event ID <b>only</b>.
|
||||
*
|
||||
* @throws If <code>duplicateStrategy</code> is not falsey, 'replace' or 'ignore'.
|
||||
*/
|
||||
Room.prototype.addEvents = function(events, duplicateStrategy) {
|
||||
Room.prototype.addLiveEvents = function(events, duplicateStrategy) {
|
||||
if (duplicateStrategy && ["replace", "ignore"].indexOf(duplicateStrategy) === -1) {
|
||||
throw new Error("duplicateStrategy MUST be either 'replace' or 'ignore'");
|
||||
}
|
||||
|
||||
// sanity check that the live timeline is still live
|
||||
if (this._liveTimeline.getPaginationToken(EventTimeline.FORWARDS)) {
|
||||
throw new Error(
|
||||
"live timeline is no longer live - it has a pagination token (" +
|
||||
this._liveTimeline.getPaginationToken(EventTimeline.FORWARDS) + ")"
|
||||
);
|
||||
}
|
||||
if (this._liveTimeline.getNeighbouringTimeline(EventTimeline.FORWARDS)) {
|
||||
throw new Error(
|
||||
"live timeline is no longer live - it has a neighbouring timeline"
|
||||
);
|
||||
}
|
||||
|
||||
for (var i = 0; i < events.length; i++) {
|
||||
if (events[i].getType() === "m.typing") {
|
||||
this.currentState.setTypingEvent(events[i]);
|
||||
@@ -879,41 +944,9 @@ Room.prototype.addEvents = function(events, duplicateStrategy) {
|
||||
// N.B. account_data is added directly by /sync to avoid
|
||||
// having to maintain an event.isAccountData() here
|
||||
else {
|
||||
var timeline = this._eventIdToTimeline[events[i].getId()];
|
||||
if (timeline && duplicateStrategy) {
|
||||
// is there a duplicate?
|
||||
var shouldIgnore = false;
|
||||
var tlEvents = timeline.getEvents();
|
||||
for (var j = 0; j < tlEvents.length; j++) {
|
||||
if (tlEvents[j].getId() === events[i].getId()) {
|
||||
if (duplicateStrategy === "replace") {
|
||||
// still need to set the right metadata on this event
|
||||
setEventMetadata(
|
||||
events[i],
|
||||
timeline.getState(EventTimeline.FORWARDS),
|
||||
false
|
||||
);
|
||||
|
||||
if (!tlEvents[j].encryptedType) {
|
||||
tlEvents[j] = events[i];
|
||||
}
|
||||
// skip the insert so we don't add this event twice.
|
||||
// Don't break in case we replace multiple events.
|
||||
shouldIgnore = true;
|
||||
}
|
||||
else if (duplicateStrategy === "ignore") {
|
||||
shouldIgnore = true;
|
||||
break; // stop searching, we're skipping the insert
|
||||
}
|
||||
}
|
||||
}
|
||||
if (shouldIgnore) {
|
||||
continue; // skip the insertion of this event.
|
||||
}
|
||||
}
|
||||
// TODO: We should have a filter to say "only add state event
|
||||
// types X Y Z to the timeline".
|
||||
this._addLiveEvents([events[i]]);
|
||||
this._addLiveEvent(events[i], duplicateStrategy);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1444,14 +1477,14 @@ module.exports = Room;
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fires wheneer the live timeline in a room is reset.
|
||||
* Fires whenever the live timeline in a room is reset.
|
||||
*
|
||||
* When we get a 'limited' sync (for example, after a network outage), we reset
|
||||
* the live timeline to be empty before adding the recent events to the new
|
||||
* timeline. This event is fired after the timeline is reset, and before the
|
||||
* new events are added.
|
||||
*
|
||||
* @event module:clinet~MatrixClient#"Room.timelineReset"
|
||||
* @event module:client~MatrixClient#"Room.timelineReset"
|
||||
* @param {Room} room The room whose live timeline was reset.
|
||||
*/
|
||||
|
||||
|
||||
+23
-3
@@ -30,7 +30,12 @@ limitations under the License.
|
||||
* @prop {string} displayName The 'displayname' of the user if known.
|
||||
* @prop {string} avatarUrl The 'avatar_url' of the user if known.
|
||||
* @prop {string} presence The presence enum if known.
|
||||
* @prop {Number} lastActiveAgo The last time the user performed some action in ms.
|
||||
* @prop {Number} lastActiveAgo The time elapsed in ms since the user interacted
|
||||
* proactively with the server, or we saw a message from the user
|
||||
* @prop {Number} lastPresenceTs Timestamp (ms since the epoch) for when we last
|
||||
* received presence data for this user. We can subtract
|
||||
* lastActiveAgo from this to approximate an absolute value for
|
||||
* when a user was last active.
|
||||
* @prop {Boolean} currentlyActive Whether we should consider lastActiveAgo to be
|
||||
* an approximation and that the user should be seen as active 'now'
|
||||
* @prop {Object} events The events describing this user.
|
||||
@@ -42,6 +47,7 @@ function User(userId) {
|
||||
this.displayName = userId;
|
||||
this.avatarUrl = null;
|
||||
this.lastActiveAgo = 0;
|
||||
this.lastPresenceTs = 0;
|
||||
this.currentlyActive = false;
|
||||
this.events = {
|
||||
presence: null,
|
||||
@@ -79,9 +85,14 @@ User.prototype.setPresenceEvent = function(event) {
|
||||
}
|
||||
|
||||
this.presence = event.getContent().presence;
|
||||
this.displayName = event.getContent().displayname;
|
||||
this.avatarUrl = event.getContent().avatar_url;
|
||||
if (event.getContent().displayname) {
|
||||
this.displayName = event.getContent().displayname;
|
||||
}
|
||||
if (event.getContent().avatarUrl) {
|
||||
this.avatarUrl = event.getContent().avatar_url;
|
||||
}
|
||||
this.lastActiveAgo = event.getContent().last_active_ago;
|
||||
this.lastPresenceTs = Date.now();
|
||||
this.currentlyActive = event.getContent().currently_active;
|
||||
|
||||
if (eventsToFire.length > 0) {
|
||||
@@ -136,6 +147,15 @@ User.prototype.getLastModifiedTime = function() {
|
||||
return this._modified;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the absolute timestamp when this User was last known active on the server.
|
||||
* It is *NOT* accurate if this.currentlyActive is true.
|
||||
* @return {number} The timestamp
|
||||
*/
|
||||
User.prototype.getLastActiveTs = function() {
|
||||
return this.lastPresenceTs - this.lastActiveAgo;
|
||||
};
|
||||
|
||||
/**
|
||||
* The User class.
|
||||
*/
|
||||
|
||||
@@ -125,7 +125,9 @@ function PushProcessor(client) {
|
||||
var room = client.getRoom(ev.room_id);
|
||||
if (!room || !room.currentState || !room.currentState.members) { return false; }
|
||||
|
||||
var memberCount = Object.keys(room.currentState.members).length;
|
||||
var memberCount = Object.keys(room.currentState.members).filter(function(m) {
|
||||
return room.currentState.members[m].membership == 'join';
|
||||
}).length;
|
||||
|
||||
var m = cond.is.match(/^([=<>]*)([0-9]*)$/);
|
||||
if (!m) { return false; }
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/* A re-implementation of the javascript callback functions (setTimeout,
|
||||
* clearTimeout; setInterval and clearInterval are not yet implemented) which
|
||||
* try to improve handling of large clock jumps (as seen when
|
||||
* suspending/resuming the system).
|
||||
*
|
||||
* In particular, if a timeout would have fired while the system was suspended,
|
||||
* it will instead fire as soon as possible after resume.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
// we schedule a callback at least this often, to check if we've missed out on
|
||||
// some wall-clock time due to being suspended.
|
||||
var TIMER_CHECK_PERIOD_MS = 1000;
|
||||
|
||||
// counter, for making up ids to return from setTimeout
|
||||
var _count = 0;
|
||||
|
||||
// the key for our callback with the real global.setTimeout
|
||||
var _realCallbackKey;
|
||||
|
||||
// a sorted list of the callbacks to be run.
|
||||
// each is an object with keys [runAt, func, params, key].
|
||||
var _callbackList = [];
|
||||
|
||||
// var debuglog = console.log.bind(console);
|
||||
var debuglog = function() {};
|
||||
|
||||
/**
|
||||
* Replace the function used by this module to get the current time.
|
||||
*
|
||||
* Intended for use by the unit tests.
|
||||
*
|
||||
* @param {function} f function which should return a millisecond counter
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
module.exports.setNow = function(f) {
|
||||
_now = f || Date.now;
|
||||
};
|
||||
var _now = Date.now;
|
||||
|
||||
/**
|
||||
* reimplementation of window.setTimeout, which will call the callback if
|
||||
* the wallclock time goes past the deadline.
|
||||
*
|
||||
* @param {function} func callback to be called after a delay
|
||||
* @param {Number} delayMs number of milliseconds to delay by
|
||||
*
|
||||
* @return {Number} an identifier for this callback, which may be passed into
|
||||
* clearTimeout later.
|
||||
*/
|
||||
module.exports.setTimeout = function(func, delayMs) {
|
||||
delayMs = delayMs || 0;
|
||||
if (delayMs < 0) {
|
||||
delayMs = 0;
|
||||
}
|
||||
|
||||
var params = Array.prototype.slice.call(arguments, 2);
|
||||
var runAt = _now() + delayMs;
|
||||
var key = _count++;
|
||||
debuglog("setTimeout: scheduling cb", key, "at", runAt,
|
||||
"(delay", delayMs, ")");
|
||||
var data = {
|
||||
runAt: runAt,
|
||||
func: func,
|
||||
params: params,
|
||||
key: key,
|
||||
};
|
||||
|
||||
// figure out where it goes in the list
|
||||
var idx = binarySearch(
|
||||
_callbackList, function(el) {
|
||||
return el.runAt - runAt;
|
||||
}
|
||||
);
|
||||
|
||||
_callbackList.splice(idx, 0, data);
|
||||
_scheduleRealCallback();
|
||||
|
||||
return key;
|
||||
};
|
||||
|
||||
/**
|
||||
* reimplementation of window.clearTimeout, which mirrors setTimeout
|
||||
*
|
||||
* @param {Number} key result from an earlier setTimeout call
|
||||
*/
|
||||
module.exports.clearTimeout = function(key) {
|
||||
if (_callbackList.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// remove the element from the list
|
||||
var i;
|
||||
for (i = 0; i < _callbackList.length; i++) {
|
||||
var cb = _callbackList[i];
|
||||
if (cb.key == key) {
|
||||
_callbackList.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// iff it was the first one in the list, reschedule our callback.
|
||||
if (i === 0) {
|
||||
_scheduleRealCallback();
|
||||
}
|
||||
};
|
||||
|
||||
// use the real global.setTimeout to schedule a callback to _runCallbacks.
|
||||
function _scheduleRealCallback() {
|
||||
if (_realCallbackKey) {
|
||||
global.clearTimeout(_realCallbackKey);
|
||||
}
|
||||
|
||||
var first = _callbackList[0];
|
||||
|
||||
if (!first) {
|
||||
debuglog("_scheduleRealCallback: no more callbacks, not rescheduling");
|
||||
return;
|
||||
}
|
||||
|
||||
var now = _now();
|
||||
var delayMs = Math.min(first.runAt - now, TIMER_CHECK_PERIOD_MS);
|
||||
|
||||
debuglog("_scheduleRealCallback: now:", now, "delay:", delayMs);
|
||||
_realCallbackKey = global.setTimeout(_runCallbacks, delayMs);
|
||||
}
|
||||
|
||||
function _runCallbacks() {
|
||||
var cb;
|
||||
var now = _now();
|
||||
debuglog("_runCallbacks: now:", now);
|
||||
|
||||
// get the list of things to call
|
||||
var callbacksToRun = [];
|
||||
while (true) {
|
||||
var first = _callbackList[0];
|
||||
if (!first || first.runAt > now) {
|
||||
break;
|
||||
}
|
||||
cb = _callbackList.shift();
|
||||
debuglog("_runCallbacks: popping", cb.key);
|
||||
callbacksToRun.push(cb);
|
||||
}
|
||||
|
||||
// reschedule the real callback before running our functions, to
|
||||
// keep the codepaths the same whether or not our functions
|
||||
// register their own setTimeouts.
|
||||
_scheduleRealCallback();
|
||||
|
||||
for (var i = 0; i < callbacksToRun.length; i++) {
|
||||
cb = callbacksToRun[i];
|
||||
try {
|
||||
cb.func.apply(null, cb.params);
|
||||
} catch (e) {
|
||||
console.error("Uncaught exception in callback function",
|
||||
e.stack || e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* search in a sorted array.
|
||||
*
|
||||
* returns the index of the last element for which func returns
|
||||
* greater than zero, or array.length if no such element exists.
|
||||
*/
|
||||
function binarySearch(array, func) {
|
||||
// min is inclusive, max exclusive.
|
||||
var min = 0,
|
||||
max = array.length;
|
||||
|
||||
while (min < max) {
|
||||
var mid = (min + max) >> 1;
|
||||
var res = func(array[mid]);
|
||||
if (res > 0) {
|
||||
// the element at 'mid' is too big; set it as the new max.
|
||||
max = mid;
|
||||
} else {
|
||||
// the element at 'mid' is too small. 'min' is inclusive, so +1.
|
||||
min = mid + 1;
|
||||
}
|
||||
}
|
||||
// presumably, min==max now.
|
||||
return min;
|
||||
}
|
||||
+6
-5
@@ -96,11 +96,12 @@ module.exports.MatrixInMemoryStore.prototype = {
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
user.setAvatarUrl(rawUrl);
|
||||
if (member.name) {
|
||||
user.setDisplayName(member.name);
|
||||
}
|
||||
if (member.events.member && member.events.member.getContent().avatar_url) {
|
||||
user.setAvatarUrl(member.events.member.getContent().avatar_url);
|
||||
}
|
||||
this.users[user.userId] = user;
|
||||
},
|
||||
|
||||
|
||||
@@ -373,7 +373,7 @@ WebStorageStore.prototype.scrollback = function(room, limit) {
|
||||
);
|
||||
room.addEventsToTimeline(utils.map(scrollback, function(e) {
|
||||
return new MatrixEvent(e);
|
||||
}), true);
|
||||
}), true, room.getLiveTimeline());
|
||||
|
||||
this._tokens[room.storageToken] = {
|
||||
earliestIndex: earliestIndex
|
||||
@@ -594,7 +594,7 @@ function loadRoom(store, roomId, numEvents, tokenArray) {
|
||||
index--;
|
||||
}
|
||||
// add events backwards to diverge old state correctly.
|
||||
room.addEventsToTimeline(recentEvents.reverse(), true);
|
||||
room.addEventsToTimeline(recentEvents.reverse(), true, room.getLiveTimeline());
|
||||
room.oldState.paginationToken = currentStateMap.pagination_token;
|
||||
// set the token data to let us know which index this room instance is at
|
||||
// for scrollback.
|
||||
|
||||
+27
-11
@@ -262,7 +262,8 @@ SyncApi.prototype.peek = function(roomId) {
|
||||
// will overwrite the pagination token, so make sure it overwrites
|
||||
// it with the right thing.
|
||||
peekRoom.addEventsToTimeline(messages.reverse(), true,
|
||||
undefined, response.messages.start);
|
||||
peekRoom.getLiveTimeline(),
|
||||
response.messages.start);
|
||||
|
||||
client.store.storeRoom(peekRoom);
|
||||
client.emit("Room", peekRoom);
|
||||
@@ -328,7 +329,7 @@ SyncApi.prototype._peekPoll = function(roomId, token) {
|
||||
return e.room_id === roomId;
|
||||
}).map(self.client.getEventMapper());
|
||||
var room = self.client.getRoom(roomId);
|
||||
room.addEvents(events);
|
||||
room.addLiveEvents(events);
|
||||
self._peekPoll(roomId, res.end);
|
||||
}, function(err) {
|
||||
console.error("[%s] Peek poll failed: %s", roomId, err);
|
||||
@@ -351,7 +352,9 @@ SyncApi.prototype.getSyncState = function() {
|
||||
* Main entry point
|
||||
*/
|
||||
SyncApi.prototype.sync = function() {
|
||||
debuglog("SyncApi.sync");
|
||||
debuglog("SyncApi.sync: starting with sync token " +
|
||||
this.client.store.getSyncToken());
|
||||
|
||||
var client = this.client;
|
||||
var self = this;
|
||||
|
||||
@@ -417,6 +420,10 @@ SyncApi.prototype.stop = function() {
|
||||
}
|
||||
this._running = false;
|
||||
if (this._currentSyncRequest) { this._currentSyncRequest.abort(); }
|
||||
if (this._keepAliveTimer) {
|
||||
clearTimeout(this._keepAliveTimer);
|
||||
this._keepAliveTimer = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -459,9 +466,17 @@ SyncApi.prototype._sync = function(syncOptions) {
|
||||
var qps = {
|
||||
filter: filterId,
|
||||
timeout: this.opts.pollTimeout,
|
||||
since: syncToken || undefined // do not send 'null'
|
||||
};
|
||||
|
||||
if (syncToken) {
|
||||
qps.since = syncToken;
|
||||
} else {
|
||||
// use a cachebuster for initialsyncs, to make sure that
|
||||
// we don't get a stale sync
|
||||
// (https://github.com/vector-im/vector-web/issues/1354)
|
||||
qps._cacheBuster = Date.now();
|
||||
}
|
||||
|
||||
if (self._syncConnectionLost) {
|
||||
// we think the connection is dead. If it comes back up, we won't know
|
||||
// about it till /sync returns. If the timeout= is high, this could
|
||||
@@ -703,7 +718,7 @@ SyncApi.prototype._processSyncResponse = function(syncToken, data) {
|
||||
// XXX: should we be adding ephemeralEvents to the timeline?
|
||||
// It feels like that for symmetry with room.addAccountData()
|
||||
// there should be a room.addEphemeralEvents() or similar.
|
||||
room.addEvents(ephemeralEvents);
|
||||
room.addLiveEvents(ephemeralEvents);
|
||||
|
||||
// we deliberately don't add accountData to the timeline
|
||||
room.addAccountData(accountDataEvents);
|
||||
@@ -821,16 +836,17 @@ SyncApi.prototype._getOrCreateFilter = function(filterName, filter) {
|
||||
promise = client.getFilter(client.credentials.userId,
|
||||
filterId, true
|
||||
).then(function(existingFilter) {
|
||||
var oldStr = JSON.stringify(existingFilter.getDefinition());
|
||||
var newStr = JSON.stringify(filter.getDefinition());
|
||||
var oldDef = existingFilter.getDefinition();
|
||||
var newDef = filter.getDefinition();
|
||||
|
||||
if (oldStr == newStr) {
|
||||
if (utils.deepCompare(oldDef, newDef)) {
|
||||
// super, just use that.
|
||||
debuglog("Using existing filter ID %s: %s", filterId, oldStr);
|
||||
debuglog("Using existing filter ID %s: %s", filterId,
|
||||
JSON.stringify(oldDef));
|
||||
return q(filterId);
|
||||
}
|
||||
debuglog("Existing filter ID %s: %s; new filter: %s",
|
||||
filterId, oldStr, newStr);
|
||||
filterId, JSON.stringify(oldDef), JSON.stringify(newDef));
|
||||
return;
|
||||
});
|
||||
}
|
||||
@@ -978,7 +994,7 @@ SyncApi.prototype._processRoomEvents = function(room, stateEventList,
|
||||
|
||||
// execute the timeline events, this will begin to diverge the current state
|
||||
// if the timeline has any state events in it.
|
||||
room.addEventsToTimeline(timelineEventList);
|
||||
room.addLiveEvents(timelineEventList);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
+25
-3
@@ -30,6 +30,13 @@ var DEBUG = false;
|
||||
*/
|
||||
var debuglog = DEBUG ? console.log.bind(console) : function() {};
|
||||
|
||||
/**
|
||||
* the number of times we ask the server for more events before giving up
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
var DEFAULT_PAGINATE_LOOP_LIMIT = 5;
|
||||
|
||||
/**
|
||||
* Construct a TimelineWindow.
|
||||
*
|
||||
@@ -179,10 +186,14 @@ TimelineWindow.prototype.canPaginate = function(direction) {
|
||||
* even if there are fewer than 'size' of them, as we will just return those
|
||||
* we already know about.)
|
||||
*
|
||||
* @param {number} [requestLimit = 5] limit for the number of API requests we
|
||||
* should make.
|
||||
*
|
||||
* @return {module:client.Promise} Resolves to a boolean which is true if more events
|
||||
* were successfully retrieved.
|
||||
*/
|
||||
TimelineWindow.prototype.paginate = function(direction, size, makeRequest) {
|
||||
TimelineWindow.prototype.paginate = function(direction, size, makeRequest,
|
||||
requestLimit) {
|
||||
// Either wind back the message cap (if there are enough events in the
|
||||
// timeline to do so), or fire off a pagination request.
|
||||
|
||||
@@ -190,6 +201,10 @@ TimelineWindow.prototype.paginate = function(direction, size, makeRequest) {
|
||||
makeRequest = true;
|
||||
}
|
||||
|
||||
if (requestLimit === undefined) {
|
||||
requestLimit = DEFAULT_PAGINATE_LOOP_LIMIT;
|
||||
}
|
||||
|
||||
var tl;
|
||||
if (direction == EventTimeline.BACKWARDS) {
|
||||
tl = this._start;
|
||||
@@ -224,7 +239,9 @@ TimelineWindow.prototype.paginate = function(direction, size, makeRequest) {
|
||||
return q(true);
|
||||
}
|
||||
|
||||
if (!makeRequest) {
|
||||
if (!makeRequest || requestLimit === 0) {
|
||||
// todo: should we return something different to indicate that there
|
||||
// might be more events out there, but we haven't found them yet?
|
||||
return q(false);
|
||||
}
|
||||
|
||||
@@ -256,7 +273,12 @@ TimelineWindow.prototype.paginate = function(direction, size, makeRequest) {
|
||||
// token. In particular, we want to know if we've actually hit the
|
||||
// start of the timeline, or if we just happened to know about all of
|
||||
// the events thanks to https://matrix.org/jira/browse/SYN-645.
|
||||
return self.paginate(direction, size, true);
|
||||
//
|
||||
// On the other hand, we necessarily want to wait forever for the
|
||||
// server to make its mind up about whether there are other events,
|
||||
// because it gives a bad user experience
|
||||
// (https://github.com/vector-im/vector-web/issues/1204).
|
||||
return self.paginate(direction, size, true, requestLimit - 1);
|
||||
});
|
||||
tl.pendingPaginate = prom;
|
||||
return prom;
|
||||
|
||||
@@ -246,6 +246,92 @@ module.exports.deepCopy = function(obj) {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
};
|
||||
|
||||
/**
|
||||
* Compare two objects for equality. The objects MUST NOT have circular references.
|
||||
*
|
||||
* @param {Object} x The first object to compare.
|
||||
* @param {Object} y The second object to compare.
|
||||
*
|
||||
* @return {boolean} true if the two objects are equal
|
||||
*/
|
||||
var deepCompare = module.exports.deepCompare = function(x, y) {
|
||||
// Inspired by
|
||||
// http://stackoverflow.com/questions/1068834/object-comparison-in-javascript#1144249
|
||||
|
||||
// Compare primitives and functions.
|
||||
// Also check if both arguments link to the same object.
|
||||
if (x === y) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof x !== typeof y) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// special-case NaN (since NaN !== NaN)
|
||||
if (typeof x === 'number' && isNaN(x) && isNaN(y)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// special-case null (since typeof null == 'object', but null.constructor
|
||||
// throws)
|
||||
if (x === null || y === null) {
|
||||
return x === y;
|
||||
}
|
||||
|
||||
// everything else is either an unequal primitive, or an object
|
||||
if (!(x instanceof Object)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check they are the same type of object
|
||||
if (x.constructor !== y.constructor || x.prototype !== y.prototype) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// special-casing for some special types of object
|
||||
if (x instanceof RegExp || x instanceof Date) {
|
||||
return x.toString() === y.toString();
|
||||
}
|
||||
|
||||
// the object algorithm works for Array, but it's sub-optimal.
|
||||
if (x instanceof Array) {
|
||||
if (x.length !== y.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < x.length; i++) {
|
||||
if (!deepCompare(x[i], y[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// disable jshint "The body of a for in should be wrapped in an if
|
||||
// statement"
|
||||
/* jshint -W089 */
|
||||
|
||||
// check that all of y's direct keys are in x
|
||||
var p;
|
||||
for (p in y) {
|
||||
if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// finally, compare each of x's keys with y
|
||||
for (p in y) {
|
||||
if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
|
||||
return false;
|
||||
}
|
||||
if (!deepCompare(x[p], y[p])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* jshint +W089 */
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Run polyfills to add Array.map and Array.filter if they are missing.
|
||||
|
||||
+3
-3
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "matrix-js-sdk",
|
||||
"version": "0.5.0",
|
||||
"version": "0.5.3",
|
||||
"description": "Matrix Client-Server SDK for Javascript",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "istanbul cover --report cobertura --config .istanbul.yml -i \"lib/**/*.js\" jasmine-node -- spec --verbose --junitreport --forceexit --captureExceptions",
|
||||
"check": "jasmine-node spec --verbose --junitreport --forceexit --captureExceptions",
|
||||
"test": "istanbul cover --report cobertura --config .istanbul.yml -i \"lib/**/*.js\" jasmine-node -- spec --verbose --junitreport --captureExceptions",
|
||||
"check": "jasmine-node spec --verbose --junitreport --captureExceptions",
|
||||
"gendoc": "jsdoc -r lib -P package.json -R README.md -d .jsdoc",
|
||||
"build": "jshint -c .jshint lib/ && browserify browser-index.js -o dist/browser-matrix-dev.js --ignore-missing",
|
||||
"watch": "watchify browser-index.js -o dist/browser-matrix-dev.js -v",
|
||||
|
||||
+14
-14
@@ -3,7 +3,7 @@
|
||||
# Script to perform a release of matrix-js-sdk. Performs the steps documented
|
||||
# in RELEASING.md
|
||||
#
|
||||
# Requires githib-changelog-generator; to install, do
|
||||
# Requires github-changelog-generator; to install, do
|
||||
# pip install git+https://github.com/matrix-org/github-changelog-generator.git
|
||||
|
||||
set -e
|
||||
@@ -47,21 +47,19 @@ if [ $# -ne 1 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tag="$1"
|
||||
|
||||
case "$tag" in
|
||||
v*) ;;
|
||||
|
||||
*)
|
||||
echo 2>&1 "Tag $tag must start with v"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# strip leading 'v' to get release
|
||||
release="${tag#v}"
|
||||
# ignore leading v on release
|
||||
release="${1#v}"
|
||||
tag="v${release}"
|
||||
rel_branch="release-$tag"
|
||||
|
||||
if [ -z "$skip_changelog" ]; then
|
||||
if ! command -v update_changelog >/dev/null 2>&1; then
|
||||
echo "release.sh requires github-changelog-generator. Try:" >&2
|
||||
echo " pip install git+https://github.com/matrix-org/github-changelog-generator.git" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# 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"
|
||||
@@ -79,6 +77,8 @@ if [ -z "$skip_changelog" ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
set -x
|
||||
|
||||
# Bump package.json, build the dist, and tag
|
||||
echo "npm version"
|
||||
npm version "$release"
|
||||
|
||||
@@ -24,6 +24,7 @@ describe("MatrixClient events", function() {
|
||||
|
||||
afterEach(function() {
|
||||
httpBackend.verifyNoOutstandingExpectation();
|
||||
client.stopClient();
|
||||
});
|
||||
|
||||
describe("emissions", function() {
|
||||
|
||||
@@ -97,6 +97,7 @@ function startClient(httpBackend, client) {
|
||||
|
||||
describe("getEventTimeline support", function() {
|
||||
var httpBackend;
|
||||
var client;
|
||||
|
||||
beforeEach(function() {
|
||||
utils.beforeEach(this);
|
||||
@@ -104,8 +105,14 @@ describe("getEventTimeline support", function() {
|
||||
sdk.request(httpBackend.requestFn);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
if (client) {
|
||||
client.stopClient();
|
||||
}
|
||||
});
|
||||
|
||||
it("timeline support must be enabled to work", function(done) {
|
||||
var client = sdk.createClient({
|
||||
client = sdk.createClient({
|
||||
baseUrl: baseUrl,
|
||||
userId: userId,
|
||||
accessToken: accessToken,
|
||||
@@ -120,7 +127,7 @@ describe("getEventTimeline support", function() {
|
||||
});
|
||||
|
||||
it("timeline support works when enabled", function(done) {
|
||||
var client = sdk.createClient({
|
||||
client = sdk.createClient({
|
||||
baseUrl: baseUrl,
|
||||
userId: userId,
|
||||
accessToken: accessToken,
|
||||
@@ -141,7 +148,7 @@ describe("getEventTimeline support", function() {
|
||||
it("scrollback should be able to scroll back to before a gappy /sync",
|
||||
function(done) {
|
||||
// need a client with timelineSupport disabled to make this work
|
||||
var client = sdk.createClient({
|
||||
client = sdk.createClient({
|
||||
baseUrl: baseUrl,
|
||||
userId: userId,
|
||||
accessToken: accessToken,
|
||||
@@ -229,6 +236,7 @@ describe("MatrixClient event timelines", function() {
|
||||
|
||||
afterEach(function() {
|
||||
httpBackend.verifyNoOutstandingExpectation();
|
||||
client.stopClient();
|
||||
});
|
||||
|
||||
describe("getEventTimeline", function() {
|
||||
|
||||
@@ -34,7 +34,7 @@ describe("MatrixClient", function() {
|
||||
it("should no-op if you've already joined a room", function() {
|
||||
var roomId = "!foo:bar";
|
||||
var room = new Room(roomId);
|
||||
room.addEvents([
|
||||
room.addLiveEvents([
|
||||
utils.mkMembership({
|
||||
user: userId, room: roomId, mship: "join", event: true
|
||||
})
|
||||
|
||||
@@ -73,6 +73,10 @@ describe("MatrixClient opts", function() {
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
client.stopClient();
|
||||
});
|
||||
|
||||
it("should be able to send messages", function(done) {
|
||||
var eventId = "$flibble:wibble";
|
||||
httpBackend.when("PUT", "/txn1").respond(200, {
|
||||
|
||||
@@ -126,6 +126,7 @@ describe("MatrixClient room timelines", function() {
|
||||
|
||||
afterEach(function() {
|
||||
httpBackend.verifyNoOutstandingExpectation();
|
||||
client.stopClient();
|
||||
});
|
||||
|
||||
describe("local echo events", function() {
|
||||
@@ -391,16 +392,16 @@ describe("MatrixClient room timelines", function() {
|
||||
});
|
||||
|
||||
httpBackend.flush("/messages", 1);
|
||||
httpBackend.flush("/sync", 1).done(function() {
|
||||
httpBackend.flush("/sync", 1).then(function() {
|
||||
expect(index).toEqual(2);
|
||||
expect(room.timeline[room.timeline.length - 1].event).toEqual(
|
||||
expect(room.timeline.length).toEqual(3);
|
||||
expect(room.timeline[2].event).toEqual(
|
||||
eventData[1]
|
||||
);
|
||||
expect(room.timeline[room.timeline.length - 2].event).toEqual(
|
||||
expect(room.timeline[1].event).toEqual(
|
||||
eventData[0]
|
||||
);
|
||||
done();
|
||||
});
|
||||
}).catch(utils.failTest).done(done);
|
||||
});
|
||||
httpBackend.flush("/sync", 1);
|
||||
});
|
||||
@@ -419,13 +420,12 @@ describe("MatrixClient room timelines", function() {
|
||||
client.on("sync", function(state) {
|
||||
if (state !== "PREPARED") { return; }
|
||||
var room = client.getRoom(roomId);
|
||||
httpBackend.flush("/sync", 1).done(function() {
|
||||
httpBackend.flush("/sync", 1).then(function() {
|
||||
var preNameEvent = room.timeline[room.timeline.length - 3];
|
||||
var postNameEvent = room.timeline[room.timeline.length - 1];
|
||||
expect(preNameEvent.sender.name).toEqual(userName);
|
||||
expect(postNameEvent.sender.name).toEqual("New Name");
|
||||
done();
|
||||
});
|
||||
}).catch(utils.failTest).done(done);
|
||||
});
|
||||
httpBackend.flush("/sync", 1);
|
||||
});
|
||||
@@ -487,7 +487,7 @@ describe("MatrixClient room timelines", function() {
|
||||
client.on("sync", function(state) {
|
||||
if (state !== "PREPARED") { return; }
|
||||
var room = client.getRoom(roomId);
|
||||
httpBackend.flush("/sync", 1).done(function() {
|
||||
httpBackend.flush("/sync", 1).then(function() {
|
||||
expect(room.currentState.getMembers().length).toEqual(4);
|
||||
expect(room.currentState.getMember(userC).name).toEqual("C");
|
||||
expect(room.currentState.getMember(userC).membership).toEqual(
|
||||
@@ -497,8 +497,7 @@ describe("MatrixClient room timelines", function() {
|
||||
expect(room.currentState.getMember(userD).membership).toEqual(
|
||||
"invite"
|
||||
);
|
||||
done();
|
||||
});
|
||||
}).catch(utils.failTest).done(done);
|
||||
});
|
||||
httpBackend.flush("/sync", 1);
|
||||
});
|
||||
|
||||
@@ -32,6 +32,7 @@ describe("MatrixClient syncing", function() {
|
||||
|
||||
afterEach(function() {
|
||||
httpBackend.verifyNoOutstandingExpectation();
|
||||
client.stopClient();
|
||||
});
|
||||
|
||||
describe("startClient", function() {
|
||||
|
||||
@@ -15,6 +15,20 @@ function HttpBackend() {
|
||||
realReq.callback = callback;
|
||||
console.log("HTTP backend received request: %s %s", opts.method, opts.uri);
|
||||
self.requests.push(realReq);
|
||||
|
||||
var abort = function() {
|
||||
var idx = self.requests.indexOf(realReq);
|
||||
if (idx >= 0) {
|
||||
console.log("Aborting HTTP request: %s %s", opts.method,
|
||||
opts.uri);
|
||||
self.requests.splice(idx, 1);
|
||||
realReq.callback("aborted");
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
abort: abort
|
||||
};
|
||||
};
|
||||
}
|
||||
HttpBackend.prototype = {
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
"use strict";
|
||||
|
||||
var callbacks = require("../../lib/realtime-callbacks");
|
||||
var test_utils = require("../test-utils.js");
|
||||
|
||||
describe("realtime-callbacks", function() {
|
||||
var clock = jasmine.Clock;
|
||||
var fakeDate;
|
||||
|
||||
function tick(millis) {
|
||||
// make sure we tick the fakedate first, otherwise nothing will happen!
|
||||
fakeDate += millis;
|
||||
clock.tick(millis);
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
test_utils.beforeEach(this);
|
||||
clock.useMock();
|
||||
fakeDate = Date.now();
|
||||
callbacks.setNow(function() { return fakeDate; });
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
callbacks.setNow();
|
||||
});
|
||||
|
||||
describe("setTimeout", function() {
|
||||
it("should call the callback after the timeout", function() {
|
||||
var callback = jasmine.createSpy();
|
||||
callbacks.setTimeout(callback, 100);
|
||||
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
tick(100);
|
||||
expect(callback).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
it("should default to a zero timeout", function() {
|
||||
var callback = jasmine.createSpy();
|
||||
callbacks.setTimeout(callback);
|
||||
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
tick(0);
|
||||
expect(callback).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should pass any parameters to the callback", function() {
|
||||
var callback = jasmine.createSpy();
|
||||
callbacks.setTimeout(callback, 0, "a", "b", "c");
|
||||
tick(0);
|
||||
expect(callback).toHaveBeenCalledWith("a", "b", "c");
|
||||
});
|
||||
|
||||
it("should set 'this' to the global object", function() {
|
||||
var callback = jasmine.createSpy();
|
||||
callback.andCallFake(function() {
|
||||
expect(this).toBe(global);
|
||||
expect(this.console).toBeDefined();
|
||||
});
|
||||
callbacks.setTimeout(callback);
|
||||
tick(0);
|
||||
expect(callback).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should handle timeouts of several seconds", function() {
|
||||
var callback = jasmine.createSpy();
|
||||
callbacks.setTimeout(callback, 2000);
|
||||
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
for (var i = 0; i < 4; i++) {
|
||||
tick(500);
|
||||
}
|
||||
expect(callback).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should call multiple callbacks in the right order", function() {
|
||||
var callback1 = jasmine.createSpy("callback1");
|
||||
var callback2 = jasmine.createSpy("callback2");
|
||||
var callback3 = jasmine.createSpy("callback3");
|
||||
callbacks.setTimeout(callback2, 200);
|
||||
callbacks.setTimeout(callback1, 100);
|
||||
callbacks.setTimeout(callback3, 300);
|
||||
|
||||
expect(callback1).not.toHaveBeenCalled();
|
||||
expect(callback2).not.toHaveBeenCalled();
|
||||
expect(callback3).not.toHaveBeenCalled();
|
||||
tick(100);
|
||||
expect(callback1).toHaveBeenCalled();
|
||||
expect(callback2).not.toHaveBeenCalled();
|
||||
expect(callback3).not.toHaveBeenCalled();
|
||||
tick(100);
|
||||
expect(callback1).toHaveBeenCalled();
|
||||
expect(callback2).toHaveBeenCalled();
|
||||
expect(callback3).not.toHaveBeenCalled();
|
||||
tick(100);
|
||||
expect(callback1).toHaveBeenCalled();
|
||||
expect(callback2).toHaveBeenCalled();
|
||||
expect(callback3).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should treat -ve timeouts the same as a zero timeout", function() {
|
||||
var callback1 = jasmine.createSpy("callback1");
|
||||
var callback2 = jasmine.createSpy("callback2");
|
||||
|
||||
// check that cb1 is called before cb2
|
||||
callback1.andCallFake(function() {
|
||||
expect(callback2).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
callbacks.setTimeout(callback1);
|
||||
callbacks.setTimeout(callback2, -100);
|
||||
|
||||
expect(callback1).not.toHaveBeenCalled();
|
||||
expect(callback2).not.toHaveBeenCalled();
|
||||
tick(0);
|
||||
expect(callback1).toHaveBeenCalled();
|
||||
expect(callback2).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not get confused by chained calls", function() {
|
||||
var callback2 = jasmine.createSpy("callback2");
|
||||
var callback1 = jasmine.createSpy("callback1");
|
||||
callback1.andCallFake(function() {
|
||||
callbacks.setTimeout(callback2, 0);
|
||||
expect(callback2).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
callbacks.setTimeout(callback1);
|
||||
expect(callback1).not.toHaveBeenCalled();
|
||||
expect(callback2).not.toHaveBeenCalled();
|
||||
tick(0);
|
||||
expect(callback1).toHaveBeenCalled();
|
||||
expect(callback2).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should be immune to exceptions", function() {
|
||||
var callback1 = jasmine.createSpy("callback1");
|
||||
callback1.andCallFake(function() {
|
||||
throw new Error("prepare to die");
|
||||
});
|
||||
var callback2 = jasmine.createSpy("callback2");
|
||||
callbacks.setTimeout(callback1, 0);
|
||||
callbacks.setTimeout(callback2, 0);
|
||||
|
||||
expect(callback1).not.toHaveBeenCalled();
|
||||
expect(callback2).not.toHaveBeenCalled();
|
||||
tick(0);
|
||||
expect(callback1).toHaveBeenCalled();
|
||||
expect(callback2).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("cancelTimeout", function() {
|
||||
it("should cancel a pending timeout", function() {
|
||||
var callback = jasmine.createSpy();
|
||||
var k = callbacks.setTimeout(callback);
|
||||
callbacks.clearTimeout(k);
|
||||
tick(0);
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not affect sooner timeouts", function() {
|
||||
var callback1 = jasmine.createSpy("callback1");
|
||||
var callback2 = jasmine.createSpy("callback2");
|
||||
|
||||
callbacks.setTimeout(callback1, 100);
|
||||
var k = callbacks.setTimeout(callback2, 200);
|
||||
callbacks.clearTimeout(k);
|
||||
|
||||
tick(100);
|
||||
expect(callback1).toHaveBeenCalled();
|
||||
expect(callback2).not.toHaveBeenCalled();
|
||||
|
||||
tick(150);
|
||||
expect(callback2).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -362,4 +362,74 @@ describe("RoomState", function() {
|
||||
expect(state.maySendStateEvent('m.room.other_thing', userB)).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("maySendEvent", function() {
|
||||
it("should say non-joined members may not send events",
|
||||
function() {
|
||||
expect(state.maySendEvent(
|
||||
'm.room.message', "@nobody:nowhere"
|
||||
)).toEqual(false);
|
||||
expect(state.maySendMessage("@nobody:nowhere")).toEqual(false);
|
||||
});
|
||||
|
||||
it("should say any member may send events with no power level event",
|
||||
function() {
|
||||
expect(state.maySendEvent('m.room.message', userA)).toEqual(true);
|
||||
expect(state.maySendMessage(userA)).toEqual(true);
|
||||
});
|
||||
|
||||
it("should obey events_default",
|
||||
function() {
|
||||
var powerLevelEvent = {
|
||||
type: "m.room.power_levels", room: roomId, user: userA, event: true,
|
||||
content: {
|
||||
users_default: 10,
|
||||
state_default: 30,
|
||||
events_default: 25,
|
||||
users: {
|
||||
}
|
||||
}
|
||||
};
|
||||
powerLevelEvent.content.users[userA] = 26;
|
||||
powerLevelEvent.content.users[userB] = 24;
|
||||
|
||||
state.setStateEvents([utils.mkEvent(powerLevelEvent)]);
|
||||
|
||||
expect(state.maySendEvent('m.room.message', userA)).toEqual(true);
|
||||
expect(state.maySendEvent('m.room.message', userB)).toEqual(false);
|
||||
|
||||
expect(state.maySendMessage(userA)).toEqual(true);
|
||||
expect(state.maySendMessage(userB)).toEqual(false);
|
||||
});
|
||||
|
||||
it("should honour explicit event power levels in the power_levels event",
|
||||
function() {
|
||||
var powerLevelEvent = {
|
||||
type: "m.room.power_levels", room: roomId, user: userA, event: true,
|
||||
content: {
|
||||
events: {
|
||||
"m.room.other_thing": 33
|
||||
},
|
||||
users_default: 10,
|
||||
state_default: 50,
|
||||
events_default: 25,
|
||||
users: {
|
||||
}
|
||||
}
|
||||
};
|
||||
powerLevelEvent.content.users[userA] = 40;
|
||||
powerLevelEvent.content.users[userB] = 30;
|
||||
|
||||
state.setStateEvents([utils.mkEvent(powerLevelEvent)]);
|
||||
|
||||
expect(state.maySendEvent('m.room.message', userA)).toEqual(true);
|
||||
expect(state.maySendEvent('m.room.message', userB)).toEqual(true);
|
||||
|
||||
expect(state.maySendMessage(userA)).toEqual(true);
|
||||
expect(state.maySendMessage(userB)).toEqual(true);
|
||||
|
||||
expect(state.maySendEvent('m.room.other_thing', userA)).toEqual(true);
|
||||
expect(state.maySendEvent('m.room.other_thing', userB)).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+168
-168
@@ -82,7 +82,7 @@ describe("Room", function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe("addEvents", function() {
|
||||
describe("addLiveEvents", function() {
|
||||
var events = [
|
||||
utils.mkMessage({
|
||||
room: roomId, user: userA, msg: "changing room name", event: true
|
||||
@@ -100,12 +100,12 @@ describe("Room", function() {
|
||||
user_ids: [userA]
|
||||
}
|
||||
});
|
||||
room.addEvents([typing]);
|
||||
room.addLiveEvents([typing]);
|
||||
expect(room.currentState.setTypingEvent).toHaveBeenCalledWith(typing);
|
||||
});
|
||||
|
||||
it("should throw if duplicateStrategy isn't 'replace' or 'ignore'", function() {
|
||||
expect(function() { room.addEvents(events, "foo"); }).toThrow();
|
||||
expect(function() { room.addLiveEvents(events, "foo"); }).toThrow();
|
||||
});
|
||||
|
||||
it("should replace a timeline event if dupe strategy is 'replace'", function() {
|
||||
@@ -114,9 +114,9 @@ describe("Room", function() {
|
||||
room: roomId, user: userA, msg: "dupe", event: true
|
||||
});
|
||||
dupe.event.event_id = events[0].getId();
|
||||
room.addEvents(events);
|
||||
room.addLiveEvents(events);
|
||||
expect(room.timeline[0]).toEqual(events[0]);
|
||||
room.addEvents([dupe], "replace");
|
||||
room.addLiveEvents([dupe], "replace");
|
||||
expect(room.timeline[0]).toEqual(dupe);
|
||||
});
|
||||
|
||||
@@ -126,39 +126,13 @@ describe("Room", function() {
|
||||
room: roomId, user: userA, msg: "dupe", event: true
|
||||
});
|
||||
dupe.event.event_id = events[0].getId();
|
||||
room.addEvents(events);
|
||||
room.addLiveEvents(events);
|
||||
expect(room.timeline[0]).toEqual(events[0]);
|
||||
room.addEvents([dupe], "ignore");
|
||||
room.addLiveEvents([dupe], "ignore");
|
||||
expect(room.timeline[0]).toEqual(events[0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("addEventsToTimeline", function() {
|
||||
var events = [
|
||||
utils.mkMessage({
|
||||
room: roomId, user: userA, msg: "changing room name", event: true
|
||||
}),
|
||||
utils.mkEvent({
|
||||
type: "m.room.name", room: roomId, user: userA, event: true,
|
||||
content: { name: "New Room Name" }
|
||||
})
|
||||
];
|
||||
|
||||
it("should be able to add events to the end", function() {
|
||||
room.addEventsToTimeline(events);
|
||||
expect(room.timeline.length).toEqual(2);
|
||||
expect(room.timeline[0]).toEqual(events[0]);
|
||||
expect(room.timeline[1]).toEqual(events[1]);
|
||||
});
|
||||
|
||||
it("should be able to add events to the start", function() {
|
||||
room.addEventsToTimeline(events, true);
|
||||
expect(room.timeline.length).toEqual(2);
|
||||
expect(room.timeline[0]).toEqual(events[1]);
|
||||
expect(room.timeline[1]).toEqual(events[0]);
|
||||
});
|
||||
|
||||
it("should emit 'Room.timeline' events when added to the end",
|
||||
it("should emit 'Room.timeline' events",
|
||||
function() {
|
||||
var callCount = 0;
|
||||
room.on("Room.timeline", function(event, emitRoom, toStart) {
|
||||
@@ -168,99 +142,10 @@ describe("Room", function() {
|
||||
expect(emitRoom).toEqual(room);
|
||||
expect(toStart).toBeFalsy();
|
||||
});
|
||||
room.addEventsToTimeline(events);
|
||||
room.addLiveEvents(events);
|
||||
expect(callCount).toEqual(2);
|
||||
});
|
||||
|
||||
it("should emit 'Room.timeline' events when added to the start",
|
||||
function() {
|
||||
var callCount = 0;
|
||||
room.on("Room.timeline", function(event, emitRoom, toStart) {
|
||||
callCount += 1;
|
||||
expect(room.timeline.length).toEqual(callCount);
|
||||
expect(event).toEqual(events[callCount - 1]);
|
||||
expect(emitRoom).toEqual(room);
|
||||
expect(toStart).toBe(true);
|
||||
});
|
||||
room.addEventsToTimeline(events, true);
|
||||
expect(callCount).toEqual(2);
|
||||
});
|
||||
|
||||
it("should set event.sender for new and old events", function() {
|
||||
var sentinel = {
|
||||
userId: userA,
|
||||
membership: "join",
|
||||
name: "Alice"
|
||||
};
|
||||
var oldSentinel = {
|
||||
userId: userA,
|
||||
membership: "join",
|
||||
name: "Old Alice"
|
||||
};
|
||||
room.currentState.getSentinelMember.andCallFake(function(uid) {
|
||||
if (uid === userA) {
|
||||
return sentinel;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
room.oldState.getSentinelMember.andCallFake(function(uid) {
|
||||
if (uid === userA) {
|
||||
return oldSentinel;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
var newEv = utils.mkEvent({
|
||||
type: "m.room.name", room: roomId, user: userA, event: true,
|
||||
content: { name: "New Room Name" }
|
||||
});
|
||||
var oldEv = utils.mkEvent({
|
||||
type: "m.room.name", room: roomId, user: userA, event: true,
|
||||
content: { name: "Old Room Name" }
|
||||
});
|
||||
room.addEventsToTimeline([newEv]);
|
||||
expect(newEv.sender).toEqual(sentinel);
|
||||
room.addEventsToTimeline([oldEv], true);
|
||||
expect(oldEv.sender).toEqual(oldSentinel);
|
||||
});
|
||||
|
||||
it("should set event.target for new and old m.room.member events",
|
||||
function() {
|
||||
var sentinel = {
|
||||
userId: userA,
|
||||
membership: "join",
|
||||
name: "Alice"
|
||||
};
|
||||
var oldSentinel = {
|
||||
userId: userA,
|
||||
membership: "join",
|
||||
name: "Old Alice"
|
||||
};
|
||||
room.currentState.getSentinelMember.andCallFake(function(uid) {
|
||||
if (uid === userA) {
|
||||
return sentinel;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
room.oldState.getSentinelMember.andCallFake(function(uid) {
|
||||
if (uid === userA) {
|
||||
return oldSentinel;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
var newEv = utils.mkMembership({
|
||||
room: roomId, mship: "invite", user: userB, skey: userA, event: true
|
||||
});
|
||||
var oldEv = utils.mkMembership({
|
||||
room: roomId, mship: "ban", user: userB, skey: userA, event: true
|
||||
});
|
||||
room.addEventsToTimeline([newEv]);
|
||||
expect(newEv.target).toEqual(sentinel);
|
||||
room.addEventsToTimeline([oldEv], true);
|
||||
expect(oldEv.target).toEqual(oldSentinel);
|
||||
});
|
||||
|
||||
it("should call setStateEvents on the right RoomState with the right " +
|
||||
"forwardLooking value for new events", function() {
|
||||
var events = [
|
||||
@@ -274,7 +159,7 @@ describe("Room", function() {
|
||||
}
|
||||
})
|
||||
];
|
||||
room.addEventsToTimeline(events);
|
||||
room.addLiveEvents(events);
|
||||
expect(room.currentState.setStateEvents).toHaveBeenCalledWith(
|
||||
[events[0]]
|
||||
);
|
||||
@@ -286,33 +171,6 @@ describe("Room", function() {
|
||||
expect(room.oldState.setStateEvents).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
it("should call setStateEvents on the right RoomState with the right " +
|
||||
"forwardLooking value for old events", function() {
|
||||
var events = [
|
||||
utils.mkMembership({
|
||||
room: roomId, mship: "invite", user: userB, skey: userA, event: true
|
||||
}),
|
||||
utils.mkEvent({
|
||||
type: "m.room.name", room: roomId, user: userB, event: true,
|
||||
content: {
|
||||
name: "New room"
|
||||
}
|
||||
})
|
||||
];
|
||||
|
||||
room.addEventsToTimeline(events, true);
|
||||
expect(room.oldState.setStateEvents).toHaveBeenCalledWith(
|
||||
[events[0]]
|
||||
);
|
||||
expect(room.oldState.setStateEvents).toHaveBeenCalledWith(
|
||||
[events[1]]
|
||||
);
|
||||
expect(events[0].forwardLooking).toBe(false);
|
||||
expect(events[1].forwardLooking).toBe(false);
|
||||
expect(room.currentState.setStateEvents).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should synthesize read receipts for the senders of events", function() {
|
||||
var sentinel = {
|
||||
userId: userA,
|
||||
@@ -325,7 +183,7 @@ describe("Room", function() {
|
||||
}
|
||||
return null;
|
||||
});
|
||||
room.addEventsToTimeline(events);
|
||||
room.addLiveEvents(events);
|
||||
expect(room.getEventReadUpTo(userA)).toEqual(events[1].getId());
|
||||
});
|
||||
|
||||
@@ -370,13 +228,155 @@ describe("Room", function() {
|
||||
expect(room.timeline.length).toEqual(1);
|
||||
|
||||
// then the remoteEvent
|
||||
room.addEventsToTimeline([remoteEvent]);
|
||||
room.addLiveEvents([remoteEvent]);
|
||||
expect(room.timeline.length).toEqual(1);
|
||||
|
||||
expect(callCount).toEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("addEventsToTimeline", function() {
|
||||
var events = [
|
||||
utils.mkMessage({
|
||||
room: roomId, user: userA, msg: "changing room name", event: true
|
||||
}),
|
||||
utils.mkEvent({
|
||||
type: "m.room.name", room: roomId, user: userA, event: true,
|
||||
content: { name: "New Room Name" }
|
||||
})
|
||||
];
|
||||
|
||||
it("should not be able to add events to the end", function() {
|
||||
expect(function() {
|
||||
room.addEventsToTimeline(events, false, room.getLiveTimeline());
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it("should be able to add events to the start", function() {
|
||||
room.addEventsToTimeline(events, true, room.getLiveTimeline());
|
||||
expect(room.timeline.length).toEqual(2);
|
||||
expect(room.timeline[0]).toEqual(events[1]);
|
||||
expect(room.timeline[1]).toEqual(events[0]);
|
||||
});
|
||||
|
||||
it("should emit 'Room.timeline' events when added to the start",
|
||||
function() {
|
||||
var callCount = 0;
|
||||
room.on("Room.timeline", function(event, emitRoom, toStart) {
|
||||
callCount += 1;
|
||||
expect(room.timeline.length).toEqual(callCount);
|
||||
expect(event).toEqual(events[callCount - 1]);
|
||||
expect(emitRoom).toEqual(room);
|
||||
expect(toStart).toBe(true);
|
||||
});
|
||||
room.addEventsToTimeline(events, true, room.getLiveTimeline());
|
||||
expect(callCount).toEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("event metadata handling", function() {
|
||||
it("should set event.sender for new and old events", function() {
|
||||
var sentinel = {
|
||||
userId: userA,
|
||||
membership: "join",
|
||||
name: "Alice"
|
||||
};
|
||||
var oldSentinel = {
|
||||
userId: userA,
|
||||
membership: "join",
|
||||
name: "Old Alice"
|
||||
};
|
||||
room.currentState.getSentinelMember.andCallFake(function(uid) {
|
||||
if (uid === userA) {
|
||||
return sentinel;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
room.oldState.getSentinelMember.andCallFake(function(uid) {
|
||||
if (uid === userA) {
|
||||
return oldSentinel;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
var newEv = utils.mkEvent({
|
||||
type: "m.room.name", room: roomId, user: userA, event: true,
|
||||
content: { name: "New Room Name" }
|
||||
});
|
||||
var oldEv = utils.mkEvent({
|
||||
type: "m.room.name", room: roomId, user: userA, event: true,
|
||||
content: { name: "Old Room Name" }
|
||||
});
|
||||
room.addLiveEvents([newEv]);
|
||||
expect(newEv.sender).toEqual(sentinel);
|
||||
room.addEventsToTimeline([oldEv], true, room.getLiveTimeline());
|
||||
expect(oldEv.sender).toEqual(oldSentinel);
|
||||
});
|
||||
|
||||
it("should set event.target for new and old m.room.member events",
|
||||
function() {
|
||||
var sentinel = {
|
||||
userId: userA,
|
||||
membership: "join",
|
||||
name: "Alice"
|
||||
};
|
||||
var oldSentinel = {
|
||||
userId: userA,
|
||||
membership: "join",
|
||||
name: "Old Alice"
|
||||
};
|
||||
room.currentState.getSentinelMember.andCallFake(function(uid) {
|
||||
if (uid === userA) {
|
||||
return sentinel;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
room.oldState.getSentinelMember.andCallFake(function(uid) {
|
||||
if (uid === userA) {
|
||||
return oldSentinel;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
var newEv = utils.mkMembership({
|
||||
room: roomId, mship: "invite", user: userB, skey: userA, event: true
|
||||
});
|
||||
var oldEv = utils.mkMembership({
|
||||
room: roomId, mship: "ban", user: userB, skey: userA, event: true
|
||||
});
|
||||
room.addLiveEvents([newEv]);
|
||||
expect(newEv.target).toEqual(sentinel);
|
||||
room.addEventsToTimeline([oldEv], true, room.getLiveTimeline());
|
||||
expect(oldEv.target).toEqual(oldSentinel);
|
||||
});
|
||||
|
||||
it("should call setStateEvents on the right RoomState with the right " +
|
||||
"forwardLooking value for old events", function() {
|
||||
var events = [
|
||||
utils.mkMembership({
|
||||
room: roomId, mship: "invite", user: userB, skey: userA, event: true
|
||||
}),
|
||||
utils.mkEvent({
|
||||
type: "m.room.name", room: roomId, user: userB, event: true,
|
||||
content: {
|
||||
name: "New room"
|
||||
}
|
||||
})
|
||||
];
|
||||
|
||||
room.addEventsToTimeline(events, true, room.getLiveTimeline());
|
||||
expect(room.oldState.setStateEvents).toHaveBeenCalledWith(
|
||||
[events[0]]
|
||||
);
|
||||
expect(room.oldState.setStateEvents).toHaveBeenCalledWith(
|
||||
[events[1]]
|
||||
);
|
||||
expect(events[0].forwardLooking).toBe(false);
|
||||
expect(events[1].forwardLooking).toBe(false);
|
||||
expect(room.currentState.setStateEvents).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
var resetTimelineTests = function(timelineSupport) {
|
||||
var events = [
|
||||
utils.mkMessage({
|
||||
@@ -397,11 +397,11 @@ describe("Room", function() {
|
||||
});
|
||||
|
||||
it("should copy state from previous timeline", function() {
|
||||
room.addEventsToTimeline([events[0], events[1]]);
|
||||
room.addLiveEvents([events[0], events[1]]);
|
||||
expect(room.getLiveTimeline().getEvents().length).toEqual(2);
|
||||
room.resetLiveTimeline();
|
||||
|
||||
room.addEventsToTimeline([events[2]]);
|
||||
room.addLiveEvents([events[2]]);
|
||||
var oldState = room.getLiveTimeline().getState(EventTimeline.BACKWARDS);
|
||||
var newState = room.getLiveTimeline().getState(EventTimeline.FORWARDS);
|
||||
expect(room.getLiveTimeline().getEvents().length).toEqual(1);
|
||||
@@ -410,11 +410,11 @@ describe("Room", function() {
|
||||
});
|
||||
|
||||
it("should reset the legacy timeline fields", function() {
|
||||
room.addEventsToTimeline([events[0], events[1]]);
|
||||
room.addLiveEvents([events[0], events[1]]);
|
||||
expect(room.timeline.length).toEqual(2);
|
||||
room.resetLiveTimeline();
|
||||
|
||||
room.addEventsToTimeline([events[2]]);
|
||||
room.addLiveEvents([events[2]]);
|
||||
var newLiveTimeline = room.getLiveTimeline();
|
||||
expect(room.timeline).toEqual(newLiveTimeline.getEvents());
|
||||
expect(room.oldState).toEqual(
|
||||
@@ -443,7 +443,7 @@ describe("Room", function() {
|
||||
|
||||
it("should " + (timelineSupport ? "remember" : "forget") +
|
||||
" old timelines", function() {
|
||||
room.addEventsToTimeline([events[0]]);
|
||||
room.addLiveEvents([events[0]]);
|
||||
expect(room.timeline.length).toEqual(1);
|
||||
var firstLiveTimeline = room.getLiveTimeline();
|
||||
room.resetLiveTimeline();
|
||||
@@ -477,7 +477,7 @@ describe("Room", function() {
|
||||
];
|
||||
|
||||
it("should handle events in the same timeline", function() {
|
||||
room.addEventsToTimeline(events);
|
||||
room.addLiveEvents(events);
|
||||
|
||||
expect(room.compareEventOrdering(events[0].getId(),
|
||||
events[1].getId()))
|
||||
@@ -496,7 +496,7 @@ describe("Room", function() {
|
||||
room.getLiveTimeline().setNeighbouringTimeline(oldTimeline, 'b');
|
||||
|
||||
room.addEventsToTimeline([events[0]], false, oldTimeline);
|
||||
room.addEventsToTimeline([events[1]]);
|
||||
room.addLiveEvents([events[1]]);
|
||||
|
||||
expect(room.compareEventOrdering(events[0].getId(),
|
||||
events[1].getId()))
|
||||
@@ -510,7 +510,7 @@ describe("Room", function() {
|
||||
var oldTimeline = room.addTimeline();
|
||||
|
||||
room.addEventsToTimeline([events[0]], false, oldTimeline);
|
||||
room.addEventsToTimeline([events[1]]);
|
||||
room.addLiveEvents([events[1]]);
|
||||
|
||||
expect(room.compareEventOrdering(events[0].getId(),
|
||||
events[1].getId()))
|
||||
@@ -521,7 +521,7 @@ describe("Room", function() {
|
||||
});
|
||||
|
||||
it("should return null for unknown events", function() {
|
||||
room.addEventsToTimeline(events);
|
||||
room.addLiveEvents(events);
|
||||
|
||||
expect(room.compareEventOrdering(events[0].getId(), "xxx"))
|
||||
.toBe(null);
|
||||
@@ -1068,7 +1068,7 @@ describe("Room", function() {
|
||||
}),
|
||||
];
|
||||
|
||||
room.addEventsToTimeline(events);
|
||||
room.addLiveEvents(events);
|
||||
var ts = 13787898424;
|
||||
|
||||
// check it initialises correctly
|
||||
@@ -1159,9 +1159,9 @@ describe("Room", function() {
|
||||
var eventC = utils.mkMessage({
|
||||
room: roomId, user: userA, msg: "remote 2", event: true
|
||||
});
|
||||
room.addEvents([eventA]);
|
||||
room.addLiveEvents([eventA]);
|
||||
room.addPendingEvent(eventB, "TXN1");
|
||||
room.addEvents([eventC]);
|
||||
room.addLiveEvents([eventC]);
|
||||
expect(room.timeline).toEqual(
|
||||
[eventA, eventC]
|
||||
);
|
||||
@@ -1185,9 +1185,9 @@ describe("Room", function() {
|
||||
var eventC = utils.mkMessage({
|
||||
room: roomId, user: userA, msg: "remote 2", event: true
|
||||
});
|
||||
room.addEvents([eventA]);
|
||||
room.addLiveEvents([eventA]);
|
||||
room.addPendingEvent(eventB, "TXN1");
|
||||
room.addEvents([eventC]);
|
||||
room.addLiveEvents([eventC]);
|
||||
expect(room.timeline).toEqual(
|
||||
[eventA, eventB, eventC]
|
||||
);
|
||||
|
||||
@@ -418,5 +418,43 @@ describe("TimelineWindow", function() {
|
||||
}).catch(utils.failTest).done(done);
|
||||
});
|
||||
|
||||
it("should limit the number of unsuccessful pagination requests",
|
||||
function(done) {
|
||||
var timeline = createTimeline();
|
||||
timeline.setPaginationToken("toktok", EventTimeline.FORWARDS);
|
||||
|
||||
var timelineWindow = createWindow(timeline, {windowLimit: 5});
|
||||
var eventId = timeline.getEvents()[1].getId();
|
||||
|
||||
var paginateCount = 0;
|
||||
client.paginateEventTimeline = function(timeline0, opts) {
|
||||
expect(timeline0).toBe(timeline);
|
||||
expect(opts.backwards).toBe(false);
|
||||
expect(opts.limit).toEqual(2);
|
||||
paginateCount += 1;
|
||||
return q(true);
|
||||
};
|
||||
|
||||
timelineWindow.load(eventId, 3).then(function() {
|
||||
var expectedEvents = timeline.getEvents();
|
||||
expect(timelineWindow.getEvents()).toEqual(expectedEvents);
|
||||
|
||||
expect(timelineWindow.canPaginate(EventTimeline.BACKWARDS))
|
||||
.toBe(false);
|
||||
expect(timelineWindow.canPaginate(EventTimeline.FORWARDS))
|
||||
.toBe(true);
|
||||
return timelineWindow.paginate(EventTimeline.FORWARDS, 2, true, 3);
|
||||
}).then(function(success) {
|
||||
expect(success).toBe(false);
|
||||
expect(paginateCount).toEqual(3);
|
||||
var expectedEvents = timeline.getEvents().slice(0, 3);
|
||||
expect(timelineWindow.getEvents()).toEqual(expectedEvents);
|
||||
|
||||
expect(timelineWindow.canPaginate(EventTimeline.BACKWARDS))
|
||||
.toBe(false);
|
||||
expect(timelineWindow.canPaginate(EventTimeline.FORWARDS))
|
||||
.toBe(true);
|
||||
}).catch(utils.failTest).done(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -132,4 +132,72 @@ describe("utils", function() {
|
||||
}, ["foo"]); }).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("deepCompare", function() {
|
||||
var assert = {
|
||||
isTrue: function(x) { expect(x).toBe(true); },
|
||||
isFalse: function(x) { expect(x).toBe(false); },
|
||||
};
|
||||
|
||||
it("should handle primitives", function() {
|
||||
assert.isTrue(utils.deepCompare(null, null));
|
||||
assert.isFalse(utils.deepCompare(null, undefined));
|
||||
assert.isTrue(utils.deepCompare("hi", "hi"));
|
||||
assert.isTrue(utils.deepCompare(5, 5));
|
||||
assert.isFalse(utils.deepCompare(5, 10));
|
||||
});
|
||||
|
||||
it("should handle regexps", function() {
|
||||
assert.isTrue(utils.deepCompare(/abc/, /abc/));
|
||||
assert.isFalse(utils.deepCompare(/abc/, /123/));
|
||||
var r = /abc/;
|
||||
assert.isTrue(utils.deepCompare(r, r));
|
||||
});
|
||||
|
||||
it("should handle dates", function() {
|
||||
assert.isTrue(utils.deepCompare(new Date("2011-03-31"),
|
||||
new Date("2011-03-31")));
|
||||
assert.isFalse(utils.deepCompare(new Date("2011-03-31"),
|
||||
new Date("1970-01-01")));
|
||||
});
|
||||
|
||||
it("should handle arrays", function() {
|
||||
assert.isTrue(utils.deepCompare([], []));
|
||||
assert.isTrue(utils.deepCompare([1, 2], [1, 2]));
|
||||
assert.isFalse(utils.deepCompare([1, 2], [2, 1]));
|
||||
assert.isFalse(utils.deepCompare([1, 2], [1, 2, 3]));
|
||||
});
|
||||
|
||||
it("should handle simple objects", function() {
|
||||
assert.isTrue(utils.deepCompare({}, {}));
|
||||
assert.isTrue(utils.deepCompare({a: 1, b: 2}, {a: 1, b: 2}));
|
||||
assert.isTrue(utils.deepCompare({a: 1, b: 2}, {b: 2, a: 1}));
|
||||
assert.isFalse(utils.deepCompare({a: 1, b: 2}, {a: 1, b: 3}));
|
||||
|
||||
assert.isTrue(utils.deepCompare({1: {name: "mhc", age: 28},
|
||||
2: {name: "arb", age: 26}},
|
||||
{1: {name: "mhc", age: 28},
|
||||
2: {name: "arb", age: 26}}));
|
||||
|
||||
assert.isFalse(utils.deepCompare({1: {name: "mhc", age: 28},
|
||||
2: {name: "arb", age: 26}},
|
||||
{1: {name: "mhc", age: 28},
|
||||
2: {name: "arb", age: 27}}));
|
||||
|
||||
assert.isFalse(utils.deepCompare({}, null));
|
||||
assert.isFalse(utils.deepCompare({}, undefined));
|
||||
});
|
||||
|
||||
it("should handle functions", function() {
|
||||
// no two different function is equal really, they capture their
|
||||
// context variables so even if they have same toString(), they
|
||||
// won't have same functionality
|
||||
var func = function(x) { return true; };
|
||||
var func2 = function(x) { return true; };
|
||||
assert.isTrue(utils.deepCompare(func, func));
|
||||
assert.isFalse(utils.deepCompare(func, func2));
|
||||
assert.isTrue(utils.deepCompare({ a: { b: func } }, { a: { b: func } }));
|
||||
assert.isFalse(utils.deepCompare({ a: { b: func } }, { a: { b: func2 } }));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user