You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-25 05:23:13 +03:00
6491 lines
206 KiB
JavaScript
6491 lines
206 KiB
JavaScript
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
||
"use strict";
|
||
/**
|
||
* This is an internal module. See {@link MatrixClient} for the public class.
|
||
* @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 utils = require("./utils");
|
||
|
||
// TODO:
|
||
// Internal: rate limiting
|
||
|
||
/**
|
||
* 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. 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. 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", "scheduler"]
|
||
);
|
||
|
||
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;
|
||
|
||
var httpOpts = {
|
||
baseUrl: opts.baseUrl,
|
||
accessToken: opts.accessToken,
|
||
request: opts.request,
|
||
prefix: httpApi.PREFIX_V1,
|
||
onlyData: true
|
||
};
|
||
this.credentials = {
|
||
userId: (opts.userId || null)
|
||
};
|
||
this._http = new httpApi.MatrixHttpApi(httpOpts);
|
||
this._syncingRooms = {
|
||
// room_id: Promise
|
||
};
|
||
}
|
||
utils.inherits(MatrixClient, EventEmitter);
|
||
|
||
/**
|
||
* Get the room for the given room ID.
|
||
* @param {string} roomId The room ID
|
||
* @return {Room} The Room or null if it doesn't exist or there is no data store.
|
||
*/
|
||
MatrixClient.prototype.getRoom = function(roomId) {
|
||
return this.store.getRoom(roomId);
|
||
};
|
||
|
||
/**
|
||
* Retrieve all known rooms.
|
||
* @return {Room[]} A list of rooms, or an empty list if there is no data store.
|
||
*/
|
||
MatrixClient.prototype.getRooms = function() {
|
||
return this.store.getRooms();
|
||
};
|
||
|
||
// Room operations
|
||
// ===============
|
||
|
||
/**
|
||
* Create a new room.
|
||
* @param {Object} options a list of options to pass to the /createRoom API.
|
||
* @param {string} options.room_alias_name The alias localpart to assign to
|
||
* this room.
|
||
* @param {string} options.visibility Either 'public' or 'private'.
|
||
* @param {string[]} options.invite A list of user IDs to invite to this room.
|
||
* @param {string} options.name The name to give this room.
|
||
* @param {string} options.topic The topic to give this room.
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: <code>{room_id: {string},
|
||
* room_alias: {string(opt)}}</code>
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.createRoom = function(options, callback) {
|
||
// valid options include: room_alias_name, visibility, invite
|
||
return this._http.authedRequest(
|
||
callback, "POST", "/createRoom", undefined, options
|
||
);
|
||
};
|
||
|
||
/**
|
||
* 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: 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});
|
||
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);
|
||
};
|
||
|
||
/**
|
||
* @param {string} roomId
|
||
* @param {string} name
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.setRoomName = function(roomId, name, callback) {
|
||
return this.sendStateEvent(roomId, "m.room.name", {name: name},
|
||
undefined, callback);
|
||
};
|
||
|
||
/**
|
||
* @param {string} roomId
|
||
* @param {string} topic
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.setRoomTopic = function(roomId, topic, callback) {
|
||
return this.sendStateEvent(roomId, "m.room.topic", {topic: topic},
|
||
undefined, callback);
|
||
};
|
||
|
||
/**
|
||
* Set a user's power level.
|
||
* @param {string} roomId
|
||
* @param {string} userId
|
||
* @param {Number} powerLevel
|
||
* @param {Object} event
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.setPowerLevel = function(roomId, userId, powerLevel,
|
||
event, callback) {
|
||
var content = {
|
||
users: {}
|
||
};
|
||
if (event && event.type === "m.room.power_levels") {
|
||
content = event.content;
|
||
}
|
||
content.users[userId] = powerLevel;
|
||
var path = utils.encodeUri("/rooms/$roomId/state/m.room.power_levels", {
|
||
$roomId: roomId
|
||
});
|
||
return this._http.authedRequest(
|
||
callback, "PUT", path, undefined, content
|
||
);
|
||
};
|
||
|
||
/**
|
||
* Retrieve a state event.
|
||
* @param {string} roomId
|
||
* @param {string} eventType
|
||
* @param {string} stateKey
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.getStateEvent = function(roomId, eventType, stateKey, callback) {
|
||
var pathParams = {
|
||
$roomId: roomId,
|
||
$eventType: eventType,
|
||
$stateKey: stateKey
|
||
};
|
||
var path = utils.encodeUri("/rooms/$roomId/state/$eventType", pathParams);
|
||
if (stateKey !== undefined) {
|
||
path = utils.encodeUri(path + "/$stateKey", pathParams);
|
||
}
|
||
return this._http.authedRequest(
|
||
callback, "GET", path
|
||
);
|
||
};
|
||
|
||
/**
|
||
* @param {string} roomId
|
||
* @param {string} eventType
|
||
* @param {Object} content
|
||
* @param {string} stateKey
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.sendStateEvent = function(roomId, eventType, content, stateKey,
|
||
callback) {
|
||
var pathParams = {
|
||
$roomId: roomId,
|
||
$eventType: eventType,
|
||
$stateKey: stateKey
|
||
};
|
||
var path = utils.encodeUri("/rooms/$roomId/state/$eventType", pathParams);
|
||
if (stateKey !== undefined) {
|
||
path = utils.encodeUri(path + "/$stateKey", pathParams);
|
||
}
|
||
return this._http.authedRequest(
|
||
callback, "PUT", path, undefined, content
|
||
);
|
||
};
|
||
|
||
/**
|
||
* @param {string} roomId
|
||
* @param {string} eventType
|
||
* @param {Object} content
|
||
* @param {string} txnId Optional.
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.sendEvent = function(roomId, eventType, content, txnId,
|
||
callback) {
|
||
if (utils.isFunction(txnId)) { callback = txnId; txnId = undefined; }
|
||
if (!txnId) {
|
||
txnId = "m" + new Date().getTime();
|
||
}
|
||
|
||
// 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
|
||
});
|
||
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
|
||
* @param {string} txnId Optional.
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.sendMessage = function(roomId, content, txnId, callback) {
|
||
if (utils.isFunction(txnId)) { callback = txnId; txnId = undefined; }
|
||
return this.sendEvent(
|
||
roomId, "m.room.message", content, txnId, callback
|
||
);
|
||
};
|
||
|
||
/**
|
||
* @param {string} roomId
|
||
* @param {string} body
|
||
* @param {string} txnId Optional.
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.sendTextMessage = function(roomId, body, txnId, callback) {
|
||
var content = {
|
||
msgtype: "m.text",
|
||
body: body
|
||
};
|
||
return this.sendMessage(roomId, content, txnId, callback);
|
||
};
|
||
|
||
/**
|
||
* @param {string} roomId
|
||
* @param {string} body
|
||
* @param {string} txnId Optional.
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.sendEmoteMessage = function(roomId, body, txnId, callback) {
|
||
var content = {
|
||
msgtype: "m.emote",
|
||
body: body
|
||
};
|
||
return this.sendMessage(roomId, content, txnId, callback);
|
||
};
|
||
|
||
/**
|
||
* @param {string} roomId
|
||
* @param {string} url
|
||
* @param {Object} info
|
||
* @param {string} text
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.sendImageMessage = function(roomId, url, info, text, callback) {
|
||
if (utils.isFunction(text)) { callback = text; text = undefined; }
|
||
if (!text) { text = "Image"; }
|
||
var content = {
|
||
msgtype: "m.image",
|
||
url: url,
|
||
info: info,
|
||
body: text
|
||
};
|
||
return this.sendMessage(roomId, content, callback);
|
||
};
|
||
|
||
/**
|
||
* @param {string} roomId
|
||
* @param {string} body
|
||
* @param {string} htmlBody
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.sendHtmlMessage = function(roomId, body, htmlBody, callback) {
|
||
var content = {
|
||
msgtype: "m.text",
|
||
format: "org.matrix.custom.html",
|
||
body: body,
|
||
formatted_body: htmlBody
|
||
};
|
||
return this.sendMessage(roomId, content, callback);
|
||
};
|
||
|
||
/**
|
||
* @param {string} roomId
|
||
* @param {boolean} isTyping
|
||
* @param {Number} timeoutMs
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.sendTyping = function(roomId, isTyping, timeoutMs, callback) {
|
||
var path = utils.encodeUri("/rooms/$roomId/typing/$userId", {
|
||
$roomId: roomId,
|
||
$userId: this.credentials.userId
|
||
});
|
||
var data = {
|
||
typing: isTyping
|
||
};
|
||
if (isTyping) {
|
||
data.timeout = timeoutMs ? timeoutMs : 20000;
|
||
}
|
||
return this._http.authedRequest(
|
||
callback, "PUT", path, undefined, data
|
||
);
|
||
};
|
||
|
||
/**
|
||
* @param {string} roomId
|
||
* @param {string} eventId
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.redactEvent = function(roomId, eventId, callback) {
|
||
var path = utils.encodeUri("/rooms/$roomId/redact/$eventId", {
|
||
$roomId: roomId,
|
||
$eventId: eventId
|
||
});
|
||
return this._http.authedRequest(callback, "POST", path, undefined, {});
|
||
};
|
||
|
||
/**
|
||
* @param {string} roomId
|
||
* @param {string} userId
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.invite = function(roomId, userId, callback) {
|
||
return _membershipChange(this, roomId, userId, "invite", undefined,
|
||
callback);
|
||
};
|
||
|
||
/**
|
||
* @param {string} roomId
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.leave = function(roomId, callback) {
|
||
return _membershipChange(this, roomId, undefined, "leave", undefined,
|
||
callback);
|
||
};
|
||
|
||
/**
|
||
* @param {string} roomId
|
||
* @param {string} userId
|
||
* @param {string} reason Optional.
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.ban = function(roomId, userId, reason, callback) {
|
||
return _membershipChange(this, roomId, userId, "ban", reason,
|
||
callback);
|
||
};
|
||
|
||
/**
|
||
* @param {string} roomId
|
||
* @param {string} userId
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.unban = function(roomId, userId, callback) {
|
||
// unbanning = set their state to leave
|
||
return _setMembershipState(
|
||
this, roomId, userId, "leave", undefined, callback
|
||
);
|
||
};
|
||
|
||
/**
|
||
* @param {string} roomId
|
||
* @param {string} userId
|
||
* @param {string} reason Optional.
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.kick = function(roomId, userId, reason, callback) {
|
||
return _setMembershipState(
|
||
this, roomId, userId, "leave", reason, callback
|
||
);
|
||
};
|
||
|
||
/**
|
||
* This is an internal method.
|
||
* @param {MatrixClient} client
|
||
* @param {string} roomId
|
||
* @param {string} userId
|
||
* @param {string} membershipValue
|
||
* @param {string} reason
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
function _setMembershipState(client, roomId, userId, membershipValue, reason,
|
||
callback) {
|
||
if (utils.isFunction(reason)) { callback = reason; reason = undefined; }
|
||
|
||
var path = utils.encodeUri(
|
||
"/rooms/$roomId/state/m.room.member/$userId",
|
||
{ $roomId: roomId, $userId: userId}
|
||
);
|
||
|
||
return client._http.authedRequest(callback, "PUT", path, undefined, {
|
||
membership: membershipValue,
|
||
reason: reason
|
||
});
|
||
}
|
||
|
||
/**
|
||
* This is an internal method.
|
||
* @param {MatrixClient} client
|
||
* @param {string} roomId
|
||
* @param {string} userId
|
||
* @param {string} membership
|
||
* @param {string} reason
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
function _membershipChange(client, roomId, userId, membership, reason, callback) {
|
||
if (utils.isFunction(reason)) { callback = reason; reason = undefined; }
|
||
|
||
var path = utils.encodeUri("/rooms/$room_id/$membership", {
|
||
$room_id: roomId,
|
||
$membership: membership
|
||
});
|
||
return client._http.authedRequest(
|
||
callback, "POST", path, undefined, {
|
||
user_id: userId, // may be undefined e.g. on leave
|
||
reason: reason
|
||
}
|
||
);
|
||
}
|
||
|
||
// Profile operations
|
||
// ==================
|
||
|
||
/**
|
||
* @param {string} userId
|
||
* @param {string} info The kind of info to retrieve (e.g. 'displayname',
|
||
* 'avatar_url').
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.getProfileInfo = function(userId, info, callback) {
|
||
if (utils.isFunction(info)) { callback = info; info = undefined; }
|
||
|
||
var path = info ?
|
||
utils.encodeUri("/profile/$userId/$info",
|
||
{ $userId: userId, $info: info }) :
|
||
utils.encodeUri("/profile/$userId",
|
||
{ $userId: userId });
|
||
return this._http.authedRequest(callback, "GET", path);
|
||
};
|
||
|
||
/**
|
||
* @param {string} info The kind of info to set (e.g. 'avatar_url')
|
||
* @param {Object} data The JSON object to set.
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.setProfileInfo = function(info, data, callback) {
|
||
var path = utils.encodeUri("/profile/$userId/$info", {
|
||
$userId: this.credentials.userId,
|
||
$info: info
|
||
});
|
||
return this._http.authedRequest(
|
||
callback, "PUT", path, undefined, data
|
||
);
|
||
};
|
||
|
||
/**
|
||
* @param {string} name
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.setDisplayName = function(name, callback) {
|
||
return this.setProfileInfo(
|
||
"displayname", { displayname: name }, callback
|
||
);
|
||
};
|
||
|
||
/**
|
||
* @param {string} url
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.setAvatarUrl = function(url, callback) {
|
||
return this.setProfileInfo(
|
||
"avatar_url", { avatar_url: url }, callback
|
||
);
|
||
};
|
||
|
||
/**
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.getThreePids = function(callback) {
|
||
var path = "/account/3pid";
|
||
return this._http.authedRequestWithPrefix(
|
||
callback, "GET", path, undefined, undefined, httpApi.PREFIX_V2_ALPHA
|
||
);
|
||
};
|
||
|
||
/**
|
||
* @param {Object} creds
|
||
* @param {boolean} bind
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.addThreePid = function(creds, bind, callback) {
|
||
var path = "/account/3pid";
|
||
var data = {
|
||
'threePidCreds': creds,
|
||
'bind': bind
|
||
};
|
||
return this._http.authedRequestWithPrefix(
|
||
callback, "POST", path, null, data, httpApi.PREFIX_V2_ALPHA
|
||
);
|
||
};
|
||
|
||
/**
|
||
* @param {string} presence
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
* @throws If 'presence' isn't a valid presence enum value.
|
||
*/
|
||
MatrixClient.prototype.setPresence = function(presence, callback) {
|
||
var path = utils.encodeUri("/presence/$userId/status", {
|
||
$userId: this.credentials.userId
|
||
});
|
||
var validStates = ["offline", "online", "unavailable"];
|
||
if (validStates.indexOf(presence) == -1) {
|
||
throw new Error("Bad presence value: " + presence);
|
||
}
|
||
var content = {
|
||
presence: presence
|
||
};
|
||
return this._http.authedRequest(
|
||
callback, "PUT", path, undefined, content
|
||
);
|
||
};
|
||
|
||
// Public (non-authed) operations
|
||
// ==============================
|
||
|
||
/**
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.publicRooms = function(callback) {
|
||
return this._http.request(callback, "GET", "/publicRooms");
|
||
};
|
||
|
||
/**
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.registerFlows = function(callback) {
|
||
return this._http.request(callback, "GET", "/register");
|
||
};
|
||
|
||
/**
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.loginFlows = function(callback) {
|
||
return this._http.request(callback, "GET", "/login");
|
||
};
|
||
|
||
/**
|
||
* @param {string} roomAlias
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.resolveRoomAlias = function(roomAlias, callback) {
|
||
var path = utils.encodeUri("/directory/room/$alias", {$alias: roomAlias});
|
||
return this._http.request(callback, "GET", path);
|
||
};
|
||
|
||
/**
|
||
* @param {string} roomId
|
||
* @param {Number} limit
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.roomInitialSync = function(roomId, limit, callback) {
|
||
if (utils.isFunction(limit)) { callback = limit; limit = undefined; }
|
||
var path = utils.encodeUri("/rooms/$roomId/initialSync",
|
||
{$roomId: roomId}
|
||
);
|
||
if (!limit) {
|
||
limit = 30;
|
||
}
|
||
return this._http.authedRequest(
|
||
callback, "GET", path, { limit: limit }
|
||
);
|
||
};
|
||
|
||
/**
|
||
* @param {string} roomId
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.roomState = function(roomId, callback) {
|
||
var path = utils.encodeUri("/rooms/$roomId/state", {$roomId: roomId});
|
||
return this._http.authedRequest(callback, "GET", path);
|
||
};
|
||
|
||
/**
|
||
* 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: Room.
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.scrollback = function(room, limit, callback) {
|
||
if (utils.isFunction(limit)) { callback = limit; limit = undefined; }
|
||
var path = utils.encodeUri(
|
||
"/rooms/$roomId/messages", {$roomId: room.roomId}
|
||
);
|
||
var params = {
|
||
from: room.oldState.paginationToken,
|
||
limit: (limit || 30),
|
||
dir: 'b'
|
||
};
|
||
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
|
||
// =============================
|
||
|
||
/**
|
||
* @param {string} loginType
|
||
* @param {Object} data
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.login = function(loginType, data, callback) {
|
||
data.type = loginType;
|
||
return this._http.authedRequest(
|
||
callback, "POST", "/login", undefined, data
|
||
);
|
||
};
|
||
|
||
/**
|
||
* @param {string} loginType
|
||
* @param {Object} data
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.register = function(loginType, data, callback) {
|
||
data.type = loginType;
|
||
return this._http.authedRequest(
|
||
callback, "POST", "/register", undefined, data
|
||
);
|
||
};
|
||
|
||
/**
|
||
* @param {string} user
|
||
* @param {string} password
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.loginWithPassword = function(user, password, callback) {
|
||
return this.login("m.login.password", {
|
||
user: user,
|
||
password: password
|
||
}, callback);
|
||
};
|
||
|
||
// Push operations
|
||
// ===============
|
||
|
||
/**
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.pushRules = function(callback) {
|
||
return this._http.authedRequest(callback, "GET", "/pushrules/");
|
||
};
|
||
|
||
/**
|
||
* @param {string} scope
|
||
* @param {string} kind
|
||
* @param {string} ruleId
|
||
* @param {Object} body
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.addPushRule = function(scope, kind, ruleId, body, callback) {
|
||
// NB. Scope not uri encoded because devices need the '/'
|
||
var path = utils.encodeUri("/pushrules/" + scope + "/$kind/$ruleId", {
|
||
$kind: kind,
|
||
$ruleId: ruleId
|
||
});
|
||
return this._http.authedRequest(
|
||
callback, "PUT", path, undefined, body
|
||
);
|
||
};
|
||
|
||
/**
|
||
* @param {string} scope
|
||
* @param {string} kind
|
||
* @param {string} ruleId
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.deletePushRule = function(scope, kind, ruleId, callback) {
|
||
// NB. Scope not uri encoded because devices need the '/'
|
||
var path = utils.encodeUri("/pushrules/" + scope + "/$kind/$ruleId", {
|
||
$kind: kind,
|
||
$ruleId: ruleId
|
||
});
|
||
return this._http.authedRequest(callback, "DELETE", path);
|
||
};
|
||
|
||
// VoIP operations
|
||
// ===============
|
||
|
||
/**
|
||
* @param {module:client.callback} callback Optional.
|
||
* @return {module:client.Promise} Resolves: TODO
|
||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||
*/
|
||
MatrixClient.prototype.turnServer = function(callback) {
|
||
return this._http.authedRequest(callback, "GET", "/voip/turnServer");
|
||
};
|
||
|
||
/**
|
||
* @return {boolean} true if there is a valid access_token for this client.
|
||
*/
|
||
MatrixClient.prototype.isLoggedIn = function() {
|
||
return this._http.opts.accessToken !== undefined;
|
||
};
|
||
|
||
|
||
// Higher level APIs
|
||
// =================
|
||
|
||
// TODO: stuff to handle:
|
||
// local echo
|
||
// event dup suppression? - apparently we should still be doing this
|
||
// tracking current display name / avatar per-message
|
||
// pagination
|
||
// re-sending (including persisting pending messages to be sent)
|
||
// - Need a nice way to callback the app for arbitrary events like
|
||
// displayname changes
|
||
// due to ambiguity (or should this be on a chat-specific layer)?
|
||
// reconnect after connectivity outages
|
||
|
||
/**
|
||
* High level helper method to call initialSync, emit the resulting events,
|
||
* and then start polling the eventStream for new events. To listen for these
|
||
* events, add a listener for {@link module:client~MatrixClient#event:"event"}
|
||
* via {@link module:client~MatrixClient#on}.
|
||
* @param {Number} historyLen amount of historical timeline events to
|
||
* emit during from the initial sync. Default: 12.
|
||
*/
|
||
MatrixClient.prototype.startClient = function(historyLen) {
|
||
if (this.clientRunning) {
|
||
// client is already running.
|
||
return;
|
||
}
|
||
if (this.fromToken) {
|
||
// resume from where we left off.
|
||
_pollForEvents(this);
|
||
return;
|
||
}
|
||
|
||
var self = this;
|
||
this._http.authedRequest(
|
||
undefined, "GET", "/initialSync", { limit: (historyLen || 12) }
|
||
).done(function(data) {
|
||
var i, j;
|
||
// intercept the results and put them into our store
|
||
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);
|
||
});
|
||
for (i = 0; i < data.rooms.length; i++) {
|
||
var room = createNewRoom(self, data.rooms[i].room_id);
|
||
if (!data.rooms[i].state) {
|
||
data.rooms[i].state = [];
|
||
}
|
||
if (data.rooms[i].membership === "invite") {
|
||
// create fake invite state event (v1 sucks)
|
||
data.rooms[i].state.push({
|
||
event_id: "$fake_" + room.roomId,
|
||
content: {
|
||
membership: "invite"
|
||
},
|
||
state_key: self.credentials.userId,
|
||
user_id: data.rooms[i].inviter,
|
||
room_id: room.roomId,
|
||
type: "m.room.member"
|
||
});
|
||
}
|
||
|
||
_processRoomEvents(
|
||
room, data.rooms[i].state, data.rooms[i].messages
|
||
);
|
||
|
||
// cache the name/summary/etc prior to storage since we don't
|
||
// know how the store will serialise the Room.
|
||
room.recalculate(self.credentials.userId);
|
||
|
||
self.store.storeRoom(room);
|
||
self.emit("Room", room);
|
||
}
|
||
}
|
||
|
||
if (data) {
|
||
self.fromToken = data.end;
|
||
var events = [];
|
||
for (i = 0; i < data.presence.length; i++) {
|
||
events.push(new MatrixEvent(data.presence[i]));
|
||
}
|
||
for (i = 0; i < data.rooms.length; i++) {
|
||
if (data.rooms[i].state) {
|
||
for (j = 0; j < data.rooms[i].state.length; j++) {
|
||
events.push(new MatrixEvent(data.rooms[i].state[j]));
|
||
}
|
||
}
|
||
if (data.rooms[i].messages) {
|
||
for (j = 0; j < data.rooms[i].messages.chunk.length; j++) {
|
||
events.push(
|
||
new MatrixEvent(data.rooms[i].messages.chunk[j])
|
||
);
|
||
}
|
||
}
|
||
}
|
||
utils.forEach(events, function(e) {
|
||
self.emit("event", e);
|
||
});
|
||
}
|
||
|
||
self.clientRunning = true;
|
||
self.emit("syncComplete");
|
||
_pollForEvents(self);
|
||
}, function(err) {
|
||
self.emit("syncError", err);
|
||
// TODO: Retries.
|
||
});
|
||
};
|
||
|
||
/**
|
||
* This is an internal method.
|
||
* @param {MatrixClient} client
|
||
*/
|
||
function _pollForEvents(client) {
|
||
var self = 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 instanceof StubStore)) {
|
||
// bucket events based on room.
|
||
var i = 0;
|
||
var roomIdToEvents = {};
|
||
for (i = 0; i < events.length; i++) {
|
||
var roomId = events[i].getRoomId();
|
||
// possible to have no room ID e.g. for presence events.
|
||
if (roomId) {
|
||
if (!roomIdToEvents[roomId]) {
|
||
roomIdToEvents[roomId] = [];
|
||
}
|
||
roomIdToEvents[roomId].push(events[i]);
|
||
}
|
||
}
|
||
// add events to room
|
||
var roomIds = utils.keys(roomIdToEvents);
|
||
utils.forEach(roomIds, function(roomId) {
|
||
var room = self.store.getRoom(roomId);
|
||
var isBrandNewRoom = false;
|
||
if (!room) {
|
||
room = createNewRoom(self, roomId);
|
||
isBrandNewRoom = true;
|
||
}
|
||
|
||
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;
|
||
utils.forEach(events, function(e) {
|
||
self.emit("event", e);
|
||
});
|
||
}
|
||
_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
|
||
setTimeout(function() {
|
||
_pollForEvents(self);
|
||
}, 2000);
|
||
});
|
||
}
|
||
|
||
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.
|
||
*/
|
||
MatrixClient.prototype.stopClient = function() {
|
||
this.clientRunning = false;
|
||
// TODO: f.e. Room => self.store.storeRoom(room) ?
|
||
};
|
||
|
||
|
||
function reEmit(reEmitEntity, emittableEntity, eventNames) {
|
||
utils.forEach(eventNames, function(eventName) {
|
||
// setup a listener on the entity (the Room, User, etc) for this event
|
||
emittableEntity.on(eventName, function() {
|
||
// take the args from the listener and reuse them, adding the
|
||
// event name to the arg list so it works with .emit()
|
||
// Transformation Example:
|
||
// listener on "foo" => function(a,b) { ... }
|
||
// Re-emit on "thing" => thing.emit("foo", a, b)
|
||
var newArgs = [eventName];
|
||
for (var i = 0; i < arguments.length; i++) {
|
||
newArgs.push(arguments[i]);
|
||
}
|
||
reEmitEntity.emit.apply(reEmitEntity, newArgs);
|
||
});
|
||
});
|
||
}
|
||
|
||
function createNewUser(client, userId) {
|
||
var user = new User(userId);
|
||
reEmit(client, user, ["User.avatarUrl", "User.displayName", "User.presence"]);
|
||
return user;
|
||
}
|
||
|
||
function createNewRoom(client, roomId) {
|
||
var room = new Room(roomId);
|
||
reEmit(client, room, ["Room.name", "Room.timeline"]);
|
||
|
||
// 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
|
||
// order to hook them correctly. (TODO: find a better way?)
|
||
reEmit(client, room.currentState, [
|
||
"RoomState.events", "RoomState.members", "RoomState.newMember"
|
||
]);
|
||
room.currentState.on("RoomState.newMember", function(event, state, member) {
|
||
reEmit(
|
||
client, member,
|
||
[
|
||
"RoomMember.name", "RoomMember.typing", "RoomMember.powerLevel",
|
||
"RoomMember.membership"
|
||
]
|
||
);
|
||
});
|
||
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;
|
||
|
||
// MatrixClient Event JSDocs
|
||
|
||
/**
|
||
* Fires whenever the SDK receives a new event.
|
||
* @event module:client~MatrixClient#"event"
|
||
* @param {MatrixEvent} event The matrix event which caused this event to fire.
|
||
* @example
|
||
* matrixClient.on("event", function(event){
|
||
* var sender = event.getSender();
|
||
* });
|
||
*/
|
||
|
||
/**
|
||
* Fires whenever the SDK has a problem syncing. <strong>This event is experimental
|
||
* and may change.</strong>
|
||
* @event module:client~MatrixClient#"syncError"
|
||
* @param {MatrixError} err The matrix error which caused this event to fire.
|
||
* @example
|
||
* matrixClient.on("syncError", function(err){
|
||
* // update UI to say "Connection Lost"
|
||
* });
|
||
*/
|
||
|
||
/**
|
||
* Fires when the SDK has finished catching up and is now listening for live
|
||
* events. <strong>This event is experimental and may change.</strong>
|
||
* @event module:client~MatrixClient#"syncComplete"
|
||
* @example
|
||
* matrixClient.on("syncComplete", function(){
|
||
* var rooms = matrixClient.getRooms();
|
||
* });
|
||
*/
|
||
|
||
/**
|
||
* 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, fully populated room.
|
||
* @example
|
||
* matrixClient.on("Room", function(room){
|
||
* var roomId = room.roomId;
|
||
* });
|
||
*/
|
||
|
||
// EventEmitter JSDocs
|
||
|
||
/**
|
||
* The {@link https://nodejs.org/api/events.html|EventEmitter} class.
|
||
* @external EventEmitter
|
||
* @see {@link https://nodejs.org/api/events.html}
|
||
*/
|
||
|
||
/**
|
||
* Adds a listener to the end of the listeners array for the specified event.
|
||
* No checks are made to see if the listener has already been added. Multiple
|
||
* calls passing the same combination of event and listener will result in the
|
||
* listener being added multiple times.
|
||
* @function external:EventEmitter#on
|
||
* @param {string} event The event to listen for.
|
||
* @param {Function} listener The function to invoke.
|
||
* @return {EventEmitter} for call chaining.
|
||
*/
|
||
|
||
/**
|
||
* Alias for {@link external:EventEmitter#on}.
|
||
* @function external:EventEmitter#addListener
|
||
* @param {string} event The event to listen for.
|
||
* @param {Function} listener The function to invoke.
|
||
* @return {EventEmitter} for call chaining.
|
||
*/
|
||
|
||
/**
|
||
* Adds a <b>one time</b> listener for the event. This listener is invoked only
|
||
* the next time the event is fired, after which it is removed.
|
||
* @function external:EventEmitter#once
|
||
* @param {string} event The event to listen for.
|
||
* @param {Function} listener The function to invoke.
|
||
* @return {EventEmitter} for call chaining.
|
||
*/
|
||
|
||
/**
|
||
* Remove a listener from the listener array for the specified event.
|
||
* <b>Caution:</b> changes array indices in the listener array behind the
|
||
* listener.
|
||
* @function external:EventEmitter#removeListener
|
||
* @param {string} event The event to listen for.
|
||
* @param {Function} listener The function to invoke.
|
||
* @return {EventEmitter} for call chaining.
|
||
*/
|
||
|
||
/**
|
||
* Removes all listeners, or those of the specified event. It's not a good idea
|
||
* to remove listeners that were added elsewhere in the code, especially when
|
||
* it's on an emitter that you didn't create (e.g. sockets or file streams).
|
||
* @function external:EventEmitter#removeAllListeners
|
||
* @param {string} event Optional. The event to remove listeners for.
|
||
* @return {EventEmitter} for call chaining.
|
||
*/
|
||
|
||
/**
|
||
* Execute each of the listeners in order with the supplied arguments.
|
||
* @function external:EventEmitter#emit
|
||
* @param {string} event The event to emit.
|
||
* @param {Function} listener The function to invoke.
|
||
* @return {boolean} true if event had listeners, false otherwise.
|
||
*/
|
||
|
||
/**
|
||
* By default EventEmitters will print a warning if more than 10 listeners are
|
||
* added for a particular event. This is a useful default which helps finding
|
||
* memory leaks. Obviously not all Emitters should be limited to 10. This
|
||
* function allows that to be increased. Set to zero for unlimited.
|
||
* @function external:EventEmitter#setMaxListeners
|
||
* @param {Number} n The max number of listeners.
|
||
* @return {EventEmitter} for call chaining.
|
||
*/
|
||
|
||
// MatrixClient Callback JSDocs
|
||
|
||
/**
|
||
* The standard MatrixClient callback interface. Functions which accept this
|
||
* will specify 2 return arguments. These arguments map to the 2 parameters
|
||
* specified in this callback.
|
||
* @callback module:client.callback
|
||
* @param {Object} err The error value, the "rejected" value or null.
|
||
* @param {Object} data The data returned, the "resolved" value.
|
||
*/
|
||
|
||
/**
|
||
* {@link https://github.com/kriskowal/q|A promise implementation (Q)}. Functions
|
||
* which return this will specify 2 return arguments. These arguments map to the
|
||
* "onFulfilled" and "onRejected" values of the Promise.
|
||
* @typedef {Object} Promise
|
||
* @static
|
||
* @property {Function} then promise.then(onFulfilled, onRejected, onProgress)
|
||
* @property {Function} catch promise.catch(onRejected)
|
||
* @property {Function} finally promise.finally(callback)
|
||
* @property {Function} done promise.done(onFulfilled, onRejected, onProgress)
|
||
*/
|
||
|
||
},{"./http-api":2,"./models/event":4,"./models/room":8,"./models/user":9,"./store/stub":12,"./utils":13,"events":15,"q":17}],2:[function(require,module,exports){
|
||
"use strict";
|
||
/**
|
||
* This is an internal module. See {@link MatrixHttpApi} for the public class.
|
||
* @module http-api
|
||
*/
|
||
var q = require("q");
|
||
var utils = require("./utils");
|
||
|
||
/*
|
||
TODO:
|
||
- CS: complete register function (doing stages)
|
||
- Identity server: linkEmail, authEmail, bindEmail, lookup3pid
|
||
- uploadContent (?)
|
||
*/
|
||
|
||
/**
|
||
* A constant representing the URI path for version 1 of the Client-Server HTTP API.
|
||
*/
|
||
module.exports.PREFIX_V1 = "/_matrix/client/api/v1";
|
||
|
||
/**
|
||
* A constant representing the URI path for version 2 alpha of the Client-Server
|
||
* HTTP API.
|
||
*/
|
||
module.exports.PREFIX_V2_ALPHA_PREFIX = "/_matrix/client/v2_alpha";
|
||
|
||
/**
|
||
* Construct a MatrixHttpApi.
|
||
* @constructor
|
||
* @param {Object} opts The options to use for this HTTP API.
|
||
* @param {string} opts.baseUrl Required. The base client-server URL e.g.
|
||
* 'http://localhost:8008'.
|
||
* @param {Function} opts.request Required. The function to call for HTTP
|
||
* requests. This function must look like function(opts, callback){ ... }.
|
||
* @param {string} opts.prefix Required. The matrix client prefix to use, e.g.
|
||
* '/_matrix/client/api/v1'. See PREFIX_V1 and PREFIX_V2_ALPHA for constants.
|
||
* @param {bool} opts.onlyData True to return only the 'data' component of the
|
||
* response (e.g. the parsed HTTP body). If false, requests will return status
|
||
* codes and headers in addition to data. Default: false.
|
||
* @param {string} opts.accessToken The access_token to send with requests. Can be
|
||
* null to not send an access token.
|
||
*/
|
||
module.exports.MatrixHttpApi = function MatrixHttpApi(opts) {
|
||
utils.checkObjectHasKeys(opts, ["baseUrl", "request", "prefix"]);
|
||
opts.onlyData = opts.onlyData || false;
|
||
this.opts = opts;
|
||
};
|
||
|
||
module.exports.MatrixHttpApi.prototype = {
|
||
|
||
// URI functions
|
||
// =============
|
||
|
||
/**
|
||
* Get the HTTP URL for an MXC URI.
|
||
* @param {string} mxc The mxc:// URI.
|
||
* @param {Number} width The desired width of the thumbnail.
|
||
* @param {Number} height The desired height of the thumbnail.
|
||
* @param {string} resizeMethod The thumbnail resize method to use, either
|
||
* "crop" or "scale".
|
||
* @return {string} The complete URL to the content.
|
||
*/
|
||
getHttpUriForMxc: function(mxc, width, height, resizeMethod) {
|
||
if (typeof mxc !== "string" || !mxc) {
|
||
return mxc;
|
||
}
|
||
if (mxc.indexOf("mxc://") !== 0) {
|
||
return mxc;
|
||
}
|
||
var serverAndMediaId = mxc.slice(6); // strips mxc://
|
||
var prefix = "/_matrix/media/v1/download/";
|
||
var params = {};
|
||
|
||
if (width) {
|
||
params.width = width;
|
||
}
|
||
if (height) {
|
||
params.height = height;
|
||
}
|
||
if (resizeMethod) {
|
||
params.method = resizeMethod;
|
||
}
|
||
if (utils.keys(params).length > 0) {
|
||
// these are thumbnailing params so they probably want the
|
||
// thumbnailing API...
|
||
prefix = "/_matrix/media/v1/thumbnail/";
|
||
}
|
||
|
||
var fragmentOffset = serverAndMediaId.indexOf("#"),
|
||
fragment = "";
|
||
if (fragmentOffset >= 0) {
|
||
fragment = serverAndMediaId.substr(fragmentOffset);
|
||
serverAndMediaId = serverAndMediaId.substr(0, fragmentOffset);
|
||
}
|
||
return this.credentials.baseUrl + prefix + serverAndMediaId +
|
||
(utils.keys(params).length === 0 ? "" :
|
||
("?" + utils.encodeParams(params))) + fragment;
|
||
},
|
||
|
||
/**
|
||
* Get an identicon URL from an arbitrary string.
|
||
* @param {string} identiconString The string to create an identicon for.
|
||
* @param {Number} width The desired width of the image in pixels.
|
||
* @param {Number} height The desired height of the image in pixels.
|
||
* @return {string} The complete URL to the identicon.
|
||
*/
|
||
getIdenticonUri: function(identiconString, width, height) {
|
||
if (!identiconString) {
|
||
return;
|
||
}
|
||
if (!width) { width = 96; }
|
||
if (!height) { height = 96; }
|
||
var params = {
|
||
width: width,
|
||
height: height
|
||
};
|
||
|
||
var path = utils.encodeUri("/_matrix/media/v1/identicon/$ident", {
|
||
$ident: identiconString
|
||
});
|
||
return this.credentials.baseUrl + path +
|
||
(utils.keys(params).length === 0 ? "" :
|
||
("?" + utils.encodeParams(params)));
|
||
},
|
||
|
||
/**
|
||
* Get the content repository url with query parameters.
|
||
* @return {Object} An object with a 'base', 'path' and 'params' for base URL,
|
||
* path and query parameters respectively.
|
||
*/
|
||
getContentUri: function() {
|
||
var params = {
|
||
access_token: this.credentials.accessToken
|
||
};
|
||
return {
|
||
base: this.credentials.baseUrl,
|
||
path: "/_matrix/media/v1/upload",
|
||
params: params
|
||
};
|
||
},
|
||
|
||
/**
|
||
* Perform an authorised request to the homeserver.
|
||
* @param {Function} callback Optional. The callback to invoke on
|
||
* success/failure. See the promise return values for more information.
|
||
* @param {string} method The HTTP method e.g. "GET".
|
||
* @param {string} path The HTTP path <b>after</b> the supplied prefix e.g.
|
||
* "/createRoom".
|
||
* @param {Object} queryParams A dict of query params (these will NOT be
|
||
* urlencoded).
|
||
* @param {Object} data The HTTP JSON body.
|
||
* @return {module:client.Promise} Resolves to <code>{data: {Object},
|
||
* headers: {Object}, code: {Number}}</code>.
|
||
* If <code>onlyData</code> is set, this will resolve to the <code>data</code>
|
||
* object only.
|
||
* @return {module:http-api.MatrixError} Rejects with an error if a problem
|
||
* occurred. This includes network problems and Matrix-specific error JSON.
|
||
*/
|
||
authedRequest: function(callback, method, path, queryParams, data) {
|
||
if (!queryParams) { queryParams = {}; }
|
||
queryParams.access_token = this.opts.accessToken;
|
||
return this.request(callback, method, path, queryParams, data);
|
||
},
|
||
|
||
/**
|
||
* Perform a request to the homeserver without any credentials.
|
||
* @param {Function} callback Optional. The callback to invoke on
|
||
* success/failure. See the promise return values for more information.
|
||
* @param {string} method The HTTP method e.g. "GET".
|
||
* @param {string} path The HTTP path <b>after</b> the supplied prefix e.g.
|
||
* "/createRoom".
|
||
* @param {Object} queryParams A dict of query params (these will NOT be
|
||
* urlencoded).
|
||
* @param {Object} data The HTTP JSON body.
|
||
* @return {module:client.Promise} Resolves to <code>{data: {Object},
|
||
* headers: {Object}, code: {Number}}</code>.
|
||
* If <code>onlyData</code> is set, this will resolve to the <code>data</code>
|
||
* object only.
|
||
* @return {module:http-api.MatrixError} Rejects with an error if a problem
|
||
* occurred. This includes network problems and Matrix-specific error JSON.
|
||
*/
|
||
request: function(callback, method, path, queryParams, data) {
|
||
return this.requestWithPrefix(
|
||
callback, method, path, queryParams, data, this.opts.prefix
|
||
);
|
||
},
|
||
|
||
/**
|
||
* Perform an authorised request to the homeserver with a specific path
|
||
* prefix which overrides the default for this call only. Useful for hitting
|
||
* different Matrix Client-Server versions.
|
||
* @param {Function} callback Optional. The callback to invoke on
|
||
* success/failure. See the promise return values for more information.
|
||
* @param {string} method The HTTP method e.g. "GET".
|
||
* @param {string} path The HTTP path <b>after</b> the supplied prefix e.g.
|
||
* "/createRoom".
|
||
* @param {Object} queryParams A dict of query params (these will NOT be
|
||
* urlencoded).
|
||
* @param {Object} data The HTTP JSON body.
|
||
* @param {string} prefix The full prefix to use e.g.
|
||
* "/_matrix/client/v2_alpha".
|
||
* @return {module:client.Promise} Resolves to <code>{data: {Object},
|
||
* headers: {Object}, code: {Number}}</code>.
|
||
* If <code>onlyData</code> is set, this will resolve to the <code>data</code>
|
||
* object only.
|
||
* @return {module:http-api.MatrixError} Rejects with an error if a problem
|
||
* occurred. This includes network problems and Matrix-specific error JSON.
|
||
*/
|
||
authedRequestWithPrefix: function(callback, method, path, queryParams, data,
|
||
prefix) {
|
||
var fullUri = this.opts.baseUrl + prefix + path;
|
||
if (!queryParams) {
|
||
queryParams = {};
|
||
}
|
||
queryParams.access_token = this.opts.accessToken;
|
||
return this._request(callback, method, fullUri, queryParams, data);
|
||
},
|
||
|
||
/**
|
||
* Perform a request to the homeserver without any credentials but with a
|
||
* specific path prefix which overrides the default for this call only.
|
||
* Useful for hitting different Matrix Client-Server versions.
|
||
* @param {Function} callback Optional. The callback to invoke on
|
||
* success/failure. See the promise return values for more information.
|
||
* @param {string} method The HTTP method e.g. "GET".
|
||
* @param {string} path The HTTP path <b>after</b> the supplied prefix e.g.
|
||
* "/createRoom".
|
||
* @param {Object} queryParams A dict of query params (these will NOT be
|
||
* urlencoded).
|
||
* @param {Object} data The HTTP JSON body.
|
||
* @param {string} prefix The full prefix to use e.g.
|
||
* "/_matrix/client/v2_alpha".
|
||
* @return {module:client.Promise} Resolves to <code>{data: {Object},
|
||
* headers: {Object}, code: {Number}}</code>.
|
||
* If <code>onlyData</code> is set, this will resolve to the <code>data</code>
|
||
* object only.
|
||
* @return {module:http-api.MatrixError} Rejects with an error if a problem
|
||
* occurred. This includes network problems and Matrix-specific error JSON.
|
||
*/
|
||
requestWithPrefix: function(callback, method, path, queryParams, data, prefix) {
|
||
var fullUri = this.opts.baseUrl + prefix + path;
|
||
if (!queryParams) {
|
||
queryParams = {};
|
||
}
|
||
return this._request(callback, method, fullUri, queryParams, data);
|
||
},
|
||
|
||
_request: function(callback, method, uri, queryParams, data) {
|
||
if (callback !== undefined && !utils.isFunction(callback)) {
|
||
throw Error(
|
||
"Expected callback to be a function but got " + typeof callback
|
||
);
|
||
}
|
||
var defer = q.defer();
|
||
this.opts.request(
|
||
{
|
||
uri: uri,
|
||
method: method,
|
||
withCredentials: false,
|
||
qs: queryParams,
|
||
body: data,
|
||
json: true,
|
||
_matrix_opts: this.opts
|
||
},
|
||
requestCallback(defer, callback, this.opts.onlyData)
|
||
);
|
||
return defer.promise;
|
||
}
|
||
};
|
||
|
||
|
||
var requestCallback = function(defer, userDefinedCallback, onlyData) {
|
||
userDefinedCallback = userDefinedCallback || function() {};
|
||
|
||
return function(err, response, body) {
|
||
if (!err && response.statusCode >= 400) {
|
||
err = new module.exports.MatrixError(body);
|
||
}
|
||
|
||
if (err) {
|
||
defer.reject(err);
|
||
userDefinedCallback(err);
|
||
}
|
||
else {
|
||
var res = {
|
||
code: response.statusCode,
|
||
headers: response.headers,
|
||
data: body
|
||
};
|
||
defer.resolve(onlyData ? body : res);
|
||
userDefinedCallback(null, onlyData ? body : res);
|
||
}
|
||
};
|
||
};
|
||
|
||
/**
|
||
* Construct a Matrix error. This is a JavaScript Error with additional
|
||
* information specific to the standard Matrix error response.
|
||
* @constructor
|
||
* @param {Object} errorJson The Matrix error JSON returned from the homeserver.
|
||
* @prop {string} name The Matrix 'errcode' value, e.g. "M_FORBIDDEN".
|
||
* @prop {string} message The Matrix 'error' value, e.g. "Missing token."
|
||
* @prop {Object} data The raw Matrix error JSON used to construct this object.
|
||
*/
|
||
module.exports.MatrixError = function MatrixError(errorJson) {
|
||
this.name = errorJson.errcode || "Unknown error code";
|
||
this.message = errorJson.error || "Unknown message";
|
||
this.data = errorJson;
|
||
};
|
||
module.exports.MatrixError.prototype = Object.create(Error.prototype);
|
||
/** */
|
||
module.exports.MatrixError.prototype.constructor = module.exports.MatrixError;
|
||
|
||
},{"./utils":13,"q":17}],3:[function(require,module,exports){
|
||
"use strict";
|
||
|
||
/** 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. */
|
||
module.exports.MatrixHttpApi = require("./http-api").MatrixHttpApi;
|
||
/** The {@link module:http-api.MatrixError|MatrixError} class. */
|
||
module.exports.MatrixError = require("./http-api").MatrixError;
|
||
/** The {@link module:client.MatrixClient|MatrixClient} class. */
|
||
module.exports.MatrixClient = require("./client").MatrixClient;
|
||
/** The {@link module:models/room~Room|Room} class. */
|
||
module.exports.Room = require("./models/room");
|
||
/** The {@link module:models/room-member~RoomMember|RoomMember} class. */
|
||
module.exports.RoomMember = require("./models/room-member");
|
||
/** The {@link module:models/room-state~RoomState|RoomState} class. */
|
||
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)
|
||
var request;
|
||
/**
|
||
* The function used to perform HTTP requests. Only use this if you want to
|
||
* use a different HTTP library, e.g. Angular's <code>$http</code>. This should
|
||
* be set prior to calling {@link createClient}.
|
||
* @param {requestFunction} r The request function to use.
|
||
*/
|
||
module.exports.request = function(r) {
|
||
request = r;
|
||
};
|
||
|
||
/**
|
||
* 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. 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 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) {
|
||
if (typeof opts === "string") {
|
||
opts = {
|
||
"baseUrl": opts
|
||
};
|
||
}
|
||
opts.request = request;
|
||
opts.store = new module.exports.MatrixInMemoryStore();
|
||
opts.scheduler = new module.exports.MatrixScheduler();
|
||
return new module.exports.MatrixClient(opts);
|
||
};
|
||
|
||
/**
|
||
* The request function interface for performing HTTP requests. This matches the
|
||
* API for the {@link https://github.com/request/request#requestoptions-callback|
|
||
* request NPM module}. The SDK will attempt to call this function in order to
|
||
* perform an HTTP request.
|
||
* @callback requestFunction
|
||
* @param {Object} opts The options for this HTTP request.
|
||
* @param {string} opts.uri The complete URI.
|
||
* @param {string} opts.method The HTTP method.
|
||
* @param {Object} opts.qs The query parameters to append to the URI.
|
||
* @param {Object} opts.body The JSON-serializable object.
|
||
* @param {boolean} opts.json True if this is a JSON request.
|
||
* @param {Object} opts._matrix_opts The underlying options set for
|
||
* {@link MatrixHttpApi}.
|
||
* @param {requestCallback} callback The request callback.
|
||
*/
|
||
|
||
/**
|
||
* The request callback interface for performing HTTP requests. This matches the
|
||
* API for the {@link https://github.com/request/request#requestoptions-callback|
|
||
* request NPM module}. The SDK will implement a callback which meets this
|
||
* interface in order to handle the HTTP response.
|
||
* @callback requestCallback
|
||
* @param {Error} err The error if one occurred, else falsey.
|
||
* @param {Object} response The HTTP response which consists of
|
||
* <code>{statusCode: {Number}, headers: {Object}}</code>
|
||
* @param {Object} body The parsed HTTP response body.
|
||
*/
|
||
|
||
},{"./client":1,"./http-api":2,"./models/event":4,"./models/room":8,"./models/room-member":5,"./models/room-state":6,"./models/user":9,"./scheduler":10,"./store/memory":11}],4:[function(require,module,exports){
|
||
"use strict";
|
||
/**
|
||
* This is an internal module. See {@link MatrixEvent} and {@link RoomEvent} for
|
||
* the public classes.
|
||
* @module models/event
|
||
*/
|
||
|
||
|
||
/**
|
||
* Enum for event statuses.
|
||
* @readonly
|
||
* @enum {string}
|
||
*/
|
||
module.exports.EventStatus = {
|
||
/** The event was not sent and will no longer be retried. */
|
||
NOT_SENT: "not_sent",
|
||
/** The event is in the process of being sent. */
|
||
SENDING: "sending"
|
||
};
|
||
|
||
/**
|
||
* Construct a Matrix Event object
|
||
* @constructor
|
||
* @param {Object} event The raw event to be wrapped in this DAO
|
||
* @prop {Object} event The raw event. <b>Do not access this property</b>
|
||
* directly unless you absolutely have to. Prefer the getter methods defined on
|
||
* this class. Using the getter methods shields your app from
|
||
* changes to event JSON between Matrix versions.
|
||
* @prop {RoomMember} sender The room member who sent this event, or null e.g.
|
||
* this is a presence event.
|
||
* @prop {RoomMember} target The room member who is the target of this event, e.g.
|
||
* the invitee, the person being banned, etc.
|
||
* @prop {EventStatus} status The sending status of the event.
|
||
* @prop {boolean} forwardLooking True if this event is 'forward looking', meaning
|
||
* that getDirectionalContent() will return event.content and not event.prev_content.
|
||
* Default: true. <strong>This property is experimental and may change.</strong>
|
||
*/
|
||
module.exports.MatrixEvent = function MatrixEvent(event) {
|
||
this.event = event || {};
|
||
this.sender = null;
|
||
this.target = null;
|
||
this.status = null;
|
||
this.forwardLooking = true;
|
||
};
|
||
module.exports.MatrixEvent.prototype = {
|
||
|
||
/**
|
||
* Get the event_id for this event.
|
||
* @return {string} The event ID, e.g. <code>$143350589368169JsLZx:localhost
|
||
* </code>
|
||
*/
|
||
getId: function() {
|
||
return this.event.event_id;
|
||
},
|
||
|
||
/**
|
||
* Get the user_id for this event.
|
||
* @return {string} The user ID, e.g. <code>@alice:matrix.org</code>
|
||
*/
|
||
getSender: function() {
|
||
return this.event.user_id;
|
||
},
|
||
|
||
/**
|
||
* Get the type of event.
|
||
* @return {string} The event type, e.g. <code>m.room.message</code>
|
||
*/
|
||
getType: function() {
|
||
return this.event.type;
|
||
},
|
||
|
||
/**
|
||
* Get the room_id for this event. This will return <code>undefined</code>
|
||
* for <code>m.presence</code> events.
|
||
* @return {string} The room ID, e.g. <code>!cURbafjkfsMDVwdRDQ:matrix.org
|
||
* </code>
|
||
*/
|
||
getRoomId: function() {
|
||
return this.event.room_id;
|
||
},
|
||
|
||
/**
|
||
* Get the timestamp of this event.
|
||
* @return {Number} The event timestamp, e.g. <code>1433502692297</code>
|
||
*/
|
||
getTs: function() {
|
||
return this.event.origin_server_ts;
|
||
},
|
||
|
||
/**
|
||
* Get the event content JSON.
|
||
* @return {Object} The event content JSON, or an empty object.
|
||
*/
|
||
getContent: function() {
|
||
return this.event.content || {};
|
||
},
|
||
|
||
/**
|
||
* Get the previous event content JSON. This will only return something for
|
||
* state events which exist in the timeline.
|
||
* @return {Object} The previous event content JSON, or an empty object.
|
||
*/
|
||
getPrevContent: function() {
|
||
return this.event.prev_content || {};
|
||
},
|
||
|
||
/**
|
||
* Get either 'content' or 'prev_content' depending on if this event is
|
||
* 'forward-looking' or not. This can be modified via event.forwardLooking.
|
||
* <strong>This method is experimental and may change.</strong>
|
||
* @return {Object} event.content if this event is forward-looking, else
|
||
* event.prev_content.
|
||
*/
|
||
getDirectionalContent: function() {
|
||
return this.forwardLooking ? this.getContent() : this.getPrevContent();
|
||
},
|
||
|
||
/**
|
||
* Get the age of this event. This represents the age of the event when the
|
||
* event arrived at the device, and not the age of the event when this
|
||
* function was called.
|
||
* @return {Number} The age of this event in milliseconds.
|
||
*/
|
||
getAge: function() {
|
||
return this.event.age;
|
||
},
|
||
|
||
/**
|
||
* Get the event state_key if it has one. This will return <code>undefined
|
||
* </code> for message events.
|
||
* @return {string} The event's <code>state_key</code>.
|
||
*/
|
||
getStateKey: function() {
|
||
return this.event.state_key;
|
||
},
|
||
|
||
/**
|
||
* Check if this event is a state event.
|
||
* @return {boolean} True if this is a state event.
|
||
*/
|
||
isState: function() {
|
||
return this.event.state_key !== undefined;
|
||
}
|
||
};
|
||
|
||
},{}],5:[function(require,module,exports){
|
||
"use strict";
|
||
/**
|
||
* @module models/room-member
|
||
*/
|
||
var EventEmitter = require("events").EventEmitter;
|
||
|
||
var utils = require("../utils");
|
||
|
||
/**
|
||
* Construct a new room member.
|
||
* @constructor
|
||
* @param {string} roomId The room ID of the member.
|
||
* @param {string} userId The user ID of the member.
|
||
* @prop {string} roomId The room ID for this member.
|
||
* @prop {string} userId The user ID of this member.
|
||
* @prop {boolean} typing True if the room member is currently typing.
|
||
* @prop {string} name The human-readable name for this room member.
|
||
* @prop {Number} powerLevel The power level for this room member.
|
||
* @prop {Number} powerLevelNorm The normalised power level (0-100) for this
|
||
* 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'.
|
||
* @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;
|
||
this.userId = userId;
|
||
this.typing = false;
|
||
this.name = userId;
|
||
this.powerLevel = 0;
|
||
this.powerLevelNorm = 0;
|
||
this.user = null;
|
||
this.membership = null;
|
||
this.events = {
|
||
member: null
|
||
};
|
||
}
|
||
utils.inherits(RoomMember, EventEmitter);
|
||
|
||
/**
|
||
* Update this room member's membership event. May fire "RoomMember.name" if
|
||
* this event updates this member's name.
|
||
* @param {MatrixEvent} event The <code>m.room.member</code> event
|
||
* @param {RoomState} roomState Optional. The room state to take into account
|
||
* when calculating (e.g. for disambiguating users with the same name).
|
||
* @fires module:client~MatrixClient#event:"RoomMember.name"
|
||
* @fires module:client~MatrixClient#event:"RoomMember.membership"
|
||
*/
|
||
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;
|
||
|
||
var oldName = this.name;
|
||
this.name = calculateDisplayName(this, event, roomState);
|
||
if (oldMembership !== this.membership) {
|
||
this.emit("RoomMember.membership", event, this);
|
||
}
|
||
if (oldName !== this.name) {
|
||
this.emit("RoomMember.name", event, this);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Update this room member's power level event. May fire
|
||
* "RoomMember.powerLevel" if this event updates this member's power levels.
|
||
* @param {MatrixEvent} powerLevelEvent The <code>m.room.power_levels</code>
|
||
* event
|
||
* @fires module:client~MatrixClient#event:"RoomMember.powerLevel"
|
||
*/
|
||
RoomMember.prototype.setPowerLevelEvent = function(powerLevelEvent) {
|
||
if (powerLevelEvent.getType() !== "m.room.power_levels") {
|
||
return;
|
||
}
|
||
var maxLevel = powerLevelEvent.getContent().users_default || 0;
|
||
utils.forEach(utils.values(powerLevelEvent.getContent().users), function(lvl) {
|
||
maxLevel = Math.max(maxLevel, lvl);
|
||
});
|
||
var oldPowerLevel = this.powerLevel;
|
||
var oldPowerLevelNorm = this.powerLevelNorm;
|
||
this.powerLevel = (
|
||
powerLevelEvent.getContent().users[this.userId] ||
|
||
powerLevelEvent.getContent().users_default ||
|
||
0
|
||
);
|
||
this.powerLevelNorm = 0;
|
||
if (maxLevel > 0) {
|
||
this.powerLevelNorm = (this.powerLevel * 100) / maxLevel;
|
||
}
|
||
|
||
// emit for changes in powerLevelNorm as well (since the app will need to
|
||
// redraw everyone's level if the max has changed)
|
||
if (oldPowerLevel !== this.powerLevel || oldPowerLevelNorm !== this.powerLevelNorm) {
|
||
this.emit("RoomMember.powerLevel", powerLevelEvent, this);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Update this room member's typing event. May fire "RoomMember.typing" if
|
||
* this event changes this member's typing state.
|
||
* @param {MatrixEvent} event The typing event
|
||
* @fires module:client~MatrixClient#event:"RoomMember.typing"
|
||
*/
|
||
RoomMember.prototype.setTypingEvent = function(event) {
|
||
if (event.getType() !== "m.typing") {
|
||
return;
|
||
}
|
||
var oldTyping = this.typing;
|
||
this.typing = false;
|
||
var typingList = event.getContent().user_ids;
|
||
if (!utils.isArray(typingList)) {
|
||
// malformed event :/ bail early. TODO: whine?
|
||
return;
|
||
}
|
||
if (typingList.indexOf(this.userId) !== -1) {
|
||
this.typing = true;
|
||
}
|
||
if (oldTyping !== this.typing) {
|
||
this.emit("RoomMember.typing", event, this);
|
||
}
|
||
};
|
||
|
||
function calculateDisplayName(member, event, roomState) {
|
||
var displayName = event.getDirectionalContent().displayname;
|
||
var selfUserId = member.userId;
|
||
if (!displayName) {
|
||
return selfUserId;
|
||
}
|
||
if (!roomState) {
|
||
return displayName;
|
||
}
|
||
|
||
var stateEvents = utils.filter(
|
||
roomState.getStateEvents("m.room.member"),
|
||
function(e) {
|
||
return e.getContent().displayname === displayName &&
|
||
e.getSender() !== selfUserId;
|
||
}
|
||
);
|
||
if (stateEvents.length > 1) {
|
||
// need to disambiguate
|
||
return displayName + " (" + selfUserId + ")";
|
||
}
|
||
|
||
return displayName;
|
||
}
|
||
|
||
/**
|
||
* The RoomMember class.
|
||
*/
|
||
module.exports = RoomMember;
|
||
|
||
/**
|
||
* Fires whenever any room member's name changes.
|
||
* @event module:client~MatrixClient#"RoomMember.name"
|
||
* @param {MatrixEvent} event The matrix event which caused this event to fire.
|
||
* @param {RoomMember} member The member whose RoomMember.name changed.
|
||
* @example
|
||
* matrixClient.on("RoomMember.name", function(event, member){
|
||
* var newName = member.name;
|
||
* });
|
||
*/
|
||
|
||
/**
|
||
* Fires whenever any room member's membership state changes.
|
||
* @event module:client~MatrixClient#"RoomMember.membership"
|
||
* @param {MatrixEvent} event The matrix event which caused this event to fire.
|
||
* @param {RoomMember} member The member whose RoomMember.membership changed.
|
||
* @example
|
||
* matrixClient.on("RoomMember.membership", function(event, member){
|
||
* var newState = member.membership;
|
||
* });
|
||
*/
|
||
|
||
/**
|
||
* Fires whenever any room member's typing state changes.
|
||
* @event module:client~MatrixClient#"RoomMember.typing"
|
||
* @param {MatrixEvent} event The matrix event which caused this event to fire.
|
||
* @param {RoomMember} member The member whose RoomMember.typing changed.
|
||
* @example
|
||
* matrixClient.on("RoomMember.typing", function(event, member){
|
||
* var isTyping = member.typing;
|
||
* });
|
||
*/
|
||
|
||
/**
|
||
* Fires whenever any room member's power level changes.
|
||
* @event module:client~MatrixClient#"RoomMember.powerLevel"
|
||
* @param {MatrixEvent} event The matrix event which caused this event to fire.
|
||
* @param {RoomMember} member The member whose RoomMember.powerLevel changed.
|
||
* @example
|
||
* matrixClient.on("RoomMember.powerLevel", function(event, member){
|
||
* var newPowerLevel = member.powerLevel;
|
||
* var newNormPowerLevel = member.powerLevelNorm;
|
||
* });
|
||
*/
|
||
|
||
},{"../utils":13,"events":15}],6:[function(require,module,exports){
|
||
"use strict";
|
||
/**
|
||
* @module models/room-state
|
||
*/
|
||
var EventEmitter = require("events").EventEmitter;
|
||
|
||
var utils = require("../utils");
|
||
var RoomMember = require("./room-member");
|
||
|
||
/**
|
||
* Construct room state.
|
||
* @constructor
|
||
* @param {string} roomId Required. The ID of the room which has this state.
|
||
* @prop {Object.<string, RoomMember>} members The room member dictionary, keyed
|
||
* on the user's ID.
|
||
* @prop {Object.<string, Object.<string, MatrixEvent>>} events The state
|
||
* events dictionary, keyed on the event type and then the state_key value.
|
||
* @prop {string} paginationToken The pagination token for this state.
|
||
*/
|
||
function RoomState(roomId) {
|
||
this.roomId = roomId;
|
||
this.members = {
|
||
// userId: RoomMember
|
||
};
|
||
this.events = {
|
||
// eventType: { stateKey: MatrixEvent }
|
||
};
|
||
this.paginationToken = null;
|
||
|
||
this._sentinels = {
|
||
// userId: RoomMember
|
||
};
|
||
}
|
||
utils.inherits(RoomState, EventEmitter);
|
||
|
||
/**
|
||
* Get all RoomMembers in this room.
|
||
* @return {Array<RoomMember>} A list of RoomMembers.
|
||
*/
|
||
RoomState.prototype.getMembers = function() {
|
||
return utils.values(this.members);
|
||
};
|
||
|
||
/**
|
||
* Get a room member by their user ID.
|
||
* @param {string} userId The room member's user ID.
|
||
* @return {RoomMember} The member or null if they do not exist.
|
||
*/
|
||
RoomState.prototype.getMember = function(userId) {
|
||
return this.members[userId] || null;
|
||
};
|
||
|
||
/**
|
||
* Get a room member whose properties will not change with this room state. You
|
||
* typically want this if you want to attach a RoomMember to a MatrixEvent which
|
||
* may no longer be represented correctly by Room.currentState or Room.oldState.
|
||
* The term 'sentinel' refers to the fact that this RoomMember is an unchanging
|
||
* guardian for state at this particular point in time.
|
||
* @param {string} userId The room member's user ID.
|
||
* @return {RoomMember} The member or null if they do not exist.
|
||
*/
|
||
RoomState.prototype.getSentinelMember = function(userId) {
|
||
return this._sentinels[userId] || null;
|
||
};
|
||
|
||
/**
|
||
* Get state events from the state of the room.
|
||
* @param {string} eventType The event type of the state event.
|
||
* @param {string} stateKey Optional. The state_key of the state event. If
|
||
* this is <code>undefined</code> then all matching state events will be
|
||
* returned.
|
||
* @return {MatrixEvent[]|MatrixEvent} A list of events if state_key was
|
||
* <code>undefined</code>, else a single event (or null if no match found).
|
||
*/
|
||
RoomState.prototype.getStateEvents = function(eventType, stateKey) {
|
||
if (!this.events[eventType]) {
|
||
// no match
|
||
return stateKey === undefined ? [] : null;
|
||
}
|
||
if (stateKey === undefined) { // return all values
|
||
return utils.values(this.events[eventType]);
|
||
}
|
||
var event = this.events[eventType][stateKey];
|
||
return event ? event : null;
|
||
};
|
||
|
||
/**
|
||
* Add an array of one or more state MatrixEvents, overwriting
|
||
* any existing state with the same {type, stateKey} tuple. Will fire
|
||
* "RoomState.events" for every event added. May fire "RoomState.members"
|
||
* if there are <code>m.room.member</code> events.
|
||
* @param {MatrixEvent[]} stateEvents a list of state events for this room.
|
||
* @fires module:client~MatrixClient#event:"RoomState.members"
|
||
* @fires module:client~MatrixClient#event:"RoomState.events"
|
||
*/
|
||
RoomState.prototype.setStateEvents = function(stateEvents) {
|
||
var self = this;
|
||
utils.forEach(stateEvents, function(event) {
|
||
if (event.getRoomId() !== self.roomId) { return; }
|
||
if (!event.isState()) { return; }
|
||
|
||
if (self.events[event.getType()] === undefined) {
|
||
self.events[event.getType()] = {};
|
||
}
|
||
self.events[event.getType()][event.getStateKey()] = event;
|
||
self.emit("RoomState.events", event, self);
|
||
|
||
if (event.getType() === "m.room.member") {
|
||
var userId = event.getStateKey();
|
||
var member = self.members[userId];
|
||
if (!member) {
|
||
member = new RoomMember(event.getRoomId(), userId);
|
||
self.emit("RoomState.newMember", event, self, member);
|
||
}
|
||
// Add a new sentinel for this change. We apply the same
|
||
// operations to both sentinel and member rather than deep copying
|
||
// so we don't make assumptions about the properties of RoomMember
|
||
// (e.g. and manage to break it because deep copying doesn't do
|
||
// everything).
|
||
var sentinel = new RoomMember(event.getRoomId(), userId);
|
||
utils.forEach([member, sentinel], function(roomMember) {
|
||
roomMember.setMembershipEvent(event, self);
|
||
// this member may have a power level already, so set it.
|
||
var pwrLvlEvent = self.getStateEvents("m.room.power_levels", "");
|
||
if (pwrLvlEvent) {
|
||
roomMember.setPowerLevelEvent(pwrLvlEvent);
|
||
}
|
||
});
|
||
|
||
self._sentinels[userId] = sentinel;
|
||
self.members[userId] = member;
|
||
self.emit("RoomState.members", event, self, member);
|
||
}
|
||
else if (event.getType() === "m.room.power_levels") {
|
||
var members = utils.values(self.members);
|
||
utils.forEach(members, function(member) {
|
||
member.setPowerLevelEvent(event);
|
||
});
|
||
}
|
||
});
|
||
};
|
||
|
||
/**
|
||
* Set the current typing event for this room.
|
||
* @param {MatrixEvent} event The typing event
|
||
*/
|
||
RoomState.prototype.setTypingEvent = function(event) {
|
||
utils.forEach(utils.values(this.members), function(member) {
|
||
member.setTypingEvent(event);
|
||
});
|
||
};
|
||
|
||
/**
|
||
* The RoomState class.
|
||
*/
|
||
module.exports = RoomState;
|
||
|
||
/**
|
||
* Fires whenever the event dictionary in room state is updated.
|
||
* @event module:client~MatrixClient#"RoomState.events"
|
||
* @param {MatrixEvent} event The matrix event which caused this event to fire.
|
||
* @param {RoomState} state The room state whose RoomState.events dictionary
|
||
* was updated.
|
||
* @example
|
||
* matrixClient.on("RoomState.events", function(event, state){
|
||
* var newStateEvent = event;
|
||
* });
|
||
*/
|
||
|
||
/**
|
||
* Fires whenever a member in the members dictionary is updated in any way.
|
||
* @event module:client~MatrixClient#"RoomState.members"
|
||
* @param {MatrixEvent} event The matrix event which caused this event to fire.
|
||
* @param {RoomState} state The room state whose RoomState.members dictionary
|
||
* was updated.
|
||
* @param {RoomMember} member The room member that was updated.
|
||
* @example
|
||
* matrixClient.on("RoomState.members", function(event, state, member){
|
||
* var newMembershipState = member.membership;
|
||
* });
|
||
*/
|
||
|
||
/**
|
||
* Fires whenever a member is added to the members dictionary. The RoomMember
|
||
* will not be fully populated yet (e.g. no membership state).
|
||
* @event module:client~MatrixClient#"RoomState.newMember"
|
||
* @param {MatrixEvent} event The matrix event which caused this event to fire.
|
||
* @param {RoomState} state The room state whose RoomState.members dictionary
|
||
* was updated with a new entry.
|
||
* @param {RoomMember} member The room member that was added.
|
||
* @example
|
||
* matrixClient.on("RoomState.newMember", function(event, state, member){
|
||
* // add event listeners on 'member'
|
||
* });
|
||
*/
|
||
|
||
},{"../utils":13,"./room-member":5,"events":15}],7:[function(require,module,exports){
|
||
"use strict";
|
||
/**
|
||
* @module models/room-summary
|
||
*/
|
||
|
||
/**
|
||
* Construct a new Room Summary. A summary can be used for display on a recent
|
||
* list, without having to load the entire room list into memory.
|
||
* @constructor
|
||
* @param {string} roomId Required. The ID of this room.
|
||
* @param {Object} info Optional. The summary info. Additional keys are supported.
|
||
* @param {string} info.title The title of the room (e.g. <code>m.room.name</code>)
|
||
* @param {string} info.desc The description of the room (e.g.
|
||
* <code>m.room.topic</code>)
|
||
* @param {Number} info.numMembers The number of joined users.
|
||
* @param {string[]} info.aliases The list of aliases for this room.
|
||
* @param {Number} info.timestamp The timestamp for this room.
|
||
*/
|
||
function RoomSummary(roomId, info) {
|
||
this.roomId = roomId;
|
||
this.info = info;
|
||
}
|
||
|
||
/**
|
||
* The RoomSummary class.
|
||
*/
|
||
module.exports = RoomSummary;
|
||
|
||
},{}],8:[function(require,module,exports){
|
||
"use strict";
|
||
/**
|
||
* @module models/room
|
||
*/
|
||
var EventEmitter = require("events").EventEmitter;
|
||
|
||
var RoomState = require("./room-state");
|
||
var RoomSummary = require("./room-summary");
|
||
var utils = require("../utils");
|
||
|
||
/**
|
||
* Construct a new Room.
|
||
* @constructor
|
||
* @param {string} roomId Required. The ID of this room.
|
||
* @prop {string} roomId The ID of this room.
|
||
* @prop {string} name The human-readable display name for this room.
|
||
* @prop {Array<MatrixEvent>} timeline The ordered list of message events for
|
||
* this room.
|
||
* @prop {RoomState} oldState The state of the room at the time of the oldest
|
||
* event in the timeline.
|
||
* @prop {RoomState} currentState The state of the room at the time of the
|
||
* newest event in the timeline.
|
||
* @prop {RoomSummary} summary The room summary.
|
||
*/
|
||
function Room(roomId) {
|
||
this.roomId = roomId;
|
||
this.name = roomId;
|
||
this.timeline = [];
|
||
this.oldState = new RoomState(roomId);
|
||
this.currentState = new RoomState(roomId);
|
||
this.summary = null;
|
||
}
|
||
utils.inherits(Room, EventEmitter);
|
||
|
||
/**
|
||
* Get a member from the current room state.
|
||
* @param {string} userId The user ID of the member.
|
||
* @return {RoomMember} The member or <code>null</code>.
|
||
*/
|
||
Room.prototype.getMember = function(userId) {
|
||
var member = this.currentState.members[userId];
|
||
if (!member) {
|
||
return null;
|
||
}
|
||
return member;
|
||
};
|
||
|
||
/**
|
||
* Get a list of members whose membership state is "join".
|
||
* @return {RoomMember[]} A list of currently joined members.
|
||
*/
|
||
Room.prototype.getJoinedMembers = function() {
|
||
return utils.filter(this.currentState.getMembers(), function(m) {
|
||
return m.membership === "join";
|
||
});
|
||
};
|
||
|
||
/**
|
||
* 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.
|
||
* @param {MatrixEvent[]} events A list of events to add.
|
||
* @param {boolean} toStartOfTimeline True to add these events to the start
|
||
* (oldest) instead of the end (newest) of the timeline. If true, the oldest
|
||
* event will be the <b>last</b> element of 'events'.
|
||
* @fires module:client~MatrixClient#event:"Room.timeline"
|
||
*/
|
||
Room.prototype.addEventsToTimeline = function(events, toStartOfTimeline) {
|
||
var stateContext = toStartOfTimeline ? this.oldState : this.currentState;
|
||
for (var i = 0; i < events.length; i++) {
|
||
// set sender and target properties
|
||
events[i].sender = stateContext.getSentinelMember(
|
||
events[i].getSender()
|
||
);
|
||
if (events[i].getType() === "m.room.member") {
|
||
events[i].target = stateContext.getSentinelMember(
|
||
events[i].getStateKey()
|
||
);
|
||
}
|
||
|
||
// modify state
|
||
if (events[i].isState()) {
|
||
// room state has no concept of 'old' or 'current', but we want the
|
||
// room state to regress back to previous values if toStartOfTimeline
|
||
// is set, which means inspecting prev_content if it exists. This
|
||
// is done by toggling the forwardLooking flag.
|
||
if (toStartOfTimeline) {
|
||
events[i].forwardLooking = false;
|
||
}
|
||
stateContext.setStateEvents([events[i]], toStartOfTimeline);
|
||
}
|
||
// TODO: pass through filter to see if this should be added to the timeline.
|
||
if (toStartOfTimeline) {
|
||
this.timeline.unshift(events[i]);
|
||
}
|
||
else {
|
||
this.timeline.push(events[i]);
|
||
}
|
||
this.emit("Room.timeline", events[i], this, toStartOfTimeline);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Add some events to this room. This can include state events, message
|
||
* events and typing notifications. These events are treated as "live" so
|
||
* they will go to the end of the timeline.
|
||
* @param {MatrixEvent[]} events A list of events to add.
|
||
* @param {string} duplicateStrategy Optional. Applies to events in the
|
||
* timeline only. If this is not specified, no duplicate suppression is
|
||
* performed (this improves performance). If this is 'replace' then if a
|
||
* duplicate is encountered, the event passed to this function will replace the
|
||
* existing event in the timeline. If this is 'ignore', then the event passed to
|
||
* this function will be ignored entirely, preserving the existing event in the
|
||
* timeline. Events are identical based on their event ID <b>only</b>.
|
||
* @throws If <code>duplicateStrategy</code> is not falsey, 'replace' or 'ignore'.
|
||
*/
|
||
Room.prototype.addEvents = function(events, duplicateStrategy) {
|
||
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]]);
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Recalculate various aspects of the room, including the room name and
|
||
* room summary. Call this any time the room's current state is modified.
|
||
* May fire "Room.name" if the room name is updated.
|
||
* @param {string} userId The client's user ID.
|
||
* @fires module:client~MatrixClient#event:"Room.name"
|
||
*/
|
||
Room.prototype.recalculate = function(userId) {
|
||
var oldName = this.name;
|
||
this.name = calculateRoomName(this, userId);
|
||
this.summary = new RoomSummary(this.roomId, {
|
||
title: this.name
|
||
});
|
||
|
||
if (oldName !== this.name) {
|
||
this.emit("Room.name", this);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 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.
|
||
*/
|
||
function calculateRoomName(room, userId) {
|
||
// check for an alias, if any. for now, assume first alias is the
|
||
// official one.
|
||
var alias;
|
||
var mRoomAliases = room.currentState.getStateEvents("m.room.aliases")[0];
|
||
if (mRoomAliases && utils.isArray(mRoomAliases.getContent().aliases)) {
|
||
alias = mRoomAliases.getContent().aliases[0];
|
||
}
|
||
|
||
var mRoomName = room.currentState.getStateEvents('m.room.name', '');
|
||
if (mRoomName) {
|
||
return mRoomName.getContent().name + (alias ? " (" + alias + ")" : "");
|
||
}
|
||
else if (alias) {
|
||
return alias;
|
||
}
|
||
else {
|
||
// get members that are NOT ourselves.
|
||
var members = utils.filter(room.currentState.getMembers(), function(m) {
|
||
return m.userId !== userId;
|
||
});
|
||
// TODO: Localisation
|
||
if (members.length === 0) {
|
||
var memberList = room.currentState.getMembers();
|
||
if (memberList.length === 1) {
|
||
// we exist, but no one else... self-chat or invite.
|
||
if (memberList[0].membership === "invite") {
|
||
return "Room Invite";
|
||
}
|
||
else {
|
||
return userId;
|
||
}
|
||
}
|
||
else {
|
||
// there really isn't anyone in this room...
|
||
return "?";
|
||
}
|
||
}
|
||
else if (members.length === 1) {
|
||
return members[0].name;
|
||
}
|
||
else if (members.length === 2) {
|
||
return (
|
||
members[0].name + " and " + members[1].name
|
||
);
|
||
}
|
||
else {
|
||
return (
|
||
members[0].name + " and " + (members.length - 1) + " others"
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* The Room class.
|
||
*/
|
||
module.exports = Room;
|
||
|
||
/**
|
||
* Fires whenever the timeline in a room is updated.
|
||
* @event module:client~MatrixClient#"Room.timeline"
|
||
* @param {MatrixEvent} event The matrix event which caused this event to fire.
|
||
* @param {Room} room The room whose Room.timeline was updated.
|
||
* @param {boolean} toStartOfTimeline True if this event was added to the start
|
||
* (beginning; oldest) of the timeline e.g. due to pagination.
|
||
* @example
|
||
* matrixClient.on("Room.timeline", function(event, room, toStartOfTimeline){
|
||
* if (toStartOfTimeline) {
|
||
* var messageToAppend = room.timeline[room.timeline.length - 1];
|
||
* }
|
||
* });
|
||
*/
|
||
|
||
/**
|
||
* Fires whenever the name of a room is updated.
|
||
* @event module:client~MatrixClient#"Room.name"
|
||
* @param {Room} room The room whose Room.name was updated.
|
||
* @example
|
||
* matrixClient.on("Room.name", function(room){
|
||
* var newName = room.name;
|
||
* });
|
||
*/
|
||
|
||
},{"../utils":13,"./room-state":6,"./room-summary":7,"events":15}],9:[function(require,module,exports){
|
||
"use strict";
|
||
/**
|
||
* @module models/user
|
||
*/
|
||
var EventEmitter = require("events").EventEmitter;
|
||
var utils = require("../utils");
|
||
|
||
/**
|
||
* Construct a new User. A User must have an ID and can optionally have extra
|
||
* information associated with it.
|
||
* @constructor
|
||
* @param {string} userId Required. The ID of this user.
|
||
* @prop {string} userId The ID of the user.
|
||
* @prop {Object} info The info object supplied in the constructor.
|
||
* @prop {string} displayName The 'displayname' of the user if known.
|
||
* @prop {string} avatarUrl The 'avatar_url' of the user if known.
|
||
* @prop {string} presence The presence enum if known.
|
||
* @prop {Number} lastActiveAgo The last time the user performed some action in ms.
|
||
* @prop {Object} events The events describing this user.
|
||
* @prop {MatrixEvent} events.presence The m.presence event for this user.
|
||
*/
|
||
function User(userId) {
|
||
this.userId = userId;
|
||
this.presence = "offline";
|
||
this.displayName = userId;
|
||
this.avatarUrl = null;
|
||
this.lastActiveAgo = 0;
|
||
this.events = {
|
||
presence: null,
|
||
profile: null
|
||
};
|
||
}
|
||
utils.inherits(User, EventEmitter);
|
||
|
||
/**
|
||
* Update this User with the given presence event. May fire "User.presence",
|
||
* "User.avatarUrl" and/or "User.displayName" if this event updates this user's
|
||
* properties.
|
||
* @param {MatrixEvent} event The <code>m.presence</code> event.
|
||
* @fires module:client~MatrixClient#event:"User.presence"
|
||
* @fires module:client~MatrixClient#event:"User.displayName"
|
||
* @fires module:client~MatrixClient#event:"User.avatarUrl"
|
||
*/
|
||
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");
|
||
}
|
||
if (event.getContent().avatar_url !== this.avatarUrl) {
|
||
eventsToFire.push("User.avatarUrl");
|
||
}
|
||
if (event.getContent().displayname !== this.displayName) {
|
||
eventsToFire.push("User.displayName");
|
||
}
|
||
|
||
this.presence = event.getContent().presence;
|
||
this.displayName = event.getContent().displayname;
|
||
this.avatarUrl = event.getContent().avatar_url;
|
||
this.lastActiveAgo = event.getContent().last_active_ago;
|
||
|
||
for (var i = 0; i < eventsToFire.length; i++) {
|
||
this.emit(eventsToFire[i], event, this);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* The User class.
|
||
*/
|
||
module.exports = User;
|
||
|
||
/**
|
||
* Fires whenever any user's presence changes.
|
||
* @event module:client~MatrixClient#"User.presence"
|
||
* @param {MatrixEvent} event The matrix event which caused this event to fire.
|
||
* @param {User} user The user whose User.presence changed.
|
||
* @example
|
||
* matrixClient.on("User.presence", function(event, user){
|
||
* var newPresence = user.presence;
|
||
* });
|
||
*/
|
||
|
||
/**
|
||
* Fires whenever any user's display name changes.
|
||
* @event module:client~MatrixClient#"User.displayName"
|
||
* @param {MatrixEvent} event The matrix event which caused this event to fire.
|
||
* @param {User} user The user whose User.displayName changed.
|
||
* @example
|
||
* matrixClient.on("User.displayName", function(event, user){
|
||
* var newName = user.displayName;
|
||
* });
|
||
*/
|
||
|
||
/**
|
||
* Fires whenever any user's avatar URL changes.
|
||
* @event module:client~MatrixClient#"User.avatarUrl"
|
||
* @param {MatrixEvent} event The matrix event which caused this event to fire.
|
||
* @param {User} user The user whose User.avatarUrl changed.
|
||
* @example
|
||
* matrixClient.on("User.avatarUrl", function(event, user){
|
||
* var newUrl = user.avatarUrl;
|
||
* });
|
||
*/
|
||
|
||
},{"../utils":13,"events":15}],10:[function(require,module,exports){
|
||
"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;
|
||
|
||
},{"./utils":13,"q":17}],11:[function(require,module,exports){
|
||
"use strict";
|
||
/**
|
||
* This is an internal module. See {@link MatrixInMemoryStore} for the public class.
|
||
* @module store/memory
|
||
*/
|
||
var utils = require("../utils");
|
||
|
||
/**
|
||
* Construct a new in-memory data store for the Matrix Client.
|
||
* @constructor
|
||
*/
|
||
module.exports.MatrixInMemoryStore = function MatrixInMemoryStore() {
|
||
this.rooms = {
|
||
// roomId: Room
|
||
};
|
||
this.users = {
|
||
// userId: User
|
||
};
|
||
};
|
||
|
||
module.exports.MatrixInMemoryStore.prototype = {
|
||
|
||
/**
|
||
* Store the given room.
|
||
* @param {Room} room The room to be stored. All properties must be stored.
|
||
*/
|
||
storeRoom: function(room) {
|
||
this.rooms[room.roomId] = room;
|
||
},
|
||
|
||
/**
|
||
* Retrieve a room by its' room ID.
|
||
* @param {string} roomId The room ID.
|
||
* @return {Room} The room or null.
|
||
*/
|
||
getRoom: function(roomId) {
|
||
return this.rooms[roomId] || null;
|
||
},
|
||
|
||
/**
|
||
* Retrieve all known rooms.
|
||
* @return {Room[]} A list of rooms, which may be empty.
|
||
*/
|
||
getRooms: function() {
|
||
return utils.values(this.rooms);
|
||
},
|
||
|
||
/**
|
||
* Retrieve a summary of all the rooms.
|
||
* @return {RoomSummary[]} A summary of each room.
|
||
*/
|
||
getRoomSummaries: function() {
|
||
return utils.map(utils.values(this.rooms), function(room) {
|
||
return room.summary;
|
||
});
|
||
},
|
||
|
||
/**
|
||
* Store a User.
|
||
* @param {User} user The user to store.
|
||
*/
|
||
storeUser: function(user) {
|
||
this.users[user.userId] = user;
|
||
},
|
||
|
||
/**
|
||
* Retrieve a User by its' user ID.
|
||
* @param {string} userId The user ID.
|
||
* @return {User} The user or null.
|
||
*/
|
||
getUser: function(userId) {
|
||
return this.users[userId] || null;
|
||
}
|
||
|
||
// TODO
|
||
//setMaxHistoryPerRoom: function(maxHistory) {},
|
||
|
||
// TODO
|
||
//reapOldMessages: function() {},
|
||
};
|
||
|
||
},{"../utils":13}],12:[function(require,module,exports){
|
||
"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;
|
||
|
||
},{}],13:[function(require,module,exports){
|
||
"use strict";
|
||
/**
|
||
* This is an internal module.
|
||
* @module utils
|
||
*/
|
||
|
||
/**
|
||
* Encode a dictionary of query parameters.
|
||
* @param {Object} params A dict of key/values to encode e.g.
|
||
* {"foo": "bar", "baz": "taz"}
|
||
* @return {string} The encoded string e.g. foo=bar&baz=taz
|
||
*/
|
||
module.exports.encodeParams = function(params) {
|
||
var qs = "";
|
||
for (var key in params) {
|
||
if (!params.hasOwnProperty(key)) { continue; }
|
||
qs += "&" + encodeURIComponent(key) + "=" +
|
||
encodeURIComponent(params[key]);
|
||
}
|
||
return qs.substring(1);
|
||
};
|
||
|
||
/**
|
||
* Encodes a URI according to a set of template variables. Variables will be
|
||
* passed through encodeURIComponent.
|
||
* @param {string} pathTemplate The path with template variables e.g. '/foo/$bar'.
|
||
* @param {Object} variables The key/value pairs to replace the template
|
||
* variables with. E.g. { "$bar": "baz" }.
|
||
* @return {string} The result of replacing all template variables e.g. '/foo/baz'.
|
||
*/
|
||
module.exports.encodeUri = function(pathTemplate, variables) {
|
||
for (var key in variables) {
|
||
if (!variables.hasOwnProperty(key)) { continue; }
|
||
pathTemplate = pathTemplate.replace(
|
||
key, encodeURIComponent(variables[key])
|
||
);
|
||
}
|
||
return pathTemplate;
|
||
};
|
||
|
||
/**
|
||
* Applies a map function to the given array.
|
||
* @param {Array} array The array to apply the function to.
|
||
* @param {Function} fn The function that will be invoked for each element in
|
||
* the array with the signature <code>fn(element){...}</code>
|
||
* @return {Array} A new array with the results of the function.
|
||
*/
|
||
module.exports.map = function(array, fn) {
|
||
var results = new Array(array.length);
|
||
for (var i = 0; i < array.length; i++) {
|
||
results[i] = fn(array[i]);
|
||
}
|
||
return results;
|
||
};
|
||
|
||
/**
|
||
* Applies a filter function to the given array.
|
||
* @param {Array} array The array to apply the function to.
|
||
* @param {Function} fn The function that will be invoked for each element in
|
||
* the array. It should return true to keep the element. The function signature
|
||
* looks like <code>fn(element, index, array){...}</code>.
|
||
* @return {Array} A new array with the results of the function.
|
||
*/
|
||
module.exports.filter = function(array, fn) {
|
||
var results = [];
|
||
for (var i = 0; i < array.length; i++) {
|
||
if (fn(array[i], i, array)) {
|
||
results.push(array[i]);
|
||
}
|
||
}
|
||
return results;
|
||
};
|
||
|
||
/**
|
||
* Get the keys for an object. Same as <code>Object.keys()</code>.
|
||
* @param {Object} obj The object to get the keys for.
|
||
* @return {string[]} The keys of the object.
|
||
*/
|
||
module.exports.keys = function(obj) {
|
||
var keys = [];
|
||
for (var key in obj) {
|
||
if (!obj.hasOwnProperty(key)) { continue; }
|
||
keys.push(key);
|
||
}
|
||
return keys;
|
||
};
|
||
|
||
/**
|
||
* Get the values for an object.
|
||
* @param {Object} obj The object to get the values for.
|
||
* @return {Array<*>} The values of the object.
|
||
*/
|
||
module.exports.values = function(obj) {
|
||
var values = [];
|
||
for (var key in obj) {
|
||
if (!obj.hasOwnProperty(key)) { continue; }
|
||
values.push(obj[key]);
|
||
}
|
||
return values;
|
||
};
|
||
|
||
/**
|
||
* Invoke a function for each item in the array.
|
||
* @param {Array} array The array.
|
||
* @param {Function} fn The function to invoke for each element. Has the
|
||
* function signature <code>fn(element, index)</code>.
|
||
*/
|
||
module.exports.forEach = function(array, fn) {
|
||
for (var i = 0; i < array.length; i++) {
|
||
fn(array[i], i);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 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.
|
||
* @return {boolean} True if it is a function.
|
||
*/
|
||
module.exports.isFunction = function(value) {
|
||
return Object.prototype.toString.call(value) == "[object Function]";
|
||
};
|
||
|
||
/**
|
||
* Checks if the given thing is an array.
|
||
* @param {*} value The thing to check.
|
||
* @return {boolean} True if it is an array.
|
||
*/
|
||
module.exports.isArray = function(value) {
|
||
return Boolean(value && value.constructor === Array);
|
||
};
|
||
|
||
/**
|
||
* Checks that the given object has the specified keys.
|
||
* @param {Object} obj The object to check.
|
||
* @param {string[]} keys The list of keys that 'obj' must have.
|
||
* @throws If the object is missing keys.
|
||
*/
|
||
module.exports.checkObjectHasKeys = function(obj, keys) {
|
||
for (var i = 0; i < keys.length; i++) {
|
||
if (!obj.hasOwnProperty(keys[i])) {
|
||
throw new Error("Missing required key: " + keys[i]);
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Checks that the given object has no extra keys other than the specified ones.
|
||
* @param {Object} obj The object to check.
|
||
* @param {string[]} allowedKeys The list of allowed key names.
|
||
* @throws If there are extra keys.
|
||
*/
|
||
module.exports.checkObjectHasNoAdditionalKeys = function(obj, allowedKeys) {
|
||
for (var key in obj) {
|
||
if (!obj.hasOwnProperty(key)) { continue; }
|
||
if (allowedKeys.indexOf(key) === -1) {
|
||
throw new Error("Unknown key: " + key);
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Assigns all the properties in src to dst. If these properties are Objects,
|
||
* then both src and dst will refer to the same thing.
|
||
* @param {Object} src The object to copy properties from.
|
||
* @param {Object} dst The object to write properties to.
|
||
*/
|
||
module.exports.shallowCopy = function(src, dst) {
|
||
for (var i in src) {
|
||
if (src.hasOwnProperty(i)) {
|
||
dst[i] = src[i];
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Deep copy the given object. The object MUST NOT have circular references and
|
||
* MUST NOT have functions.
|
||
* @param {Object} obj The object to deep copy.
|
||
* @return {Object} A copy of the object without any references to the original.
|
||
*/
|
||
module.exports.deepCopy = function(obj) {
|
||
return JSON.parse(JSON.stringify(obj));
|
||
};
|
||
|
||
/**
|
||
* Inherit the prototype methods from one constructor into another. This is a
|
||
* port of the Node.js implementation with an Object.create polyfill.
|
||
*
|
||
* @param {function} ctor Constructor function which needs to inherit the
|
||
* prototype.
|
||
* @param {function} superCtor Constructor function to inherit prototype from.
|
||
*/
|
||
module.exports.inherits = function(ctor, superCtor) {
|
||
// Add Object.create polyfill for IE8
|
||
// Source:
|
||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript
|
||
// /Reference/Global_Objects/Object/create#Polyfill
|
||
if (typeof Object.create != 'function') {
|
||
// Production steps of ECMA-262, Edition 5, 15.2.3.5
|
||
// Reference: http://es5.github.io/#x15.2.3.5
|
||
Object.create = (function() {
|
||
// To save on memory, use a shared constructor
|
||
function Temp() {}
|
||
|
||
// make a safe reference to Object.prototype.hasOwnProperty
|
||
var hasOwn = Object.prototype.hasOwnProperty;
|
||
|
||
return function(O) {
|
||
// 1. If Type(O) is not Object or Null throw a TypeError exception.
|
||
if (typeof O != 'object') {
|
||
throw new TypeError('Object prototype may only be an Object or null');
|
||
}
|
||
|
||
// 2. Let obj be the result of creating a new object as if by the
|
||
// expression new Object() where Object is the standard built-in
|
||
// constructor with that name
|
||
// 3. Set the [[Prototype]] internal property of obj to O.
|
||
Temp.prototype = O;
|
||
var obj = new Temp();
|
||
Temp.prototype = null; // Let's not keep a stray reference to O...
|
||
|
||
// 4. If the argument Properties is present and not undefined, add
|
||
// own properties to obj as if by calling the standard built-in
|
||
// function Object.defineProperties with arguments obj and
|
||
// Properties.
|
||
if (arguments.length > 1) {
|
||
// Object.defineProperties does ToObject on its first argument.
|
||
var Properties = Object(arguments[1]);
|
||
for (var prop in Properties) {
|
||
if (hasOwn.call(Properties, prop)) {
|
||
obj[prop] = Properties[prop];
|
||
}
|
||
}
|
||
}
|
||
|
||
// 5. Return obj
|
||
return obj;
|
||
};
|
||
})();
|
||
}
|
||
// END polyfill
|
||
|
||
// Add util.inherits from Node.js
|
||
// Source:
|
||
// https://github.com/joyent/node/blob/master/lib/util.js
|
||
// Copyright Joyent, Inc. and other Node contributors.
|
||
//
|
||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||
// copy of this software and associated documentation files (the
|
||
// "Software"), to deal in the Software without restriction, including
|
||
// without limitation the rights to use, copy, modify, merge, publish,
|
||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||
// persons to whom the Software is furnished to do so, subject to the
|
||
// following conditions:
|
||
//
|
||
// The above copyright notice and this permission notice shall be included
|
||
// in all copies or substantial portions of the Software.
|
||
//
|
||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||
ctor.super_ = superCtor;
|
||
ctor.prototype = Object.create(superCtor.prototype, {
|
||
constructor: {
|
||
value: ctor,
|
||
enumerable: false,
|
||
writable: true,
|
||
configurable: true
|
||
}
|
||
});
|
||
};
|
||
|
||
},{}],14:[function(require,module,exports){
|
||
// Browser Request
|
||
//
|
||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
// you may not use this file except in compliance with the License.
|
||
// You may obtain a copy of the License at
|
||
//
|
||
// http://www.apache.org/licenses/LICENSE-2.0
|
||
//
|
||
// Unless required by applicable law or agreed to in writing, software
|
||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
// See the License for the specific language governing permissions and
|
||
// limitations under the License.
|
||
|
||
// UMD HEADER START
|
||
(function (root, factory) {
|
||
if (typeof define === 'function' && define.amd) {
|
||
// AMD. Register as an anonymous module.
|
||
define([], factory);
|
||
} else if (typeof exports === 'object') {
|
||
// Node. Does not work with strict CommonJS, but
|
||
// only CommonJS-like enviroments that support module.exports,
|
||
// like Node.
|
||
module.exports = factory();
|
||
} else {
|
||
// Browser globals (root is window)
|
||
root.returnExports = factory();
|
||
}
|
||
}(this, function () {
|
||
// UMD HEADER END
|
||
|
||
var XHR = XMLHttpRequest
|
||
if (!XHR) throw new Error('missing XMLHttpRequest')
|
||
request.log = {
|
||
'trace': noop, 'debug': noop, 'info': noop, 'warn': noop, 'error': noop
|
||
}
|
||
|
||
var DEFAULT_TIMEOUT = 3 * 60 * 1000 // 3 minutes
|
||
|
||
//
|
||
// request
|
||
//
|
||
|
||
function request(options, callback) {
|
||
// The entry-point to the API: prep the options object and pass the real work to run_xhr.
|
||
if(typeof callback !== 'function')
|
||
throw new Error('Bad callback given: ' + callback)
|
||
|
||
if(!options)
|
||
throw new Error('No options given')
|
||
|
||
var options_onResponse = options.onResponse; // Save this for later.
|
||
|
||
if(typeof options === 'string')
|
||
options = {'uri':options};
|
||
else
|
||
options = JSON.parse(JSON.stringify(options)); // Use a duplicate for mutating.
|
||
|
||
options.onResponse = options_onResponse // And put it back.
|
||
|
||
if (options.verbose) request.log = getLogger();
|
||
|
||
if(options.url) {
|
||
options.uri = options.url;
|
||
delete options.url;
|
||
}
|
||
|
||
if(!options.uri && options.uri !== "")
|
||
throw new Error("options.uri is a required argument");
|
||
|
||
if(typeof options.uri != "string")
|
||
throw new Error("options.uri must be a string");
|
||
|
||
var unsupported_options = ['proxy', '_redirectsFollowed', 'maxRedirects', 'followRedirect']
|
||
for (var i = 0; i < unsupported_options.length; i++)
|
||
if(options[ unsupported_options[i] ])
|
||
throw new Error("options." + unsupported_options[i] + " is not supported")
|
||
|
||
options.callback = callback
|
||
options.method = options.method || 'GET';
|
||
options.headers = options.headers || {};
|
||
options.body = options.body || null
|
||
options.timeout = options.timeout || request.DEFAULT_TIMEOUT
|
||
|
||
if(options.headers.host)
|
||
throw new Error("Options.headers.host is not supported");
|
||
|
||
if(options.json) {
|
||
options.headers.accept = options.headers.accept || 'application/json'
|
||
if(options.method !== 'GET')
|
||
options.headers['content-type'] = 'application/json'
|
||
|
||
if(typeof options.json !== 'boolean')
|
||
options.body = JSON.stringify(options.json)
|
||
else if(typeof options.body !== 'string')
|
||
options.body = JSON.stringify(options.body)
|
||
}
|
||
|
||
//BEGIN QS Hack
|
||
var serialize = function(obj) {
|
||
var str = [];
|
||
for(var p in obj)
|
||
if (obj.hasOwnProperty(p)) {
|
||
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
|
||
}
|
||
return str.join("&");
|
||
}
|
||
|
||
if(options.qs){
|
||
var qs = (typeof options.qs == 'string')? options.qs : serialize(options.qs);
|
||
if(options.uri.indexOf('?') !== -1){ //no get params
|
||
options.uri = options.uri+'&'+qs;
|
||
}else{ //existing get params
|
||
options.uri = options.uri+'?'+qs;
|
||
}
|
||
}
|
||
//END QS Hack
|
||
|
||
//BEGIN FORM Hack
|
||
var multipart = function(obj) {
|
||
//todo: support file type (useful?)
|
||
var result = {};
|
||
result.boundry = '-------------------------------'+Math.floor(Math.random()*1000000000);
|
||
var lines = [];
|
||
for(var p in obj){
|
||
if (obj.hasOwnProperty(p)) {
|
||
lines.push(
|
||
'--'+result.boundry+"\n"+
|
||
'Content-Disposition: form-data; name="'+p+'"'+"\n"+
|
||
"\n"+
|
||
obj[p]+"\n"
|
||
);
|
||
}
|
||
}
|
||
lines.push( '--'+result.boundry+'--' );
|
||
result.body = lines.join('');
|
||
result.length = result.body.length;
|
||
result.type = 'multipart/form-data; boundary='+result.boundry;
|
||
return result;
|
||
}
|
||
|
||
if(options.form){
|
||
if(typeof options.form == 'string') throw('form name unsupported');
|
||
if(options.method === 'POST'){
|
||
var encoding = (options.encoding || 'application/x-www-form-urlencoded').toLowerCase();
|
||
options.headers['content-type'] = encoding;
|
||
switch(encoding){
|
||
case 'application/x-www-form-urlencoded':
|
||
options.body = serialize(options.form).replace(/%20/g, "+");
|
||
break;
|
||
case 'multipart/form-data':
|
||
var multi = multipart(options.form);
|
||
//options.headers['content-length'] = multi.length;
|
||
options.body = multi.body;
|
||
options.headers['content-type'] = multi.type;
|
||
break;
|
||
default : throw new Error('unsupported encoding:'+encoding);
|
||
}
|
||
}
|
||
}
|
||
//END FORM Hack
|
||
|
||
// If onResponse is boolean true, call back immediately when the response is known,
|
||
// not when the full request is complete.
|
||
options.onResponse = options.onResponse || noop
|
||
if(options.onResponse === true) {
|
||
options.onResponse = callback
|
||
options.callback = noop
|
||
}
|
||
|
||
// XXX Browsers do not like this.
|
||
//if(options.body)
|
||
// options.headers['content-length'] = options.body.length;
|
||
|
||
// HTTP basic authentication
|
||
if(!options.headers.authorization && options.auth)
|
||
options.headers.authorization = 'Basic ' + b64_enc(options.auth.username + ':' + options.auth.password);
|
||
|
||
return run_xhr(options)
|
||
}
|
||
|
||
var req_seq = 0
|
||
function run_xhr(options) {
|
||
var xhr = new XHR
|
||
, timed_out = false
|
||
, is_cors = is_crossDomain(options.uri)
|
||
, supports_cors = ('withCredentials' in xhr)
|
||
|
||
req_seq += 1
|
||
xhr.seq_id = req_seq
|
||
xhr.id = req_seq + ': ' + options.method + ' ' + options.uri
|
||
xhr._id = xhr.id // I know I will type "_id" from habit all the time.
|
||
|
||
if(is_cors && !supports_cors) {
|
||
var cors_err = new Error('Browser does not support cross-origin request: ' + options.uri)
|
||
cors_err.cors = 'unsupported'
|
||
return options.callback(cors_err, xhr)
|
||
}
|
||
|
||
xhr.timeoutTimer = setTimeout(too_late, options.timeout)
|
||
function too_late() {
|
||
timed_out = true
|
||
var er = new Error('ETIMEDOUT')
|
||
er.code = 'ETIMEDOUT'
|
||
er.duration = options.timeout
|
||
|
||
request.log.error('Timeout', { 'id':xhr._id, 'milliseconds':options.timeout })
|
||
return options.callback(er, xhr)
|
||
}
|
||
|
||
// Some states can be skipped over, so remember what is still incomplete.
|
||
var did = {'response':false, 'loading':false, 'end':false}
|
||
|
||
xhr.onreadystatechange = on_state_change
|
||
xhr.open(options.method, options.uri, true) // asynchronous
|
||
if(is_cors)
|
||
xhr.withCredentials = !! options.withCredentials
|
||
xhr.send(options.body)
|
||
return xhr
|
||
|
||
function on_state_change(event) {
|
||
if(timed_out)
|
||
return request.log.debug('Ignoring timed out state change', {'state':xhr.readyState, 'id':xhr.id})
|
||
|
||
request.log.debug('State change', {'state':xhr.readyState, 'id':xhr.id, 'timed_out':timed_out})
|
||
|
||
if(xhr.readyState === XHR.OPENED) {
|
||
request.log.debug('Request started', {'id':xhr.id})
|
||
for (var key in options.headers)
|
||
xhr.setRequestHeader(key, options.headers[key])
|
||
}
|
||
|
||
else if(xhr.readyState === XHR.HEADERS_RECEIVED)
|
||
on_response()
|
||
|
||
else if(xhr.readyState === XHR.LOADING) {
|
||
on_response()
|
||
on_loading()
|
||
}
|
||
|
||
else if(xhr.readyState === XHR.DONE) {
|
||
on_response()
|
||
on_loading()
|
||
on_end()
|
||
}
|
||
}
|
||
|
||
function on_response() {
|
||
if(did.response)
|
||
return
|
||
|
||
did.response = true
|
||
request.log.debug('Got response', {'id':xhr.id, 'status':xhr.status})
|
||
clearTimeout(xhr.timeoutTimer)
|
||
xhr.statusCode = xhr.status // Node request compatibility
|
||
|
||
// Detect failed CORS requests.
|
||
if(is_cors && xhr.statusCode == 0) {
|
||
var cors_err = new Error('CORS request rejected: ' + options.uri)
|
||
cors_err.cors = 'rejected'
|
||
|
||
// Do not process this request further.
|
||
did.loading = true
|
||
did.end = true
|
||
|
||
return options.callback(cors_err, xhr)
|
||
}
|
||
|
||
options.onResponse(null, xhr)
|
||
}
|
||
|
||
function on_loading() {
|
||
if(did.loading)
|
||
return
|
||
|
||
did.loading = true
|
||
request.log.debug('Response body loading', {'id':xhr.id})
|
||
// TODO: Maybe simulate "data" events by watching xhr.responseText
|
||
}
|
||
|
||
function on_end() {
|
||
if(did.end)
|
||
return
|
||
|
||
did.end = true
|
||
request.log.debug('Request done', {'id':xhr.id})
|
||
|
||
xhr.body = xhr.responseText
|
||
if(options.json) {
|
||
try { xhr.body = JSON.parse(xhr.responseText) }
|
||
catch (er) { return options.callback(er, xhr) }
|
||
}
|
||
|
||
options.callback(null, xhr, xhr.body)
|
||
}
|
||
|
||
} // request
|
||
|
||
request.withCredentials = false;
|
||
request.DEFAULT_TIMEOUT = DEFAULT_TIMEOUT;
|
||
|
||
//
|
||
// defaults
|
||
//
|
||
|
||
request.defaults = function(options, requester) {
|
||
var def = function (method) {
|
||
var d = function (params, callback) {
|
||
if(typeof params === 'string')
|
||
params = {'uri': params};
|
||
else {
|
||
params = JSON.parse(JSON.stringify(params));
|
||
}
|
||
for (var i in options) {
|
||
if (params[i] === undefined) params[i] = options[i]
|
||
}
|
||
return method(params, callback)
|
||
}
|
||
return d
|
||
}
|
||
var de = def(request)
|
||
de.get = def(request.get)
|
||
de.post = def(request.post)
|
||
de.put = def(request.put)
|
||
de.head = def(request.head)
|
||
return de
|
||
}
|
||
|
||
//
|
||
// HTTP method shortcuts
|
||
//
|
||
|
||
var shortcuts = [ 'get', 'put', 'post', 'head' ];
|
||
shortcuts.forEach(function(shortcut) {
|
||
var method = shortcut.toUpperCase();
|
||
var func = shortcut.toLowerCase();
|
||
|
||
request[func] = function(opts) {
|
||
if(typeof opts === 'string')
|
||
opts = {'method':method, 'uri':opts};
|
||
else {
|
||
opts = JSON.parse(JSON.stringify(opts));
|
||
opts.method = method;
|
||
}
|
||
|
||
var args = [opts].concat(Array.prototype.slice.apply(arguments, [1]));
|
||
return request.apply(this, args);
|
||
}
|
||
})
|
||
|
||
//
|
||
// CouchDB shortcut
|
||
//
|
||
|
||
request.couch = function(options, callback) {
|
||
if(typeof options === 'string')
|
||
options = {'uri':options}
|
||
|
||
// Just use the request API to do JSON.
|
||
options.json = true
|
||
if(options.body)
|
||
options.json = options.body
|
||
delete options.body
|
||
|
||
callback = callback || noop
|
||
|
||
var xhr = request(options, couch_handler)
|
||
return xhr
|
||
|
||
function couch_handler(er, resp, body) {
|
||
if(er)
|
||
return callback(er, resp, body)
|
||
|
||
if((resp.statusCode < 200 || resp.statusCode > 299) && body.error) {
|
||
// The body is a Couch JSON object indicating the error.
|
||
er = new Error('CouchDB error: ' + (body.error.reason || body.error.error))
|
||
for (var key in body)
|
||
er[key] = body[key]
|
||
return callback(er, resp, body);
|
||
}
|
||
|
||
return callback(er, resp, body);
|
||
}
|
||
}
|
||
|
||
//
|
||
// Utility
|
||
//
|
||
|
||
function noop() {}
|
||
|
||
function getLogger() {
|
||
var logger = {}
|
||
, levels = ['trace', 'debug', 'info', 'warn', 'error']
|
||
, level, i
|
||
|
||
for(i = 0; i < levels.length; i++) {
|
||
level = levels[i]
|
||
|
||
logger[level] = noop
|
||
if(typeof console !== 'undefined' && console && console[level])
|
||
logger[level] = formatted(console, level)
|
||
}
|
||
|
||
return logger
|
||
}
|
||
|
||
function formatted(obj, method) {
|
||
return formatted_logger
|
||
|
||
function formatted_logger(str, context) {
|
||
if(typeof context === 'object')
|
||
str += ' ' + JSON.stringify(context)
|
||
|
||
return obj[method].call(obj, str)
|
||
}
|
||
}
|
||
|
||
// Return whether a URL is a cross-domain request.
|
||
function is_crossDomain(url) {
|
||
var rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/
|
||
|
||
// jQuery #8138, IE may throw an exception when accessing
|
||
// a field from window.location if document.domain has been set
|
||
var ajaxLocation
|
||
try { ajaxLocation = location.href }
|
||
catch (e) {
|
||
// Use the href attribute of an A element since IE will modify it given document.location
|
||
ajaxLocation = document.createElement( "a" );
|
||
ajaxLocation.href = "";
|
||
ajaxLocation = ajaxLocation.href;
|
||
}
|
||
|
||
var ajaxLocParts = rurl.exec(ajaxLocation.toLowerCase()) || []
|
||
, parts = rurl.exec(url.toLowerCase() )
|
||
|
||
var result = !!(
|
||
parts &&
|
||
( parts[1] != ajaxLocParts[1]
|
||
|| parts[2] != ajaxLocParts[2]
|
||
|| (parts[3] || (parts[1] === "http:" ? 80 : 443)) != (ajaxLocParts[3] || (ajaxLocParts[1] === "http:" ? 80 : 443))
|
||
)
|
||
)
|
||
|
||
//console.debug('is_crossDomain('+url+') -> ' + result)
|
||
return result
|
||
}
|
||
|
||
// MIT License from http://phpjs.org/functions/base64_encode:358
|
||
function b64_enc (data) {
|
||
// Encodes string using MIME base64 algorithm
|
||
var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
||
var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, enc="", tmp_arr = [];
|
||
|
||
if (!data) {
|
||
return data;
|
||
}
|
||
|
||
// assume utf8 data
|
||
// data = this.utf8_encode(data+'');
|
||
|
||
do { // pack three octets into four hexets
|
||
o1 = data.charCodeAt(i++);
|
||
o2 = data.charCodeAt(i++);
|
||
o3 = data.charCodeAt(i++);
|
||
|
||
bits = o1<<16 | o2<<8 | o3;
|
||
|
||
h1 = bits>>18 & 0x3f;
|
||
h2 = bits>>12 & 0x3f;
|
||
h3 = bits>>6 & 0x3f;
|
||
h4 = bits & 0x3f;
|
||
|
||
// use hexets to index into b64, and append result to encoded string
|
||
tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4);
|
||
} while (i < data.length);
|
||
|
||
enc = tmp_arr.join('');
|
||
|
||
switch (data.length % 3) {
|
||
case 1:
|
||
enc = enc.slice(0, -2) + '==';
|
||
break;
|
||
case 2:
|
||
enc = enc.slice(0, -1) + '=';
|
||
break;
|
||
}
|
||
|
||
return enc;
|
||
}
|
||
return request;
|
||
//UMD FOOTER START
|
||
}));
|
||
//UMD FOOTER END
|
||
|
||
},{}],15:[function(require,module,exports){
|
||
// Copyright Joyent, Inc. and other Node contributors.
|
||
//
|
||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||
// copy of this software and associated documentation files (the
|
||
// "Software"), to deal in the Software without restriction, including
|
||
// without limitation the rights to use, copy, modify, merge, publish,
|
||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||
// persons to whom the Software is furnished to do so, subject to the
|
||
// following conditions:
|
||
//
|
||
// The above copyright notice and this permission notice shall be included
|
||
// in all copies or substantial portions of the Software.
|
||
//
|
||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||
|
||
function EventEmitter() {
|
||
this._events = this._events || {};
|
||
this._maxListeners = this._maxListeners || undefined;
|
||
}
|
||
module.exports = EventEmitter;
|
||
|
||
// Backwards-compat with node 0.10.x
|
||
EventEmitter.EventEmitter = EventEmitter;
|
||
|
||
EventEmitter.prototype._events = undefined;
|
||
EventEmitter.prototype._maxListeners = undefined;
|
||
|
||
// By default EventEmitters will print a warning if more than 10 listeners are
|
||
// added to it. This is a useful default which helps finding memory leaks.
|
||
EventEmitter.defaultMaxListeners = 10;
|
||
|
||
// Obviously not all Emitters should be limited to 10. This function allows
|
||
// that to be increased. Set to zero for unlimited.
|
||
EventEmitter.prototype.setMaxListeners = function(n) {
|
||
if (!isNumber(n) || n < 0 || isNaN(n))
|
||
throw TypeError('n must be a positive number');
|
||
this._maxListeners = n;
|
||
return this;
|
||
};
|
||
|
||
EventEmitter.prototype.emit = function(type) {
|
||
var er, handler, len, args, i, listeners;
|
||
|
||
if (!this._events)
|
||
this._events = {};
|
||
|
||
// If there is no 'error' event listener then throw.
|
||
if (type === 'error') {
|
||
if (!this._events.error ||
|
||
(isObject(this._events.error) && !this._events.error.length)) {
|
||
er = arguments[1];
|
||
if (er instanceof Error) {
|
||
throw er; // Unhandled 'error' event
|
||
}
|
||
throw TypeError('Uncaught, unspecified "error" event.');
|
||
}
|
||
}
|
||
|
||
handler = this._events[type];
|
||
|
||
if (isUndefined(handler))
|
||
return false;
|
||
|
||
if (isFunction(handler)) {
|
||
switch (arguments.length) {
|
||
// fast cases
|
||
case 1:
|
||
handler.call(this);
|
||
break;
|
||
case 2:
|
||
handler.call(this, arguments[1]);
|
||
break;
|
||
case 3:
|
||
handler.call(this, arguments[1], arguments[2]);
|
||
break;
|
||
// slower
|
||
default:
|
||
len = arguments.length;
|
||
args = new Array(len - 1);
|
||
for (i = 1; i < len; i++)
|
||
args[i - 1] = arguments[i];
|
||
handler.apply(this, args);
|
||
}
|
||
} else if (isObject(handler)) {
|
||
len = arguments.length;
|
||
args = new Array(len - 1);
|
||
for (i = 1; i < len; i++)
|
||
args[i - 1] = arguments[i];
|
||
|
||
listeners = handler.slice();
|
||
len = listeners.length;
|
||
for (i = 0; i < len; i++)
|
||
listeners[i].apply(this, args);
|
||
}
|
||
|
||
return true;
|
||
};
|
||
|
||
EventEmitter.prototype.addListener = function(type, listener) {
|
||
var m;
|
||
|
||
if (!isFunction(listener))
|
||
throw TypeError('listener must be a function');
|
||
|
||
if (!this._events)
|
||
this._events = {};
|
||
|
||
// To avoid recursion in the case that type === "newListener"! Before
|
||
// adding it to the listeners, first emit "newListener".
|
||
if (this._events.newListener)
|
||
this.emit('newListener', type,
|
||
isFunction(listener.listener) ?
|
||
listener.listener : listener);
|
||
|
||
if (!this._events[type])
|
||
// Optimize the case of one listener. Don't need the extra array object.
|
||
this._events[type] = listener;
|
||
else if (isObject(this._events[type]))
|
||
// If we've already got an array, just append.
|
||
this._events[type].push(listener);
|
||
else
|
||
// Adding the second element, need to change to array.
|
||
this._events[type] = [this._events[type], listener];
|
||
|
||
// Check for listener leak
|
||
if (isObject(this._events[type]) && !this._events[type].warned) {
|
||
var m;
|
||
if (!isUndefined(this._maxListeners)) {
|
||
m = this._maxListeners;
|
||
} else {
|
||
m = EventEmitter.defaultMaxListeners;
|
||
}
|
||
|
||
if (m && m > 0 && this._events[type].length > m) {
|
||
this._events[type].warned = true;
|
||
console.error('(node) warning: possible EventEmitter memory ' +
|
||
'leak detected. %d listeners added. ' +
|
||
'Use emitter.setMaxListeners() to increase limit.',
|
||
this._events[type].length);
|
||
if (typeof console.trace === 'function') {
|
||
// not supported in IE 10
|
||
console.trace();
|
||
}
|
||
}
|
||
}
|
||
|
||
return this;
|
||
};
|
||
|
||
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
|
||
|
||
EventEmitter.prototype.once = function(type, listener) {
|
||
if (!isFunction(listener))
|
||
throw TypeError('listener must be a function');
|
||
|
||
var fired = false;
|
||
|
||
function g() {
|
||
this.removeListener(type, g);
|
||
|
||
if (!fired) {
|
||
fired = true;
|
||
listener.apply(this, arguments);
|
||
}
|
||
}
|
||
|
||
g.listener = listener;
|
||
this.on(type, g);
|
||
|
||
return this;
|
||
};
|
||
|
||
// emits a 'removeListener' event iff the listener was removed
|
||
EventEmitter.prototype.removeListener = function(type, listener) {
|
||
var list, position, length, i;
|
||
|
||
if (!isFunction(listener))
|
||
throw TypeError('listener must be a function');
|
||
|
||
if (!this._events || !this._events[type])
|
||
return this;
|
||
|
||
list = this._events[type];
|
||
length = list.length;
|
||
position = -1;
|
||
|
||
if (list === listener ||
|
||
(isFunction(list.listener) && list.listener === listener)) {
|
||
delete this._events[type];
|
||
if (this._events.removeListener)
|
||
this.emit('removeListener', type, listener);
|
||
|
||
} else if (isObject(list)) {
|
||
for (i = length; i-- > 0;) {
|
||
if (list[i] === listener ||
|
||
(list[i].listener && list[i].listener === listener)) {
|
||
position = i;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (position < 0)
|
||
return this;
|
||
|
||
if (list.length === 1) {
|
||
list.length = 0;
|
||
delete this._events[type];
|
||
} else {
|
||
list.splice(position, 1);
|
||
}
|
||
|
||
if (this._events.removeListener)
|
||
this.emit('removeListener', type, listener);
|
||
}
|
||
|
||
return this;
|
||
};
|
||
|
||
EventEmitter.prototype.removeAllListeners = function(type) {
|
||
var key, listeners;
|
||
|
||
if (!this._events)
|
||
return this;
|
||
|
||
// not listening for removeListener, no need to emit
|
||
if (!this._events.removeListener) {
|
||
if (arguments.length === 0)
|
||
this._events = {};
|
||
else if (this._events[type])
|
||
delete this._events[type];
|
||
return this;
|
||
}
|
||
|
||
// emit removeListener for all listeners on all events
|
||
if (arguments.length === 0) {
|
||
for (key in this._events) {
|
||
if (key === 'removeListener') continue;
|
||
this.removeAllListeners(key);
|
||
}
|
||
this.removeAllListeners('removeListener');
|
||
this._events = {};
|
||
return this;
|
||
}
|
||
|
||
listeners = this._events[type];
|
||
|
||
if (isFunction(listeners)) {
|
||
this.removeListener(type, listeners);
|
||
} else {
|
||
// LIFO order
|
||
while (listeners.length)
|
||
this.removeListener(type, listeners[listeners.length - 1]);
|
||
}
|
||
delete this._events[type];
|
||
|
||
return this;
|
||
};
|
||
|
||
EventEmitter.prototype.listeners = function(type) {
|
||
var ret;
|
||
if (!this._events || !this._events[type])
|
||
ret = [];
|
||
else if (isFunction(this._events[type]))
|
||
ret = [this._events[type]];
|
||
else
|
||
ret = this._events[type].slice();
|
||
return ret;
|
||
};
|
||
|
||
EventEmitter.listenerCount = function(emitter, type) {
|
||
var ret;
|
||
if (!emitter._events || !emitter._events[type])
|
||
ret = 0;
|
||
else if (isFunction(emitter._events[type]))
|
||
ret = 1;
|
||
else
|
||
ret = emitter._events[type].length;
|
||
return ret;
|
||
};
|
||
|
||
function isFunction(arg) {
|
||
return typeof arg === 'function';
|
||
}
|
||
|
||
function isNumber(arg) {
|
||
return typeof arg === 'number';
|
||
}
|
||
|
||
function isObject(arg) {
|
||
return typeof arg === 'object' && arg !== null;
|
||
}
|
||
|
||
function isUndefined(arg) {
|
||
return arg === void 0;
|
||
}
|
||
|
||
},{}],16:[function(require,module,exports){
|
||
// shim for using process in browser
|
||
|
||
var process = module.exports = {};
|
||
var queue = [];
|
||
var draining = false;
|
||
var currentQueue;
|
||
var queueIndex = -1;
|
||
|
||
function cleanUpNextTick() {
|
||
draining = false;
|
||
if (currentQueue.length) {
|
||
queue = currentQueue.concat(queue);
|
||
} else {
|
||
queueIndex = -1;
|
||
}
|
||
if (queue.length) {
|
||
drainQueue();
|
||
}
|
||
}
|
||
|
||
function drainQueue() {
|
||
if (draining) {
|
||
return;
|
||
}
|
||
var timeout = setTimeout(cleanUpNextTick);
|
||
draining = true;
|
||
|
||
var len = queue.length;
|
||
while(len) {
|
||
currentQueue = queue;
|
||
queue = [];
|
||
while (++queueIndex < len) {
|
||
currentQueue[queueIndex].run();
|
||
}
|
||
queueIndex = -1;
|
||
len = queue.length;
|
||
}
|
||
currentQueue = null;
|
||
draining = false;
|
||
clearTimeout(timeout);
|
||
}
|
||
|
||
process.nextTick = function (fun) {
|
||
var args = new Array(arguments.length - 1);
|
||
if (arguments.length > 1) {
|
||
for (var i = 1; i < arguments.length; i++) {
|
||
args[i - 1] = arguments[i];
|
||
}
|
||
}
|
||
queue.push(new Item(fun, args));
|
||
if (queue.length === 1 && !draining) {
|
||
setTimeout(drainQueue, 0);
|
||
}
|
||
};
|
||
|
||
// v8 likes predictible objects
|
||
function Item(fun, array) {
|
||
this.fun = fun;
|
||
this.array = array;
|
||
}
|
||
Item.prototype.run = function () {
|
||
this.fun.apply(null, this.array);
|
||
};
|
||
process.title = 'browser';
|
||
process.browser = true;
|
||
process.env = {};
|
||
process.argv = [];
|
||
process.version = ''; // empty string to avoid regexp issues
|
||
process.versions = {};
|
||
|
||
function noop() {}
|
||
|
||
process.on = noop;
|
||
process.addListener = noop;
|
||
process.once = noop;
|
||
process.off = noop;
|
||
process.removeListener = noop;
|
||
process.removeAllListeners = noop;
|
||
process.emit = noop;
|
||
|
||
process.binding = function (name) {
|
||
throw new Error('process.binding is not supported');
|
||
};
|
||
|
||
// TODO(shtylman)
|
||
process.cwd = function () { return '/' };
|
||
process.chdir = function (dir) {
|
||
throw new Error('process.chdir is not supported');
|
||
};
|
||
process.umask = function() { return 0; };
|
||
|
||
},{}],17:[function(require,module,exports){
|
||
(function (process){
|
||
// vim:ts=4:sts=4:sw=4:
|
||
/*!
|
||
*
|
||
* Copyright 2009-2012 Kris Kowal under the terms of the MIT
|
||
* license found at http://github.com/kriskowal/q/raw/master/LICENSE
|
||
*
|
||
* With parts by Tyler Close
|
||
* Copyright 2007-2009 Tyler Close under the terms of the MIT X license found
|
||
* at http://www.opensource.org/licenses/mit-license.html
|
||
* Forked at ref_send.js version: 2009-05-11
|
||
*
|
||
* With parts by Mark Miller
|
||
* Copyright (C) 2011 Google Inc.
|
||
*
|
||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
* you may not use this file except in compliance with the License.
|
||
* You may obtain a copy of the License at
|
||
*
|
||
* http://www.apache.org/licenses/LICENSE-2.0
|
||
*
|
||
* Unless required by applicable law or agreed to in writing, software
|
||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
* See the License for the specific language governing permissions and
|
||
* limitations under the License.
|
||
*
|
||
*/
|
||
|
||
(function (definition) {
|
||
"use strict";
|
||
|
||
// This file will function properly as a <script> tag, or a module
|
||
// using CommonJS and NodeJS or RequireJS module formats. In
|
||
// Common/Node/RequireJS, the module exports the Q API and when
|
||
// executed as a simple <script>, it creates a Q global instead.
|
||
|
||
// Montage Require
|
||
if (typeof bootstrap === "function") {
|
||
bootstrap("promise", definition);
|
||
|
||
// CommonJS
|
||
} else if (typeof exports === "object" && typeof module === "object") {
|
||
module.exports = definition();
|
||
|
||
// RequireJS
|
||
} else if (typeof define === "function" && define.amd) {
|
||
define(definition);
|
||
|
||
// SES (Secure EcmaScript)
|
||
} else if (typeof ses !== "undefined") {
|
||
if (!ses.ok()) {
|
||
return;
|
||
} else {
|
||
ses.makeQ = definition;
|
||
}
|
||
|
||
// <script>
|
||
} else if (typeof window !== "undefined" || typeof self !== "undefined") {
|
||
// Prefer window over self for add-on scripts. Use self for
|
||
// non-windowed contexts.
|
||
var global = typeof window !== "undefined" ? window : self;
|
||
|
||
// Get the `window` object, save the previous Q global
|
||
// and initialize Q as a global.
|
||
var previousQ = global.Q;
|
||
global.Q = definition();
|
||
|
||
// Add a noConflict function so Q can be removed from the
|
||
// global namespace.
|
||
global.Q.noConflict = function () {
|
||
global.Q = previousQ;
|
||
return this;
|
||
};
|
||
|
||
} else {
|
||
throw new Error("This environment was not anticipated by Q. Please file a bug.");
|
||
}
|
||
|
||
})(function () {
|
||
"use strict";
|
||
|
||
var hasStacks = false;
|
||
try {
|
||
throw new Error();
|
||
} catch (e) {
|
||
hasStacks = !!e.stack;
|
||
}
|
||
|
||
// All code after this point will be filtered from stack traces reported
|
||
// by Q.
|
||
var qStartingLine = captureLine();
|
||
var qFileName;
|
||
|
||
// shims
|
||
|
||
// used for fallback in "allResolved"
|
||
var noop = function () {};
|
||
|
||
// Use the fastest possible means to execute a task in a future turn
|
||
// of the event loop.
|
||
var nextTick =(function () {
|
||
// linked list of tasks (single, with head node)
|
||
var head = {task: void 0, next: null};
|
||
var tail = head;
|
||
var flushing = false;
|
||
var requestTick = void 0;
|
||
var isNodeJS = false;
|
||
// queue for late tasks, used by unhandled rejection tracking
|
||
var laterQueue = [];
|
||
|
||
function flush() {
|
||
/* jshint loopfunc: true */
|
||
var task, domain;
|
||
|
||
while (head.next) {
|
||
head = head.next;
|
||
task = head.task;
|
||
head.task = void 0;
|
||
domain = head.domain;
|
||
|
||
if (domain) {
|
||
head.domain = void 0;
|
||
domain.enter();
|
||
}
|
||
runSingle(task, domain);
|
||
|
||
}
|
||
while (laterQueue.length) {
|
||
task = laterQueue.pop();
|
||
runSingle(task);
|
||
}
|
||
flushing = false;
|
||
}
|
||
// runs a single function in the async queue
|
||
function runSingle(task, domain) {
|
||
try {
|
||
task();
|
||
|
||
} catch (e) {
|
||
if (isNodeJS) {
|
||
// In node, uncaught exceptions are considered fatal errors.
|
||
// Re-throw them synchronously to interrupt flushing!
|
||
|
||
// Ensure continuation if the uncaught exception is suppressed
|
||
// listening "uncaughtException" events (as domains does).
|
||
// Continue in next event to avoid tick recursion.
|
||
if (domain) {
|
||
domain.exit();
|
||
}
|
||
setTimeout(flush, 0);
|
||
if (domain) {
|
||
domain.enter();
|
||
}
|
||
|
||
throw e;
|
||
|
||
} else {
|
||
// In browsers, uncaught exceptions are not fatal.
|
||
// Re-throw them asynchronously to avoid slow-downs.
|
||
setTimeout(function () {
|
||
throw e;
|
||
}, 0);
|
||
}
|
||
}
|
||
|
||
if (domain) {
|
||
domain.exit();
|
||
}
|
||
}
|
||
|
||
nextTick = function (task) {
|
||
tail = tail.next = {
|
||
task: task,
|
||
domain: isNodeJS && process.domain,
|
||
next: null
|
||
};
|
||
|
||
if (!flushing) {
|
||
flushing = true;
|
||
requestTick();
|
||
}
|
||
};
|
||
|
||
if (typeof process === "object" &&
|
||
process.toString() === "[object process]" && process.nextTick) {
|
||
// Ensure Q is in a real Node environment, with a `process.nextTick`.
|
||
// To see through fake Node environments:
|
||
// * Mocha test runner - exposes a `process` global without a `nextTick`
|
||
// * Browserify - exposes a `process.nexTick` function that uses
|
||
// `setTimeout`. In this case `setImmediate` is preferred because
|
||
// it is faster. Browserify's `process.toString()` yields
|
||
// "[object Object]", while in a real Node environment
|
||
// `process.nextTick()` yields "[object process]".
|
||
isNodeJS = true;
|
||
|
||
requestTick = function () {
|
||
process.nextTick(flush);
|
||
};
|
||
|
||
} else if (typeof setImmediate === "function") {
|
||
// In IE10, Node.js 0.9+, or https://github.com/NobleJS/setImmediate
|
||
if (typeof window !== "undefined") {
|
||
requestTick = setImmediate.bind(window, flush);
|
||
} else {
|
||
requestTick = function () {
|
||
setImmediate(flush);
|
||
};
|
||
}
|
||
|
||
} else if (typeof MessageChannel !== "undefined") {
|
||
// modern browsers
|
||
// http://www.nonblocking.io/2011/06/windownexttick.html
|
||
var channel = new MessageChannel();
|
||
// At least Safari Version 6.0.5 (8536.30.1) intermittently cannot create
|
||
// working message ports the first time a page loads.
|
||
channel.port1.onmessage = function () {
|
||
requestTick = requestPortTick;
|
||
channel.port1.onmessage = flush;
|
||
flush();
|
||
};
|
||
var requestPortTick = function () {
|
||
// Opera requires us to provide a message payload, regardless of
|
||
// whether we use it.
|
||
channel.port2.postMessage(0);
|
||
};
|
||
requestTick = function () {
|
||
setTimeout(flush, 0);
|
||
requestPortTick();
|
||
};
|
||
|
||
} else {
|
||
// old browsers
|
||
requestTick = function () {
|
||
setTimeout(flush, 0);
|
||
};
|
||
}
|
||
// runs a task after all other tasks have been run
|
||
// this is useful for unhandled rejection tracking that needs to happen
|
||
// after all `then`d tasks have been run.
|
||
nextTick.runAfter = function (task) {
|
||
laterQueue.push(task);
|
||
if (!flushing) {
|
||
flushing = true;
|
||
requestTick();
|
||
}
|
||
};
|
||
return nextTick;
|
||
})();
|
||
|
||
// Attempt to make generics safe in the face of downstream
|
||
// modifications.
|
||
// There is no situation where this is necessary.
|
||
// If you need a security guarantee, these primordials need to be
|
||
// deeply frozen anyway, and if you don’t need a security guarantee,
|
||
// this is just plain paranoid.
|
||
// However, this **might** have the nice side-effect of reducing the size of
|
||
// the minified code by reducing x.call() to merely x()
|
||
// See Mark Miller’s explanation of what this does.
|
||
// http://wiki.ecmascript.org/doku.php?id=conventions:safe_meta_programming
|
||
var call = Function.call;
|
||
function uncurryThis(f) {
|
||
return function () {
|
||
return call.apply(f, arguments);
|
||
};
|
||
}
|
||
// This is equivalent, but slower:
|
||
// uncurryThis = Function_bind.bind(Function_bind.call);
|
||
// http://jsperf.com/uncurrythis
|
||
|
||
var array_slice = uncurryThis(Array.prototype.slice);
|
||
|
||
var array_reduce = uncurryThis(
|
||
Array.prototype.reduce || function (callback, basis) {
|
||
var index = 0,
|
||
length = this.length;
|
||
// concerning the initial value, if one is not provided
|
||
if (arguments.length === 1) {
|
||
// seek to the first value in the array, accounting
|
||
// for the possibility that is is a sparse array
|
||
do {
|
||
if (index in this) {
|
||
basis = this[index++];
|
||
break;
|
||
}
|
||
if (++index >= length) {
|
||
throw new TypeError();
|
||
}
|
||
} while (1);
|
||
}
|
||
// reduce
|
||
for (; index < length; index++) {
|
||
// account for the possibility that the array is sparse
|
||
if (index in this) {
|
||
basis = callback(basis, this[index], index);
|
||
}
|
||
}
|
||
return basis;
|
||
}
|
||
);
|
||
|
||
var array_indexOf = uncurryThis(
|
||
Array.prototype.indexOf || function (value) {
|
||
// not a very good shim, but good enough for our one use of it
|
||
for (var i = 0; i < this.length; i++) {
|
||
if (this[i] === value) {
|
||
return i;
|
||
}
|
||
}
|
||
return -1;
|
||
}
|
||
);
|
||
|
||
var array_map = uncurryThis(
|
||
Array.prototype.map || function (callback, thisp) {
|
||
var self = this;
|
||
var collect = [];
|
||
array_reduce(self, function (undefined, value, index) {
|
||
collect.push(callback.call(thisp, value, index, self));
|
||
}, void 0);
|
||
return collect;
|
||
}
|
||
);
|
||
|
||
var object_create = Object.create || function (prototype) {
|
||
function Type() { }
|
||
Type.prototype = prototype;
|
||
return new Type();
|
||
};
|
||
|
||
var object_hasOwnProperty = uncurryThis(Object.prototype.hasOwnProperty);
|
||
|
||
var object_keys = Object.keys || function (object) {
|
||
var keys = [];
|
||
for (var key in object) {
|
||
if (object_hasOwnProperty(object, key)) {
|
||
keys.push(key);
|
||
}
|
||
}
|
||
return keys;
|
||
};
|
||
|
||
var object_toString = uncurryThis(Object.prototype.toString);
|
||
|
||
function isObject(value) {
|
||
return value === Object(value);
|
||
}
|
||
|
||
// generator related shims
|
||
|
||
// FIXME: Remove this function once ES6 generators are in SpiderMonkey.
|
||
function isStopIteration(exception) {
|
||
return (
|
||
object_toString(exception) === "[object StopIteration]" ||
|
||
exception instanceof QReturnValue
|
||
);
|
||
}
|
||
|
||
// FIXME: Remove this helper and Q.return once ES6 generators are in
|
||
// SpiderMonkey.
|
||
var QReturnValue;
|
||
if (typeof ReturnValue !== "undefined") {
|
||
QReturnValue = ReturnValue;
|
||
} else {
|
||
QReturnValue = function (value) {
|
||
this.value = value;
|
||
};
|
||
}
|
||
|
||
// long stack traces
|
||
|
||
var STACK_JUMP_SEPARATOR = "From previous event:";
|
||
|
||
function makeStackTraceLong(error, promise) {
|
||
// If possible, transform the error stack trace by removing Node and Q
|
||
// cruft, then concatenating with the stack trace of `promise`. See #57.
|
||
if (hasStacks &&
|
||
promise.stack &&
|
||
typeof error === "object" &&
|
||
error !== null &&
|
||
error.stack &&
|
||
error.stack.indexOf(STACK_JUMP_SEPARATOR) === -1
|
||
) {
|
||
var stacks = [];
|
||
for (var p = promise; !!p; p = p.source) {
|
||
if (p.stack) {
|
||
stacks.unshift(p.stack);
|
||
}
|
||
}
|
||
stacks.unshift(error.stack);
|
||
|
||
var concatedStacks = stacks.join("\n" + STACK_JUMP_SEPARATOR + "\n");
|
||
error.stack = filterStackString(concatedStacks);
|
||
}
|
||
}
|
||
|
||
function filterStackString(stackString) {
|
||
var lines = stackString.split("\n");
|
||
var desiredLines = [];
|
||
for (var i = 0; i < lines.length; ++i) {
|
||
var line = lines[i];
|
||
|
||
if (!isInternalFrame(line) && !isNodeFrame(line) && line) {
|
||
desiredLines.push(line);
|
||
}
|
||
}
|
||
return desiredLines.join("\n");
|
||
}
|
||
|
||
function isNodeFrame(stackLine) {
|
||
return stackLine.indexOf("(module.js:") !== -1 ||
|
||
stackLine.indexOf("(node.js:") !== -1;
|
||
}
|
||
|
||
function getFileNameAndLineNumber(stackLine) {
|
||
// Named functions: "at functionName (filename:lineNumber:columnNumber)"
|
||
// In IE10 function name can have spaces ("Anonymous function") O_o
|
||
var attempt1 = /at .+ \((.+):(\d+):(?:\d+)\)$/.exec(stackLine);
|
||
if (attempt1) {
|
||
return [attempt1[1], Number(attempt1[2])];
|
||
}
|
||
|
||
// Anonymous functions: "at filename:lineNumber:columnNumber"
|
||
var attempt2 = /at ([^ ]+):(\d+):(?:\d+)$/.exec(stackLine);
|
||
if (attempt2) {
|
||
return [attempt2[1], Number(attempt2[2])];
|
||
}
|
||
|
||
// Firefox style: "function@filename:lineNumber or @filename:lineNumber"
|
||
var attempt3 = /.*@(.+):(\d+)$/.exec(stackLine);
|
||
if (attempt3) {
|
||
return [attempt3[1], Number(attempt3[2])];
|
||
}
|
||
}
|
||
|
||
function isInternalFrame(stackLine) {
|
||
var fileNameAndLineNumber = getFileNameAndLineNumber(stackLine);
|
||
|
||
if (!fileNameAndLineNumber) {
|
||
return false;
|
||
}
|
||
|
||
var fileName = fileNameAndLineNumber[0];
|
||
var lineNumber = fileNameAndLineNumber[1];
|
||
|
||
return fileName === qFileName &&
|
||
lineNumber >= qStartingLine &&
|
||
lineNumber <= qEndingLine;
|
||
}
|
||
|
||
// discover own file name and line number range for filtering stack
|
||
// traces
|
||
function captureLine() {
|
||
if (!hasStacks) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
throw new Error();
|
||
} catch (e) {
|
||
var lines = e.stack.split("\n");
|
||
var firstLine = lines[0].indexOf("@") > 0 ? lines[1] : lines[2];
|
||
var fileNameAndLineNumber = getFileNameAndLineNumber(firstLine);
|
||
if (!fileNameAndLineNumber) {
|
||
return;
|
||
}
|
||
|
||
qFileName = fileNameAndLineNumber[0];
|
||
return fileNameAndLineNumber[1];
|
||
}
|
||
}
|
||
|
||
function deprecate(callback, name, alternative) {
|
||
return function () {
|
||
if (typeof console !== "undefined" &&
|
||
typeof console.warn === "function") {
|
||
console.warn(name + " is deprecated, use " + alternative +
|
||
" instead.", new Error("").stack);
|
||
}
|
||
return callback.apply(callback, arguments);
|
||
};
|
||
}
|
||
|
||
// end of shims
|
||
// beginning of real work
|
||
|
||
/**
|
||
* Constructs a promise for an immediate reference, passes promises through, or
|
||
* coerces promises from different systems.
|
||
* @param value immediate reference or promise
|
||
*/
|
||
function Q(value) {
|
||
// If the object is already a Promise, return it directly. This enables
|
||
// the resolve function to both be used to created references from objects,
|
||
// but to tolerably coerce non-promises to promises.
|
||
if (value instanceof Promise) {
|
||
return value;
|
||
}
|
||
|
||
// assimilate thenables
|
||
if (isPromiseAlike(value)) {
|
||
return coerce(value);
|
||
} else {
|
||
return fulfill(value);
|
||
}
|
||
}
|
||
Q.resolve = Q;
|
||
|
||
/**
|
||
* Performs a task in a future turn of the event loop.
|
||
* @param {Function} task
|
||
*/
|
||
Q.nextTick = nextTick;
|
||
|
||
/**
|
||
* Controls whether or not long stack traces will be on
|
||
*/
|
||
Q.longStackSupport = false;
|
||
|
||
// enable long stacks if Q_DEBUG is set
|
||
if (typeof process === "object" && process && process.env && process.env.Q_DEBUG) {
|
||
Q.longStackSupport = true;
|
||
}
|
||
|
||
/**
|
||
* Constructs a {promise, resolve, reject} object.
|
||
*
|
||
* `resolve` is a callback to invoke with a more resolved value for the
|
||
* promise. To fulfill the promise, invoke `resolve` with any value that is
|
||
* not a thenable. To reject the promise, invoke `resolve` with a rejected
|
||
* thenable, or invoke `reject` with the reason directly. To resolve the
|
||
* promise to another thenable, thus putting it in the same state, invoke
|
||
* `resolve` with that other thenable.
|
||
*/
|
||
Q.defer = defer;
|
||
function defer() {
|
||
// if "messages" is an "Array", that indicates that the promise has not yet
|
||
// been resolved. If it is "undefined", it has been resolved. Each
|
||
// element of the messages array is itself an array of complete arguments to
|
||
// forward to the resolved promise. We coerce the resolution value to a
|
||
// promise using the `resolve` function because it handles both fully
|
||
// non-thenable values and other thenables gracefully.
|
||
var messages = [], progressListeners = [], resolvedPromise;
|
||
|
||
var deferred = object_create(defer.prototype);
|
||
var promise = object_create(Promise.prototype);
|
||
|
||
promise.promiseDispatch = function (resolve, op, operands) {
|
||
var args = array_slice(arguments);
|
||
if (messages) {
|
||
messages.push(args);
|
||
if (op === "when" && operands[1]) { // progress operand
|
||
progressListeners.push(operands[1]);
|
||
}
|
||
} else {
|
||
Q.nextTick(function () {
|
||
resolvedPromise.promiseDispatch.apply(resolvedPromise, args);
|
||
});
|
||
}
|
||
};
|
||
|
||
// XXX deprecated
|
||
promise.valueOf = function () {
|
||
if (messages) {
|
||
return promise;
|
||
}
|
||
var nearerValue = nearer(resolvedPromise);
|
||
if (isPromise(nearerValue)) {
|
||
resolvedPromise = nearerValue; // shorten chain
|
||
}
|
||
return nearerValue;
|
||
};
|
||
|
||
promise.inspect = function () {
|
||
if (!resolvedPromise) {
|
||
return { state: "pending" };
|
||
}
|
||
return resolvedPromise.inspect();
|
||
};
|
||
|
||
if (Q.longStackSupport && hasStacks) {
|
||
try {
|
||
throw new Error();
|
||
} catch (e) {
|
||
// NOTE: don't try to use `Error.captureStackTrace` or transfer the
|
||
// accessor around; that causes memory leaks as per GH-111. Just
|
||
// reify the stack trace as a string ASAP.
|
||
//
|
||
// At the same time, cut off the first line; it's always just
|
||
// "[object Promise]\n", as per the `toString`.
|
||
promise.stack = e.stack.substring(e.stack.indexOf("\n") + 1);
|
||
}
|
||
}
|
||
|
||
// NOTE: we do the checks for `resolvedPromise` in each method, instead of
|
||
// consolidating them into `become`, since otherwise we'd create new
|
||
// promises with the lines `become(whatever(value))`. See e.g. GH-252.
|
||
|
||
function become(newPromise) {
|
||
resolvedPromise = newPromise;
|
||
promise.source = newPromise;
|
||
|
||
array_reduce(messages, function (undefined, message) {
|
||
Q.nextTick(function () {
|
||
newPromise.promiseDispatch.apply(newPromise, message);
|
||
});
|
||
}, void 0);
|
||
|
||
messages = void 0;
|
||
progressListeners = void 0;
|
||
}
|
||
|
||
deferred.promise = promise;
|
||
deferred.resolve = function (value) {
|
||
if (resolvedPromise) {
|
||
return;
|
||
}
|
||
|
||
become(Q(value));
|
||
};
|
||
|
||
deferred.fulfill = function (value) {
|
||
if (resolvedPromise) {
|
||
return;
|
||
}
|
||
|
||
become(fulfill(value));
|
||
};
|
||
deferred.reject = function (reason) {
|
||
if (resolvedPromise) {
|
||
return;
|
||
}
|
||
|
||
become(reject(reason));
|
||
};
|
||
deferred.notify = function (progress) {
|
||
if (resolvedPromise) {
|
||
return;
|
||
}
|
||
|
||
array_reduce(progressListeners, function (undefined, progressListener) {
|
||
Q.nextTick(function () {
|
||
progressListener(progress);
|
||
});
|
||
}, void 0);
|
||
};
|
||
|
||
return deferred;
|
||
}
|
||
|
||
/**
|
||
* Creates a Node-style callback that will resolve or reject the deferred
|
||
* promise.
|
||
* @returns a nodeback
|
||
*/
|
||
defer.prototype.makeNodeResolver = function () {
|
||
var self = this;
|
||
return function (error, value) {
|
||
if (error) {
|
||
self.reject(error);
|
||
} else if (arguments.length > 2) {
|
||
self.resolve(array_slice(arguments, 1));
|
||
} else {
|
||
self.resolve(value);
|
||
}
|
||
};
|
||
};
|
||
|
||
/**
|
||
* @param resolver {Function} a function that returns nothing and accepts
|
||
* the resolve, reject, and notify functions for a deferred.
|
||
* @returns a promise that may be resolved with the given resolve and reject
|
||
* functions, or rejected by a thrown exception in resolver
|
||
*/
|
||
Q.Promise = promise; // ES6
|
||
Q.promise = promise;
|
||
function promise(resolver) {
|
||
if (typeof resolver !== "function") {
|
||
throw new TypeError("resolver must be a function.");
|
||
}
|
||
var deferred = defer();
|
||
try {
|
||
resolver(deferred.resolve, deferred.reject, deferred.notify);
|
||
} catch (reason) {
|
||
deferred.reject(reason);
|
||
}
|
||
return deferred.promise;
|
||
}
|
||
|
||
promise.race = race; // ES6
|
||
promise.all = all; // ES6
|
||
promise.reject = reject; // ES6
|
||
promise.resolve = Q; // ES6
|
||
|
||
// XXX experimental. This method is a way to denote that a local value is
|
||
// serializable and should be immediately dispatched to a remote upon request,
|
||
// instead of passing a reference.
|
||
Q.passByCopy = function (object) {
|
||
//freeze(object);
|
||
//passByCopies.set(object, true);
|
||
return object;
|
||
};
|
||
|
||
Promise.prototype.passByCopy = function () {
|
||
//freeze(object);
|
||
//passByCopies.set(object, true);
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* If two promises eventually fulfill to the same value, promises that value,
|
||
* but otherwise rejects.
|
||
* @param x {Any*}
|
||
* @param y {Any*}
|
||
* @returns {Any*} a promise for x and y if they are the same, but a rejection
|
||
* otherwise.
|
||
*
|
||
*/
|
||
Q.join = function (x, y) {
|
||
return Q(x).join(y);
|
||
};
|
||
|
||
Promise.prototype.join = function (that) {
|
||
return Q([this, that]).spread(function (x, y) {
|
||
if (x === y) {
|
||
// TODO: "===" should be Object.is or equiv
|
||
return x;
|
||
} else {
|
||
throw new Error("Can't join: not the same: " + x + " " + y);
|
||
}
|
||
});
|
||
};
|
||
|
||
/**
|
||
* Returns a promise for the first of an array of promises to become settled.
|
||
* @param answers {Array[Any*]} promises to race
|
||
* @returns {Any*} the first promise to be settled
|
||
*/
|
||
Q.race = race;
|
||
function race(answerPs) {
|
||
return promise(function (resolve, reject) {
|
||
// Switch to this once we can assume at least ES5
|
||
// answerPs.forEach(function (answerP) {
|
||
// Q(answerP).then(resolve, reject);
|
||
// });
|
||
// Use this in the meantime
|
||
for (var i = 0, len = answerPs.length; i < len; i++) {
|
||
Q(answerPs[i]).then(resolve, reject);
|
||
}
|
||
});
|
||
}
|
||
|
||
Promise.prototype.race = function () {
|
||
return this.then(Q.race);
|
||
};
|
||
|
||
/**
|
||
* Constructs a Promise with a promise descriptor object and optional fallback
|
||
* function. The descriptor contains methods like when(rejected), get(name),
|
||
* set(name, value), post(name, args), and delete(name), which all
|
||
* return either a value, a promise for a value, or a rejection. The fallback
|
||
* accepts the operation name, a resolver, and any further arguments that would
|
||
* have been forwarded to the appropriate method above had a method been
|
||
* provided with the proper name. The API makes no guarantees about the nature
|
||
* of the returned object, apart from that it is usable whereever promises are
|
||
* bought and sold.
|
||
*/
|
||
Q.makePromise = Promise;
|
||
function Promise(descriptor, fallback, inspect) {
|
||
if (fallback === void 0) {
|
||
fallback = function (op) {
|
||
return reject(new Error(
|
||
"Promise does not support operation: " + op
|
||
));
|
||
};
|
||
}
|
||
if (inspect === void 0) {
|
||
inspect = function () {
|
||
return {state: "unknown"};
|
||
};
|
||
}
|
||
|
||
var promise = object_create(Promise.prototype);
|
||
|
||
promise.promiseDispatch = function (resolve, op, args) {
|
||
var result;
|
||
try {
|
||
if (descriptor[op]) {
|
||
result = descriptor[op].apply(promise, args);
|
||
} else {
|
||
result = fallback.call(promise, op, args);
|
||
}
|
||
} catch (exception) {
|
||
result = reject(exception);
|
||
}
|
||
if (resolve) {
|
||
resolve(result);
|
||
}
|
||
};
|
||
|
||
promise.inspect = inspect;
|
||
|
||
// XXX deprecated `valueOf` and `exception` support
|
||
if (inspect) {
|
||
var inspected = inspect();
|
||
if (inspected.state === "rejected") {
|
||
promise.exception = inspected.reason;
|
||
}
|
||
|
||
promise.valueOf = function () {
|
||
var inspected = inspect();
|
||
if (inspected.state === "pending" ||
|
||
inspected.state === "rejected") {
|
||
return promise;
|
||
}
|
||
return inspected.value;
|
||
};
|
||
}
|
||
|
||
return promise;
|
||
}
|
||
|
||
Promise.prototype.toString = function () {
|
||
return "[object Promise]";
|
||
};
|
||
|
||
Promise.prototype.then = function (fulfilled, rejected, progressed) {
|
||
var self = this;
|
||
var deferred = defer();
|
||
var done = false; // ensure the untrusted promise makes at most a
|
||
// single call to one of the callbacks
|
||
|
||
function _fulfilled(value) {
|
||
try {
|
||
return typeof fulfilled === "function" ? fulfilled(value) : value;
|
||
} catch (exception) {
|
||
return reject(exception);
|
||
}
|
||
}
|
||
|
||
function _rejected(exception) {
|
||
if (typeof rejected === "function") {
|
||
makeStackTraceLong(exception, self);
|
||
try {
|
||
return rejected(exception);
|
||
} catch (newException) {
|
||
return reject(newException);
|
||
}
|
||
}
|
||
return reject(exception);
|
||
}
|
||
|
||
function _progressed(value) {
|
||
return typeof progressed === "function" ? progressed(value) : value;
|
||
}
|
||
|
||
Q.nextTick(function () {
|
||
self.promiseDispatch(function (value) {
|
||
if (done) {
|
||
return;
|
||
}
|
||
done = true;
|
||
|
||
deferred.resolve(_fulfilled(value));
|
||
}, "when", [function (exception) {
|
||
if (done) {
|
||
return;
|
||
}
|
||
done = true;
|
||
|
||
deferred.resolve(_rejected(exception));
|
||
}]);
|
||
});
|
||
|
||
// Progress propagator need to be attached in the current tick.
|
||
self.promiseDispatch(void 0, "when", [void 0, function (value) {
|
||
var newValue;
|
||
var threw = false;
|
||
try {
|
||
newValue = _progressed(value);
|
||
} catch (e) {
|
||
threw = true;
|
||
if (Q.onerror) {
|
||
Q.onerror(e);
|
||
} else {
|
||
throw e;
|
||
}
|
||
}
|
||
|
||
if (!threw) {
|
||
deferred.notify(newValue);
|
||
}
|
||
}]);
|
||
|
||
return deferred.promise;
|
||
};
|
||
|
||
Q.tap = function (promise, callback) {
|
||
return Q(promise).tap(callback);
|
||
};
|
||
|
||
/**
|
||
* Works almost like "finally", but not called for rejections.
|
||
* Original resolution value is passed through callback unaffected.
|
||
* Callback may return a promise that will be awaited for.
|
||
* @param {Function} callback
|
||
* @returns {Q.Promise}
|
||
* @example
|
||
* doSomething()
|
||
* .then(...)
|
||
* .tap(console.log)
|
||
* .then(...);
|
||
*/
|
||
Promise.prototype.tap = function (callback) {
|
||
callback = Q(callback);
|
||
|
||
return this.then(function (value) {
|
||
return callback.fcall(value).thenResolve(value);
|
||
});
|
||
};
|
||
|
||
/**
|
||
* Registers an observer on a promise.
|
||
*
|
||
* Guarantees:
|
||
*
|
||
* 1. that fulfilled and rejected will be called only once.
|
||
* 2. that either the fulfilled callback or the rejected callback will be
|
||
* called, but not both.
|
||
* 3. that fulfilled and rejected will not be called in this turn.
|
||
*
|
||
* @param value promise or immediate reference to observe
|
||
* @param fulfilled function to be called with the fulfilled value
|
||
* @param rejected function to be called with the rejection exception
|
||
* @param progressed function to be called on any progress notifications
|
||
* @return promise for the return value from the invoked callback
|
||
*/
|
||
Q.when = when;
|
||
function when(value, fulfilled, rejected, progressed) {
|
||
return Q(value).then(fulfilled, rejected, progressed);
|
||
}
|
||
|
||
Promise.prototype.thenResolve = function (value) {
|
||
return this.then(function () { return value; });
|
||
};
|
||
|
||
Q.thenResolve = function (promise, value) {
|
||
return Q(promise).thenResolve(value);
|
||
};
|
||
|
||
Promise.prototype.thenReject = function (reason) {
|
||
return this.then(function () { throw reason; });
|
||
};
|
||
|
||
Q.thenReject = function (promise, reason) {
|
||
return Q(promise).thenReject(reason);
|
||
};
|
||
|
||
/**
|
||
* If an object is not a promise, it is as "near" as possible.
|
||
* If a promise is rejected, it is as "near" as possible too.
|
||
* If it’s a fulfilled promise, the fulfillment value is nearer.
|
||
* If it’s a deferred promise and the deferred has been resolved, the
|
||
* resolution is "nearer".
|
||
* @param object
|
||
* @returns most resolved (nearest) form of the object
|
||
*/
|
||
|
||
// XXX should we re-do this?
|
||
Q.nearer = nearer;
|
||
function nearer(value) {
|
||
if (isPromise(value)) {
|
||
var inspected = value.inspect();
|
||
if (inspected.state === "fulfilled") {
|
||
return inspected.value;
|
||
}
|
||
}
|
||
return value;
|
||
}
|
||
|
||
/**
|
||
* @returns whether the given object is a promise.
|
||
* Otherwise it is a fulfilled value.
|
||
*/
|
||
Q.isPromise = isPromise;
|
||
function isPromise(object) {
|
||
return object instanceof Promise;
|
||
}
|
||
|
||
Q.isPromiseAlike = isPromiseAlike;
|
||
function isPromiseAlike(object) {
|
||
return isObject(object) && typeof object.then === "function";
|
||
}
|
||
|
||
/**
|
||
* @returns whether the given object is a pending promise, meaning not
|
||
* fulfilled or rejected.
|
||
*/
|
||
Q.isPending = isPending;
|
||
function isPending(object) {
|
||
return isPromise(object) && object.inspect().state === "pending";
|
||
}
|
||
|
||
Promise.prototype.isPending = function () {
|
||
return this.inspect().state === "pending";
|
||
};
|
||
|
||
/**
|
||
* @returns whether the given object is a value or fulfilled
|
||
* promise.
|
||
*/
|
||
Q.isFulfilled = isFulfilled;
|
||
function isFulfilled(object) {
|
||
return !isPromise(object) || object.inspect().state === "fulfilled";
|
||
}
|
||
|
||
Promise.prototype.isFulfilled = function () {
|
||
return this.inspect().state === "fulfilled";
|
||
};
|
||
|
||
/**
|
||
* @returns whether the given object is a rejected promise.
|
||
*/
|
||
Q.isRejected = isRejected;
|
||
function isRejected(object) {
|
||
return isPromise(object) && object.inspect().state === "rejected";
|
||
}
|
||
|
||
Promise.prototype.isRejected = function () {
|
||
return this.inspect().state === "rejected";
|
||
};
|
||
|
||
//// BEGIN UNHANDLED REJECTION TRACKING
|
||
|
||
// This promise library consumes exceptions thrown in handlers so they can be
|
||
// handled by a subsequent promise. The exceptions get added to this array when
|
||
// they are created, and removed when they are handled. Note that in ES6 or
|
||
// shimmed environments, this would naturally be a `Set`.
|
||
var unhandledReasons = [];
|
||
var unhandledRejections = [];
|
||
var reportedUnhandledRejections = [];
|
||
var trackUnhandledRejections = true;
|
||
|
||
function resetUnhandledRejections() {
|
||
unhandledReasons.length = 0;
|
||
unhandledRejections.length = 0;
|
||
|
||
if (!trackUnhandledRejections) {
|
||
trackUnhandledRejections = true;
|
||
}
|
||
}
|
||
|
||
function trackRejection(promise, reason) {
|
||
if (!trackUnhandledRejections) {
|
||
return;
|
||
}
|
||
if (typeof process === "object" && typeof process.emit === "function") {
|
||
Q.nextTick.runAfter(function () {
|
||
if (array_indexOf(unhandledRejections, promise) !== -1) {
|
||
process.emit("unhandledRejection", reason, promise);
|
||
reportedUnhandledRejections.push(promise);
|
||
}
|
||
});
|
||
}
|
||
|
||
unhandledRejections.push(promise);
|
||
if (reason && typeof reason.stack !== "undefined") {
|
||
unhandledReasons.push(reason.stack);
|
||
} else {
|
||
unhandledReasons.push("(no stack) " + reason);
|
||
}
|
||
}
|
||
|
||
function untrackRejection(promise) {
|
||
if (!trackUnhandledRejections) {
|
||
return;
|
||
}
|
||
|
||
var at = array_indexOf(unhandledRejections, promise);
|
||
if (at !== -1) {
|
||
if (typeof process === "object" && typeof process.emit === "function") {
|
||
Q.nextTick.runAfter(function () {
|
||
var atReport = array_indexOf(reportedUnhandledRejections, promise);
|
||
if (atReport !== -1) {
|
||
process.emit("rejectionHandled", unhandledReasons[at], promise);
|
||
reportedUnhandledRejections.splice(atReport, 1);
|
||
}
|
||
});
|
||
}
|
||
unhandledRejections.splice(at, 1);
|
||
unhandledReasons.splice(at, 1);
|
||
}
|
||
}
|
||
|
||
Q.resetUnhandledRejections = resetUnhandledRejections;
|
||
|
||
Q.getUnhandledReasons = function () {
|
||
// Make a copy so that consumers can't interfere with our internal state.
|
||
return unhandledReasons.slice();
|
||
};
|
||
|
||
Q.stopUnhandledRejectionTracking = function () {
|
||
resetUnhandledRejections();
|
||
trackUnhandledRejections = false;
|
||
};
|
||
|
||
resetUnhandledRejections();
|
||
|
||
//// END UNHANDLED REJECTION TRACKING
|
||
|
||
/**
|
||
* Constructs a rejected promise.
|
||
* @param reason value describing the failure
|
||
*/
|
||
Q.reject = reject;
|
||
function reject(reason) {
|
||
var rejection = Promise({
|
||
"when": function (rejected) {
|
||
// note that the error has been handled
|
||
if (rejected) {
|
||
untrackRejection(this);
|
||
}
|
||
return rejected ? rejected(reason) : this;
|
||
}
|
||
}, function fallback() {
|
||
return this;
|
||
}, function inspect() {
|
||
return { state: "rejected", reason: reason };
|
||
});
|
||
|
||
// Note that the reason has not been handled.
|
||
trackRejection(rejection, reason);
|
||
|
||
return rejection;
|
||
}
|
||
|
||
/**
|
||
* Constructs a fulfilled promise for an immediate reference.
|
||
* @param value immediate reference
|
||
*/
|
||
Q.fulfill = fulfill;
|
||
function fulfill(value) {
|
||
return Promise({
|
||
"when": function () {
|
||
return value;
|
||
},
|
||
"get": function (name) {
|
||
return value[name];
|
||
},
|
||
"set": function (name, rhs) {
|
||
value[name] = rhs;
|
||
},
|
||
"delete": function (name) {
|
||
delete value[name];
|
||
},
|
||
"post": function (name, args) {
|
||
// Mark Miller proposes that post with no name should apply a
|
||
// promised function.
|
||
if (name === null || name === void 0) {
|
||
return value.apply(void 0, args);
|
||
} else {
|
||
return value[name].apply(value, args);
|
||
}
|
||
},
|
||
"apply": function (thisp, args) {
|
||
return value.apply(thisp, args);
|
||
},
|
||
"keys": function () {
|
||
return object_keys(value);
|
||
}
|
||
}, void 0, function inspect() {
|
||
return { state: "fulfilled", value: value };
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Converts thenables to Q promises.
|
||
* @param promise thenable promise
|
||
* @returns a Q promise
|
||
*/
|
||
function coerce(promise) {
|
||
var deferred = defer();
|
||
Q.nextTick(function () {
|
||
try {
|
||
promise.then(deferred.resolve, deferred.reject, deferred.notify);
|
||
} catch (exception) {
|
||
deferred.reject(exception);
|
||
}
|
||
});
|
||
return deferred.promise;
|
||
}
|
||
|
||
/**
|
||
* Annotates an object such that it will never be
|
||
* transferred away from this process over any promise
|
||
* communication channel.
|
||
* @param object
|
||
* @returns promise a wrapping of that object that
|
||
* additionally responds to the "isDef" message
|
||
* without a rejection.
|
||
*/
|
||
Q.master = master;
|
||
function master(object) {
|
||
return Promise({
|
||
"isDef": function () {}
|
||
}, function fallback(op, args) {
|
||
return dispatch(object, op, args);
|
||
}, function () {
|
||
return Q(object).inspect();
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Spreads the values of a promised array of arguments into the
|
||
* fulfillment callback.
|
||
* @param fulfilled callback that receives variadic arguments from the
|
||
* promised array
|
||
* @param rejected callback that receives the exception if the promise
|
||
* is rejected.
|
||
* @returns a promise for the return value or thrown exception of
|
||
* either callback.
|
||
*/
|
||
Q.spread = spread;
|
||
function spread(value, fulfilled, rejected) {
|
||
return Q(value).spread(fulfilled, rejected);
|
||
}
|
||
|
||
Promise.prototype.spread = function (fulfilled, rejected) {
|
||
return this.all().then(function (array) {
|
||
return fulfilled.apply(void 0, array);
|
||
}, rejected);
|
||
};
|
||
|
||
/**
|
||
* The async function is a decorator for generator functions, turning
|
||
* them into asynchronous generators. Although generators are only part
|
||
* of the newest ECMAScript 6 drafts, this code does not cause syntax
|
||
* errors in older engines. This code should continue to work and will
|
||
* in fact improve over time as the language improves.
|
||
*
|
||
* ES6 generators are currently part of V8 version 3.19 with the
|
||
* --harmony-generators runtime flag enabled. SpiderMonkey has had them
|
||
* for longer, but under an older Python-inspired form. This function
|
||
* works on both kinds of generators.
|
||
*
|
||
* Decorates a generator function such that:
|
||
* - it may yield promises
|
||
* - execution will continue when that promise is fulfilled
|
||
* - the value of the yield expression will be the fulfilled value
|
||
* - it returns a promise for the return value (when the generator
|
||
* stops iterating)
|
||
* - the decorated function returns a promise for the return value
|
||
* of the generator or the first rejected promise among those
|
||
* yielded.
|
||
* - if an error is thrown in the generator, it propagates through
|
||
* every following yield until it is caught, or until it escapes
|
||
* the generator function altogether, and is translated into a
|
||
* rejection for the promise returned by the decorated generator.
|
||
*/
|
||
Q.async = async;
|
||
function async(makeGenerator) {
|
||
return function () {
|
||
// when verb is "send", arg is a value
|
||
// when verb is "throw", arg is an exception
|
||
function continuer(verb, arg) {
|
||
var result;
|
||
|
||
// Until V8 3.19 / Chromium 29 is released, SpiderMonkey is the only
|
||
// engine that has a deployed base of browsers that support generators.
|
||
// However, SM's generators use the Python-inspired semantics of
|
||
// outdated ES6 drafts. We would like to support ES6, but we'd also
|
||
// like to make it possible to use generators in deployed browsers, so
|
||
// we also support Python-style generators. At some point we can remove
|
||
// this block.
|
||
|
||
if (typeof StopIteration === "undefined") {
|
||
// ES6 Generators
|
||
try {
|
||
result = generator[verb](arg);
|
||
} catch (exception) {
|
||
return reject(exception);
|
||
}
|
||
if (result.done) {
|
||
return Q(result.value);
|
||
} else {
|
||
return when(result.value, callback, errback);
|
||
}
|
||
} else {
|
||
// SpiderMonkey Generators
|
||
// FIXME: Remove this case when SM does ES6 generators.
|
||
try {
|
||
result = generator[verb](arg);
|
||
} catch (exception) {
|
||
if (isStopIteration(exception)) {
|
||
return Q(exception.value);
|
||
} else {
|
||
return reject(exception);
|
||
}
|
||
}
|
||
return when(result, callback, errback);
|
||
}
|
||
}
|
||
var generator = makeGenerator.apply(this, arguments);
|
||
var callback = continuer.bind(continuer, "next");
|
||
var errback = continuer.bind(continuer, "throw");
|
||
return callback();
|
||
};
|
||
}
|
||
|
||
/**
|
||
* The spawn function is a small wrapper around async that immediately
|
||
* calls the generator and also ends the promise chain, so that any
|
||
* unhandled errors are thrown instead of forwarded to the error
|
||
* handler. This is useful because it's extremely common to run
|
||
* generators at the top-level to work with libraries.
|
||
*/
|
||
Q.spawn = spawn;
|
||
function spawn(makeGenerator) {
|
||
Q.done(Q.async(makeGenerator)());
|
||
}
|
||
|
||
// FIXME: Remove this interface once ES6 generators are in SpiderMonkey.
|
||
/**
|
||
* Throws a ReturnValue exception to stop an asynchronous generator.
|
||
*
|
||
* This interface is a stop-gap measure to support generator return
|
||
* values in older Firefox/SpiderMonkey. In browsers that support ES6
|
||
* generators like Chromium 29, just use "return" in your generator
|
||
* functions.
|
||
*
|
||
* @param value the return value for the surrounding generator
|
||
* @throws ReturnValue exception with the value.
|
||
* @example
|
||
* // ES6 style
|
||
* Q.async(function* () {
|
||
* var foo = yield getFooPromise();
|
||
* var bar = yield getBarPromise();
|
||
* return foo + bar;
|
||
* })
|
||
* // Older SpiderMonkey style
|
||
* Q.async(function () {
|
||
* var foo = yield getFooPromise();
|
||
* var bar = yield getBarPromise();
|
||
* Q.return(foo + bar);
|
||
* })
|
||
*/
|
||
Q["return"] = _return;
|
||
function _return(value) {
|
||
throw new QReturnValue(value);
|
||
}
|
||
|
||
/**
|
||
* The promised function decorator ensures that any promise arguments
|
||
* are settled and passed as values (`this` is also settled and passed
|
||
* as a value). It will also ensure that the result of a function is
|
||
* always a promise.
|
||
*
|
||
* @example
|
||
* var add = Q.promised(function (a, b) {
|
||
* return a + b;
|
||
* });
|
||
* add(Q(a), Q(B));
|
||
*
|
||
* @param {function} callback The function to decorate
|
||
* @returns {function} a function that has been decorated.
|
||
*/
|
||
Q.promised = promised;
|
||
function promised(callback) {
|
||
return function () {
|
||
return spread([this, all(arguments)], function (self, args) {
|
||
return callback.apply(self, args);
|
||
});
|
||
};
|
||
}
|
||
|
||
/**
|
||
* sends a message to a value in a future turn
|
||
* @param object* the recipient
|
||
* @param op the name of the message operation, e.g., "when",
|
||
* @param args further arguments to be forwarded to the operation
|
||
* @returns result {Promise} a promise for the result of the operation
|
||
*/
|
||
Q.dispatch = dispatch;
|
||
function dispatch(object, op, args) {
|
||
return Q(object).dispatch(op, args);
|
||
}
|
||
|
||
Promise.prototype.dispatch = function (op, args) {
|
||
var self = this;
|
||
var deferred = defer();
|
||
Q.nextTick(function () {
|
||
self.promiseDispatch(deferred.resolve, op, args);
|
||
});
|
||
return deferred.promise;
|
||
};
|
||
|
||
/**
|
||
* Gets the value of a property in a future turn.
|
||
* @param object promise or immediate reference for target object
|
||
* @param name name of property to get
|
||
* @return promise for the property value
|
||
*/
|
||
Q.get = function (object, key) {
|
||
return Q(object).dispatch("get", [key]);
|
||
};
|
||
|
||
Promise.prototype.get = function (key) {
|
||
return this.dispatch("get", [key]);
|
||
};
|
||
|
||
/**
|
||
* Sets the value of a property in a future turn.
|
||
* @param object promise or immediate reference for object object
|
||
* @param name name of property to set
|
||
* @param value new value of property
|
||
* @return promise for the return value
|
||
*/
|
||
Q.set = function (object, key, value) {
|
||
return Q(object).dispatch("set", [key, value]);
|
||
};
|
||
|
||
Promise.prototype.set = function (key, value) {
|
||
return this.dispatch("set", [key, value]);
|
||
};
|
||
|
||
/**
|
||
* Deletes a property in a future turn.
|
||
* @param object promise or immediate reference for target object
|
||
* @param name name of property to delete
|
||
* @return promise for the return value
|
||
*/
|
||
Q.del = // XXX legacy
|
||
Q["delete"] = function (object, key) {
|
||
return Q(object).dispatch("delete", [key]);
|
||
};
|
||
|
||
Promise.prototype.del = // XXX legacy
|
||
Promise.prototype["delete"] = function (key) {
|
||
return this.dispatch("delete", [key]);
|
||
};
|
||
|
||
/**
|
||
* Invokes a method in a future turn.
|
||
* @param object promise or immediate reference for target object
|
||
* @param name name of method to invoke
|
||
* @param value a value to post, typically an array of
|
||
* invocation arguments for promises that
|
||
* are ultimately backed with `resolve` values,
|
||
* as opposed to those backed with URLs
|
||
* wherein the posted value can be any
|
||
* JSON serializable object.
|
||
* @return promise for the return value
|
||
*/
|
||
// bound locally because it is used by other methods
|
||
Q.mapply = // XXX As proposed by "Redsandro"
|
||
Q.post = function (object, name, args) {
|
||
return Q(object).dispatch("post", [name, args]);
|
||
};
|
||
|
||
Promise.prototype.mapply = // XXX As proposed by "Redsandro"
|
||
Promise.prototype.post = function (name, args) {
|
||
return this.dispatch("post", [name, args]);
|
||
};
|
||
|
||
/**
|
||
* Invokes a method in a future turn.
|
||
* @param object promise or immediate reference for target object
|
||
* @param name name of method to invoke
|
||
* @param ...args array of invocation arguments
|
||
* @return promise for the return value
|
||
*/
|
||
Q.send = // XXX Mark Miller's proposed parlance
|
||
Q.mcall = // XXX As proposed by "Redsandro"
|
||
Q.invoke = function (object, name /*...args*/) {
|
||
return Q(object).dispatch("post", [name, array_slice(arguments, 2)]);
|
||
};
|
||
|
||
Promise.prototype.send = // XXX Mark Miller's proposed parlance
|
||
Promise.prototype.mcall = // XXX As proposed by "Redsandro"
|
||
Promise.prototype.invoke = function (name /*...args*/) {
|
||
return this.dispatch("post", [name, array_slice(arguments, 1)]);
|
||
};
|
||
|
||
/**
|
||
* Applies the promised function in a future turn.
|
||
* @param object promise or immediate reference for target function
|
||
* @param args array of application arguments
|
||
*/
|
||
Q.fapply = function (object, args) {
|
||
return Q(object).dispatch("apply", [void 0, args]);
|
||
};
|
||
|
||
Promise.prototype.fapply = function (args) {
|
||
return this.dispatch("apply", [void 0, args]);
|
||
};
|
||
|
||
/**
|
||
* Calls the promised function in a future turn.
|
||
* @param object promise or immediate reference for target function
|
||
* @param ...args array of application arguments
|
||
*/
|
||
Q["try"] =
|
||
Q.fcall = function (object /* ...args*/) {
|
||
return Q(object).dispatch("apply", [void 0, array_slice(arguments, 1)]);
|
||
};
|
||
|
||
Promise.prototype.fcall = function (/*...args*/) {
|
||
return this.dispatch("apply", [void 0, array_slice(arguments)]);
|
||
};
|
||
|
||
/**
|
||
* Binds the promised function, transforming return values into a fulfilled
|
||
* promise and thrown errors into a rejected one.
|
||
* @param object promise or immediate reference for target function
|
||
* @param ...args array of application arguments
|
||
*/
|
||
Q.fbind = function (object /*...args*/) {
|
||
var promise = Q(object);
|
||
var args = array_slice(arguments, 1);
|
||
return function fbound() {
|
||
return promise.dispatch("apply", [
|
||
this,
|
||
args.concat(array_slice(arguments))
|
||
]);
|
||
};
|
||
};
|
||
Promise.prototype.fbind = function (/*...args*/) {
|
||
var promise = this;
|
||
var args = array_slice(arguments);
|
||
return function fbound() {
|
||
return promise.dispatch("apply", [
|
||
this,
|
||
args.concat(array_slice(arguments))
|
||
]);
|
||
};
|
||
};
|
||
|
||
/**
|
||
* Requests the names of the owned properties of a promised
|
||
* object in a future turn.
|
||
* @param object promise or immediate reference for target object
|
||
* @return promise for the keys of the eventually settled object
|
||
*/
|
||
Q.keys = function (object) {
|
||
return Q(object).dispatch("keys", []);
|
||
};
|
||
|
||
Promise.prototype.keys = function () {
|
||
return this.dispatch("keys", []);
|
||
};
|
||
|
||
/**
|
||
* Turns an array of promises into a promise for an array. If any of
|
||
* the promises gets rejected, the whole array is rejected immediately.
|
||
* @param {Array*} an array (or promise for an array) of values (or
|
||
* promises for values)
|
||
* @returns a promise for an array of the corresponding values
|
||
*/
|
||
// By Mark Miller
|
||
// http://wiki.ecmascript.org/doku.php?id=strawman:concurrency&rev=1308776521#allfulfilled
|
||
Q.all = all;
|
||
function all(promises) {
|
||
return when(promises, function (promises) {
|
||
var pendingCount = 0;
|
||
var deferred = defer();
|
||
array_reduce(promises, function (undefined, promise, index) {
|
||
var snapshot;
|
||
if (
|
||
isPromise(promise) &&
|
||
(snapshot = promise.inspect()).state === "fulfilled"
|
||
) {
|
||
promises[index] = snapshot.value;
|
||
} else {
|
||
++pendingCount;
|
||
when(
|
||
promise,
|
||
function (value) {
|
||
promises[index] = value;
|
||
if (--pendingCount === 0) {
|
||
deferred.resolve(promises);
|
||
}
|
||
},
|
||
deferred.reject,
|
||
function (progress) {
|
||
deferred.notify({ index: index, value: progress });
|
||
}
|
||
);
|
||
}
|
||
}, void 0);
|
||
if (pendingCount === 0) {
|
||
deferred.resolve(promises);
|
||
}
|
||
return deferred.promise;
|
||
});
|
||
}
|
||
|
||
Promise.prototype.all = function () {
|
||
return all(this);
|
||
};
|
||
|
||
/**
|
||
* Returns the first resolved promise of an array. Prior rejected promises are
|
||
* ignored. Rejects only if all promises are rejected.
|
||
* @param {Array*} an array containing values or promises for values
|
||
* @returns a promise fulfilled with the value of the first resolved promise,
|
||
* or a rejected promise if all promises are rejected.
|
||
*/
|
||
Q.any = any;
|
||
|
||
function any(promises) {
|
||
if (promises.length === 0) {
|
||
return Q.resolve();
|
||
}
|
||
|
||
var deferred = Q.defer();
|
||
var pendingCount = 0;
|
||
array_reduce(promises, function (prev, current, index) {
|
||
var promise = promises[index];
|
||
|
||
pendingCount++;
|
||
|
||
when(promise, onFulfilled, onRejected, onProgress);
|
||
function onFulfilled(result) {
|
||
deferred.resolve(result);
|
||
}
|
||
function onRejected() {
|
||
pendingCount--;
|
||
if (pendingCount === 0) {
|
||
deferred.reject(new Error(
|
||
"Can't get fulfillment value from any promise, all " +
|
||
"promises were rejected."
|
||
));
|
||
}
|
||
}
|
||
function onProgress(progress) {
|
||
deferred.notify({
|
||
index: index,
|
||
value: progress
|
||
});
|
||
}
|
||
}, undefined);
|
||
|
||
return deferred.promise;
|
||
}
|
||
|
||
Promise.prototype.any = function () {
|
||
return any(this);
|
||
};
|
||
|
||
/**
|
||
* Waits for all promises to be settled, either fulfilled or
|
||
* rejected. This is distinct from `all` since that would stop
|
||
* waiting at the first rejection. The promise returned by
|
||
* `allResolved` will never be rejected.
|
||
* @param promises a promise for an array (or an array) of promises
|
||
* (or values)
|
||
* @return a promise for an array of promises
|
||
*/
|
||
Q.allResolved = deprecate(allResolved, "allResolved", "allSettled");
|
||
function allResolved(promises) {
|
||
return when(promises, function (promises) {
|
||
promises = array_map(promises, Q);
|
||
return when(all(array_map(promises, function (promise) {
|
||
return when(promise, noop, noop);
|
||
})), function () {
|
||
return promises;
|
||
});
|
||
});
|
||
}
|
||
|
||
Promise.prototype.allResolved = function () {
|
||
return allResolved(this);
|
||
};
|
||
|
||
/**
|
||
* @see Promise#allSettled
|
||
*/
|
||
Q.allSettled = allSettled;
|
||
function allSettled(promises) {
|
||
return Q(promises).allSettled();
|
||
}
|
||
|
||
/**
|
||
* Turns an array of promises into a promise for an array of their states (as
|
||
* returned by `inspect`) when they have all settled.
|
||
* @param {Array[Any*]} values an array (or promise for an array) of values (or
|
||
* promises for values)
|
||
* @returns {Array[State]} an array of states for the respective values.
|
||
*/
|
||
Promise.prototype.allSettled = function () {
|
||
return this.then(function (promises) {
|
||
return all(array_map(promises, function (promise) {
|
||
promise = Q(promise);
|
||
function regardless() {
|
||
return promise.inspect();
|
||
}
|
||
return promise.then(regardless, regardless);
|
||
}));
|
||
});
|
||
};
|
||
|
||
/**
|
||
* Captures the failure of a promise, giving an oportunity to recover
|
||
* with a callback. If the given promise is fulfilled, the returned
|
||
* promise is fulfilled.
|
||
* @param {Any*} promise for something
|
||
* @param {Function} callback to fulfill the returned promise if the
|
||
* given promise is rejected
|
||
* @returns a promise for the return value of the callback
|
||
*/
|
||
Q.fail = // XXX legacy
|
||
Q["catch"] = function (object, rejected) {
|
||
return Q(object).then(void 0, rejected);
|
||
};
|
||
|
||
Promise.prototype.fail = // XXX legacy
|
||
Promise.prototype["catch"] = function (rejected) {
|
||
return this.then(void 0, rejected);
|
||
};
|
||
|
||
/**
|
||
* Attaches a listener that can respond to progress notifications from a
|
||
* promise's originating deferred. This listener receives the exact arguments
|
||
* passed to ``deferred.notify``.
|
||
* @param {Any*} promise for something
|
||
* @param {Function} callback to receive any progress notifications
|
||
* @returns the given promise, unchanged
|
||
*/
|
||
Q.progress = progress;
|
||
function progress(object, progressed) {
|
||
return Q(object).then(void 0, void 0, progressed);
|
||
}
|
||
|
||
Promise.prototype.progress = function (progressed) {
|
||
return this.then(void 0, void 0, progressed);
|
||
};
|
||
|
||
/**
|
||
* Provides an opportunity to observe the settling of a promise,
|
||
* regardless of whether the promise is fulfilled or rejected. Forwards
|
||
* the resolution to the returned promise when the callback is done.
|
||
* The callback can return a promise to defer completion.
|
||
* @param {Any*} promise
|
||
* @param {Function} callback to observe the resolution of the given
|
||
* promise, takes no arguments.
|
||
* @returns a promise for the resolution of the given promise when
|
||
* ``fin`` is done.
|
||
*/
|
||
Q.fin = // XXX legacy
|
||
Q["finally"] = function (object, callback) {
|
||
return Q(object)["finally"](callback);
|
||
};
|
||
|
||
Promise.prototype.fin = // XXX legacy
|
||
Promise.prototype["finally"] = function (callback) {
|
||
callback = Q(callback);
|
||
return this.then(function (value) {
|
||
return callback.fcall().then(function () {
|
||
return value;
|
||
});
|
||
}, function (reason) {
|
||
// TODO attempt to recycle the rejection with "this".
|
||
return callback.fcall().then(function () {
|
||
throw reason;
|
||
});
|
||
});
|
||
};
|
||
|
||
/**
|
||
* Terminates a chain of promises, forcing rejections to be
|
||
* thrown as exceptions.
|
||
* @param {Any*} promise at the end of a chain of promises
|
||
* @returns nothing
|
||
*/
|
||
Q.done = function (object, fulfilled, rejected, progress) {
|
||
return Q(object).done(fulfilled, rejected, progress);
|
||
};
|
||
|
||
Promise.prototype.done = function (fulfilled, rejected, progress) {
|
||
var onUnhandledError = function (error) {
|
||
// forward to a future turn so that ``when``
|
||
// does not catch it and turn it into a rejection.
|
||
Q.nextTick(function () {
|
||
makeStackTraceLong(error, promise);
|
||
if (Q.onerror) {
|
||
Q.onerror(error);
|
||
} else {
|
||
throw error;
|
||
}
|
||
});
|
||
};
|
||
|
||
// Avoid unnecessary `nextTick`ing via an unnecessary `when`.
|
||
var promise = fulfilled || rejected || progress ?
|
||
this.then(fulfilled, rejected, progress) :
|
||
this;
|
||
|
||
if (typeof process === "object" && process && process.domain) {
|
||
onUnhandledError = process.domain.bind(onUnhandledError);
|
||
}
|
||
|
||
promise.then(void 0, onUnhandledError);
|
||
};
|
||
|
||
/**
|
||
* Causes a promise to be rejected if it does not get fulfilled before
|
||
* some milliseconds time out.
|
||
* @param {Any*} promise
|
||
* @param {Number} milliseconds timeout
|
||
* @param {Any*} custom error message or Error object (optional)
|
||
* @returns a promise for the resolution of the given promise if it is
|
||
* fulfilled before the timeout, otherwise rejected.
|
||
*/
|
||
Q.timeout = function (object, ms, error) {
|
||
return Q(object).timeout(ms, error);
|
||
};
|
||
|
||
Promise.prototype.timeout = function (ms, error) {
|
||
var deferred = defer();
|
||
var timeoutId = setTimeout(function () {
|
||
if (!error || "string" === typeof error) {
|
||
error = new Error(error || "Timed out after " + ms + " ms");
|
||
error.code = "ETIMEDOUT";
|
||
}
|
||
deferred.reject(error);
|
||
}, ms);
|
||
|
||
this.then(function (value) {
|
||
clearTimeout(timeoutId);
|
||
deferred.resolve(value);
|
||
}, function (exception) {
|
||
clearTimeout(timeoutId);
|
||
deferred.reject(exception);
|
||
}, deferred.notify);
|
||
|
||
return deferred.promise;
|
||
};
|
||
|
||
/**
|
||
* Returns a promise for the given value (or promised value), some
|
||
* milliseconds after it resolved. Passes rejections immediately.
|
||
* @param {Any*} promise
|
||
* @param {Number} milliseconds
|
||
* @returns a promise for the resolution of the given promise after milliseconds
|
||
* time has elapsed since the resolution of the given promise.
|
||
* If the given promise rejects, that is passed immediately.
|
||
*/
|
||
Q.delay = function (object, timeout) {
|
||
if (timeout === void 0) {
|
||
timeout = object;
|
||
object = void 0;
|
||
}
|
||
return Q(object).delay(timeout);
|
||
};
|
||
|
||
Promise.prototype.delay = function (timeout) {
|
||
return this.then(function (value) {
|
||
var deferred = defer();
|
||
setTimeout(function () {
|
||
deferred.resolve(value);
|
||
}, timeout);
|
||
return deferred.promise;
|
||
});
|
||
};
|
||
|
||
/**
|
||
* Passes a continuation to a Node function, which is called with the given
|
||
* arguments provided as an array, and returns a promise.
|
||
*
|
||
* Q.nfapply(FS.readFile, [__filename])
|
||
* .then(function (content) {
|
||
* })
|
||
*
|
||
*/
|
||
Q.nfapply = function (callback, args) {
|
||
return Q(callback).nfapply(args);
|
||
};
|
||
|
||
Promise.prototype.nfapply = function (args) {
|
||
var deferred = defer();
|
||
var nodeArgs = array_slice(args);
|
||
nodeArgs.push(deferred.makeNodeResolver());
|
||
this.fapply(nodeArgs).fail(deferred.reject);
|
||
return deferred.promise;
|
||
};
|
||
|
||
/**
|
||
* Passes a continuation to a Node function, which is called with the given
|
||
* arguments provided individually, and returns a promise.
|
||
* @example
|
||
* Q.nfcall(FS.readFile, __filename)
|
||
* .then(function (content) {
|
||
* })
|
||
*
|
||
*/
|
||
Q.nfcall = function (callback /*...args*/) {
|
||
var args = array_slice(arguments, 1);
|
||
return Q(callback).nfapply(args);
|
||
};
|
||
|
||
Promise.prototype.nfcall = function (/*...args*/) {
|
||
var nodeArgs = array_slice(arguments);
|
||
var deferred = defer();
|
||
nodeArgs.push(deferred.makeNodeResolver());
|
||
this.fapply(nodeArgs).fail(deferred.reject);
|
||
return deferred.promise;
|
||
};
|
||
|
||
/**
|
||
* Wraps a NodeJS continuation passing function and returns an equivalent
|
||
* version that returns a promise.
|
||
* @example
|
||
* Q.nfbind(FS.readFile, __filename)("utf-8")
|
||
* .then(console.log)
|
||
* .done()
|
||
*/
|
||
Q.nfbind =
|
||
Q.denodeify = function (callback /*...args*/) {
|
||
var baseArgs = array_slice(arguments, 1);
|
||
return function () {
|
||
var nodeArgs = baseArgs.concat(array_slice(arguments));
|
||
var deferred = defer();
|
||
nodeArgs.push(deferred.makeNodeResolver());
|
||
Q(callback).fapply(nodeArgs).fail(deferred.reject);
|
||
return deferred.promise;
|
||
};
|
||
};
|
||
|
||
Promise.prototype.nfbind =
|
||
Promise.prototype.denodeify = function (/*...args*/) {
|
||
var args = array_slice(arguments);
|
||
args.unshift(this);
|
||
return Q.denodeify.apply(void 0, args);
|
||
};
|
||
|
||
Q.nbind = function (callback, thisp /*...args*/) {
|
||
var baseArgs = array_slice(arguments, 2);
|
||
return function () {
|
||
var nodeArgs = baseArgs.concat(array_slice(arguments));
|
||
var deferred = defer();
|
||
nodeArgs.push(deferred.makeNodeResolver());
|
||
function bound() {
|
||
return callback.apply(thisp, arguments);
|
||
}
|
||
Q(bound).fapply(nodeArgs).fail(deferred.reject);
|
||
return deferred.promise;
|
||
};
|
||
};
|
||
|
||
Promise.prototype.nbind = function (/*thisp, ...args*/) {
|
||
var args = array_slice(arguments, 0);
|
||
args.unshift(this);
|
||
return Q.nbind.apply(void 0, args);
|
||
};
|
||
|
||
/**
|
||
* Calls a method of a Node-style object that accepts a Node-style
|
||
* callback with a given array of arguments, plus a provided callback.
|
||
* @param object an object that has the named method
|
||
* @param {String} name name of the method of object
|
||
* @param {Array} args arguments to pass to the method; the callback
|
||
* will be provided by Q and appended to these arguments.
|
||
* @returns a promise for the value or error
|
||
*/
|
||
Q.nmapply = // XXX As proposed by "Redsandro"
|
||
Q.npost = function (object, name, args) {
|
||
return Q(object).npost(name, args);
|
||
};
|
||
|
||
Promise.prototype.nmapply = // XXX As proposed by "Redsandro"
|
||
Promise.prototype.npost = function (name, args) {
|
||
var nodeArgs = array_slice(args || []);
|
||
var deferred = defer();
|
||
nodeArgs.push(deferred.makeNodeResolver());
|
||
this.dispatch("post", [name, nodeArgs]).fail(deferred.reject);
|
||
return deferred.promise;
|
||
};
|
||
|
||
/**
|
||
* Calls a method of a Node-style object that accepts a Node-style
|
||
* callback, forwarding the given variadic arguments, plus a provided
|
||
* callback argument.
|
||
* @param object an object that has the named method
|
||
* @param {String} name name of the method of object
|
||
* @param ...args arguments to pass to the method; the callback will
|
||
* be provided by Q and appended to these arguments.
|
||
* @returns a promise for the value or error
|
||
*/
|
||
Q.nsend = // XXX Based on Mark Miller's proposed "send"
|
||
Q.nmcall = // XXX Based on "Redsandro's" proposal
|
||
Q.ninvoke = function (object, name /*...args*/) {
|
||
var nodeArgs = array_slice(arguments, 2);
|
||
var deferred = defer();
|
||
nodeArgs.push(deferred.makeNodeResolver());
|
||
Q(object).dispatch("post", [name, nodeArgs]).fail(deferred.reject);
|
||
return deferred.promise;
|
||
};
|
||
|
||
Promise.prototype.nsend = // XXX Based on Mark Miller's proposed "send"
|
||
Promise.prototype.nmcall = // XXX Based on "Redsandro's" proposal
|
||
Promise.prototype.ninvoke = function (name /*...args*/) {
|
||
var nodeArgs = array_slice(arguments, 1);
|
||
var deferred = defer();
|
||
nodeArgs.push(deferred.makeNodeResolver());
|
||
this.dispatch("post", [name, nodeArgs]).fail(deferred.reject);
|
||
return deferred.promise;
|
||
};
|
||
|
||
/**
|
||
* If a function would like to support both Node continuation-passing-style and
|
||
* promise-returning-style, it can end its internal promise chain with
|
||
* `nodeify(nodeback)`, forwarding the optional nodeback argument. If the user
|
||
* elects to use a nodeback, the result will be sent there. If they do not
|
||
* pass a nodeback, they will receive the result promise.
|
||
* @param object a result (or a promise for a result)
|
||
* @param {Function} nodeback a Node.js-style callback
|
||
* @returns either the promise or nothing
|
||
*/
|
||
Q.nodeify = nodeify;
|
||
function nodeify(object, nodeback) {
|
||
return Q(object).nodeify(nodeback);
|
||
}
|
||
|
||
Promise.prototype.nodeify = function (nodeback) {
|
||
if (nodeback) {
|
||
this.then(function (value) {
|
||
Q.nextTick(function () {
|
||
nodeback(null, value);
|
||
});
|
||
}, function (error) {
|
||
Q.nextTick(function () {
|
||
nodeback(error);
|
||
});
|
||
});
|
||
} else {
|
||
return this;
|
||
}
|
||
};
|
||
|
||
Q.noConflict = function() {
|
||
throw new Error("Q.noConflict only works when Q is used as a global");
|
||
};
|
||
|
||
// All code before this point will be filtered from stack traces.
|
||
var qEndingLine = captureLine();
|
||
|
||
return Q;
|
||
|
||
});
|
||
|
||
}).call(this,require('_process'))
|
||
},{"_process":16}],18:[function(require,module,exports){
|
||
(function (global){
|
||
var matrixcs = require("./lib/matrix");
|
||
matrixcs.request(require("browser-request"));
|
||
module.exports = matrixcs; // keep export for browserify package deps
|
||
global.matrixcs = matrixcs;
|
||
|
||
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
||
},{"./lib/matrix":3,"browser-request":14}]},{},[18]);
|