Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b0ca6609f4 | |||
| ff9bc0b865 | |||
| b3ef0b90ac | |||
| 805d914940 | |||
| de560d1a4a | |||
| 07d3f43d8b | |||
| 57914969b8 | |||
| 378c7fd6cb | |||
| 7534f59af2 | |||
| 553325d66f | |||
| 6737388766 | |||
| b7ed78b432 | |||
| 9c2a2902eb | |||
| ef77c6f989 | |||
| bc0e2ad504 | |||
| ad70b3d434 | |||
| 002ae08f04 | |||
| 402a648a92 | |||
| 756fe4ddcb | |||
| 898d16ae76 | |||
| 18db0d8c02 | |||
| 2f78ceb6fc | |||
| 8a9f84a4b2 | |||
| 1e3162128a | |||
| 7e12ea4273 | |||
| 18a3ce415c | |||
| ca414d1611 | |||
| ba56f5f621 | |||
| 4810d04257 | |||
| 8d77edce86 | |||
| 9cb37fbe4f | |||
| d151ac49f2 | |||
| d74a71cc2d | |||
| 8e6eb35dfe | |||
| ee4d66024a | |||
| f77e8e3bf7 | |||
| 94931cdc07 | |||
| c6b11fc382 |
@@ -0,0 +1,51 @@
|
||||
Changes in 0.1.1
|
||||
================
|
||||
|
||||
Breaking changes:
|
||||
* `Room.calculateRoomName` is now private. Use `Room.recalculate` instead, and
|
||||
access the calculated name via `Room.name`.
|
||||
* `new MatrixClient(...)` no longer creates a `MatrixInMemoryStore` if
|
||||
`opts.store` is not specified. Instead, the `createClient` global function
|
||||
creates it and passes it to the constructor. This change will not affect
|
||||
users who have always used `createClient` to create a `MatrixClient`.
|
||||
* `"Room"` events will now be emitted when the Room has *finished* being
|
||||
populated with state rather than at the moment of creation. This will fire
|
||||
when the SDK encounters a room it doesn't know about (just arrived from the
|
||||
event stream; e.g. a room invite) and will also fire after syncing room
|
||||
state (e.g. after calling joinRoom).
|
||||
* `MatrixClient.joinRoom` now returns a `Room` object when resolved, not an
|
||||
object with a `room_id` property.
|
||||
* `MatrixClient.scrollback` now expects a `Room` arg instead of a `room_id`
|
||||
and `from` token. Construct a `new Room(roomId)` if you want to continue
|
||||
using this directly, then set the pagination token using
|
||||
`room.oldState.paginationToken = from`. It now resolves to a `Room` object
|
||||
instead of the raw HTTP response.
|
||||
|
||||
New properties:
|
||||
* `User.events`
|
||||
* `RoomMember.events`
|
||||
|
||||
New methods:
|
||||
* `Room.hasMembershipState(userId, membership)`
|
||||
* `MatrixClient.resendEvent(event, room)`
|
||||
|
||||
New features:
|
||||
* Local echo. When you send an event using the SDK it will immediately be
|
||||
added to `Room.timeline` with the `event.status` of `EventStatus.SENDING`.
|
||||
When the event is finally sent, this status will be removed.
|
||||
* Not sent status. When an event fails to send using the SDK, it will have the
|
||||
`event.status` of `EventStatus.NOT_SENT`.
|
||||
* Retries. If events fail to send, they will be automatically retried.
|
||||
* Manual resending. Events which failed to send can be passed to
|
||||
`MatrixClient.resendEvent(event, room)` to resend them.
|
||||
* Queueing. Messages sent in quick succession will be queued to preserve the
|
||||
order in which they were submitted.
|
||||
* Room state is automatcally synchronised when joining a room (including if
|
||||
another device joins a room).
|
||||
* Scrollback. You can request earlier events in a room using
|
||||
`MatrixClient.scrollback(room, limit, callback)`.
|
||||
|
||||
Bug fixes:
|
||||
* Fixed a bug which prevented the event stream from polling. Some devices will
|
||||
black hole requests when they hibernate, meaning that the callbacks will
|
||||
never fire. We now maintain a local timer to forcibly restart the request.
|
||||
@@ -10,7 +10,7 @@ Quickstart
|
||||
|
||||
In a browser
|
||||
------------
|
||||
Copy ``dist/browser-matrix-$VERSION.js`` and add that as a ``<script>`` to
|
||||
Copy ``dist/$VERSION/browser-matrix-$VERSION.js`` and add that as a ``<script>`` to
|
||||
your page. There will be a global variable ``matrixcs`` attached to
|
||||
``window`` through which you can access the SDK.
|
||||
|
||||
@@ -44,14 +44,18 @@ events for incoming data and state changes. Aside from wrapping the HTTP API, it
|
||||
- Exposes high-level objects like `Rooms`, `RoomState`, `RoomMembers` and `Users`
|
||||
which can be listened to for things like name changes, new messages, membership
|
||||
changes, presence changes, and more.
|
||||
|
||||
Later versions of the SDK will:
|
||||
- Handle "local echo" of messages sent using the SDK. This means that messages
|
||||
that have just been sent will appear in the timeline as 'sending', until it
|
||||
completes. This is beneficial because it prevents there being a gap between
|
||||
hitting the send button and having the "remote echo" arrive.
|
||||
- Mark messages which failed to send as not sent.
|
||||
- Automatically retry requests to send messages due to network errors.
|
||||
- Automatically retry requests to send messages due to rate limiting errors.
|
||||
- Mark events' sent status (e.g. 'not sent').
|
||||
- Handle "local echo" of messages sent.
|
||||
- Handle queueing of messages.
|
||||
- Handle pagination.
|
||||
- Handles pagination.
|
||||
- Handles room initial sync on accepting invites.
|
||||
|
||||
Later versions of the SDK will:
|
||||
- Expose a `RoomSummary` which would be suitable for a recents page.
|
||||
- Provide different pluggable storage layers (e.g. local storage, database-backed)
|
||||
|
||||
|
||||
Vendored
+6490
File diff suppressed because it is too large
Load Diff
+2
File diff suppressed because one or more lines are too long
+192
-48
@@ -3,6 +3,7 @@
|
||||
var myUserId = "@example:localhost";
|
||||
var myAccessToken = "QGV4YW1wbGU6bG9jYWxob3N0.qPEvLuYfNBjxikiCjP";
|
||||
var sdk = require("matrix-js-sdk");
|
||||
var clc = require("cli-color");
|
||||
var matrixClient = sdk.createClient({
|
||||
baseUrl: "http://localhost:8008",
|
||||
accessToken: myAccessToken,
|
||||
@@ -15,46 +16,106 @@ var viewingRoom = null;
|
||||
var numMessagesToShow = 20;
|
||||
|
||||
// Reading from stdin
|
||||
var CLEAR_CONSOLE = '\x1B[2J';
|
||||
var readline = require("readline");
|
||||
var rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
terminal: false
|
||||
completer: completer
|
||||
});
|
||||
rl.setPrompt("$ ");
|
||||
rl.on('line', function(line) {
|
||||
if (line.indexOf("/enter ") === 0 && !viewingRoom) {
|
||||
var roomIndex = line.split(" ")[1];
|
||||
viewingRoom = roomList[roomIndex];
|
||||
if (viewingRoom.getMember(myUserId).membership === "invite") {
|
||||
// join the room first
|
||||
matrixClient.joinRoom(viewingRoom.roomId).done(function() {
|
||||
if (line.trim().length === 0) {
|
||||
rl.prompt();
|
||||
return;
|
||||
}
|
||||
if (line === "/help") {
|
||||
printHelp();
|
||||
rl.prompt();
|
||||
return;
|
||||
}
|
||||
|
||||
if (viewingRoom) {
|
||||
if (line === "/exit") {
|
||||
viewingRoom = null;
|
||||
printRoomList();
|
||||
}
|
||||
else if (line === "/members") {
|
||||
printMemberList(viewingRoom);
|
||||
}
|
||||
else if (line === "/roominfo") {
|
||||
printRoomInfo(viewingRoom);
|
||||
}
|
||||
else if (line === "/resend") {
|
||||
// get the oldest not sent event.
|
||||
var notSentEvent;
|
||||
for (var i = 0; i < viewingRoom.timeline.length; i++) {
|
||||
if (viewingRoom.timeline[i].status == sdk.EventStatus.NOT_SENT) {
|
||||
notSentEvent = viewingRoom.timeline[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (notSentEvent) {
|
||||
matrixClient.resendEvent(notSentEvent, viewingRoom).done(function() {
|
||||
printMessages();
|
||||
rl.prompt();
|
||||
}, function(err) {
|
||||
printMessages();
|
||||
print("/resend Error: %s", err);
|
||||
rl.prompt();
|
||||
});
|
||||
printMessages();
|
||||
rl.prompt();
|
||||
}
|
||||
}
|
||||
else if (line.indexOf("/more ") === 0) {
|
||||
var amount = parseInt(line.split(" ")[1]) || 20;
|
||||
matrixClient.scrollback(viewingRoom, amount).done(function(room) {
|
||||
printMessages();
|
||||
rl.prompt();
|
||||
}, function(err) {
|
||||
console.log("Error: %s", err);
|
||||
print("/more Error: %s", err);
|
||||
});
|
||||
}
|
||||
else if (line.indexOf("/invite ") === 0) {
|
||||
var userId = line.split(" ")[1].trim();
|
||||
matrixClient.invite(viewingRoom.roomId, userId).done(function() {
|
||||
printMessages();
|
||||
rl.prompt();
|
||||
}, function(err) {
|
||||
print("/invite Error: %s", err);
|
||||
});
|
||||
}
|
||||
else {
|
||||
matrixClient.sendTextMessage(viewingRoom.roomId, line).finally(function() {
|
||||
printMessages();
|
||||
rl.prompt();
|
||||
});
|
||||
// print local echo immediately
|
||||
printMessages();
|
||||
}
|
||||
}
|
||||
else if (line === "/exit" && viewingRoom) {
|
||||
viewingRoom = null;
|
||||
printRoomList();
|
||||
}
|
||||
else if (line === "/members" && viewingRoom) {
|
||||
printMemberList();
|
||||
}
|
||||
else if (line === "/help") {
|
||||
printHelp();
|
||||
}
|
||||
else if (viewingRoom) {
|
||||
matrixClient.sendTextMessage(viewingRoom.roomId, line).done(function() {
|
||||
console.log('\x1B[2J'); // clear console
|
||||
printMessages();
|
||||
}, function(err) {
|
||||
console.log("Error: %s", err);
|
||||
});
|
||||
else {
|
||||
if (line.indexOf("/join ") === 0) {
|
||||
var roomIndex = line.split(" ")[1];
|
||||
viewingRoom = roomList[roomIndex];
|
||||
if (viewingRoom.getMember(myUserId).membership === "invite") {
|
||||
// join the room first
|
||||
matrixClient.joinRoom(viewingRoom.roomId).done(function(room) {
|
||||
roomList = matrixClient.getRooms();
|
||||
viewingRoom = room;
|
||||
printMessages();
|
||||
rl.prompt();
|
||||
}, function(err) {
|
||||
print("/join Error: %s", err);
|
||||
});
|
||||
}
|
||||
else {
|
||||
printMessages();
|
||||
}
|
||||
}
|
||||
}
|
||||
rl.prompt();
|
||||
});
|
||||
// ==== END User input
|
||||
|
||||
@@ -63,6 +124,15 @@ matrixClient.on("syncComplete", function() {
|
||||
roomList = matrixClient.getRooms();
|
||||
printRoomList();
|
||||
printHelp();
|
||||
rl.prompt();
|
||||
});
|
||||
|
||||
matrixClient.on("Room", function() {
|
||||
roomList = matrixClient.getRooms();
|
||||
if (!viewingRoom) {
|
||||
printRoomList();
|
||||
rl.prompt();
|
||||
}
|
||||
});
|
||||
|
||||
// print incoming messages.
|
||||
@@ -77,9 +147,9 @@ matrixClient.on("Room.timeline", function(event, room, toStartOfTimeline) {
|
||||
});
|
||||
|
||||
function printRoomList() {
|
||||
console.log("Room List:");
|
||||
print("Room List:");
|
||||
for (var i = 0; i < roomList.length; i++) {
|
||||
console.log(
|
||||
print(
|
||||
"[%s] %s (%s members)",
|
||||
i, roomList[i].name, roomList[i].getJoinedMembers().length
|
||||
);
|
||||
@@ -87,13 +157,27 @@ function printRoomList() {
|
||||
}
|
||||
|
||||
function printHelp() {
|
||||
console.log("Global commands:");
|
||||
console.log(" '/help' : Show this help.");
|
||||
console.log("Room list index commands:");
|
||||
console.log(" '/enter <index>' Enter a room, e.g. '/enter 5'");
|
||||
console.log("Room commands:");
|
||||
console.log(" '/exit' Return to the room list index.");
|
||||
console.log(" '/members' Show the room member list.");
|
||||
var hlp = clc.italic;
|
||||
print("Global commands:", hlp);
|
||||
print(" '/help' : Show this help.");
|
||||
print("Room list index commands:", hlp);
|
||||
print(" '/join <index>' Join a room, e.g. '/join 5'");
|
||||
print("Room commands:", hlp);
|
||||
print(" '/exit' Return to the room list index.");
|
||||
print(" '/members' Show the room member list.");
|
||||
print(" '/invite @foo:bar' Invite @foo:bar to the room.");
|
||||
print(" '/more 15' Scrollback 15 events");
|
||||
print(" '/resend' Resend the oldest event which failed to send.");
|
||||
print(" '/roominfo' Display room info e.g. name, topic.");
|
||||
}
|
||||
|
||||
function completer(line) {
|
||||
var completions = [
|
||||
"/help", "/join ", "/exit", "/members", "/more ", "/resend", "/invite"
|
||||
];
|
||||
var hits = completions.filter(function(c) { return c.indexOf(line) == 0 });
|
||||
// show all completions if none found
|
||||
return [hits.length ? hits : completions, line]
|
||||
}
|
||||
|
||||
function printMessages() {
|
||||
@@ -101,19 +185,15 @@ function printMessages() {
|
||||
printRoomList();
|
||||
return;
|
||||
}
|
||||
console.log('\x1B[2J'); // clear console
|
||||
var mostRecentMessages = viewingRoom.timeline.slice(numMessagesToShow * -1);
|
||||
print(CLEAR_CONSOLE);
|
||||
var mostRecentMessages = viewingRoom.timeline;
|
||||
for (var i = 0; i < mostRecentMessages.length; i++) {
|
||||
printLine(mostRecentMessages[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function printMemberList() {
|
||||
if (!viewingRoom) {
|
||||
printRoomList();
|
||||
return;
|
||||
}
|
||||
var members = viewingRoom.currentState.getMembers();
|
||||
function printMemberList(room) {
|
||||
var members = room.currentState.getMembers();
|
||||
// sorted based on name.
|
||||
members.sort(function(a, b) {
|
||||
if (a.name > b.name) {
|
||||
@@ -124,23 +204,54 @@ function printMemberList() {
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
console.log("Membership list for room \"%s\"", viewingRoom.name);
|
||||
console.log(new Array(viewingRoom.name.length + 28).join("-"));
|
||||
viewingRoom.currentState.getMembers().forEach(function(member) {
|
||||
print("Membership list for room \"%s\"", room.name);
|
||||
print(new Array(room.name.length + 28).join("-"));
|
||||
room.currentState.getMembers().forEach(function(member) {
|
||||
if (!member.membership) {
|
||||
return;
|
||||
}
|
||||
var membershipWithPadding = (
|
||||
member.membership + new Array(10 - member.membership.length).join(" ")
|
||||
);
|
||||
console.log(
|
||||
print(
|
||||
"%s :: %s (%s)", membershipWithPadding, member.name,
|
||||
(member.userId === myUserId ? "Me" : member.userId)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function printRoomInfo(room) {
|
||||
var eventDict = room.currentState.events;
|
||||
var eTypeHeader = " Event Type(state_key) ";
|
||||
var sendHeader = " Sender ";
|
||||
// pad content to 100
|
||||
var restCount = (
|
||||
100 - "Content".length - " | ".length - " | ".length -
|
||||
eTypeHeader.length - sendHeader.length
|
||||
);
|
||||
var padSide = new Array(Math.floor(restCount/2)).join(" ");
|
||||
var contentHeader = padSide + "Content" + padSide;
|
||||
print(eTypeHeader+sendHeader+contentHeader);
|
||||
print(new Array(100).join("-"));
|
||||
Object.keys(eventDict).forEach(function(eventType) {
|
||||
if (eventType === "m.room.member") { return; } // use /members instead.
|
||||
Object.keys(eventDict[eventType]).forEach(function(stateKey) {
|
||||
var typeAndKey = eventType + (
|
||||
stateKey.length > 0 ? "("+stateKey+")" : ""
|
||||
);
|
||||
var typeStr = fixWidth(typeAndKey, eTypeHeader.length);
|
||||
var event = eventDict[eventType][stateKey];
|
||||
var sendStr = fixWidth(event.getSender(), sendHeader.length);
|
||||
var contentStr = fixWidth(
|
||||
JSON.stringify(event.getContent()), contentHeader.length
|
||||
);
|
||||
print(typeStr+" | "+sendStr+" | "+contentStr);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function printLine(event) {
|
||||
var fmt;
|
||||
var name = event.sender ? event.sender.name : event.getSender();
|
||||
var time = new Date(
|
||||
event.getTs()
|
||||
@@ -149,6 +260,14 @@ function printLine(event) {
|
||||
if (event.getSender() === myUserId) {
|
||||
name = "Me";
|
||||
separator = ">>>";
|
||||
if (event.status === sdk.EventStatus.SENDING) {
|
||||
separator = "...";
|
||||
fmt = clc.xterm(8);
|
||||
}
|
||||
else if (event.status === sdk.EventStatus.NOT_SENT) {
|
||||
separator = " x ";
|
||||
fmt = clc.redBright;
|
||||
}
|
||||
}
|
||||
var body = "";
|
||||
|
||||
@@ -177,7 +296,32 @@ function printLine(event) {
|
||||
);
|
||||
separator = "---";
|
||||
}
|
||||
console.log("[%s] %s %s %s", time, name, separator, body);
|
||||
if (fmt) {
|
||||
print(
|
||||
"[%s] %s %s %s", fmt(time), fmt(name), fmt(separator), fmt(body)
|
||||
);
|
||||
}
|
||||
else {
|
||||
print("[%s] %s %s %s", time, name, separator, body);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function print(str, formatter) {
|
||||
if (arguments.length == 2 && typeof arguments[1] === "function") {
|
||||
console.log(arguments[1](str));
|
||||
return;
|
||||
}
|
||||
console.log.apply(console.log, arguments);
|
||||
}
|
||||
|
||||
function fixWidth(str, len) {
|
||||
if (str.length > len) {
|
||||
return str.substr(0, len-2) + "\u2026";
|
||||
}
|
||||
else if (str.length < len) {
|
||||
return str + new Array(len - str.length).join(" ");
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
matrixClient.startClient(numMessagesToShow); // messages for each room.
|
||||
@@ -7,5 +7,8 @@
|
||||
"preinstall": "npm install ../.."
|
||||
},
|
||||
"author": "",
|
||||
"license": "Apache 2.0"
|
||||
"license": "Apache 2.0",
|
||||
"dependencies": {
|
||||
"cli-color": "^1.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
+303
-79
@@ -4,36 +4,55 @@
|
||||
* @module client
|
||||
*/
|
||||
var EventEmitter = require("events").EventEmitter;
|
||||
var q = require("q");
|
||||
|
||||
var httpApi = require("./http-api");
|
||||
var MatrixEvent = require("./models/event").MatrixEvent;
|
||||
var EventStatus = require("./models/event").EventStatus;
|
||||
var StubStore = require("./store/stub");
|
||||
var Room = require("./models/room");
|
||||
var User = require("./models/user");
|
||||
var MatrixInMemoryStore = require("./store/memory").MatrixInMemoryStore;
|
||||
var utils = require("./utils");
|
||||
|
||||
// TODO:
|
||||
// Internal: rate limiting
|
||||
|
||||
/**
|
||||
* Construct a Matrix Client.
|
||||
* Construct a Matrix Client. Only directly construct this if you want to use
|
||||
* custom modules. Normally, {@link createClient} should be used
|
||||
* as it specifies 'sensible' defaults for these modules.
|
||||
* @constructor
|
||||
* @extends {external:EventEmitter}
|
||||
* @param {Object} opts The configuration options for this client.
|
||||
* @param {string} opts.baseUrl Required. The base URL to the client-server HTTP API.
|
||||
* @param {Function} opts.request Required. The function to invoke for HTTP requests.
|
||||
* @param {string} opts.baseUrl Required. The base URL to the client-server
|
||||
* HTTP API.
|
||||
* @param {Function} opts.request Required. The function to invoke for HTTP
|
||||
* requests. The value of this property is typically <code>require("request")
|
||||
* </code> as it returns a function which meets the required interface. See
|
||||
* {@link requestFunction} for more information.
|
||||
* @param {string} opts.accessToken The access_token for this user.
|
||||
* @param {string} opts.userId The user ID for this user.
|
||||
* @param {Object} opts.store Optional. The data store to use. Defaults to
|
||||
* {@link module:store/memory.MatrixInMemoryStore}.
|
||||
* @param {Object} opts.store Optional. The data store to use. If not specified,
|
||||
* this client will not store any HTTP responses.
|
||||
* @param {Object} opts.scheduler Optional. The scheduler to use. If not
|
||||
* specified, this client will not retry requests on failure. This client
|
||||
* will supply its own processing function to
|
||||
* {@link module:scheduler~MatrixScheduler#setProcessFunction}.
|
||||
*/
|
||||
function MatrixClient(opts) {
|
||||
utils.checkObjectHasKeys(opts, ["baseUrl", "request"]);
|
||||
utils.checkObjectHasNoAdditionalKeys(opts,
|
||||
["baseUrl", "request", "accessToken", "userId", "store"]
|
||||
["baseUrl", "request", "accessToken", "userId", "store", "scheduler"]
|
||||
);
|
||||
|
||||
this.store = opts.store || new MatrixInMemoryStore();
|
||||
this.store = opts.store || new StubStore();
|
||||
this.scheduler = opts.scheduler;
|
||||
if (this.scheduler) {
|
||||
var self = this;
|
||||
this.scheduler.setProcessFunction(function(eventToSend) {
|
||||
return _sendEventHttpRequest(self, eventToSend);
|
||||
});
|
||||
}
|
||||
// track our position in the overall eventstream
|
||||
this.fromToken = undefined;
|
||||
this.clientRunning = false;
|
||||
@@ -49,6 +68,9 @@ function MatrixClient(opts) {
|
||||
userId: (opts.userId || null)
|
||||
};
|
||||
this._http = new httpApi.MatrixHttpApi(httpOpts);
|
||||
this._syncingRooms = {
|
||||
// room_id: Promise
|
||||
};
|
||||
}
|
||||
utils.inherits(MatrixClient, EventEmitter);
|
||||
|
||||
@@ -58,9 +80,6 @@ utils.inherits(MatrixClient, EventEmitter);
|
||||
* @return {Room} The Room or null if it doesn't exist or there is no data store.
|
||||
*/
|
||||
MatrixClient.prototype.getRoom = function(roomId) {
|
||||
if (!this.store) {
|
||||
return null;
|
||||
}
|
||||
return this.store.getRoom(roomId);
|
||||
};
|
||||
|
||||
@@ -69,9 +88,6 @@ MatrixClient.prototype.getRoom = function(roomId) {
|
||||
* @return {Room[]} A list of rooms, or an empty list if there is no data store.
|
||||
*/
|
||||
MatrixClient.prototype.getRooms = function() {
|
||||
if (!this.store) {
|
||||
return [];
|
||||
}
|
||||
return this.store.getRooms();
|
||||
};
|
||||
|
||||
@@ -103,12 +119,39 @@ MatrixClient.prototype.createRoom = function(options, callback) {
|
||||
* Join a room.
|
||||
* @param {string} roomIdOrAlias The room ID or room alias to join.
|
||||
* @param {module:client.callback} callback Optional.
|
||||
* @return {module:client.Promise} Resolves: <code>{room_id: {string}}</code>
|
||||
* @return {module:client.Promise} Resolves: Room object.
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
MatrixClient.prototype.joinRoom = function(roomIdOrAlias, callback) {
|
||||
var path = utils.encodeUri("/join/$roomid", { $roomid: roomIdOrAlias});
|
||||
return this._http.authedRequest(callback, "POST", path, undefined, {});
|
||||
var defer = q.defer();
|
||||
var self = this;
|
||||
this._http.authedRequest(undefined, "POST", path, undefined, {}).then(
|
||||
function(res) {
|
||||
var roomId = res.room_id;
|
||||
var room = createNewRoom(self, roomId);
|
||||
return _syncRoom(self, room);
|
||||
}, function(err) {
|
||||
_reject(callback, defer, err);
|
||||
}).done(function(room) {
|
||||
_resolve(callback, defer, room);
|
||||
}, function(err) {
|
||||
_reject(callback, defer, err);
|
||||
});
|
||||
return defer.promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* Resend an event.
|
||||
* @param {MatrixEvent} event The event to resend.
|
||||
* @param {Room} room Optional. The room the event is in. Will update the
|
||||
* timeline entry if provided.
|
||||
* @return {module:client.Promise} Resolves: TODO
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
MatrixClient.prototype.resendEvent = function(event, room) {
|
||||
event.status = EventStatus.SENDING;
|
||||
return _sendEvent(this, room, event);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -223,21 +266,109 @@ MatrixClient.prototype.sendStateEvent = function(roomId, eventType, content, sta
|
||||
MatrixClient.prototype.sendEvent = function(roomId, eventType, content, txnId,
|
||||
callback) {
|
||||
if (utils.isFunction(txnId)) { callback = txnId; txnId = undefined; }
|
||||
|
||||
if (!txnId) {
|
||||
txnId = "m" + new Date().getTime();
|
||||
}
|
||||
|
||||
var path = utils.encodeUri("/rooms/$roomId/send/$eventType/$txnId", {
|
||||
$roomId: roomId,
|
||||
$eventType: eventType,
|
||||
$txnId: txnId
|
||||
// we always construct a MatrixEvent when sending because the store and
|
||||
// scheduler use them. We'll extract the params back out if it turns out
|
||||
// the client has no scheduler or store.
|
||||
var room = this.getRoom(roomId);
|
||||
var localEvent = new MatrixEvent({
|
||||
event_id: "~" + roomId + ":" + txnId,
|
||||
user_id: this.credentials.userId,
|
||||
room_id: roomId,
|
||||
type: eventType,
|
||||
origin_server_ts: new Date().getTime(),
|
||||
content: content
|
||||
});
|
||||
return this._http.authedRequest(
|
||||
callback, "PUT", path, undefined, content
|
||||
);
|
||||
localEvent._txnId = txnId;
|
||||
|
||||
// add this event immediately to the local store as 'sending'.
|
||||
if (room) {
|
||||
localEvent.status = EventStatus.SENDING;
|
||||
room.addEventsToTimeline([localEvent]);
|
||||
}
|
||||
|
||||
return _sendEvent(this, room, localEvent, callback);
|
||||
};
|
||||
|
||||
function _sendEvent(client, room, event, callback) {
|
||||
var defer = q.defer();
|
||||
var promise;
|
||||
// this event may be queued
|
||||
if (client.scheduler) {
|
||||
// if this returns a promsie then the scheduler has control now and will
|
||||
// resolve/reject when it is done. Internally, the scheduler will invoke
|
||||
// processFn which is set to this._sendEventHttpRequest so the same code
|
||||
// path is executed regardless.
|
||||
promise = client.scheduler.queueEvent(event);
|
||||
}
|
||||
|
||||
if (!promise) {
|
||||
promise = _sendEventHttpRequest(client, event);
|
||||
}
|
||||
|
||||
promise.done(function(res) { // the request was sent OK
|
||||
if (room) {
|
||||
var eventId = res.event_id;
|
||||
// try to find an event with this event_id. If we find it, this is
|
||||
// the echo of this event *from the event stream* so we can remove
|
||||
// the fake event we made above. If we don't find it, we're still
|
||||
// waiting on the fake event and so should assign the fake event
|
||||
// with the real event_id for matching later.
|
||||
var matchingEvent = utils.findElement(room.timeline, function(ev) {
|
||||
return ev.getId() === eventId;
|
||||
}, true);
|
||||
if (matchingEvent) {
|
||||
utils.removeElement(room.timeline, function(ev) {
|
||||
return ev.getId() === event.getId();
|
||||
}, true);
|
||||
}
|
||||
else {
|
||||
event.event.event_id = res.event_id;
|
||||
event.status = null;
|
||||
}
|
||||
}
|
||||
|
||||
_resolve(callback, defer, res);
|
||||
}, function(err) {
|
||||
// the request failed to send.
|
||||
event.status = EventStatus.NOT_SENT;
|
||||
_reject(callback, defer, err);
|
||||
});
|
||||
|
||||
return defer.promise;
|
||||
}
|
||||
|
||||
function _sendEventHttpRequest(client, event) {
|
||||
var pathParams = {
|
||||
$roomId: event.getRoomId(),
|
||||
$eventType: event.getType(),
|
||||
$stateKey: event.getStateKey(),
|
||||
$txnId: event._txnId ? event._txnId : new Date().getTime()
|
||||
};
|
||||
|
||||
var path;
|
||||
|
||||
if (event.isState()) {
|
||||
var pathTemplate = "/rooms/$roomId/state/$eventType";
|
||||
if (event.getStateKey() && event.getStateKey().length > 0) {
|
||||
pathTemplate = "/rooms/$roomId/state/$eventType/$stateKey";
|
||||
}
|
||||
path = utils.encodeUri(pathTemplate, pathParams);
|
||||
}
|
||||
else {
|
||||
path = utils.encodeUri(
|
||||
"/rooms/$roomId/send/$eventType/$txnId", pathParams
|
||||
);
|
||||
}
|
||||
|
||||
return client._http.authedRequest(
|
||||
undefined, "PUT", path, undefined, event.getContent()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} roomId
|
||||
* @param {Object} content
|
||||
@@ -668,25 +799,36 @@ MatrixClient.prototype.roomState = function(roomId, callback) {
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} roomId
|
||||
* @param {string} from
|
||||
* @param {Number} limit
|
||||
* Retrieve older messages from the given room and put them in the timeline.
|
||||
* @param {Room} room The room to get older messages in.
|
||||
* @param {Integer} limit Optional. The maximum number of previous events to
|
||||
* pull in. Default: 30.
|
||||
* @param {module:client.callback} callback Optional.
|
||||
* @return {module:client.Promise} Resolves: TODO
|
||||
* @return {module:client.Promise} Resolves: Room.
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
MatrixClient.prototype.scrollback = function(roomId, from, limit, callback) {
|
||||
MatrixClient.prototype.scrollback = function(room, limit, callback) {
|
||||
if (utils.isFunction(limit)) { callback = limit; limit = undefined; }
|
||||
var path = utils.encodeUri("/rooms/$roomId/messages", {$roomId: roomId});
|
||||
if (!limit) {
|
||||
limit = 30;
|
||||
}
|
||||
var path = utils.encodeUri(
|
||||
"/rooms/$roomId/messages", {$roomId: room.roomId}
|
||||
);
|
||||
var params = {
|
||||
from: from,
|
||||
limit: limit,
|
||||
from: room.oldState.paginationToken,
|
||||
limit: (limit || 30),
|
||||
dir: 'b'
|
||||
};
|
||||
return this._http.authedRequest(callback, "GET", path, params);
|
||||
var defer = q.defer();
|
||||
this._http.authedRequest(callback, "GET", path, params).done(function(res) {
|
||||
// res.chunk end start
|
||||
room.addEventsToTimeline(
|
||||
utils.map(res.chunk, _PojoToMatrixEventMapper), true
|
||||
);
|
||||
room.oldState.paginationToken = res.end;
|
||||
_resolve(callback, defer, room);
|
||||
}, function(err) {
|
||||
_reject(callback, defer, err);
|
||||
});
|
||||
return defer.promise;
|
||||
};
|
||||
|
||||
// Registration/Login operations
|
||||
@@ -803,10 +945,6 @@ MatrixClient.prototype.isLoggedIn = function() {
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Higher level APIs
|
||||
// =================
|
||||
|
||||
@@ -846,11 +984,9 @@ MatrixClient.prototype.startClient = function(historyLen) {
|
||||
).done(function(data) {
|
||||
var i, j;
|
||||
// intercept the results and put them into our store
|
||||
if (self.store) {
|
||||
var eventMapper = function(event) {
|
||||
return new MatrixEvent(event);
|
||||
};
|
||||
utils.forEach(utils.map(data.presence, eventMapper), function(e) {
|
||||
if (!(self.store instanceof StubStore)) {
|
||||
utils.forEach(utils.map(data.presence, _PojoToMatrixEventMapper),
|
||||
function(e) {
|
||||
var user = createNewUser(self, e.getContent().user_id);
|
||||
user.setPresenceEvent(e);
|
||||
self.store.storeUser(user);
|
||||
@@ -874,27 +1010,8 @@ MatrixClient.prototype.startClient = function(historyLen) {
|
||||
});
|
||||
}
|
||||
|
||||
// "old" and "current" state are the same initially; they
|
||||
// start diverging if the user paginates.
|
||||
// We must deep copy otherwise membership changes in old state
|
||||
// will leak through to current state!
|
||||
var oldStateEvents = utils.map(
|
||||
utils.deepCopy(data.rooms[i].state), eventMapper
|
||||
);
|
||||
var stateEvents = utils.map(data.rooms[i].state, eventMapper);
|
||||
room.oldState.setStateEvents(oldStateEvents);
|
||||
room.currentState.setStateEvents(stateEvents);
|
||||
|
||||
// add events to the timeline *after* setting the state
|
||||
// events so messages use the right display names. Initial sync
|
||||
// returns messages in chronological order, so we need to reverse
|
||||
// it to get most recent -> oldest. We need it in that order in
|
||||
// order to diverge old/current state correctly.
|
||||
room.addEventsToTimeline(
|
||||
utils.map(
|
||||
data.rooms[i].messages ? data.rooms[i].messages.chunk : [],
|
||||
eventMapper
|
||||
).reverse(), true
|
||||
_processRoomEvents(
|
||||
room, data.rooms[i].state, data.rooms[i].messages
|
||||
);
|
||||
|
||||
// cache the name/summary/etc prior to storage since we don't
|
||||
@@ -902,6 +1019,7 @@ MatrixClient.prototype.startClient = function(historyLen) {
|
||||
room.recalculate(self.credentials.userId);
|
||||
|
||||
self.store.storeRoom(room);
|
||||
self.emit("Room", room);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -948,17 +1066,29 @@ function _pollForEvents(client) {
|
||||
if (!client.clientRunning) {
|
||||
return;
|
||||
}
|
||||
var discardResult = false;
|
||||
var timeoutObj = setTimeout(function() {
|
||||
discardResult = true;
|
||||
_pollForEvents(client);
|
||||
}, 40000);
|
||||
|
||||
client._http.authedRequest(undefined, "GET", "/events", {
|
||||
from: client.fromToken,
|
||||
timeout: 30000
|
||||
}).done(function(data) {
|
||||
if (discardResult) {
|
||||
return;
|
||||
}
|
||||
else {
|
||||
clearTimeout(timeoutObj);
|
||||
}
|
||||
var events = [];
|
||||
if (data) {
|
||||
events = utils.map(data.chunk, function(event) {
|
||||
return new MatrixEvent(event);
|
||||
});
|
||||
}
|
||||
if (self.store) {
|
||||
if (!(self.store instanceof StubStore)) {
|
||||
// bucket events based on room.
|
||||
var i = 0;
|
||||
var roomIdToEvents = {};
|
||||
@@ -974,17 +1104,38 @@ function _pollForEvents(client) {
|
||||
}
|
||||
// add events to room
|
||||
var roomIds = utils.keys(roomIdToEvents);
|
||||
for (i = 0; i < roomIds.length; i++) {
|
||||
var room = self.store.getRoom(roomIds[i]);
|
||||
utils.forEach(roomIds, function(roomId) {
|
||||
var room = self.store.getRoom(roomId);
|
||||
var isBrandNewRoom = false;
|
||||
if (!room) {
|
||||
// TODO: whine about this. We got an event for a room
|
||||
// we don't know about (we should really be doing a
|
||||
// roomInitialSync at this point to pull in state).
|
||||
room = createNewRoom(self, roomIds[i]);
|
||||
room = createNewRoom(self, roomId);
|
||||
isBrandNewRoom = true;
|
||||
}
|
||||
room.addEvents(roomIdToEvents[roomIds[i]]);
|
||||
|
||||
var wasJoined = room.hasMembershipState(
|
||||
self.credentials.userId, "join"
|
||||
);
|
||||
|
||||
room.addEvents(roomIdToEvents[roomId], "replace");
|
||||
room.recalculate(self.credentials.userId);
|
||||
}
|
||||
|
||||
// store the Room for things like invite events so developers
|
||||
// can update the UI
|
||||
if (isBrandNewRoom) {
|
||||
self.store.storeRoom(room);
|
||||
self.emit("Room", room);
|
||||
}
|
||||
|
||||
var justJoined = room.hasMembershipState(
|
||||
self.credentials.userId, "join"
|
||||
);
|
||||
|
||||
if (!wasJoined && justJoined) {
|
||||
// we've just transitioned into a join state for this room,
|
||||
// so sync state.
|
||||
_syncRoom(self, room);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (data) {
|
||||
self.fromToken = data.end;
|
||||
@@ -994,6 +1145,12 @@ function _pollForEvents(client) {
|
||||
}
|
||||
_pollForEvents(self);
|
||||
}, function(err) {
|
||||
if (discardResult) {
|
||||
return;
|
||||
}
|
||||
else {
|
||||
clearTimeout(timeoutObj);
|
||||
}
|
||||
self.emit("syncError", err);
|
||||
// retry every few seconds
|
||||
// FIXME: this should be exponential backoff with an option to nudge
|
||||
@@ -1003,6 +1160,55 @@ function _pollForEvents(client) {
|
||||
});
|
||||
}
|
||||
|
||||
function _syncRoom(client, room) {
|
||||
if (client._syncingRooms[room.roomId]) {
|
||||
return client._syncingRooms[room.roomId];
|
||||
}
|
||||
var defer = q.defer();
|
||||
client._syncingRooms[room.roomId] = defer.promise;
|
||||
client.roomInitialSync(room.roomId, 8).done(function(res) {
|
||||
room.timeline = []; // blow away any previous messages.
|
||||
_processRoomEvents(room, res.state, res.messages);
|
||||
room.recalculate(client.credentials.userId);
|
||||
client.store.storeRoom(room);
|
||||
client.emit("Room", room);
|
||||
defer.resolve(room);
|
||||
client._syncingRooms[room.roomId] = undefined;
|
||||
}, function(err) {
|
||||
defer.reject(err);
|
||||
client._syncingRooms[room.roomId] = undefined;
|
||||
});
|
||||
return defer.promise;
|
||||
}
|
||||
|
||||
function _processRoomEvents(room, stateEventList, messageChunk) {
|
||||
// "old" and "current" state are the same initially; they
|
||||
// start diverging if the user paginates.
|
||||
// We must deep copy otherwise membership changes in old state
|
||||
// will leak through to current state!
|
||||
var oldStateEvents = utils.map(
|
||||
utils.deepCopy(stateEventList), _PojoToMatrixEventMapper
|
||||
);
|
||||
var stateEvents = utils.map(stateEventList, _PojoToMatrixEventMapper);
|
||||
room.oldState.setStateEvents(oldStateEvents);
|
||||
room.currentState.setStateEvents(stateEvents);
|
||||
|
||||
// add events to the timeline *after* setting the state
|
||||
// events so messages use the right display names. Initial sync
|
||||
// returns messages in chronological order, so we need to reverse
|
||||
// it to get most recent -> oldest. We need it in that order in
|
||||
// order to diverge old/current state correctly.
|
||||
room.addEventsToTimeline(
|
||||
utils.map(
|
||||
messageChunk ? messageChunk.chunk : [],
|
||||
_PojoToMatrixEventMapper
|
||||
).reverse(), true
|
||||
);
|
||||
if (messageChunk) {
|
||||
room.oldState.paginationToken = messageChunk.start;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* High level helper method to stop the client from polling and allow a
|
||||
* clean shutdown.
|
||||
@@ -1040,7 +1246,6 @@ function createNewUser(client, userId) {
|
||||
function createNewRoom(client, roomId) {
|
||||
var room = new Room(roomId);
|
||||
reEmit(client, room, ["Room.name", "Room.timeline"]);
|
||||
client.emit("Room", room); // emit created room event
|
||||
|
||||
// we need to also re-emit room state and room member events, so hook it up
|
||||
// to the client now. We need to add a listener for RoomState.members in
|
||||
@@ -1060,6 +1265,24 @@ function createNewRoom(client, roomId) {
|
||||
return room;
|
||||
}
|
||||
|
||||
function _reject(callback, defer, err) {
|
||||
if (callback) {
|
||||
callback(err);
|
||||
}
|
||||
defer.reject(err);
|
||||
}
|
||||
|
||||
function _resolve(callback, defer, res) {
|
||||
if (callback) {
|
||||
callback(null, res);
|
||||
}
|
||||
defer.resolve(res);
|
||||
}
|
||||
|
||||
function _PojoToMatrixEventMapper(plainOldJsObject) {
|
||||
return new MatrixEvent(plainOldJsObject);
|
||||
}
|
||||
|
||||
/** */
|
||||
module.exports.MatrixClient = MatrixClient;
|
||||
|
||||
@@ -1097,10 +1320,11 @@ module.exports.MatrixClient = MatrixClient;
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fires whenever a new Room is created. This Room will not be fully populated
|
||||
* yet (e.g. no members). <strong>This event is experimental and may change.</strong>
|
||||
* Fires whenever a new Room is added. This will fire when you are invited to a
|
||||
* room, as well as when you join a room. <strong>This event is experimental and
|
||||
* may change.</strong>
|
||||
* @event module:client~MatrixClient#"Room"
|
||||
* @param {Room} room The newly created room.
|
||||
* @param {Room} room The newly created, fully populated room.
|
||||
* @example
|
||||
* matrixClient.on("Room", function(room){
|
||||
* var roomId = room.roomId;
|
||||
|
||||
+12
-5
@@ -2,6 +2,8 @@
|
||||
|
||||
/** The {@link module:models/event.MatrixEvent|MatrixEvent} class. */
|
||||
module.exports.MatrixEvent = require("./models/event").MatrixEvent;
|
||||
/** The {@link module:models/event.EventStatus|EventStatus} enum. */
|
||||
module.exports.EventStatus = require("./models/event").EventStatus;
|
||||
/** The {@link module:store/memory.MatrixInMemoryStore|MatrixInMemoryStore} class. */
|
||||
module.exports.MatrixInMemoryStore = require("./store/memory").MatrixInMemoryStore;
|
||||
/** The {@link module:http-api.MatrixHttpApi|MatrixHttpApi} class. */
|
||||
@@ -18,6 +20,8 @@ module.exports.RoomMember = require("./models/room-member");
|
||||
module.exports.RoomState = require("./models/room-state");
|
||||
/** The {@link module:models/user~User|User} class. */
|
||||
module.exports.User = require("./models/user");
|
||||
/** The {@link module:scheduler~MatrixScheduler|MatrixScheduler} class. */
|
||||
module.exports.MatrixScheduler = require("./scheduler");
|
||||
|
||||
// expose the underlying request object so different environments can use
|
||||
// different request libs (e.g. request or browser-request)
|
||||
@@ -33,15 +37,16 @@ module.exports.request = function(r) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct a Matrix Client. Identical to {@link module:client.MatrixClient}
|
||||
* except the 'request' option is already specified.
|
||||
* Construct a Matrix Client. Similar to {@link module:client~MatrixClient}
|
||||
* except that the 'request', 'store' and 'scheduler' dependencies are satisfied.
|
||||
* @param {(Object|string)} opts The configuration options for this client. If
|
||||
* this is a string, it is assumed to be the base URL.
|
||||
* this is a string, it is assumed to be the base URL. These configuration
|
||||
* options will be passed directly to {@link module:client~MatrixClient}.
|
||||
* @param {string} opts.baseUrl The base URL to the client-server HTTP API.
|
||||
* @param {string} opts.accessToken Optional. The access_token for this user.
|
||||
* @param {string} opts.userId Optional. The user ID for this user.
|
||||
* @param {Object} opts.store Optional. The data store to use. Defaults to
|
||||
* {@link module:store/memory.MatrixInMemoryStore}.
|
||||
* @param {Object} opts.store Set to {@link module:store/memory.MatrixInMemoryStore}.
|
||||
* @param {Object} opts.scheduler Set to {@link module:scheduler~MatrixScheduler}.
|
||||
* @return {MatrixClient} A new matrix client.
|
||||
*/
|
||||
module.exports.createClient = function(opts) {
|
||||
@@ -51,6 +56,8 @@ module.exports.createClient = function(opts) {
|
||||
};
|
||||
}
|
||||
opts.request = request;
|
||||
opts.store = new module.exports.MatrixInMemoryStore();
|
||||
opts.scheduler = new module.exports.MatrixScheduler();
|
||||
return new module.exports.MatrixClient(opts);
|
||||
};
|
||||
|
||||
|
||||
+4
-4
@@ -12,11 +12,10 @@
|
||||
* @enum {string}
|
||||
*/
|
||||
module.exports.EventStatus = {
|
||||
UNKNOWN: "unknown",
|
||||
SENT: "sent",
|
||||
/** The event was not sent and will no longer be retried. */
|
||||
NOT_SENT: "not_sent",
|
||||
SENDING: "sending",
|
||||
INCOMING: "incoming"
|
||||
/** The event is in the process of being sent. */
|
||||
SENDING: "sending"
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -44,6 +43,7 @@ module.exports.MatrixEvent = function MatrixEvent(event) {
|
||||
this.forwardLooking = true;
|
||||
};
|
||||
module.exports.MatrixEvent.prototype = {
|
||||
|
||||
/**
|
||||
* Get the event_id for this event.
|
||||
* @return {string} The event ID, e.g. <code>$143350589368169JsLZx:localhost
|
||||
|
||||
@@ -20,7 +20,8 @@ var utils = require("../utils");
|
||||
* room member.
|
||||
* @prop {User} user The User object for this room member, if one exists.
|
||||
* @prop {string} membership The membership state for this room member e.g. 'join'.
|
||||
* @throws If the event provided is not <code>m.room.member</code>
|
||||
* @prop {Object} events The events describing this RoomMember.
|
||||
* @prop {MatrixEvent} events.member The m.room.member event for this RoomMember.
|
||||
*/
|
||||
function RoomMember(roomId, userId) {
|
||||
this.roomId = roomId;
|
||||
@@ -31,6 +32,9 @@ function RoomMember(roomId, userId) {
|
||||
this.powerLevelNorm = 0;
|
||||
this.user = null;
|
||||
this.membership = null;
|
||||
this.events = {
|
||||
member: null
|
||||
};
|
||||
}
|
||||
utils.inherits(RoomMember, EventEmitter);
|
||||
|
||||
@@ -47,6 +51,8 @@ RoomMember.prototype.setMembershipEvent = function(event, roomState) {
|
||||
if (event.getType() !== "m.room.member") {
|
||||
return;
|
||||
}
|
||||
this.events.member = event;
|
||||
|
||||
var oldMembership = this.membership;
|
||||
this.membership = event.getDirectionalContent().membership;
|
||||
|
||||
@@ -119,15 +125,6 @@ RoomMember.prototype.setTypingEvent = function(event) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the membership state of this room member.
|
||||
* @return {string} The membership state e.g. 'join'.
|
||||
*/
|
||||
RoomMember.prototype.getMembershipState = function() {
|
||||
return this.event.getContent().membership;
|
||||
};
|
||||
|
||||
|
||||
function calculateDisplayName(member, event, roomState) {
|
||||
var displayName = event.getDirectionalContent().displayname;
|
||||
var selfUserId = member.userId;
|
||||
|
||||
@@ -176,7 +176,7 @@ module.exports = RoomState;
|
||||
* @param {RoomMember} member The room member that was updated.
|
||||
* @example
|
||||
* matrixClient.on("RoomState.members", function(event, state, member){
|
||||
* var newMembershipState = member.getMembershipState();
|
||||
* var newMembershipState = member.membership;
|
||||
* });
|
||||
*/
|
||||
|
||||
|
||||
+55
-9
@@ -55,6 +55,18 @@ utils.inherits(Room, EventEmitter);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the given user_id has the given membership state.
|
||||
* @param {string} userId The user ID to check.
|
||||
* @param {string} membership The membership e.g. <code>'join'</code>
|
||||
* @return {boolean} True if this user_id has the given membership state.
|
||||
*/
|
||||
Room.prototype.hasMembershipState = function(userId, membership) {
|
||||
return utils.filter(this.currentState.getMembers(), function(m) {
|
||||
return m.membership === membership && m.userId === userId;
|
||||
}).length > 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add some events to this room's timeline. Will fire "Room.timeline" for
|
||||
* each event added.
|
||||
@@ -104,13 +116,45 @@ Room.prototype.addEventsToTimeline = function(events, toStartOfTimeline) {
|
||||
* 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
|
||||
* 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) {
|
||||
Room.prototype.addEvents = function(events, duplicateStrategy) {
|
||||
if (duplicateStrategy && ["replace", "ignore"].indexOf(duplicateStrategy) === -1) {
|
||||
throw new Error("duplicateStrategy MUST be either 'replace' or 'ignore'");
|
||||
}
|
||||
for (var i = 0; i < events.length; i++) {
|
||||
if (events[i].getType() === "m.typing") {
|
||||
this.currentState.setTypingEvent(events[i]);
|
||||
}
|
||||
else {
|
||||
if (duplicateStrategy) {
|
||||
// is there a duplicate?
|
||||
var shouldIgnore = false;
|
||||
for (var j = 0; j < this.timeline.length; j++) {
|
||||
if (this.timeline[j].getId() === events[i].getId()) {
|
||||
if (duplicateStrategy === "replace") {
|
||||
this.timeline[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.addEventsToTimeline([events[i]]);
|
||||
@@ -127,7 +171,7 @@ Room.prototype.addEvents = function(events) {
|
||||
*/
|
||||
Room.prototype.recalculate = function(userId) {
|
||||
var oldName = this.name;
|
||||
this.name = this.calculateRoomName(userId);
|
||||
this.name = calculateRoomName(this, userId);
|
||||
this.summary = new RoomSummary(this.roomId, {
|
||||
title: this.name
|
||||
});
|
||||
@@ -138,21 +182,23 @@ Room.prototype.recalculate = function(userId) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates the name of the room from the current room state.
|
||||
* This is an internal method. Calculates the name of the room from the current
|
||||
* room state.
|
||||
* @param {Room} room The matrix room.
|
||||
* @param {string} userId The client's user ID. Used to filter room members
|
||||
* correctly.
|
||||
* @return {string} The calculated room name.
|
||||
*/
|
||||
Room.prototype.calculateRoomName = function(userId) {
|
||||
function calculateRoomName(room, userId) {
|
||||
// check for an alias, if any. for now, assume first alias is the
|
||||
// official one.
|
||||
var alias;
|
||||
var mRoomAliases = this.currentState.getStateEvents("m.room.aliases")[0];
|
||||
var mRoomAliases = room.currentState.getStateEvents("m.room.aliases")[0];
|
||||
if (mRoomAliases && utils.isArray(mRoomAliases.getContent().aliases)) {
|
||||
alias = mRoomAliases.getContent().aliases[0];
|
||||
}
|
||||
|
||||
var mRoomName = this.currentState.getStateEvents('m.room.name', '');
|
||||
var mRoomName = room.currentState.getStateEvents('m.room.name', '');
|
||||
if (mRoomName) {
|
||||
return mRoomName.getContent().name + (alias ? " (" + alias + ")" : "");
|
||||
}
|
||||
@@ -161,12 +207,12 @@ Room.prototype.calculateRoomName = function(userId) {
|
||||
}
|
||||
else {
|
||||
// get members that are NOT ourselves.
|
||||
var members = utils.filter(this.currentState.getMembers(), function(m) {
|
||||
var members = utils.filter(room.currentState.getMembers(), function(m) {
|
||||
return m.userId !== userId;
|
||||
});
|
||||
// TODO: Localisation
|
||||
if (members.length === 0) {
|
||||
var memberList = this.currentState.getMembers();
|
||||
var memberList = room.currentState.getMembers();
|
||||
if (memberList.length === 1) {
|
||||
// we exist, but no one else... self-chat or invite.
|
||||
if (memberList[0].membership === "invite") {
|
||||
@@ -195,7 +241,7 @@ Room.prototype.calculateRoomName = function(userId) {
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The Room class.
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
* @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 {Object} events The events describing this user.
|
||||
* @prop {MatrixEvent} events.presence The m.presence event for this user.
|
||||
*/
|
||||
function User(userId) {
|
||||
this.userId = userId;
|
||||
@@ -23,6 +25,10 @@ function User(userId) {
|
||||
this.displayName = userId;
|
||||
this.avatarUrl = null;
|
||||
this.lastActiveAgo = 0;
|
||||
this.events = {
|
||||
presence: null,
|
||||
profile: null
|
||||
};
|
||||
}
|
||||
utils.inherits(User, EventEmitter);
|
||||
|
||||
@@ -39,6 +45,8 @@ User.prototype.setPresenceEvent = function(event) {
|
||||
if (event.getType() !== "m.presence") {
|
||||
return;
|
||||
}
|
||||
this.events.presence = event;
|
||||
|
||||
var eventsToFire = [];
|
||||
if (event.getContent().presence !== this.presence) {
|
||||
eventsToFire.push("User.presence");
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
"use strict";
|
||||
/**
|
||||
* This is an internal module which manages queuing, scheduling and retrying
|
||||
* of requests.
|
||||
* @module scheduler
|
||||
*/
|
||||
var utils = require("./utils");
|
||||
var q = require("q");
|
||||
|
||||
var DEBUG = false; // set true to enable console logging.
|
||||
|
||||
/**
|
||||
* Construct a scheduler for Matrix. Requires
|
||||
* {@link module:scheduler~MatrixScheduler#setProcessFunction} to be provided
|
||||
* with a way of processing events.
|
||||
* @constructor
|
||||
* @param {module:scheduler~retryAlgorithm} retryAlgorithm Optional. The retry
|
||||
* algorithm to apply when determining when to try to send an event again.
|
||||
* Defaults to {@link module:scheduler~MatrixScheduler.RETRY_BACKOFF_RATELIMIT}.
|
||||
* @param {module:scheduler~queueAlgorithm} queueAlgorithm Optional. The queuing
|
||||
* algorithm to apply when determining which events should be sent before the
|
||||
* given event. Defaults to {@link module:scheduler~MatrixScheduler.QUEUE_MESSAGES}.
|
||||
*/
|
||||
function MatrixScheduler(retryAlgorithm, queueAlgorithm) {
|
||||
this.retryAlgorithm = retryAlgorithm || MatrixScheduler.RETRY_BACKOFF_RATELIMIT;
|
||||
this.queueAlgorithm = queueAlgorithm || MatrixScheduler.QUEUE_MESSAGES;
|
||||
this._queues = {
|
||||
// queueName: [{
|
||||
// event: MatrixEvent, // event to send
|
||||
// defer: Deferred, // defer to resolve/reject at the END of the retries
|
||||
// attempts: Number // number of times we've called processFn
|
||||
// }, ...]
|
||||
};
|
||||
this._activeQueues = [];
|
||||
this._procFn = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the process function. Required for events in the queue to be processed.
|
||||
* If set after events have been added to the queue, this will immediately start
|
||||
* processing them.
|
||||
* @param {module:scheduler~processFn} fn The function that can process events
|
||||
* in the queue.
|
||||
*/
|
||||
MatrixScheduler.prototype.setProcessFunction = function(fn) {
|
||||
this._procFn = fn;
|
||||
_startProcessingQueues(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Queue an event if it is required and start processing queues.
|
||||
* @param {MatrixEvent} event The event that may be queued.
|
||||
* @return {?Promise} A promise if the event was queued, which will be
|
||||
* resolved or rejected in due time, else null.
|
||||
*/
|
||||
MatrixScheduler.prototype.queueEvent = function(event) {
|
||||
var queueName = this.queueAlgorithm(event);
|
||||
if (!queueName) {
|
||||
return null;
|
||||
}
|
||||
// add the event to the queue and make a deferred for it.
|
||||
if (!this._queues[queueName]) {
|
||||
this._queues[queueName] = [];
|
||||
}
|
||||
var defer = q.defer();
|
||||
this._queues[queueName].push({
|
||||
event: event,
|
||||
defer: defer,
|
||||
attempts: 0
|
||||
});
|
||||
debuglog(
|
||||
"Queue algorithm dumped event %s into queue '%s'",
|
||||
event.getId(), queueName
|
||||
);
|
||||
_startProcessingQueues(this);
|
||||
return defer.promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retries events up to 4 times using exponential backoff. This produces wait
|
||||
* times of 2, 4, 8, and 16 seconds (30s total) after which we give up. If the
|
||||
* failure was due to a rate limited request, the time specified in the error is
|
||||
* waited before being retried.
|
||||
* @param {MatrixEvent} event
|
||||
* @param {Number} attempts
|
||||
* @param {MatrixError} err
|
||||
* @return {Number}
|
||||
* @see module:scheduler~retryAlgorithm
|
||||
*/
|
||||
MatrixScheduler.RETRY_BACKOFF_RATELIMIT = function(event, attempts, err) {
|
||||
if (err.name === "M_LIMIT_EXCEEDED") {
|
||||
var waitTime = err.data.retry_after_ms;
|
||||
if (waitTime) {
|
||||
return waitTime;
|
||||
}
|
||||
}
|
||||
if (attempts > 4) {
|
||||
return -1; // give up
|
||||
}
|
||||
return (1000 * Math.pow(2, attempts));
|
||||
};
|
||||
|
||||
/**
|
||||
* Queues <code>m.room.message</code> events and lets other events continue
|
||||
* concurrently.
|
||||
* @param {MatrixEvent} event
|
||||
* @return {string}
|
||||
* @see module:scheduler~queueAlgorithm
|
||||
*/
|
||||
MatrixScheduler.QUEUE_MESSAGES = function(event) {
|
||||
if (event.getType() === "m.room.message") {
|
||||
// put these events in the 'message' queue.
|
||||
return "message";
|
||||
}
|
||||
// allow all other events continue concurrently.
|
||||
return null;
|
||||
};
|
||||
|
||||
function _startProcessingQueues(scheduler) {
|
||||
if (!scheduler._procFn) {
|
||||
return;
|
||||
}
|
||||
// for each inactive queue with events in them
|
||||
utils.forEach(utils.filter(utils.keys(scheduler._queues), function(queueName) {
|
||||
return scheduler._activeQueues.indexOf(queueName) === -1 &&
|
||||
scheduler._queues[queueName].length > 0;
|
||||
}), function(queueName) {
|
||||
// mark the queue as active
|
||||
scheduler._activeQueues.push(queueName);
|
||||
// begin processing the head of the queue
|
||||
debuglog("Spinning up queue: '%s'", queueName);
|
||||
_processQueue(scheduler, queueName);
|
||||
});
|
||||
}
|
||||
|
||||
function _processQueue(scheduler, queueName) {
|
||||
// get head of queue
|
||||
var obj = _peekNextEvent(scheduler, queueName);
|
||||
if (!obj) {
|
||||
// queue is empty. Mark as inactive and stop recursing.
|
||||
var index = scheduler._activeQueues.indexOf(queueName);
|
||||
if (index >= 0) {
|
||||
scheduler._activeQueues.splice(index, 1);
|
||||
}
|
||||
debuglog("Stopping queue '%s' as it is now empty", queueName);
|
||||
return;
|
||||
}
|
||||
debuglog(
|
||||
"Queue '%s' has %s pending events",
|
||||
queueName, scheduler._queues[queueName].length
|
||||
);
|
||||
// fire the process function and if it resolves, resolve the deferred. Else
|
||||
// invoke the retry algorithm.
|
||||
scheduler._procFn(obj.event).done(function(res) {
|
||||
// remove this from the queue
|
||||
_removeNextEvent(scheduler, queueName);
|
||||
debuglog("Queue '%s' sent event %s", queueName, obj.event.getId());
|
||||
obj.defer.resolve(res);
|
||||
// keep processing
|
||||
_processQueue(scheduler, queueName);
|
||||
}, function(err) {
|
||||
obj.attempts += 1;
|
||||
// ask the retry algorithm when/if we should try again
|
||||
var waitTimeMs = scheduler.retryAlgorithm(obj.event, obj.attempts, err);
|
||||
debuglog(
|
||||
"retry(%s) err=%s event_id=%s waitTime=%s",
|
||||
obj.attempts, err, obj.event.getId(), waitTimeMs
|
||||
);
|
||||
if (waitTimeMs === -1) { // give up (you quitter!)
|
||||
debuglog(
|
||||
"Queue '%s' giving up on event %s", queueName, obj.event.getId()
|
||||
);
|
||||
// remove this from the queue
|
||||
_removeNextEvent(scheduler, queueName);
|
||||
obj.defer.reject(err);
|
||||
// process next event
|
||||
_processQueue(scheduler, queueName);
|
||||
}
|
||||
else {
|
||||
setTimeout(function() {
|
||||
_processQueue(scheduler, queueName);
|
||||
}, waitTimeMs);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function _peekNextEvent(scheduler, queueName) {
|
||||
var queue = scheduler._queues[queueName];
|
||||
if (!utils.isArray(queue)) {
|
||||
return null;
|
||||
}
|
||||
return queue[0];
|
||||
}
|
||||
|
||||
function _removeNextEvent(scheduler, queueName) {
|
||||
var queue = scheduler._queues[queueName];
|
||||
if (!utils.isArray(queue)) {
|
||||
return null;
|
||||
}
|
||||
return queue.shift();
|
||||
}
|
||||
|
||||
function debuglog() {
|
||||
if (DEBUG) {
|
||||
console.log.apply(console, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The retry algorithm to apply when retrying events. To stop retrying, return
|
||||
* <code>-1</code>. If this event was part of a queue, it will be removed from
|
||||
* the queue.
|
||||
* @callback retryAlgorithm
|
||||
* @param {MatrixEvent} event The event being retried.
|
||||
* @param {Number} attempts The number of failed attempts. This will always be
|
||||
* >= 1.
|
||||
* @param {MatrixError} err The most recent error message received when trying
|
||||
* to send this event.
|
||||
* @return {Number} The number of milliseconds to wait before trying again. If
|
||||
* this is 0, the request will be immediately retried. If this is
|
||||
* <code>-1</code>, the event will be marked as
|
||||
* {@link module:models/event.EventStatus.NOT_SENT} and will not be retried.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The queuing algorithm to apply to events. All queues created are serviced in
|
||||
* a FIFO manner. To send the event ASAP, return <code>null</code> which will
|
||||
* not put this event in a queue. Events that fail to send that form part of
|
||||
* a queue will be removed from the queue and the next event in the queue will
|
||||
* be sent.
|
||||
* @callback queueAlgorithm
|
||||
* @param {MatrixEvent} event The event to be sent.
|
||||
* @return {string} The name of the queue to put the event into. If a queue with
|
||||
* this name does not exist, it will be created. If this is <code>null</code>,
|
||||
* the event is not put into a queue and will be sent concurrently.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The function to invoke to process (send) events in the queue.
|
||||
* @callback processFn
|
||||
* @param {MatrixEvent} event The event to send.
|
||||
* @return {Promise} Resolved/rejected depending on the outcome of the request.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The MatrixScheduler class.
|
||||
*/
|
||||
module.exports = MatrixScheduler;
|
||||
@@ -0,0 +1,73 @@
|
||||
"use strict";
|
||||
/**
|
||||
* This is an internal module.
|
||||
* @module store/stub
|
||||
*/
|
||||
|
||||
/**
|
||||
* Construct a stub store. This does no-ops on all store methods.
|
||||
* @constructor
|
||||
*/
|
||||
function StubStore() {
|
||||
|
||||
}
|
||||
|
||||
StubStore.prototype = {
|
||||
|
||||
/**
|
||||
* No-op.
|
||||
* @param {Room} room
|
||||
*/
|
||||
storeRoom: function(room) {
|
||||
},
|
||||
|
||||
/**
|
||||
* No-op.
|
||||
* @param {string} roomId
|
||||
* @return {null}
|
||||
*/
|
||||
getRoom: function(roomId) {
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* No-op.
|
||||
* @return {Array} An empty array.
|
||||
*/
|
||||
getRooms: function() {
|
||||
return [];
|
||||
},
|
||||
|
||||
/**
|
||||
* No-op.
|
||||
* @return {Array} An empty array.
|
||||
*/
|
||||
getRoomSummaries: function() {
|
||||
return [];
|
||||
},
|
||||
|
||||
/**
|
||||
* No-op.
|
||||
* @param {User} user
|
||||
*/
|
||||
storeUser: function(user) {
|
||||
},
|
||||
|
||||
/**
|
||||
* No-op.
|
||||
* @param {string} userId
|
||||
* @return {null}
|
||||
*/
|
||||
getUser: function(userId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO
|
||||
//setMaxHistoryPerRoom: function(maxHistory) {},
|
||||
|
||||
// TODO
|
||||
//reapOldMessages: function() {},
|
||||
};
|
||||
|
||||
/** Stub Store class. */
|
||||
module.exports = StubStore;
|
||||
@@ -111,6 +111,63 @@ module.exports.forEach = function(array, fn) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The findElement() method returns a value in the array, if an element in the array
|
||||
* satisfies (returns true) the provided testing function. Otherwise undefined
|
||||
* is returned.
|
||||
* @param {Array} array The array.
|
||||
* @param {Function} fn Function to execute on each value in the array, with the
|
||||
* function signature <code>fn(element, index, array)</code>
|
||||
* @param {boolean} reverse True to search in reverse order.
|
||||
* @return {*} The first value in the array which returns <code>true</code> for
|
||||
* the given function.
|
||||
*/
|
||||
module.exports.findElement = function(array, fn, reverse) {
|
||||
var i;
|
||||
if (reverse) {
|
||||
for (i = array.length - 1; i >= 0; i--) {
|
||||
if (fn(array[i], i, array)) {
|
||||
return array[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (i = 0; i < array.length; i++) {
|
||||
if (fn(array[i], i, array)) {
|
||||
return array[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The removeElement() method removes the first element in the array that
|
||||
* satisfies (returns true) the provided testing function.
|
||||
* @param {Array} array The array.
|
||||
* @param {Function} fn Function to execute on each value in the array, with the
|
||||
* function signature <code>fn(element, index, array)</code>. Return true to
|
||||
* remove this element and break.
|
||||
* @param {boolean} reverse True to search in reverse order.
|
||||
*/
|
||||
module.exports.removeElement = function(array, fn, reverse) {
|
||||
var i;
|
||||
if (reverse) {
|
||||
for (i = array.length - 1; i >= 0; i--) {
|
||||
if (fn(array[i], i, array)) {
|
||||
array.splice(i, 1);
|
||||
return; }
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (i = 0; i < array.length; i++) {
|
||||
if (fn(array[i], i, array)) {
|
||||
array.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the given thing is a function.
|
||||
* @param {*} value The thing to check.
|
||||
|
||||
+3
-2
@@ -1,13 +1,14 @@
|
||||
{
|
||||
"name": "matrix-js-sdk",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"description": "Matrix Client-Server SDK for Javascript",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "istanbul cover --report cobertura -i \"lib/**/*.js\" jasmine-node -- spec --verbose --junitreport --forceexit --captureExceptions",
|
||||
"build": "jshint -c .jshint lib/ && browserify browser-index.js -o dist/browser-matrix-dev.js",
|
||||
"watch": "watchify browser-index.js -o dist/browser-matrix-dev.js -v",
|
||||
"lint": "jshint -c .jshint lib spec && gjslint --unix_mode --disable 0131,0211,0200 --max_line_length 90 -r spec/ -r lib/"
|
||||
"lint": "jshint -c .jshint lib spec && gjslint --unix_mode --disable 0131,0211,0200 --max_line_length 90 -r spec/ -r lib/",
|
||||
"release": "npm run build && mkdir dist/$npm_package_version && uglifyjs -c -m -o dist/$npm_package_version/browser-matrix-$npm_package_version.min.js dist/browser-matrix-dev.js && cp dist/browser-matrix-dev.js dist/$npm_package_version/browser-matrix-$npm_package_version.js"
|
||||
},
|
||||
"repository": {
|
||||
"url": "https://github.com/matrix-org/matrix-js-sdk"
|
||||
|
||||
+27
-14
@@ -58,7 +58,7 @@ describe("Room", function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe("calculateRoomName", function() {
|
||||
describe("calculate (Room Name)", function() {
|
||||
var stateLookup = {
|
||||
// event.type + "$" event.state_key : MatrixEvent
|
||||
};
|
||||
@@ -136,7 +136,8 @@ describe("Room", function() {
|
||||
addMember(userB);
|
||||
addMember(userC);
|
||||
addMember(userD);
|
||||
var name = room.calculateRoomName(userA);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
// we expect at least 1 member to be mentioned
|
||||
var others = [userB, userC, userD];
|
||||
var found = false;
|
||||
@@ -156,7 +157,8 @@ describe("Room", function() {
|
||||
addMember(userA);
|
||||
addMember(userB);
|
||||
addMember(userC);
|
||||
var name = room.calculateRoomName(userA);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
expect(name.indexOf(userB)).not.toEqual(-1, name);
|
||||
expect(name.indexOf(userC)).not.toEqual(-1, name);
|
||||
});
|
||||
@@ -168,7 +170,8 @@ describe("Room", function() {
|
||||
addMember(userA);
|
||||
addMember(userB);
|
||||
addMember(userC);
|
||||
var name = room.calculateRoomName(userA);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
expect(name.indexOf(userB)).not.toEqual(-1, name);
|
||||
expect(name.indexOf(userC)).not.toEqual(-1, name);
|
||||
});
|
||||
@@ -179,7 +182,8 @@ describe("Room", function() {
|
||||
setJoinRule("public");
|
||||
addMember(userA);
|
||||
addMember(userB);
|
||||
var name = room.calculateRoomName(userA);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
expect(name.indexOf(userB)).not.toEqual(-1, name);
|
||||
});
|
||||
|
||||
@@ -189,7 +193,8 @@ describe("Room", function() {
|
||||
setJoinRule("invite");
|
||||
addMember(userA);
|
||||
addMember(userB);
|
||||
var name = room.calculateRoomName(userA);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
expect(name.indexOf(userB)).not.toEqual(-1, name);
|
||||
});
|
||||
|
||||
@@ -198,7 +203,8 @@ describe("Room", function() {
|
||||
setJoinRule("invite");
|
||||
addMember(userA, "invite");
|
||||
addMember(userB);
|
||||
var name = room.calculateRoomName(userA);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
expect(name.indexOf(userB)).not.toEqual(-1, name);
|
||||
});
|
||||
|
||||
@@ -207,7 +213,8 @@ describe("Room", function() {
|
||||
var alias = "#room_alias:here";
|
||||
setJoinRule("invite");
|
||||
setAliases([alias, "#another:one"]);
|
||||
var name = room.calculateRoomName(userA);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
expect(name).toEqual(alias);
|
||||
});
|
||||
|
||||
@@ -216,7 +223,8 @@ describe("Room", function() {
|
||||
var alias = "#room_alias:here";
|
||||
setJoinRule("public");
|
||||
setAliases([alias, "#another:one"]);
|
||||
var name = room.calculateRoomName(userA);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
expect(name).toEqual(alias);
|
||||
});
|
||||
|
||||
@@ -225,7 +233,8 @@ describe("Room", function() {
|
||||
var roomName = "A mighty name indeed";
|
||||
setJoinRule("invite");
|
||||
setRoomName(roomName);
|
||||
var name = room.calculateRoomName(userA);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
expect(name).toEqual(roomName);
|
||||
});
|
||||
|
||||
@@ -234,7 +243,8 @@ describe("Room", function() {
|
||||
var roomName = "A mighty name indeed";
|
||||
setJoinRule("public");
|
||||
setRoomName(roomName);
|
||||
var name = room.calculateRoomName(userA);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
expect(name).toEqual(roomName);
|
||||
});
|
||||
|
||||
@@ -242,7 +252,8 @@ describe("Room", function() {
|
||||
" a room name and alias don't exist and it is a self-chat.", function() {
|
||||
setJoinRule("invite");
|
||||
addMember(userA);
|
||||
var name = room.calculateRoomName(userA);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
expect(name).toEqual(userA);
|
||||
});
|
||||
|
||||
@@ -250,13 +261,15 @@ describe("Room", function() {
|
||||
" room name and alias don't exist and it is a self-chat.", function() {
|
||||
setJoinRule("public");
|
||||
addMember(userA);
|
||||
var name = room.calculateRoomName(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() {
|
||||
var name = room.calculateRoomName(userA);
|
||||
room.recalculate(userA);
|
||||
var name = room.name;
|
||||
expect(name).toEqual("?");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user