You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-09-03 08:42:03 +03:00
2
.jshint
2
.jshint
@@ -5,7 +5,7 @@
|
|||||||
"nonew": true,
|
"nonew": true,
|
||||||
"curly": true,
|
"curly": true,
|
||||||
"forin": true,
|
"forin": true,
|
||||||
"freeze": true,
|
"freeze": false,
|
||||||
"undef": true,
|
"undef": true,
|
||||||
"unused": "vars"
|
"unused": "vars"
|
||||||
}
|
}
|
||||||
|
3
index.js
3
index.js
@@ -1,3 +1,6 @@
|
|||||||
var matrixcs = require("./lib/matrix");
|
var matrixcs = require("./lib/matrix");
|
||||||
matrixcs.request(require("request"));
|
matrixcs.request(require("request"));
|
||||||
module.exports = matrixcs;
|
module.exports = matrixcs;
|
||||||
|
|
||||||
|
var utils = require("./lib/utils");
|
||||||
|
utils.runPolyfills();
|
||||||
|
@@ -1124,6 +1124,37 @@ MatrixClient.prototype.sendHtmlNotice = function(roomId, body, htmlBody, callbac
|
|||||||
return this.sendMessage(roomId, content, callback);
|
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.
|
* Upload a file to the media repository on the home server.
|
||||||
* @param {File} file object
|
* @param {File} file object
|
||||||
@@ -1872,6 +1903,19 @@ function doInitialSync(client, historyLen) {
|
|||||||
user.setPresenceEvent(e);
|
user.setPresenceEvent(e);
|
||||||
client.store.storeUser(user);
|
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++) {
|
for (i = 0; i < data.rooms.length; i++) {
|
||||||
var room = createNewRoom(client, data.rooms[i].room_id);
|
var room = createNewRoom(client, data.rooms[i].room_id);
|
||||||
if (!data.rooms[i].state) {
|
if (!data.rooms[i].state) {
|
||||||
@@ -1895,6 +1939,11 @@ function doInitialSync(client, historyLen) {
|
|||||||
client, room, data.rooms[i].state, data.rooms[i].messages
|
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
|
// cache the name/summary/etc prior to storage since we don't
|
||||||
// know how the store will serialise the Room.
|
// know how the store will serialise the Room.
|
||||||
room.recalculate(client.credentials.userId);
|
room.recalculate(client.credentials.userId);
|
||||||
|
@@ -36,6 +36,25 @@ function Room(roomId, storageToken) {
|
|||||||
this.summary = null;
|
this.summary = null;
|
||||||
this.storageToken = storageToken;
|
this.storageToken = storageToken;
|
||||||
this._redactions = [];
|
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);
|
utils.inherits(Room, EventEmitter);
|
||||||
|
|
||||||
@@ -164,6 +183,9 @@ Room.prototype.addEvents = function(events, duplicateStrategy) {
|
|||||||
if (events[i].getType() === "m.typing") {
|
if (events[i].getType() === "m.typing") {
|
||||||
this.currentState.setTypingEvent(events[i]);
|
this.currentState.setTypingEvent(events[i]);
|
||||||
}
|
}
|
||||||
|
else if (events[i].getType() === "m.receipt") {
|
||||||
|
this.addReceipt(events[i]);
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
if (duplicateStrategy) {
|
if (duplicateStrategy) {
|
||||||
// is there a duplicate?
|
// is there a duplicate?
|
||||||
@@ -220,6 +242,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) {
|
function setEventMetadata(event, stateContext, toStartOfTimeline) {
|
||||||
// set sender and target properties
|
// set sender and target properties
|
||||||
event.sender = stateContext.getSentinelMember(
|
event.sender = stateContext.getSentinelMember(
|
||||||
|
138
lib/utils.js
138
lib/utils.js
@@ -228,6 +228,144 @@ module.exports.deepCopy = function(obj) {
|
|||||||
return JSON.parse(JSON.stringify(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
|
* Inherit the prototype methods from one constructor into another. This is a
|
||||||
* port of the Node.js implementation with an Object.create polyfill.
|
* port of the Node.js implementation with an Object.create polyfill.
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
var sdk = require("../..");
|
var sdk = require("../..");
|
||||||
var HttpBackend = require("../mock-request");
|
var HttpBackend = require("../mock-request");
|
||||||
var utils = require("../test-utils");
|
var utils = require("../test-utils");
|
||||||
|
var MatrixEvent = sdk.MatrixEvent;
|
||||||
|
|
||||||
describe("MatrixClient syncing", function() {
|
describe("MatrixClient syncing", function() {
|
||||||
var baseUrl = "http://localhost.or.something";
|
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() {
|
describe("of a room", function() {
|
||||||
xit("should sync when a join event (which changes state) for the user" +
|
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() {
|
" arrives down the event stream (e.g. join from another device)", function() {
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
var sdk = require("../..");
|
var sdk = require("../..");
|
||||||
var Room = sdk.Room;
|
var Room = sdk.Room;
|
||||||
var RoomState = sdk.RoomState;
|
var RoomState = sdk.RoomState;
|
||||||
|
var MatrixEvent = sdk.MatrixEvent;
|
||||||
var utils = require("../test-utils");
|
var utils = require("../test-utils");
|
||||||
|
|
||||||
describe("Room", function() {
|
describe("Room", function() {
|
||||||
@@ -549,4 +550,157 @@ describe("Room", function() {
|
|||||||
expect(name).toEqual("?");
|
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