diff --git a/lib/base-apis.js b/lib/base-apis.js new file mode 100644 index 000000000..6044029f8 --- /dev/null +++ b/lib/base-apis.js @@ -0,0 +1,670 @@ +/* +Copyright 2015, 2016 OpenMarket Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +"use strict"; + +/** + * This is an internal module. MatrixBaseApis is currently only meant to be used + * by {@link client~MatrixClient}. + * + * @module base-apis + */ + +var httpApi = require("./http-api"); +var utils = require("./utils"); + +/** + * Low-level wrappers for the Matrix APIs + * + * @constructor + * + * @param {Object} opts Configuration options + * + * @param {string} opts.baseUrl Required. The base URL to the client-server + * HTTP API. + * + * @param {string} opts.idBaseUrl Optional. The base identity server URL for + * identity server requests. + * + * @param {Function} opts.request Required. The function to invoke for HTTP + * requests. The value of this property is typically require("request") + * 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 {Object} opts.queryParams Optional. Extra query parameters to append + * to all requests with this client. Useful for application services which require + * ?user_id=. + * + */ +function MatrixBaseApis(opts) { + utils.checkObjectHasKeys(opts, ["baseUrl", "request"]); + + this.baseUrl = opts.baseUrl; + this.idBaseUrl = opts.idBaseUrl; + + var httpOpts = { + baseUrl: opts.baseUrl, + idBaseUrl: opts.idBaseUrl, + accessToken: opts.accessToken, + request: opts.request, + prefix: httpApi.PREFIX_R0, + onlyData: true, + extraParams: opts.queryParams + }; + this._http = new httpApi.MatrixHttpApi(this, httpOpts); +} + + +/** + * Get the Homeserver URL of this client + * @return {string} Homeserver URL of this client + */ +MatrixBaseApis.prototype.getHomeserverUrl = function() { + return this.baseUrl; +}; + +/** + * Get the Identity Server URL of this client + * @return {string} Identity Server URL of this client + */ +MatrixBaseApis.prototype.getIdentityServerUrl = function() { + return this.idBaseUrl; +}; + +/** + * Get the access token associated with this account. + * @return {?String} The access_token or null + */ +MatrixBaseApis.prototype.getAccessToken = function() { + return this._http.opts.accessToken || null; +}; + +/** + * @return {boolean} true if there is a valid access_token for this client. + */ +MatrixBaseApis.prototype.isLoggedIn = function() { + return this._http.opts.accessToken !== undefined; +}; + + +// Registration/Login operations +// ============================= + +/** + * @param {module:client.callback} callback Optional. + * @return {module:client.Promise} Resolves: TODO + * @return {module:http-api.MatrixError} Rejects: with an error response. + */ +MatrixBaseApis.prototype.loginFlows = function(callback) { + return this._http.request(callback, "GET", "/login"); +}; + + +// 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: {room_id: {string}, + * room_alias: {string(opt)}} + * @return {module:http-api.MatrixError} Rejects: with an error response. + */ +MatrixBaseApis.prototype.createRoom = function(options, callback) { + // valid options include: room_alias_name, visibility, invite + return this._http.authedRequest( + callback, "POST", "/createRoom", undefined, options + ); +}; + +/** + * @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. + */ +MatrixBaseApis.prototype.roomState = function(roomId, callback) { + var path = utils.encodeUri("/rooms/$roomId/state", {$roomId: roomId}); + return this._http.authedRequest(callback, "GET", path); +}; + +/** + * 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. + */ +MatrixBaseApis.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. + */ +MatrixBaseApis.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} eventId + * @param {module:client.callback} callback Optional. + * @return {module:client.Promise} Resolves: TODO + * @return {module:http-api.MatrixError} Rejects: with an error response. + */ +MatrixBaseApis.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 {Number} limit + * @param {module:client.callback} callback Optional. + * @return {module:client.Promise} Resolves: TODO + * @return {module:http-api.MatrixError} Rejects: with an error response. + */ +MatrixBaseApis.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 } + ); +}; + + +// Room Directory operations +// ========================= + +/** + * @param {module:client.callback} callback Optional. + * @return {module:client.Promise} Resolves: TODO + * @return {module:http-api.MatrixError} Rejects: with an error response. + */ +MatrixBaseApis.prototype.publicRooms = function(callback) { + return this._http.authedRequest(callback, "GET", "/publicRooms"); +}; + +/** + * Create an alias to room ID mapping. + * @param {string} alias The room alias to create. + * @param {string} roomId The room ID to link the alias to. + * @param {module:client.callback} callback Optional. + * @return {module:client.Promise} Resolves: TODO. + * @return {module:http-api.MatrixError} Rejects: with an error response. + */ +MatrixBaseApis.prototype.createAlias = function(alias, roomId, callback) { + var path = utils.encodeUri("/directory/room/$alias", { + $alias: alias + }); + var data = { + room_id: roomId + }; + return this._http.authedRequest( + callback, "PUT", path, undefined, data + ); +}; + +/** + * Delete an alias to room ID mapping. This alias must be on your local server + * and you must have sufficient access to do this operation. + * @param {string} alias The room alias to delete. + * @param {module:client.callback} callback Optional. + * @return {module:client.Promise} Resolves: TODO. + * @return {module:http-api.MatrixError} Rejects: with an error response. + */ +MatrixBaseApis.prototype.deleteAlias = function(alias, callback) { + var path = utils.encodeUri("/directory/room/$alias", { + $alias: alias + }); + return this._http.authedRequest( + callback, "DELETE", path, undefined, undefined + ); +}; + +/** + * Get room info for the given alias. + * @param {string} alias The room alias to resolve. + * @param {module:client.callback} callback Optional. + * @return {module:client.Promise} Resolves: Object with room_id and servers. + * @return {module:http-api.MatrixError} Rejects: with an error response. + */ +MatrixBaseApis.prototype.getRoomIdForAlias = function(alias, callback) { + // TODO: deprecate this or resolveRoomAlias + var path = utils.encodeUri("/directory/room/$alias", { + $alias: alias + }); + return this._http.authedRequest( + callback, "GET", path + ); +}; + +/** + * @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. + */ +MatrixBaseApis.prototype.resolveRoomAlias = function(roomAlias, callback) { + // TODO: deprecate this or getRoomIdForAlias + var path = utils.encodeUri("/directory/room/$alias", {$alias: roomAlias}); + return this._http.request(callback, "GET", path); +}; + +/** + * Get the visibility of a room in the current HS's room directory + * @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. + */ +MatrixBaseApis.prototype.getRoomDirectoryVisibility = + function(roomId, callback) { + var path = utils.encodeUri("/directory/list/room/$roomId", { + $roomId: roomId + }); + return this._http.authedRequest(callback, "GET", path); +}; + +/** + * Set the visbility of a room in the current HS's room directory + * @param {string} roomId + * @param {string} visibility "public" to make the room visible + * in the public directory, or "private" to make + * it invisible. + * @param {module:client.callback} callback Optional. + * @return {module:client.Promise} Resolves: result object + * @return {module:http-api.MatrixError} Rejects: with an error response. + */ +MatrixBaseApis.prototype.setRoomDirectoryVisibility = + function(roomId, visibility, callback) { + var path = utils.encodeUri("/directory/list/room/$roomId", { + $roomId: roomId + }); + return this._http.authedRequest( + callback, "PUT", path, undefined, { "visibility": visibility } + ); +}; + + +// Media operations +// ================ + +/** + * Upload a file to the media repository on the home server. + * @param {File} file object + * @param {module:client.callback} callback Optional. + * @return {module:client.Promise} Resolves: TODO + * @return {module:http-api.MatrixError} Rejects: with an error response. + */ +MatrixBaseApis.prototype.uploadContent = function(file, callback) { + return this._http.uploadContent(file, callback); +}; + +/** + * Cancel a file upload in progress + * @param {module:client.Promise} promise The promise returned from uploadContent + * @return {boolean} true if canceled, otherwise false + */ +MatrixBaseApis.prototype.cancelUpload = function(promise) { + return this._http.cancelUpload(promise); +}; + +/** + * Get a list of all file uploads in progress + * @return {array} Array of objects representing current uploads. + * Currently in progress is element 0. Keys: + * - promise: The promise associated with the upload + * - loaded: Number of bytes uploaded + * - total: Total number of bytes to upload + */ +MatrixBaseApis.prototype.getCurrentUploads = function() { + return this._http.getCurrentUploads(); +}; + + +// 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. + */ +MatrixBaseApis.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); +}; + + +// Account operations +// ================== + +/** + * @param {module:client.callback} callback Optional. + * @return {module:client.Promise} Resolves: TODO + * @return {module:http-api.MatrixError} Rejects: with an error response. + */ +MatrixBaseApis.prototype.getThreePids = function(callback) { + var path = "/account/3pid"; + return this._http.authedRequest( + callback, "GET", path, undefined, undefined + ); +}; + +/** + * @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. + */ +MatrixBaseApis.prototype.addThreePid = function(creds, bind, callback) { + var path = "/account/3pid"; + var data = { + 'threePidCreds': creds, + 'bind': bind + }; + return this._http.authedRequest( + callback, "POST", path, null, data + ); +}; + +/** + * Make a request to change your password. + * @param {Object} authDict + * @param {string} newPassword The new desired password. + * @param {module:client.callback} callback Optional. + * @return {module:client.Promise} Resolves: TODO + * @return {module:http-api.MatrixError} Rejects: with an error response. + */ +MatrixBaseApis.prototype.setPassword = function(authDict, newPassword, callback) { + var path = "/account/password"; + var data = { + 'auth': authDict, + 'new_password': newPassword + }; + + return this._http.authedRequest( + callback, "POST", path, null, data + ); +}; + + +// Push operations +// =============== + +/** + * Gets all pushers registered for the logged-in user + * + * @param {module:client.callback} callback Optional. + * @return {module:client.Promise} Resolves: Array of objects representing pushers + * @return {module:http-api.MatrixError} Rejects: with an error response. + */ +MatrixBaseApis.prototype.getPushers = function(callback) { + var path = "/pushers"; + return this._http.authedRequest( + callback, "GET", path, undefined, undefined + ); +}; + +/** + * Adds a new pusher or updates an existing pusher + * + * @param {Object} pusher Object representing a pusher + * @param {module:client.callback} callback Optional. + * @return {module:client.Promise} Resolves: Empty json object on success + * @return {module:http-api.MatrixError} Rejects: with an error response. + */ +MatrixBaseApis.prototype.setPusher = function(pusher, callback) { + var path = "/pushers/set"; + return this._http.authedRequest( + callback, "POST", path, null, pusher + ); +}; + +/** + * @param {module:client.callback} callback Optional. + * @return {module:client.Promise} Resolves: TODO + * @return {module:http-api.MatrixError} Rejects: with an error response. + */ +MatrixBaseApis.prototype.getPushRules = 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. + */ +MatrixBaseApis.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. + */ +MatrixBaseApis.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); +}; + +/** + * Enable or disable a push notification rule. + * @param {string} scope + * @param {string} kind + * @param {string} ruleId + * @param {boolean} enabled + * @param {module:client.callback} callback Optional. + * @return {module:client.Promise} Resolves: result object + * @return {module:http-api.MatrixError} Rejects: with an error response. + */ +MatrixBaseApis.prototype.setPushRuleEnabled = function(scope, kind, + ruleId, enabled, callback) { + var path = utils.encodeUri("/pushrules/" + scope + "/$kind/$ruleId/enabled", { + $kind: kind, + $ruleId: ruleId + }); + return this._http.authedRequest( + callback, "PUT", path, undefined, {"enabled": enabled} + ); +}; + +/** + * Set the actions for a push notification rule. + * @param {string} scope + * @param {string} kind + * @param {string} ruleId + * @param {array} actions + * @param {module:client.callback} callback Optional. + * @return {module:client.Promise} Resolves: result object + * @return {module:http-api.MatrixError} Rejects: with an error response. + */ +MatrixBaseApis.prototype.setPushRuleActions = function(scope, kind, + ruleId, actions, callback) { + var path = utils.encodeUri("/pushrules/" + scope + "/$kind/$ruleId/actions", { + $kind: kind, + $ruleId: ruleId + }); + return this._http.authedRequest( + callback, "PUT", path, undefined, {"actions": actions} + ); +}; + + +// Search +// ====== + +/** + * Perform a server-side search. + * @param {Object} opts + * @param {string} opts.next_batch the batch token to pass in the query string + * @param {Object} opts.body the JSON object to pass to the request body. + * @param {module:client.callback} callback Optional. + * @return {module:client.Promise} Resolves: TODO + * @return {module:http-api.MatrixError} Rejects: with an error response. + */ +MatrixBaseApis.prototype.search = function(opts, callback) { + var queryparams = {}; + if (opts.next_batch) { + queryparams.next_batch = opts.next_batch; + } + return this._http.authedRequest( + callback, "POST", "/search", queryparams, opts.body + ); +}; + + +// Identity Server Operations +// ========================== + +/** + * Requests an email verification token directly from an Identity Server. + * + * Note that the Home Server offers APIs to proxy this API for specific + * situations, allowing for better feedback to the user. + * + * @param {string} email The email address to request a token for + * @param {string} clientSecret A secret binary string generated by the client. + * It is recommended this be around 16 ASCII characters. + * @param {number} sendAttempt If an identity server sees a duplicate request + * with the same sendAttempt, it will not send another email. + * To request another email to be sent, use a larger value for + * the sendAttempt param as was used in the previous request. + * @param {string} nextLink Optional If specified, the client will be redirected + * to this link after validation. + * @param {module:client.callback} callback Optional. + * @return {module:client.Promise} Resolves: TODO + * @return {module:http-api.MatrixError} Rejects: with an error response. + * @throws Error if No ID server is set + */ +MatrixBaseApis.prototype.requestEmailToken = function(email, clientSecret, + sendAttempt, nextLink, callback) { + var params = { + client_secret: clientSecret, + email: email, + send_attempt: sendAttempt, + next_link: nextLink + }; + return this._http.idServerRequest( + callback, "POST", "/validate/email/requestToken", + params, httpApi.PREFIX_IDENTITY_V1 + ); +}; + +/** + * Looks up the public Matrix ID mapping for a given 3rd party + * identifier from the Identity Server + * @param {string} medium The medium of the threepid, eg. 'email' + * @param {string} address The textual address of the threepid + * @param {module:client.callback} callback Optional. + * @return {module:client.Promise} Resolves: A threepid mapping + * object or the empty object if no mapping + * exists + * @return {module:http-api.MatrixError} Rejects: with an error response. + */ +MatrixBaseApis.prototype.lookupThreePid = function(medium, address, callback) { + var params = { + medium: medium, + address: address, + }; + return this._http.idServerRequest( + callback, "GET", "/lookup", + params, httpApi.PREFIX_IDENTITY_V1 + ); +}; + +/** + * MatrixBaseApis object + */ +module.exports = MatrixBaseApis; diff --git a/lib/client.js b/lib/client.js index 4fdd5ec40..888976f7c 100644 --- a/lib/client.js +++ b/lib/client.js @@ -36,6 +36,7 @@ var utils = require("./utils"); var contentRepo = require("./content-repo"); var Filter = require("./filter"); var SyncApi = require("./sync"); +var MatrixBaseApis = require("./base-apis"); var MatrixError = httpApi.MatrixError; var SCROLLBACK_DELAY_MS = 3000; @@ -62,6 +63,7 @@ var DeviceVerification = { * as it specifies 'sensible' defaults for these modules. * @constructor * @extends {external:EventEmitter} + * @extends {module:base-apis~MatrixBaseApis} * * @param {Object} opts The configuration options for this client. * @param {string} opts.baseUrl Required. The base URL to the client-server @@ -110,10 +112,7 @@ var DeviceVerification = { * result with a gap. */ function MatrixClient(opts) { - utils.checkObjectHasKeys(opts, ["baseUrl", "request"]); - - this.baseUrl = opts.baseUrl; - this.idBaseUrl = opts.idBaseUrl; + MatrixBaseApis.call(this, opts); this.store = opts.store || new StubStore(); this.sessionStore = opts.sessionStore || null; @@ -177,16 +176,6 @@ function MatrixClient(opts) { } this.clientRunning = false; - var httpOpts = { - baseUrl: opts.baseUrl, - idBaseUrl: opts.idBaseUrl, - accessToken: opts.accessToken, - request: opts.request, - prefix: httpApi.PREFIX_R0, - onlyData: true, - extraParams: opts.queryParams - }; - this._http = new httpApi.MatrixHttpApi(this, httpOpts); this.callList = { // callId: MatrixCall }; @@ -209,22 +198,7 @@ function MatrixClient(opts) { this.urlPreviewCache = {}; } utils.inherits(MatrixClient, EventEmitter); - -/** - * Get the Homserver URL of this client - * @return {string} Homeserver URL of this client - */ -MatrixClient.prototype.getHomeserverUrl = function() { - return this.baseUrl; -}; - -/** - * Get the Identity Server URL of this client - * @return {string} Identity Server URL of this client - */ -MatrixClient.prototype.getIdentityServerUrl = function() { - return this.idBaseUrl; -}; +utils.extend(MatrixClient.prototype, MatrixBaseApis.prototype); /** * Get the domain for this client's MXID @@ -237,14 +211,6 @@ MatrixClient.prototype.getDomain = function() { return null; }; -/** - * Get the access token associated with this account. - * @return {?String} The access_token or null - */ -MatrixClient.prototype.getAccessToken = function() { - return this._http.opts.accessToken || null; -}; - /** * Get the local part of the current user ID e.g. "foo" in "@foo:bar". * @return {?string} The user ID localpart or null. @@ -955,27 +921,6 @@ MatrixClient.prototype.getAccountData = function(eventType) { // 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: {room_id: {string}, - * room_alias: {string(opt)}} - * @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. If you have already joined the room, this will no-op. * @param {string} roomIdOrAlias The room ID or room alias to join. @@ -1184,55 +1129,6 @@ MatrixClient.prototype.setPowerLevel = function(roomId, userId, powerLevel, ); }; -/** - * 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 @@ -1745,38 +1641,6 @@ MatrixClient.prototype.sendReadReceipt = function(event, callback) { }; -/** - * Upload a file to the media repository on the home server. - * @param {File} file object - * @param {module:client.callback} callback Optional. - * @return {module:client.Promise} Resolves: TODO - * @return {module:http-api.MatrixError} Rejects: with an error response. - */ -MatrixClient.prototype.uploadContent = function(file, callback) { - return this._http.uploadContent(file, callback); -}; - -/** - * Cancel a file upload in progress - * @param {module:client.Promise} promise The promise returned from uploadContent - * @return {boolean} true if canceled, otherwise false - */ -MatrixClient.prototype.cancelUpload = function(promise) { - return this._http.cancelUpload(promise); -}; - -/** - * Get a list of all file uploads in progress - * @return {array} Array of objects representing current uploads. - * Currently in progress is element 0. Keys: - * - promise: The promise associated with the upload - * - loaded: Number of bytes uploaded - * - total: Total number of bytes to upload - */ -MatrixClient.prototype.getCurrentUploads = function() { - return this._http.getCurrentUploads(); -}; - /** * Get a preview of the given URL as of (roughly) the given point in time, * described as an object with OpenGraph keys and associated values. @@ -1840,74 +1704,6 @@ MatrixClient.prototype.sendTyping = function(roomId, isTyping, timeoutMs, callba ); }; -/** - * Create an alias to room ID mapping. - * @param {string} alias The room alias to create. - * @param {string} roomId The room ID to link the alias to. - * @param {module:client.callback} callback Optional. - * @return {module:client.Promise} Resolves: TODO. - * @return {module:http-api.MatrixError} Rejects: with an error response. - */ -MatrixClient.prototype.createAlias = function(alias, roomId, callback) { - var path = utils.encodeUri("/directory/room/$alias", { - $alias: alias - }); - var data = { - room_id: roomId - }; - return this._http.authedRequest( - callback, "PUT", path, undefined, data - ); -}; - -/** - * Delete an alias to room ID mapping. This alias must be on your local server - * and you must have sufficient access to do this operation. - * @param {string} alias The room alias to delete. - * @param {module:client.callback} callback Optional. - * @return {module:client.Promise} Resolves: TODO. - * @return {module:http-api.MatrixError} Rejects: with an error response. - */ -MatrixClient.prototype.deleteAlias = function(alias, callback) { - var path = utils.encodeUri("/directory/room/$alias", { - $alias: alias - }); - return this._http.authedRequest( - callback, "DELETE", path, undefined, undefined - ); -}; - -/** - * Get room info for the given alias. - * @param {string} alias The room alias to resolve. - * @param {module:client.callback} callback Optional. - * @return {module:client.Promise} Resolves: Object with room_id and servers. - * @return {module:http-api.MatrixError} Rejects: with an error response. - */ -MatrixClient.prototype.getRoomIdForAlias = function(alias, callback) { - var path = utils.encodeUri("/directory/room/$alias", { - $alias: alias - }); - return this._http.authedRequest( - callback, "GET", path - ); -}; - -/** - * @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 @@ -2115,25 +1911,6 @@ MatrixClient.prototype.getPushActionsForEvent = function(event) { // 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. @@ -2195,56 +1972,6 @@ MatrixClient.prototype.mxcUrlToHttp = ); }; -/** - * @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.authedRequest( - callback, "GET", path, undefined, undefined - ); -}; - -/** - * @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.authedRequest( - callback, "POST", path, null, data - ); -}; - -/** - * Make a request to change your password. - * @param {Object} authDict - * @param {string} newPassword The new desired 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.setPassword = function(authDict, newPassword, callback) { - var path = "/account/password"; - var data = { - 'auth': authDict, - 'new_password': newPassword - }; - - return this._http.authedRequest( - callback, "POST", path, null, data - ); -}; - /** * @param {string} presence * @param {module:client.callback} callback Optional. @@ -2268,136 +1995,6 @@ MatrixClient.prototype.setPresence = function(presence, callback) { ); }; -// Pushers -// ======= - -/** - * Gets all pushers registered for the logged-in user - * - * @param {module:client.callback} callback Optional. - * @return {module:client.Promise} Resolves: Array of objects representing pushers - * @return {module:http-api.MatrixError} Rejects: with an error response. - */ -MatrixClient.prototype.getPushers = function(callback) { - var path = "/pushers"; - return this._http.authedRequest( - callback, "GET", path, undefined, undefined - ); -}; - -/** - * Adds a new pusher or updates an existing pusher - * - * @param {Object} pusher Object representing a pusher - * @param {module:client.callback} callback Optional. - * @return {module:client.Promise} Resolves: Empty json object on success - * @return {module:http-api.MatrixError} Rejects: with an error response. - */ -MatrixClient.prototype.setPusher = function(pusher, callback) { - var path = "/pushers/set"; - return this._http.authedRequest( - callback, "POST", path, null, pusher - ); -}; - -// Public (non-authed) operations -// ============================== - -/** - * @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.authedRequest(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.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); -}; - -/** - * Get the visibility of a room in the current HS's room directory - * @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.getRoomDirectoryVisibility = - function(roomId, callback) { - var path = utils.encodeUri("/directory/list/room/$roomId", { - $roomId: roomId - }); - return this._http.authedRequest(callback, "GET", path); -}; - -/** - * Set the visbility of a room in the current HS's room directory - * @param {string} roomId - * @param {string} visibility "public" to make the room visible - * in the public directory, or "private" to make - * it invisible. - * @param {module:client.callback} callback Optional. - * @return {module:client.Promise} Resolves: result object - * @return {module:http-api.MatrixError} Rejects: with an error response. - */ -MatrixClient.prototype.setRoomDirectoryVisibility = - function(roomId, visibility, callback) { - var path = utils.encodeUri("/directory/list/room/$roomId", { - $roomId: roomId - }); - return this._http.authedRequest( - callback, "PUT", path, undefined, { "visibility": visibility } - ); -}; - -/** - * @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. * @@ -2818,7 +2415,6 @@ MatrixClient.prototype.register = function(username, password, return this.registerRequest(params, undefined, callback); }; - /** * @param {Object} data parameters for registration request * @param {string=} kind type of user to register. may be "guest" @@ -3013,94 +2609,6 @@ MatrixClient.prototype.loginWithToken = function(token, 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.getPushRules = 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); -}; - -/** - * Enable or disable a push notification rule. - * @param {string} scope - * @param {string} kind - * @param {string} ruleId - * @param {boolean} enabled - * @param {module:client.callback} callback Optional. - * @return {module:client.Promise} Resolves: result object - * @return {module:http-api.MatrixError} Rejects: with an error response. - */ -MatrixClient.prototype.setPushRuleEnabled = function(scope, kind, - ruleId, enabled, callback) { - var path = utils.encodeUri("/pushrules/" + scope + "/$kind/$ruleId/enabled", { - $kind: kind, - $ruleId: ruleId - }); - return this._http.authedRequest( - callback, "PUT", path, undefined, {"enabled": enabled} - ); -}; - -/** - * Set the actions for a push notification rule. - * @param {string} scope - * @param {string} kind - * @param {string} ruleId - * @param {array} actions - * @param {module:client.callback} callback Optional. - * @return {module:client.Promise} Resolves: result object - * @return {module:http-api.MatrixError} Rejects: with an error response. - */ -MatrixClient.prototype.setPushRuleActions = function(scope, kind, - ruleId, actions, callback) { - var path = utils.encodeUri("/pushrules/" + scope + "/$kind/$ruleId/actions", { - $kind: kind, - $ruleId: ruleId - }); - return this._http.authedRequest( - callback, "PUT", path, undefined, {"actions": actions} - ); -}; - /** * Get the room-kind push rule associated with a room. * @param {string} scope "global" or device-specific. @@ -3203,6 +2711,9 @@ MatrixClient.prototype.setRoomMutePushRule = function(scope, roomId, mute) { } }; +// Search +// ====== + /** * Perform a server-side search for messages containing the given text. * @param {Object} opts Options for the search. @@ -3342,25 +2853,6 @@ MatrixClient.prototype._processRoomEventsSearch = function(searchResults, respon return searchResults; }; -/** - * Perform a server-side search. - * @param {Object} opts - * @param {string} opts.next_batch the batch token to pass in the query string - * @param {Object} opts.body the JSON object to pass to the request 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.search = function(opts, callback) { - var queryparams = {}; - if (opts.next_batch) { - queryparams.next_batch = opts.next_batch; - } - return this._http.authedRequest( - callback, "POST", "/search", queryparams, opts.body - ); -}; - /** * Populate the store with rooms the user has left. @@ -3391,6 +2883,8 @@ MatrixClient.prototype.syncLeftRooms = function() { return this._syncLeftRoomsPromise; }; +// Filters +// ======= /** * Create a new filter. @@ -3488,14 +2982,6 @@ MatrixClient.prototype.getTurnServers = function() { return this._turnServers || []; }; -/** - * @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 // ================= @@ -3841,62 +3327,6 @@ MatrixClient.prototype.getEventMapper = function() { // Identity Server Operations // ========================== -/** - * Requests an email verification token directly from an Identity Server. - * - * Note that the Home Server offers APIs to proxy this API for specific - * situations, allowing for better feedback to the user. - * - * @param {string} email The email address to request a token for - * @param {string} clientSecret A secret binary string generated by the client. - * It is recommended this be around 16 ASCII characters. - * @param {number} sendAttempt If an identity server sees a duplicate request - * with the same sendAttempt, it will not send another email. - * To request another email to be sent, use a larger value for - * the sendAttempt param as was used in the previous request. - * @param {string} nextLink Optional If specified, the client will be redirected - * to this link after validation. - * @param {module:client.callback} callback Optional. - * @return {module:client.Promise} Resolves: TODO - * @return {module:http-api.MatrixError} Rejects: with an error response. - * @throws Error if No ID server is set - */ -MatrixClient.prototype.requestEmailToken = function(email, clientSecret, - sendAttempt, nextLink, callback) { - var params = { - client_secret: clientSecret, - email: email, - send_attempt: sendAttempt, - next_link: nextLink - }; - return this._http.idServerRequest( - callback, "POST", "/validate/email/requestToken", - params, httpApi.PREFIX_IDENTITY_V1 - ); -}; - -/** - * Looks up the public Matrix ID mapping for a given 3rd party - * identifier from the Identity Server - * @param {string} medium The medium of the threepid, eg. 'email' - * @param {string} address The textual address of the threepid - * @param {module:client.callback} callback Optional. - * @return {module:client.Promise} Resolves: A threepid mapping - * object or the empty object if no mapping - * exists - * @return {module:http-api.MatrixError} Rejects: with an error response. - */ -MatrixClient.prototype.lookupThreePid = function(medium, address, callback) { - var params = { - medium: medium, - address: address, - }; - return this._http.idServerRequest( - callback, "GET", "/lookup", - params, httpApi.PREFIX_IDENTITY_V1 - ); -}; - /** * Generates a random string suitable for use as a client secret. This * method is experimental and may change. diff --git a/lib/utils.js b/lib/utils.js index d593de98a..942503d20 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -332,6 +332,30 @@ var deepCompare = module.exports.deepCompare = function(x, y) { return true; }; +/** + * Copy properties from one object to another. + * + * All enumerable properties, included inherited ones, are copied. + * + * @param {Object} target The object that will receive new properties + * @param {...Object} source Objects from which to copy properties + * + * @return {Object} target + */ +module.exports.extend = function() { + var target = arguments[0] || {}; + // disable jshint "The body of a for in should be wrapped in an if + // statement" + /* jshint -W089 */ + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i]; + for (var propName in source) { + target[propName] = source[propName]; + } + } + /* jshint +W089 */ + return target; +}; /** * Run polyfills to add Array.map and Array.filter if they are missing. diff --git a/spec/unit/utils.spec.js b/spec/unit/utils.spec.js index 49ac2693b..fe9cdeee1 100644 --- a/spec/unit/utils.spec.js +++ b/spec/unit/utils.spec.js @@ -200,4 +200,64 @@ describe("utils", function() { assert.isFalse(utils.deepCompare({ a: { b: func } }, { a: { b: func2 } })); }); }); + + + describe("extend", function() { + var SOURCE = { "prop2": 1, "string2": "x", "newprop": "new" }; + + it("should extend", function() { + var target = { + "prop1": 5, "prop2": 7, "string1": "baz", "string2": "foo", + }; + var merged = { + "prop1": 5, "prop2": 1, "string1": "baz", "string2": "x", + "newprop": "new", + }; + var source_orig = JSON.stringify(SOURCE); + + utils.extend(target, SOURCE); + expect(JSON.stringify(target)).toEqual(JSON.stringify(merged)); + + // check the originial wasn't modified + expect(JSON.stringify(SOURCE)).toEqual(source_orig); + }); + + it("should ignore null", function() { + var target = { + "prop1": 5, "prop2": 7, "string1": "baz", "string2": "foo", + }; + var merged = { + "prop1": 5, "prop2": 1, "string1": "baz", "string2": "x", + "newprop": "new", + }; + var source_orig = JSON.stringify(SOURCE); + + utils.extend(target, null, SOURCE); + expect(JSON.stringify(target)).toEqual(JSON.stringify(merged)); + + // check the originial wasn't modified + expect(JSON.stringify(SOURCE)).toEqual(source_orig); + }); + + it("should handle properties created with defineProperties", function() { + var source = Object.defineProperties({}, { + "enumerableProp": { + get: function() { + return true; + }, + enumerable: true + }, + "nonenumerableProp": { + get: function() { + return true; + } + } + }); + + var target = {}; + utils.extend(target, source); + expect(target.enumerableProp).toBe(true); + expect(target.nonenumerableProp).toBe(undefined); + }); + }); });