You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2026-01-03 23:22:30 +03:00
Merge branch 'develop' into room-avatars
This commit is contained in:
2
.jshint
2
.jshint
@@ -5,7 +5,7 @@
|
||||
"nonew": true,
|
||||
"curly": true,
|
||||
"forin": true,
|
||||
"freeze": true,
|
||||
"freeze": false,
|
||||
"undef": true,
|
||||
"unused": "vars"
|
||||
}
|
||||
|
||||
3
index.js
3
index.js
@@ -1,3 +1,6 @@
|
||||
var matrixcs = require("./lib/matrix");
|
||||
matrixcs.request(require("request"));
|
||||
module.exports = matrixcs;
|
||||
|
||||
var utils = require("./lib/utils");
|
||||
utils.runPolyfills();
|
||||
|
||||
105
lib/client.js
105
lib/client.js
@@ -1125,6 +1125,37 @@ MatrixClient.prototype.sendHtmlNotice = function(roomId, body, htmlBody, callbac
|
||||
return this.sendMessage(roomId, content, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Send a receipt.
|
||||
* @param {Event} event The event being acknowledged
|
||||
* @param {string} receiptType The kind of receipt e.g. "m.read"
|
||||
* @param {module:client.callback} callback Optional.
|
||||
* @return {module:client.Promise} Resolves: TODO
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
MatrixClient.prototype.sendReceipt = function(event, receiptType, callback) {
|
||||
var path = utils.encodeUri("/rooms/$roomId/receipt/$receiptType/$eventId", {
|
||||
$roomId: event.getRoomId(),
|
||||
$receiptType: receiptType,
|
||||
$eventId: event.getId()
|
||||
});
|
||||
return this._http.authedRequestWithPrefix(
|
||||
callback, "POST", path, undefined, {}, httpApi.PREFIX_V2_ALPHA
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Send a read receipt.
|
||||
* @param {Event} event The event that has been read.
|
||||
* @param {module:client.callback} callback Optional.
|
||||
* @return {module:client.Promise} Resolves: TODO
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
MatrixClient.prototype.sendReadReceipt = function(event, callback) {
|
||||
return this.sendReceipt(event, "m.read", callback);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Upload a file to the media repository on the home server.
|
||||
* @param {File} file object
|
||||
@@ -1799,10 +1830,15 @@ MatrixClient.prototype.isLoggedIn = function() {
|
||||
* This is an internal method.
|
||||
* @param {MatrixClient} client
|
||||
* @param {integer} historyLen
|
||||
* @param {integer} includeArchived
|
||||
*/
|
||||
function doInitialSync(client, historyLen) {
|
||||
function doInitialSync(client, historyLen, includeArchived) {
|
||||
var qps = { limit: historyLen };
|
||||
if (includeArchived) {
|
||||
qps.archived = true;
|
||||
}
|
||||
client._http.authedRequest(
|
||||
undefined, "GET", "/initialSync", { limit: (historyLen || 12) }
|
||||
undefined, "GET", "/initialSync", qps
|
||||
).done(function(data) {
|
||||
var i, j;
|
||||
// intercept the results and put them into our store
|
||||
@@ -1814,29 +1850,51 @@ function doInitialSync(client, historyLen) {
|
||||
user.setPresenceEvent(e);
|
||||
client.store.storeUser(user);
|
||||
});
|
||||
|
||||
// group receipts by room ID.
|
||||
var receiptsByRoom = {};
|
||||
data.receipts = data.receipts || [];
|
||||
utils.forEach(data.receipts.map(_PojoToMatrixEventMapper(client)),
|
||||
function(receiptEvent) {
|
||||
if (!receiptsByRoom[receiptEvent.getRoomId()]) {
|
||||
receiptsByRoom[receiptEvent.getRoomId()] = [];
|
||||
}
|
||||
receiptsByRoom[receiptEvent.getRoomId()].push(receiptEvent);
|
||||
}
|
||||
);
|
||||
|
||||
for (i = 0; i < data.rooms.length; i++) {
|
||||
var room = createNewRoom(client, data.rooms[i].room_id);
|
||||
if (!data.rooms[i].state) {
|
||||
data.rooms[i].state = [];
|
||||
}
|
||||
if (data.rooms[i].membership === "invite") {
|
||||
// create fake invite state event (v1 sucks)
|
||||
data.rooms[i].state.push({
|
||||
event_id: "$fake_" + room.roomId,
|
||||
content: {
|
||||
membership: "invite"
|
||||
},
|
||||
state_key: client.credentials.userId,
|
||||
user_id: data.rooms[i].inviter,
|
||||
room_id: room.roomId,
|
||||
type: "m.room.member"
|
||||
});
|
||||
var inviteEvent = data.rooms[i].invite;
|
||||
if (!inviteEvent) {
|
||||
// fallback for servers which don't serve the invite key yet
|
||||
inviteEvent = {
|
||||
event_id: "$fake_" + room.roomId,
|
||||
content: {
|
||||
membership: "invite"
|
||||
},
|
||||
state_key: client.credentials.userId,
|
||||
user_id: data.rooms[i].inviter,
|
||||
room_id: room.roomId,
|
||||
type: "m.room.member"
|
||||
};
|
||||
}
|
||||
data.rooms[i].state.push(inviteEvent);
|
||||
}
|
||||
|
||||
_processRoomEvents(
|
||||
client, room, data.rooms[i].state, data.rooms[i].messages
|
||||
);
|
||||
|
||||
var receipts = receiptsByRoom[room.roomId] || [];
|
||||
for (j = 0; j < receipts.length; j++) {
|
||||
room.addReceipt(receipts[j]);
|
||||
}
|
||||
|
||||
// cache the name/summary/etc prior to storage since we don't
|
||||
// know how the store will serialise the Room.
|
||||
room.recalculate(client.credentials.userId);
|
||||
@@ -1886,14 +1944,27 @@ function doInitialSync(client, historyLen) {
|
||||
* and then start polling the eventStream for new events. To listen for these
|
||||
* events, add a listener for {@link module:client~MatrixClient#event:"event"}
|
||||
* via {@link module:client~MatrixClient#on}.
|
||||
* @param {Number} historyLen amount of historical timeline events to
|
||||
* emit during from the initial sync. Default: 12.
|
||||
* @param {Object} opts Options to apply when syncing.
|
||||
* @param {Number} opts.initialSyncLimit The event <code>limit=</code> to apply
|
||||
* to initial sync. Default: 8.
|
||||
* @param {Boolean} opts.includeArchivedRooms True to put <code>archived=true</code>
|
||||
* on the <code>/initialSync</code> request. Default: false.
|
||||
*/
|
||||
MatrixClient.prototype.startClient = function(historyLen) {
|
||||
MatrixClient.prototype.startClient = function(opts) {
|
||||
if (this.clientRunning) {
|
||||
// client is already running.
|
||||
return;
|
||||
}
|
||||
// backwards compat for when 'opts' was 'historyLen'.
|
||||
if (typeof opts === "number") {
|
||||
opts = {
|
||||
initialSyncLimit: opts
|
||||
};
|
||||
}
|
||||
|
||||
opts = opts || {};
|
||||
opts.initialSyncLimit = opts.initialSyncLimit || 8;
|
||||
opts.includeArchivedRooms = opts.includeArchivedRooms || false;
|
||||
|
||||
if (CRYPTO_ENABLED && this.sessionStore !== null) {
|
||||
this.uploadKeys(5);
|
||||
@@ -1911,7 +1982,7 @@ MatrixClient.prototype.startClient = function(historyLen) {
|
||||
var self = this;
|
||||
this.pushRules().done(function(result) {
|
||||
self.pushRules = result;
|
||||
doInitialSync(self, historyLen);
|
||||
doInitialSync(self, opts.initialSyncLimit, opts.includeArchivedRooms);
|
||||
}, function(err) {
|
||||
self.emit("syncError", err);
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ var EventEmitter = require("events").EventEmitter;
|
||||
|
||||
var RoomState = require("./room-state");
|
||||
var RoomSummary = require("./room-summary");
|
||||
var MatrixEvent = require("./event").MatrixEvent;
|
||||
var utils = require("../utils");
|
||||
var ContentRepo = require("../content-repo");
|
||||
|
||||
@@ -37,6 +38,25 @@ function Room(roomId, storageToken) {
|
||||
this.summary = null;
|
||||
this.storageToken = storageToken;
|
||||
this._redactions = [];
|
||||
// receipts should clobber based on receipt_type and user_id pairs hence
|
||||
// the form of this structure. This is sub-optimal for the exposed APIs
|
||||
// which pass in an event ID and get back some receipts, so we also store
|
||||
// a pre-cached list for this purpose.
|
||||
this._receipts = {
|
||||
// receipt_type: {
|
||||
// user_id: {
|
||||
// eventId: <event_id>,
|
||||
// data: <receipt_data>
|
||||
// }
|
||||
// }
|
||||
};
|
||||
this._receiptCacheByEventId = {
|
||||
// $event_id: [{
|
||||
// type: $type,
|
||||
// userId: $user_id,
|
||||
// data: <receipt data>
|
||||
// }]
|
||||
};
|
||||
}
|
||||
utils.inherits(Room, EventEmitter);
|
||||
|
||||
@@ -200,6 +220,9 @@ Room.prototype.addEvents = function(events, duplicateStrategy) {
|
||||
if (events[i].getType() === "m.typing") {
|
||||
this.currentState.setTypingEvent(events[i]);
|
||||
}
|
||||
else if (events[i].getType() === "m.receipt") {
|
||||
this.addReceipt(events[i]);
|
||||
}
|
||||
else {
|
||||
if (duplicateStrategy) {
|
||||
// is there a duplicate?
|
||||
@@ -245,6 +268,34 @@ Room.prototype.addEvents = function(events, duplicateStrategy) {
|
||||
* @fires module:client~MatrixClient#event:"Room.name"
|
||||
*/
|
||||
Room.prototype.recalculate = function(userId) {
|
||||
// set fake stripped state events if this is an invite room so logic remains
|
||||
// consistent elsewhere.
|
||||
var self = this;
|
||||
var membershipEvent = this.currentState.getStateEvents(
|
||||
"m.room.member", userId
|
||||
);
|
||||
if (membershipEvent && membershipEvent.getContent().membership === "invite") {
|
||||
var strippedStateEvents = membershipEvent.event.invite_room_state || [];
|
||||
utils.forEach(strippedStateEvents, function(strippedEvent) {
|
||||
var existingEvent = self.currentState.getStateEvents(
|
||||
strippedEvent.type, strippedEvent.state_key
|
||||
);
|
||||
if (!existingEvent) {
|
||||
// set the fake stripped event instead
|
||||
self.currentState.setStateEvents([new MatrixEvent({
|
||||
type: strippedEvent.type,
|
||||
state_key: strippedEvent.state_key,
|
||||
content: strippedEvent.content,
|
||||
event_id: "$fake" + Date.now(),
|
||||
room_id: self.roomId,
|
||||
user_id: userId // technically a lie
|
||||
})]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
var oldName = this.name;
|
||||
this.name = calculateRoomName(this, userId);
|
||||
this.summary = new RoomSummary(this.roomId, {
|
||||
@@ -256,6 +307,82 @@ Room.prototype.recalculate = function(userId) {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get a list of user IDs who have <b>read up to</b> the given event.
|
||||
* @param {MatrixEvent} event the event to get read receipts for.
|
||||
* @return {String[]} A list of user IDs.
|
||||
*/
|
||||
Room.prototype.getUsersReadUpTo = function(event) {
|
||||
return this.getReceiptsForEvent(event).filter(function(receipt) {
|
||||
return receipt.type === "m.read";
|
||||
}).map(function(receipt) {
|
||||
return receipt.userId;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a list of receipts for the given event.
|
||||
* @param {MatrixEvent} event the event to get receipts for
|
||||
* @return {Object[]} A list of receipts with a userId, type and data keys or
|
||||
* an empty list.
|
||||
*/
|
||||
Room.prototype.getReceiptsForEvent = function(event) {
|
||||
return this._receiptCacheByEventId[event.getId()] || [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a receipt event to the room.
|
||||
* @param {MatrixEvent} event The m.receipt event.
|
||||
*/
|
||||
Room.prototype.addReceipt = function(event) {
|
||||
// event content looks like:
|
||||
// content: {
|
||||
// $event_id: {
|
||||
// $receipt_type: {
|
||||
// $user_id: {
|
||||
// ts: $timestamp
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
var self = this;
|
||||
utils.keys(event.getContent()).forEach(function(eventId) {
|
||||
utils.keys(event.getContent()[eventId]).forEach(function(receiptType) {
|
||||
utils.keys(event.getContent()[eventId][receiptType]).forEach(
|
||||
function(userId) {
|
||||
var receipt = event.getContent()[eventId][receiptType][userId];
|
||||
if (!self._receipts[receiptType]) {
|
||||
self._receipts[receiptType] = {};
|
||||
}
|
||||
if (!self._receipts[receiptType][userId]) {
|
||||
self._receipts[receiptType][userId] = {};
|
||||
}
|
||||
self._receipts[receiptType][userId] = {
|
||||
eventId: eventId,
|
||||
data: receipt
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// pre-cache receipts by event
|
||||
self._receiptCacheByEventId = {};
|
||||
utils.keys(self._receipts).forEach(function(receiptType) {
|
||||
utils.keys(self._receipts[receiptType]).forEach(function(userId) {
|
||||
var receipt = self._receipts[receiptType][userId];
|
||||
if (!self._receiptCacheByEventId[receipt.eventId]) {
|
||||
self._receiptCacheByEventId[receipt.eventId] = [];
|
||||
}
|
||||
self._receiptCacheByEventId[receipt.eventId].push({
|
||||
userId: userId,
|
||||
type: receiptType,
|
||||
data: receipt.data
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function setEventMetadata(event, stateContext, toStartOfTimeline) {
|
||||
// set sender and target properties
|
||||
event.sender = stateContext.getSentinelMember(
|
||||
@@ -289,9 +416,16 @@ function calculateRoomName(room, userId) {
|
||||
// check for an alias, if any. for now, assume first alias is the
|
||||
// official one.
|
||||
var alias;
|
||||
var mRoomAliases = room.currentState.getStateEvents("m.room.aliases")[0];
|
||||
if (mRoomAliases && utils.isArray(mRoomAliases.getContent().aliases)) {
|
||||
alias = mRoomAliases.getContent().aliases[0];
|
||||
var canonicalAlias = room.currentState.getStateEvents("m.room.canonical_alias", "");
|
||||
if (canonicalAlias) {
|
||||
alias = canonicalAlias.getContent().alias;
|
||||
}
|
||||
|
||||
if (!alias) {
|
||||
var mRoomAliases = room.currentState.getStateEvents("m.room.aliases")[0];
|
||||
if (mRoomAliases && utils.isArray(mRoomAliases.getContent().aliases)) {
|
||||
alias = mRoomAliases.getContent().aliases[0];
|
||||
}
|
||||
}
|
||||
|
||||
var mRoomName = room.currentState.getStateEvents('m.room.name', '');
|
||||
|
||||
138
lib/utils.js
138
lib/utils.js
@@ -228,6 +228,144 @@ module.exports.deepCopy = function(obj) {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Run polyfills to add Array.map and Array.filter if they are missing.
|
||||
*/
|
||||
module.exports.runPolyfills = function() {
|
||||
// Array.prototype.filter
|
||||
// ========================================================
|
||||
// SOURCE:
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
|
||||
if (!Array.prototype.filter) {
|
||||
Array.prototype.filter = function(fun/*, thisArg*/) {
|
||||
|
||||
if (this === void 0 || this === null) {
|
||||
throw new TypeError();
|
||||
}
|
||||
|
||||
var t = Object(this);
|
||||
var len = t.length >>> 0;
|
||||
if (typeof fun !== 'function') {
|
||||
throw new TypeError();
|
||||
}
|
||||
|
||||
var res = [];
|
||||
var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
|
||||
for (var i = 0; i < len; i++) {
|
||||
if (i in t) {
|
||||
var val = t[i];
|
||||
|
||||
// NOTE: Technically this should Object.defineProperty at
|
||||
// the next index, as push can be affected by
|
||||
// properties on Object.prototype and Array.prototype.
|
||||
// But that method's new, and collisions should be
|
||||
// rare, so use the more-compatible alternative.
|
||||
if (fun.call(thisArg, val, i, t)) {
|
||||
res.push(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
}
|
||||
|
||||
// Array.prototype.map
|
||||
// ========================================================
|
||||
// SOURCE:
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
|
||||
// Production steps of ECMA-262, Edition 5, 15.4.4.19
|
||||
// Reference: http://es5.github.io/#x15.4.4.19
|
||||
if (!Array.prototype.map) {
|
||||
|
||||
Array.prototype.map = function(callback, thisArg) {
|
||||
|
||||
var T, A, k;
|
||||
|
||||
if (this === null || this === undefined) {
|
||||
throw new TypeError(' this is null or not defined');
|
||||
}
|
||||
|
||||
// 1. Let O be the result of calling ToObject passing the |this|
|
||||
// value as the argument.
|
||||
var O = Object(this);
|
||||
|
||||
// 2. Let lenValue be the result of calling the Get internal
|
||||
// method of O with the argument "length".
|
||||
// 3. Let len be ToUint32(lenValue).
|
||||
var len = O.length >>> 0;
|
||||
|
||||
// 4. If IsCallable(callback) is false, throw a TypeError exception.
|
||||
// See: http://es5.github.com/#x9.11
|
||||
if (typeof callback !== 'function') {
|
||||
throw new TypeError(callback + ' is not a function');
|
||||
}
|
||||
|
||||
// 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
|
||||
if (arguments.length > 1) {
|
||||
T = thisArg;
|
||||
}
|
||||
|
||||
// 6. Let A be a new array created as if by the expression new Array(len)
|
||||
// where Array is the standard built-in constructor with that name and
|
||||
// len is the value of len.
|
||||
A = new Array(len);
|
||||
|
||||
// 7. Let k be 0
|
||||
k = 0;
|
||||
|
||||
// 8. Repeat, while k < len
|
||||
while (k < len) {
|
||||
|
||||
var kValue, mappedValue;
|
||||
|
||||
// a. Let Pk be ToString(k).
|
||||
// This is implicit for LHS operands of the in operator
|
||||
// b. Let kPresent be the result of calling the HasProperty internal
|
||||
// method of O with argument Pk.
|
||||
// This step can be combined with c
|
||||
// c. If kPresent is true, then
|
||||
if (k in O) {
|
||||
|
||||
// i. Let kValue be the result of calling the Get internal
|
||||
// method of O with argument Pk.
|
||||
kValue = O[k];
|
||||
|
||||
// ii. Let mappedValue be the result of calling the Call internal
|
||||
// method of callback with T as the this value and argument
|
||||
// list containing kValue, k, and O.
|
||||
mappedValue = callback.call(T, kValue, k, O);
|
||||
|
||||
// iii. Call the DefineOwnProperty internal method of A with arguments
|
||||
// Pk, Property Descriptor
|
||||
// { Value: mappedValue,
|
||||
// Writable: true,
|
||||
// Enumerable: true,
|
||||
// Configurable: true },
|
||||
// and false.
|
||||
|
||||
// In browsers that support Object.defineProperty, use the following:
|
||||
// Object.defineProperty(A, k, {
|
||||
// value: mappedValue,
|
||||
// writable: true,
|
||||
// enumerable: true,
|
||||
// configurable: true
|
||||
// });
|
||||
|
||||
// For best browser support, use the following:
|
||||
A[k] = mappedValue;
|
||||
}
|
||||
// d. Increase k by 1.
|
||||
k++;
|
||||
}
|
||||
|
||||
// 9. return A
|
||||
return A;
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherit the prototype methods from one constructor into another. This is a
|
||||
* port of the Node.js implementation with an Object.create polyfill.
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
var sdk = require("../..");
|
||||
var HttpBackend = require("../mock-request");
|
||||
var utils = require("../test-utils");
|
||||
var MatrixEvent = sdk.MatrixEvent;
|
||||
|
||||
describe("MatrixClient syncing", function() {
|
||||
var baseUrl = "http://localhost.or.something";
|
||||
@@ -270,6 +271,122 @@ describe("MatrixClient syncing", function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe("receipts", function() {
|
||||
var roomOne = "!foo:localhost";
|
||||
var initialSync = {
|
||||
end: "s_5_3",
|
||||
presence: [],
|
||||
receipts: [],
|
||||
rooms: [{
|
||||
membership: "join",
|
||||
room_id: roomOne,
|
||||
messages: {
|
||||
start: "f_1_1",
|
||||
end: "f_2_2",
|
||||
chunk: [
|
||||
utils.mkMessage({
|
||||
room: roomOne, user: otherUserId, msg: "hello"
|
||||
})
|
||||
]
|
||||
},
|
||||
state: [
|
||||
utils.mkEvent({
|
||||
type: "m.room.name", room: roomOne, user: otherUserId,
|
||||
content: {
|
||||
name: "Old room name"
|
||||
}
|
||||
}),
|
||||
utils.mkMembership({
|
||||
room: roomOne, mship: "join", user: otherUserId
|
||||
}),
|
||||
utils.mkMembership({
|
||||
room: roomOne, mship: "join", user: selfUserId
|
||||
}),
|
||||
utils.mkEvent({
|
||||
type: "m.room.create", room: roomOne, user: selfUserId,
|
||||
content: {
|
||||
creator: selfUserId
|
||||
}
|
||||
})
|
||||
]
|
||||
}]
|
||||
};
|
||||
var eventData = {
|
||||
start: "s_5_3",
|
||||
end: "e_6_7",
|
||||
chunk: []
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
eventData.chunk = [];
|
||||
initialSync.receipts = [];
|
||||
});
|
||||
|
||||
it("should sync receipts from /initialSync.", function(done) {
|
||||
var ackEvent = initialSync.rooms[0].messages.chunk[0];
|
||||
var receipt = {};
|
||||
receipt[ackEvent.event_id] = {
|
||||
"m.read": {}
|
||||
};
|
||||
receipt[ackEvent.event_id]["m.read"][otherUserId] = {
|
||||
ts: 176592842636
|
||||
};
|
||||
initialSync.receipts = [{
|
||||
content: receipt,
|
||||
room_id: roomOne,
|
||||
type: "m.receipt"
|
||||
}];
|
||||
httpBackend.when("GET", "/initialSync").respond(200, initialSync);
|
||||
httpBackend.when("GET", "/events").respond(200, eventData);
|
||||
|
||||
client.startClient();
|
||||
|
||||
httpBackend.flush().done(function() {
|
||||
var room = client.getRoom(roomOne);
|
||||
expect(room.getReceiptsForEvent(new MatrixEvent(ackEvent))).toEqual([{
|
||||
type: "m.read",
|
||||
userId: otherUserId,
|
||||
data: {
|
||||
ts: 176592842636
|
||||
}
|
||||
}]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should sync receipts from /events.", function(done) {
|
||||
var ackEvent = initialSync.rooms[0].messages.chunk[0];
|
||||
var receipt = {};
|
||||
receipt[ackEvent.event_id] = {
|
||||
"m.read": {}
|
||||
};
|
||||
receipt[ackEvent.event_id]["m.read"][otherUserId] = {
|
||||
ts: 176592842636
|
||||
};
|
||||
eventData.chunk = [{
|
||||
content: receipt,
|
||||
room_id: roomOne,
|
||||
type: "m.receipt"
|
||||
}];
|
||||
httpBackend.when("GET", "/initialSync").respond(200, initialSync);
|
||||
httpBackend.when("GET", "/events").respond(200, eventData);
|
||||
|
||||
client.startClient();
|
||||
|
||||
httpBackend.flush().done(function() {
|
||||
var room = client.getRoom(roomOne);
|
||||
expect(room.getReceiptsForEvent(new MatrixEvent(ackEvent))).toEqual([{
|
||||
type: "m.read",
|
||||
userId: otherUserId,
|
||||
data: {
|
||||
ts: 176592842636
|
||||
}
|
||||
}]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("of a room", function() {
|
||||
xit("should sync when a join event (which changes state) for the user" +
|
||||
" arrives down the event stream (e.g. join from another device)", function() {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
var sdk = require("../..");
|
||||
var Room = sdk.Room;
|
||||
var RoomState = sdk.RoomState;
|
||||
var MatrixEvent = sdk.MatrixEvent;
|
||||
var utils = require("../test-utils");
|
||||
|
||||
describe("Room", function() {
|
||||
@@ -374,7 +375,7 @@ describe("Room", function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe("recalculate (Room Name)", function() {
|
||||
describe("recalculate", function() {
|
||||
var stateLookup = {
|
||||
// event.type + "$" event.state_key : MatrixEvent
|
||||
};
|
||||
@@ -441,149 +442,348 @@ describe("Room", function() {
|
||||
});
|
||||
});
|
||||
|
||||
it("should return the names of members in a private (invite join_rules)" +
|
||||
" room if a room name and alias don't exist and there are >3 members.",
|
||||
function() {
|
||||
setJoinRule("invite");
|
||||
addMember(userA);
|
||||
addMember(userB);
|
||||
addMember(userC);
|
||||
addMember(userD);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
// we expect at least 1 member to be mentioned
|
||||
var others = [userB, userC, userD];
|
||||
var found = false;
|
||||
for (var i = 0; i < others.length; i++) {
|
||||
if (name.indexOf(others[i]) !== -1) {
|
||||
found = true;
|
||||
break;
|
||||
describe("Room.recalculate => Stripped State Events", function() {
|
||||
it("should set stripped state events as actual state events if the " +
|
||||
"room is an invite room", function() {
|
||||
var roomName = "flibble";
|
||||
|
||||
addMember(userA, "invite");
|
||||
stateLookup["m.room.member$" + userA].event.invite_room_state = [
|
||||
{
|
||||
type: "m.room.name",
|
||||
state_key: "",
|
||||
content: {
|
||||
name: roomName
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
room.recalculate(userA);
|
||||
expect(room.currentState.setStateEvents).toHaveBeenCalled();
|
||||
// first call, first arg (which is an array), first element in array
|
||||
var fakeEvent = room.currentState.setStateEvents.calls[0].args[0][0];
|
||||
expect(fakeEvent.getContent()).toEqual({
|
||||
name: roomName
|
||||
});
|
||||
});
|
||||
|
||||
it("should not clobber state events if it isn't an invite room", function() {
|
||||
addMember(userA, "join");
|
||||
stateLookup["m.room.member$" + userA].event.invite_room_state = [
|
||||
{
|
||||
type: "m.room.name",
|
||||
state_key: "",
|
||||
content: {
|
||||
name: "flibble"
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
room.recalculate(userA);
|
||||
expect(room.currentState.setStateEvents).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Room.recalculate => Room Name", function() {
|
||||
|
||||
it("should return the names of members in a private (invite join_rules)" +
|
||||
" room if a room name and alias don't exist and there are >3 members.",
|
||||
function() {
|
||||
setJoinRule("invite");
|
||||
addMember(userA);
|
||||
addMember(userB);
|
||||
addMember(userC);
|
||||
addMember(userD);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
// we expect at least 1 member to be mentioned
|
||||
var others = [userB, userC, userD];
|
||||
var found = false;
|
||||
for (var i = 0; i < others.length; i++) {
|
||||
if (name.indexOf(others[i]) !== -1) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
expect(found).toEqual(true, name);
|
||||
});
|
||||
expect(found).toEqual(true, name);
|
||||
});
|
||||
|
||||
it("should return the names of members in a private (invite join_rules)" +
|
||||
" room if a room name and alias don't exist and there are >2 members.",
|
||||
function() {
|
||||
setJoinRule("invite");
|
||||
addMember(userA);
|
||||
addMember(userB);
|
||||
addMember(userC);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
expect(name.indexOf(userB)).not.toEqual(-1, name);
|
||||
expect(name.indexOf(userC)).not.toEqual(-1, name);
|
||||
});
|
||||
it("should return the names of members in a private (invite join_rules)" +
|
||||
" room if a room name and alias don't exist and there are >2 members.",
|
||||
function() {
|
||||
setJoinRule("invite");
|
||||
addMember(userA);
|
||||
addMember(userB);
|
||||
addMember(userC);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
expect(name.indexOf(userB)).not.toEqual(-1, name);
|
||||
expect(name.indexOf(userC)).not.toEqual(-1, name);
|
||||
});
|
||||
|
||||
it("should return the names of members in a public (public join_rules)" +
|
||||
" room if a room name and alias don't exist and there are >2 members.",
|
||||
function() {
|
||||
setJoinRule("public");
|
||||
addMember(userA);
|
||||
addMember(userB);
|
||||
addMember(userC);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
expect(name.indexOf(userB)).not.toEqual(-1, name);
|
||||
expect(name.indexOf(userC)).not.toEqual(-1, name);
|
||||
});
|
||||
it("should return the names of members in a public (public join_rules)" +
|
||||
" room if a room name and alias don't exist and there are >2 members.",
|
||||
function() {
|
||||
setJoinRule("public");
|
||||
addMember(userA);
|
||||
addMember(userB);
|
||||
addMember(userC);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
expect(name.indexOf(userB)).not.toEqual(-1, name);
|
||||
expect(name.indexOf(userC)).not.toEqual(-1, name);
|
||||
});
|
||||
|
||||
it("should show the other user's name for public (public join_rules)" +
|
||||
" rooms if a room name and alias don't exist and it is a 1:1-chat.",
|
||||
function() {
|
||||
setJoinRule("public");
|
||||
addMember(userA);
|
||||
addMember(userB);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
expect(name.indexOf(userB)).not.toEqual(-1, name);
|
||||
});
|
||||
it("should show the other user's name for public (public join_rules)" +
|
||||
" rooms if a room name and alias don't exist and it is a 1:1-chat.",
|
||||
function() {
|
||||
setJoinRule("public");
|
||||
addMember(userA);
|
||||
addMember(userB);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
expect(name.indexOf(userB)).not.toEqual(-1, name);
|
||||
});
|
||||
|
||||
it("should show the other user's name for private " +
|
||||
"(invite join_rules) rooms if a room name and alias don't exist and it" +
|
||||
" is a 1:1-chat.", function() {
|
||||
setJoinRule("invite");
|
||||
addMember(userA);
|
||||
addMember(userB);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
expect(name.indexOf(userB)).not.toEqual(-1, name);
|
||||
});
|
||||
it("should show the other user's name for private " +
|
||||
"(invite join_rules) rooms if a room name and alias don't exist and it" +
|
||||
" is a 1:1-chat.", function() {
|
||||
setJoinRule("invite");
|
||||
addMember(userA);
|
||||
addMember(userB);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
expect(name.indexOf(userB)).not.toEqual(-1, name);
|
||||
});
|
||||
|
||||
it("should show the other user's name for private" +
|
||||
" (invite join_rules) rooms if you are invited to it.", function() {
|
||||
setJoinRule("invite");
|
||||
addMember(userA, "invite");
|
||||
addMember(userB);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
expect(name.indexOf(userB)).not.toEqual(-1, name);
|
||||
});
|
||||
it("should show the other user's name for private" +
|
||||
" (invite join_rules) rooms if you are invited to it.", function() {
|
||||
setJoinRule("invite");
|
||||
addMember(userA, "invite");
|
||||
addMember(userB);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
expect(name.indexOf(userB)).not.toEqual(-1, name);
|
||||
});
|
||||
|
||||
it("should show the room alias if one exists for private " +
|
||||
"(invite join_rules) rooms if a room name doesn't exist.", function() {
|
||||
var alias = "#room_alias:here";
|
||||
setJoinRule("invite");
|
||||
setAliases([alias, "#another:one"]);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
expect(name).toEqual(alias);
|
||||
});
|
||||
it("should show the room alias if one exists for private " +
|
||||
"(invite join_rules) rooms if a room name doesn't exist.", function() {
|
||||
var alias = "#room_alias:here";
|
||||
setJoinRule("invite");
|
||||
setAliases([alias, "#another:one"]);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
expect(name).toEqual(alias);
|
||||
});
|
||||
|
||||
it("should show the room alias if one exists for public " +
|
||||
"(public join_rules) rooms if a room name doesn't exist.", function() {
|
||||
var alias = "#room_alias:here";
|
||||
setJoinRule("public");
|
||||
setAliases([alias, "#another:one"]);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
expect(name).toEqual(alias);
|
||||
});
|
||||
it("should show the room alias if one exists for public " +
|
||||
"(public join_rules) rooms if a room name doesn't exist.", function() {
|
||||
var alias = "#room_alias:here";
|
||||
setJoinRule("public");
|
||||
setAliases([alias, "#another:one"]);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
expect(name).toEqual(alias);
|
||||
});
|
||||
|
||||
it("should show the room name if one exists for private " +
|
||||
"(invite join_rules) rooms.", function() {
|
||||
var roomName = "A mighty name indeed";
|
||||
setJoinRule("invite");
|
||||
setRoomName(roomName);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
expect(name).toEqual(roomName);
|
||||
});
|
||||
it("should show the room name if one exists for private " +
|
||||
"(invite join_rules) rooms.", function() {
|
||||
var roomName = "A mighty name indeed";
|
||||
setJoinRule("invite");
|
||||
setRoomName(roomName);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
expect(name).toEqual(roomName);
|
||||
});
|
||||
|
||||
it("should show the room name if one exists for public " +
|
||||
"(public join_rules) rooms.", function() {
|
||||
var roomName = "A mighty name indeed";
|
||||
setJoinRule("public");
|
||||
setRoomName(roomName);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
expect(name).toEqual(roomName);
|
||||
});
|
||||
it("should show the room name if one exists for public " +
|
||||
"(public join_rules) rooms.", function() {
|
||||
var roomName = "A mighty name indeed";
|
||||
setJoinRule("public");
|
||||
setRoomName(roomName);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
expect(name).toEqual(roomName);
|
||||
});
|
||||
|
||||
it("should show your name for private (invite join_rules) rooms if" +
|
||||
" a room name and alias don't exist and it is a self-chat.", function() {
|
||||
setJoinRule("invite");
|
||||
addMember(userA);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
expect(name).toEqual(userA);
|
||||
});
|
||||
it("should show your name for private (invite join_rules) rooms if" +
|
||||
" a room name and alias don't exist and it is a self-chat.", function() {
|
||||
setJoinRule("invite");
|
||||
addMember(userA);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
expect(name).toEqual(userA);
|
||||
});
|
||||
|
||||
it("should show your name for public (public join_rules) rooms if a" +
|
||||
" room name and alias don't exist and it is a self-chat.", function() {
|
||||
setJoinRule("public");
|
||||
addMember(userA);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
expect(name).toEqual(userA);
|
||||
});
|
||||
it("should show your name for public (public join_rules) rooms if a" +
|
||||
" room name and alias don't exist and it is a self-chat.", function() {
|
||||
setJoinRule("public");
|
||||
addMember(userA);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
expect(name).toEqual(userA);
|
||||
});
|
||||
|
||||
it("should return '?' if there is no name, alias or members in the room.",
|
||||
function() {
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
expect(name).toEqual("?");
|
||||
});
|
||||
|
||||
it("should return '?' if there is no name, alias or members in the room.",
|
||||
function() {
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
expect(name).toEqual("?");
|
||||
});
|
||||
});
|
||||
|
||||
describe("receipts", function() {
|
||||
|
||||
var eventToAck = utils.mkMessage({
|
||||
room: roomId, user: userA, msg: "PLEASE ACKNOWLEDGE MY EXISTENCE",
|
||||
event: true
|
||||
});
|
||||
|
||||
function mkReceipt(roomId, records) {
|
||||
var content = {};
|
||||
records.forEach(function(r) {
|
||||
if (!content[r.eventId]) { content[r.eventId] = {}; }
|
||||
if (!content[r.eventId][r.type]) { content[r.eventId][r.type] = {}; }
|
||||
content[r.eventId][r.type][r.userId] = {
|
||||
ts: r.ts
|
||||
};
|
||||
});
|
||||
return new MatrixEvent({
|
||||
content: content,
|
||||
room_id: roomId,
|
||||
type: "m.receipt"
|
||||
});
|
||||
}
|
||||
|
||||
function mkRecord(eventId, type, userId, ts) {
|
||||
ts = ts || Date.now();
|
||||
return {
|
||||
eventId: eventId,
|
||||
type: type,
|
||||
userId: userId,
|
||||
ts: ts
|
||||
};
|
||||
}
|
||||
|
||||
describe("addReceipt", function() {
|
||||
|
||||
it("should store the receipt so it can be obtained via getReceiptsForEvent",
|
||||
function() {
|
||||
var ts = 13787898424;
|
||||
room.addReceipt(mkReceipt(roomId, [
|
||||
mkRecord(eventToAck.getId(), "m.read", userB, ts)
|
||||
]));
|
||||
expect(room.getReceiptsForEvent(eventToAck)).toEqual([{
|
||||
type: "m.read",
|
||||
userId: userB,
|
||||
data: {
|
||||
ts: ts
|
||||
}
|
||||
}]);
|
||||
});
|
||||
|
||||
it("should clobber receipts based on type and user ID", function() {
|
||||
var nextEventToAck = utils.mkMessage({
|
||||
room: roomId, user: userA, msg: "I AM HERE YOU KNOW",
|
||||
event: true
|
||||
});
|
||||
var ts = 13787898424;
|
||||
room.addReceipt(mkReceipt(roomId, [
|
||||
mkRecord(eventToAck.getId(), "m.read", userB, ts)
|
||||
]));
|
||||
var ts2 = 13787899999;
|
||||
room.addReceipt(mkReceipt(roomId, [
|
||||
mkRecord(nextEventToAck.getId(), "m.read", userB, ts2)
|
||||
]));
|
||||
expect(room.getReceiptsForEvent(eventToAck)).toEqual([]);
|
||||
expect(room.getReceiptsForEvent(nextEventToAck)).toEqual([{
|
||||
type: "m.read",
|
||||
userId: userB,
|
||||
data: {
|
||||
ts: ts2
|
||||
}
|
||||
}]);
|
||||
});
|
||||
|
||||
it("should persist multiple receipts for a single event ID", function() {
|
||||
var ts = 13787898424;
|
||||
room.addReceipt(mkReceipt(roomId, [
|
||||
mkRecord(eventToAck.getId(), "m.read", userB, ts),
|
||||
mkRecord(eventToAck.getId(), "m.read", userC, ts),
|
||||
mkRecord(eventToAck.getId(), "m.read", userD, ts)
|
||||
]));
|
||||
expect(room.getUsersReadUpTo(eventToAck)).toEqual(
|
||||
[userB, userC, userD]
|
||||
);
|
||||
});
|
||||
|
||||
it("should persist multiple receipts for a single receipt type", function() {
|
||||
var eventTwo = utils.mkMessage({
|
||||
room: roomId, user: userA, msg: "2222",
|
||||
event: true
|
||||
});
|
||||
var eventThree = utils.mkMessage({
|
||||
room: roomId, user: userA, msg: "3333",
|
||||
event: true
|
||||
});
|
||||
var ts = 13787898424;
|
||||
room.addReceipt(mkReceipt(roomId, [
|
||||
mkRecord(eventToAck.getId(), "m.read", userB, ts),
|
||||
mkRecord(eventTwo.getId(), "m.read", userC, ts),
|
||||
mkRecord(eventThree.getId(), "m.read", userD, ts)
|
||||
]));
|
||||
expect(room.getUsersReadUpTo(eventToAck)).toEqual([userB]);
|
||||
expect(room.getUsersReadUpTo(eventTwo)).toEqual([userC]);
|
||||
expect(room.getUsersReadUpTo(eventThree)).toEqual([userD]);
|
||||
});
|
||||
|
||||
it("should persist multiple receipts for a single user ID", function() {
|
||||
room.addReceipt(mkReceipt(roomId, [
|
||||
mkRecord(eventToAck.getId(), "m.delivered", userB, 13787898424),
|
||||
mkRecord(eventToAck.getId(), "m.read", userB, 22222222),
|
||||
mkRecord(eventToAck.getId(), "m.seen", userB, 33333333),
|
||||
]));
|
||||
expect(room.getReceiptsForEvent(eventToAck)).toEqual([
|
||||
{
|
||||
type: "m.delivered",
|
||||
userId: userB,
|
||||
data: {
|
||||
ts: 13787898424
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "m.read",
|
||||
userId: userB,
|
||||
data: {
|
||||
ts: 22222222
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "m.seen",
|
||||
userId: userB,
|
||||
data: {
|
||||
ts: 33333333
|
||||
}
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("getUsersReadUpTo", function() {
|
||||
|
||||
it("should return user IDs read up to the given event", function() {
|
||||
var ts = 13787898424;
|
||||
room.addReceipt(mkReceipt(roomId, [
|
||||
mkRecord(eventToAck.getId(), "m.read", userB, ts)
|
||||
]));
|
||||
expect(room.getUsersReadUpTo(eventToAck)).toEqual([userB]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user