You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-29 16:43:09 +03:00
Merge branch 'release-v0.6.3'
This commit is contained in:
28
CHANGELOG.md
28
CHANGELOG.md
@@ -1,3 +1,31 @@
|
||||
Changes in [0.6.3](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.6.3) (2016-10-12)
|
||||
================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.6.2...v0.6.3)
|
||||
|
||||
Breaking Changes
|
||||
----------------
|
||||
* Add a 'RECONNECTING' state to the sync states. This is an additional state
|
||||
between 'SYNCING' and 'ERROR', so most clients should not notice.
|
||||
|
||||
Other Changes
|
||||
----------------
|
||||
* Fix params getting replaced on register calls
|
||||
[\#233](https://github.com/matrix-org/matrix-js-sdk/pull/233)
|
||||
* Fix potential 30s delay on reconnect
|
||||
[\#232](https://github.com/matrix-org/matrix-js-sdk/pull/232)
|
||||
* uploadContent: Attempt some consistency between browser and node
|
||||
[\#230](https://github.com/matrix-org/matrix-js-sdk/pull/230)
|
||||
* Fix error handling on uploadContent
|
||||
[\#229](https://github.com/matrix-org/matrix-js-sdk/pull/229)
|
||||
* Fix uploadContent for node.js
|
||||
[\#226](https://github.com/matrix-org/matrix-js-sdk/pull/226)
|
||||
* Don't emit ERROR until a keepalive poke fails
|
||||
[\#223](https://github.com/matrix-org/matrix-js-sdk/pull/223)
|
||||
* Function to get the fallback url for interactive auth
|
||||
[\#224](https://github.com/matrix-org/matrix-js-sdk/pull/224)
|
||||
* Revert "Handle the first /sync failure differently."
|
||||
[\#222](https://github.com/matrix-org/matrix-js-sdk/pull/222)
|
||||
|
||||
Changes in [0.6.2](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.6.2) (2016-10-05)
|
||||
================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.6.1...v0.6.2)
|
||||
|
||||
40708
dist/0.6.3/browser-matrix-0.6.3.js
vendored
Normal file
40708
dist/0.6.3/browser-matrix-0.6.3.js
vendored
Normal file
File diff suppressed because one or more lines are too long
34
dist/0.6.3/browser-matrix-0.6.3.min.js
vendored
Normal file
34
dist/0.6.3/browser-matrix-0.6.3.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -136,10 +136,12 @@ MatrixBaseApis.prototype.register = function(
|
||||
var params = {
|
||||
auth: auth
|
||||
};
|
||||
if (username !== undefined) { params.username = username; }
|
||||
if (password !== undefined) { params.password = password; }
|
||||
if (bindEmail !== undefined) { params.bind_email = bindEmail; }
|
||||
if (guestAccessToken !== undefined) { params.guest_access_token = guestAccessToken; }
|
||||
if (username !== undefined && username !== null) { params.username = username; }
|
||||
if (password !== undefined && password !== null) { params.password = password; }
|
||||
if (bindEmail !== undefined && bindEmail !== null) { params.bind_email = bindEmail; }
|
||||
if (guestAccessToken !== undefined && guestAccessToken !== null) {
|
||||
params.guest_access_token = guestAccessToken;
|
||||
}
|
||||
|
||||
return this.registerRequest(params, undefined, callback);
|
||||
};
|
||||
@@ -290,6 +292,23 @@ MatrixBaseApis.prototype.deactivateAccount = function(auth, callback) {
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the fallback URL to use for unknown interactive-auth stages.
|
||||
*
|
||||
* @param {string} loginType the type of stage being attempted
|
||||
* @param {string} authSessionId the auth session ID provided by the homeserver
|
||||
*
|
||||
* @return {string} HS URL to hit to for the fallback interface
|
||||
*/
|
||||
MatrixBaseApis.prototype.getFallbackAuthUrl = function(loginType, authSessionId) {
|
||||
var path = utils.encodeUri("/auth/$loginType/fallback/web", {
|
||||
$loginType: loginType,
|
||||
});
|
||||
|
||||
return this._http.getUrl(path, {
|
||||
session: authSessionId,
|
||||
}, httpApi.PREFIX_R0);
|
||||
};
|
||||
|
||||
// Room operations
|
||||
// ===============
|
||||
@@ -557,13 +576,38 @@ MatrixBaseApis.prototype.setRoomDirectoryVisibility =
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param {object} file The object to upload. On a browser, something that
|
||||
* can be sent to XMLHttpRequest.send (typically a File). Under node.js,
|
||||
* a a Buffer, String or ReadStream.
|
||||
*
|
||||
* @param {object} opts options object
|
||||
*
|
||||
* @param {string=} opts.name Name to give the file on the server. Defaults
|
||||
* to <tt>file.name</tt>.
|
||||
*
|
||||
* @param {string=} opts.type Content-type for the upload. Defaults to
|
||||
* <tt>file.type</tt>, or <tt>applicaton/octet-stream</tt>.
|
||||
*
|
||||
* @param {boolean=} opts.rawResponse Return the raw body, rather than
|
||||
* parsing the JSON. Defaults to false (except on node.js, where it
|
||||
* defaults to true for backwards compatibility).
|
||||
*
|
||||
* @param {boolean=} opts.onlyContentUri Just return the content URI,
|
||||
* rather than the whole body. Defaults to false (except on browsers,
|
||||
* where it defaults to true for backwards compatibility). Ignored if
|
||||
* opts.rawResponse is true.
|
||||
*
|
||||
* @param {Function=} opts.callback Deprecated. Optional. The callback to
|
||||
* invoke on success/failure. See the promise return values for more
|
||||
* information.
|
||||
*
|
||||
* @return {module:client.Promise} Resolves to response object, as
|
||||
* determined by this.opts.onlyData, opts.rawResponse, and
|
||||
* opts.onlyContentUri. Rejects with an error (usually a MatrixError).
|
||||
*/
|
||||
MatrixBaseApis.prototype.uploadContent = function(file, callback) {
|
||||
return this._http.uploadContent(file, callback);
|
||||
MatrixBaseApis.prototype.uploadContent = function(file, opts) {
|
||||
return this._http.uploadContent(file, opts);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -2886,6 +2886,9 @@ module.exports.CRYPTO_ENABLED = CRYPTO_ENABLED;
|
||||
* the server for updates. This may be called multiple times even if the state is
|
||||
* already ERROR. <i>This is the equivalent of "syncError" in the previous
|
||||
* API.</i></li>
|
||||
* <li>RECONNECTING: The sync connedtion has dropped, but not in a way that should
|
||||
* be considered erroneous.
|
||||
* </li>
|
||||
* <li>STOPPED: The client has stopped syncing with server due to stopClient
|
||||
* being called.
|
||||
* </li>
|
||||
@@ -2895,8 +2898,10 @@ module.exports.CRYPTO_ENABLED = CRYPTO_ENABLED;
|
||||
* +---->STOPPED
|
||||
* |
|
||||
* +----->PREPARED -------> SYNCING <--+
|
||||
* | ^ | |
|
||||
* null ------+ | +---------------+ |
|
||||
* | ^ ^ |
|
||||
* | | | |
|
||||
* | | V |
|
||||
* null ------+ | +-RECONNECTING<-+ |
|
||||
* | | V |
|
||||
* +------->ERROR ---------------------+
|
||||
*
|
||||
|
||||
239
lib/http-api.js
239
lib/http-api.js
@@ -63,9 +63,11 @@ module.exports.PREFIX_MEDIA_R0 = "/_matrix/media/r0";
|
||||
* requests. This function must look like function(opts, callback){ ... }.
|
||||
* @param {string} opts.prefix Required. The matrix client prefix to use, e.g.
|
||||
* '/_matrix/client/r0'. See PREFIX_R0 and PREFIX_UNSTABLE 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 {bool=} opts.onlyData True to return only the 'data' component of the
|
||||
* response (e.g. the parsed HTTP body). If false, requests will return an
|
||||
* object with the properties <tt>code</tt>, <tt>headers</tt> and <tt>data</tt>.
|
||||
*
|
||||
* @param {string} opts.accessToken The access_token to send with requests. Can be
|
||||
* null to not send an access token.
|
||||
* @param {Object} opts.extraParams Optional. Extra query parameters to send on
|
||||
@@ -99,20 +101,87 @@ module.exports.MatrixHttpApi.prototype = {
|
||||
|
||||
/**
|
||||
* Upload content to the Home Server
|
||||
* @param {File} file A File object (in a browser) or in Node,
|
||||
an object with properties:
|
||||
name: The file's name
|
||||
stream: A read stream
|
||||
* @param {Function} callback Optional. The callback to invoke on
|
||||
* success/failure. See the promise return values for more information.
|
||||
* @return {module:client.Promise} Resolves to <code>{data: {Object},
|
||||
*
|
||||
* @param {object} file The object to upload. On a browser, something that
|
||||
* can be sent to XMLHttpRequest.send (typically a File). Under node.js,
|
||||
* a a Buffer, String or ReadStream.
|
||||
*
|
||||
* @param {object} opts options object
|
||||
*
|
||||
* @param {string=} opts.name Name to give the file on the server. Defaults
|
||||
* to <tt>file.name</tt>.
|
||||
*
|
||||
* @param {string=} opts.type Content-type for the upload. Defaults to
|
||||
* <tt>file.type</tt>, or <tt>applicaton/octet-stream</tt>.
|
||||
*
|
||||
* @param {boolean=} opts.rawResponse Return the raw body, rather than
|
||||
* parsing the JSON. Defaults to false (except on node.js, where it
|
||||
* defaults to true for backwards compatibility).
|
||||
*
|
||||
* @param {boolean=} opts.onlyContentUri Just return the content URI,
|
||||
* rather than the whole body. Defaults to false (except on browsers,
|
||||
* where it defaults to true for backwards compatibility). Ignored if
|
||||
* opts.rawResponse is true.
|
||||
*
|
||||
* @param {Function=} opts.callback Deprecated. Optional. The callback to
|
||||
* invoke on success/failure. See the promise return values for more
|
||||
* information.
|
||||
*
|
||||
* @return {module:client.Promise} Resolves to response object, as
|
||||
* determined by this.opts.onlyData, opts.rawResponse, and
|
||||
* opts.onlyContentUri. Rejects with an error (usually a MatrixError).
|
||||
*/
|
||||
uploadContent: function(file, callback) {
|
||||
if (callback !== undefined && !utils.isFunction(callback)) {
|
||||
throw Error(
|
||||
"Expected callback to be a function but got " + typeof callback
|
||||
);
|
||||
uploadContent: function(file, opts) {
|
||||
if (utils.isFunction(opts)) {
|
||||
// opts used to be callback
|
||||
opts = {
|
||||
callback: opts,
|
||||
};
|
||||
} else if (opts === undefined) {
|
||||
opts = {};
|
||||
}
|
||||
|
||||
// if the file doesn't have a mime type, use a default since
|
||||
// the HS errors if we don't supply one.
|
||||
var contentType = opts.type || file.type || 'application/octet-stream';
|
||||
var fileName = opts.name || file.name;
|
||||
|
||||
// we used to recommend setting file.stream to the thing to upload on
|
||||
// nodejs.
|
||||
var body = file.stream ? file.stream : file;
|
||||
|
||||
// backwards-compatibility hacks where we used to do different things
|
||||
// between browser and node.
|
||||
var rawResponse = opts.rawResponse;
|
||||
if (rawResponse === undefined) {
|
||||
if (global.XMLHttpRequest) {
|
||||
rawResponse = false;
|
||||
} else {
|
||||
console.warn(
|
||||
"Returning the raw JSON from uploadContent(). Future " +
|
||||
"versions of the js-sdk will change this default, to " +
|
||||
"return the parsed object. Set opts.rawResponse=false " +
|
||||
"to change this behaviour now."
|
||||
);
|
||||
rawResponse = true;
|
||||
}
|
||||
}
|
||||
|
||||
var onlyContentUri = opts.onlyContentUri;
|
||||
if (!rawResponse && onlyContentUri === undefined) {
|
||||
if (global.XMLHttpRequest) {
|
||||
console.warn(
|
||||
"Returning only the content-uri from uploadContent(). " +
|
||||
"Future versions of the js-sdk will change this " +
|
||||
"default, to return the whole response object. Set " +
|
||||
"opts.onlyContentUri=false to change this behaviour now."
|
||||
);
|
||||
onlyContentUri = true;
|
||||
} else {
|
||||
onlyContentUri = false;
|
||||
}
|
||||
}
|
||||
|
||||
// browser-request doesn't support File objects because it deep-copies
|
||||
// the options using JSON.parse(JSON.stringify(options)). Instead of
|
||||
// loading the whole file into memory as a string and letting
|
||||
@@ -123,40 +192,59 @@ module.exports.MatrixHttpApi.prototype = {
|
||||
|
||||
var upload = { loaded: 0, total: 0 };
|
||||
var promise;
|
||||
|
||||
// XMLHttpRequest doesn't parse JSON for us. request normally does, but
|
||||
// we're setting opts.json=false so that it doesn't JSON-encode the
|
||||
// request, which also means it doesn't JSON-decode the response. Either
|
||||
// way, we have to JSON-parse the response ourselves.
|
||||
var bodyParser = null;
|
||||
if (!rawResponse) {
|
||||
bodyParser = function(rawBody) {
|
||||
var body = JSON.parse(rawBody);
|
||||
if (onlyContentUri) {
|
||||
body = body.content_uri;
|
||||
if (body === undefined) {
|
||||
throw Error('Bad response');
|
||||
}
|
||||
}
|
||||
return body;
|
||||
};
|
||||
}
|
||||
|
||||
if (global.XMLHttpRequest) {
|
||||
var defer = q.defer();
|
||||
var xhr = new global.XMLHttpRequest();
|
||||
upload.xhr = xhr;
|
||||
var cb = requestCallback(defer, callback, this.opts.onlyData);
|
||||
var cb = requestCallback(defer, opts.callback, this.opts.onlyData);
|
||||
|
||||
var timeout_fn = function() {
|
||||
xhr.abort();
|
||||
cb(new Error('Timeout'));
|
||||
};
|
||||
|
||||
// set an initial timeout of 30s; we'll advance it each time we get
|
||||
// a progress notification
|
||||
xhr.timeout_timer = callbacks.setTimeout(timeout_fn, 30000);
|
||||
|
||||
xhr.onreadystatechange = function() {
|
||||
switch (xhr.readyState) {
|
||||
case global.XMLHttpRequest.DONE:
|
||||
callbacks.clearTimeout(xhr.timeout_timer);
|
||||
var err;
|
||||
if (!xhr.responseText) {
|
||||
err = new Error('No response body.');
|
||||
var resp;
|
||||
try {
|
||||
if (!xhr.responseText) {
|
||||
throw new Error('No response body.');
|
||||
}
|
||||
resp = xhr.responseText;
|
||||
if (bodyParser) {
|
||||
resp = bodyParser(resp);
|
||||
}
|
||||
} catch (err) {
|
||||
err.http_status = xhr.status;
|
||||
cb(err);
|
||||
return;
|
||||
}
|
||||
|
||||
var resp = JSON.parse(xhr.responseText);
|
||||
if (resp.content_uri === undefined) {
|
||||
err = Error('Bad response');
|
||||
err.http_status = xhr.status;
|
||||
cb(err);
|
||||
return;
|
||||
}
|
||||
|
||||
cb(undefined, xhr, resp.content_uri);
|
||||
cb(undefined, xhr, resp);
|
||||
break;
|
||||
}
|
||||
};
|
||||
@@ -169,30 +257,26 @@ module.exports.MatrixHttpApi.prototype = {
|
||||
});
|
||||
var url = this.opts.baseUrl + "/_matrix/media/v1/upload";
|
||||
url += "?access_token=" + encodeURIComponent(this.opts.accessToken);
|
||||
url += "&filename=" + encodeURIComponent(file.name);
|
||||
url += "&filename=" + encodeURIComponent(fileName);
|
||||
|
||||
xhr.open("POST", url);
|
||||
if (file.type) {
|
||||
xhr.setRequestHeader("Content-Type", file.type);
|
||||
} else {
|
||||
// if the file doesn't have a mime type, use a default since
|
||||
// the HS errors if we don't supply one.
|
||||
xhr.setRequestHeader("Content-Type", 'application/octet-stream');
|
||||
}
|
||||
xhr.send(file);
|
||||
xhr.setRequestHeader("Content-Type", contentType);
|
||||
xhr.send(body);
|
||||
promise = defer.promise;
|
||||
|
||||
// dirty hack (as per _request) to allow the upload to be cancelled.
|
||||
promise.abort = xhr.abort.bind(xhr);
|
||||
} else {
|
||||
var queryParams = {
|
||||
filename: file.name,
|
||||
filename: fileName,
|
||||
};
|
||||
|
||||
promise = this.authedRequest(
|
||||
callback, "POST", "/upload", queryParams, file.stream, {
|
||||
opts.callback, "POST", "/upload", queryParams, body, {
|
||||
prefix: "/_matrix/media/v1",
|
||||
localTimeoutMs: 30000,
|
||||
headers: {"Content-Type": file.type},
|
||||
headers: {"Content-Type": contentType},
|
||||
json: false,
|
||||
bodyParser: bodyParser,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -351,7 +435,7 @@ module.exports.MatrixHttpApi.prototype = {
|
||||
*/
|
||||
request: function(callback, method, path, queryParams, data, opts) {
|
||||
opts = opts || {};
|
||||
var prefix = opts.prefix || this.opts.prefix;
|
||||
var prefix = opts.prefix !== undefined ? opts.prefix : this.opts.prefix;
|
||||
var fullUri = this.opts.baseUrl + prefix + path;
|
||||
|
||||
return this.requestOtherUrl(
|
||||
@@ -493,6 +577,32 @@ module.exports.MatrixHttpApi.prototype = {
|
||||
return this.opts.baseUrl + prefix + path + queryString;
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*
|
||||
* @param {function} callback
|
||||
* @param {string} method
|
||||
* @param {string} uri
|
||||
* @param {object} queryParams
|
||||
* @param {object|string} data
|
||||
* @param {object=} opts
|
||||
*
|
||||
* @param {boolean} [opts.json=true] Json-encode data before sending, and
|
||||
* decode response on receipt. (We will still json-decode error
|
||||
* responses, even if this is false.)
|
||||
*
|
||||
* @param {object=} opts.headers extra request headers
|
||||
*
|
||||
* @param {number=} opts.localTimeoutMs client-side timeout for the
|
||||
* request. No timeout if undefined.
|
||||
*
|
||||
* @param {function=} opts.bodyParser function to parse the body of the
|
||||
* response before passing it to the promise and callback.
|
||||
*
|
||||
* @return {module:client.Promise} a promise which resolves to either the
|
||||
* response object (if this.opts.onlyData is truthy), or the parsed
|
||||
* body. Rejects
|
||||
*/
|
||||
_request: function(callback, method, uri, queryParams, data, opts) {
|
||||
if (callback !== undefined && !utils.isFunction(callback)) {
|
||||
throw Error(
|
||||
@@ -508,6 +618,9 @@ module.exports.MatrixHttpApi.prototype = {
|
||||
queryParams[key] = this.opts.extraParams[key];
|
||||
}
|
||||
}
|
||||
|
||||
var json = opts.json === undefined ? true : opts.json;
|
||||
|
||||
var defer = q.defer();
|
||||
|
||||
var timeoutId;
|
||||
@@ -538,7 +651,7 @@ module.exports.MatrixHttpApi.prototype = {
|
||||
withCredentials: false,
|
||||
qs: queryParams,
|
||||
body: data,
|
||||
json: true,
|
||||
json: json,
|
||||
timeout: localTimeoutMs,
|
||||
headers: opts.headers || {},
|
||||
_matrix_opts: this.opts
|
||||
@@ -550,7 +663,15 @@ module.exports.MatrixHttpApi.prototype = {
|
||||
return; // already rejected promise
|
||||
}
|
||||
}
|
||||
var handlerFn = requestCallback(defer, callback, self.opts.onlyData);
|
||||
|
||||
// if json is falsy, we won't parse any error response, so need
|
||||
// to do so before turning it into a MatrixError
|
||||
var parseErrorJson = !json;
|
||||
var handlerFn = requestCallback(
|
||||
defer, callback, self.opts.onlyData,
|
||||
parseErrorJson,
|
||||
opts.bodyParser
|
||||
);
|
||||
handlerFn(err, response, body);
|
||||
}
|
||||
);
|
||||
@@ -577,14 +698,34 @@ module.exports.MatrixHttpApi.prototype = {
|
||||
*
|
||||
* If onlyData is true, the defer/callback is invoked with the body of the
|
||||
* response, otherwise the result code.
|
||||
*
|
||||
* If parseErrorJson is true, we will JSON.parse the body if we get a 4xx error.
|
||||
*
|
||||
*/
|
||||
var requestCallback = function(defer, userDefinedCallback, onlyData) {
|
||||
var requestCallback = function(
|
||||
defer, userDefinedCallback, onlyData,
|
||||
parseErrorJson, bodyParser
|
||||
) {
|
||||
userDefinedCallback = userDefinedCallback || function() {};
|
||||
|
||||
return function(err, response, body) {
|
||||
if (!err && response.statusCode >= 400) {
|
||||
err = new module.exports.MatrixError(body);
|
||||
err.httpStatus = response.statusCode;
|
||||
if (!err) {
|
||||
try {
|
||||
if (response.statusCode >= 400) {
|
||||
if (parseErrorJson) {
|
||||
// we won't have json-decoded the response.
|
||||
body = JSON.parse(body);
|
||||
}
|
||||
err = new module.exports.MatrixError(body);
|
||||
} else if (bodyParser) {
|
||||
body = bodyParser(body);
|
||||
}
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
if (err) {
|
||||
err.httpStatus = response.statusCode;
|
||||
}
|
||||
}
|
||||
|
||||
if (err) {
|
||||
|
||||
71
lib/sync.js
71
lib/sync.js
@@ -66,7 +66,6 @@ function SyncApi(client, opts) {
|
||||
opts.pendingEventOrdering = opts.pendingEventOrdering || "chronological";
|
||||
this.opts = opts;
|
||||
this._peekRoomId = null;
|
||||
this._syncConnectionLost = false;
|
||||
this._currentSyncRequest = null;
|
||||
this._syncState = null;
|
||||
this._running = false;
|
||||
@@ -491,7 +490,7 @@ SyncApi.prototype._sync = function(syncOptions) {
|
||||
qps._cacheBuster = Date.now();
|
||||
}
|
||||
|
||||
if (self._syncConnectionLost) {
|
||||
if (this.getSyncState() == 'ERROR' || this.getSyncState() == 'RECONNECTING') {
|
||||
// we think the connection is dead. If it comes back up, we won't know
|
||||
// about it till /sync returns. If the timeout= is high, this could
|
||||
// be a long time. Set it to 0 when doing retries so we don't have to wait
|
||||
@@ -507,8 +506,6 @@ SyncApi.prototype._sync = function(syncOptions) {
|
||||
);
|
||||
|
||||
this._currentSyncRequest.done(function(data) {
|
||||
self._syncConnectionLost = false;
|
||||
|
||||
// set the sync token NOW *before* processing the events. We do this so
|
||||
// if something barfs on an event we can skip it rather than constantly
|
||||
// polling with the same token.
|
||||
@@ -545,28 +542,19 @@ SyncApi.prototype._sync = function(syncOptions) {
|
||||
console.error("/sync error %s", err);
|
||||
console.error(err);
|
||||
|
||||
if (!self._syncConnectionLost) {
|
||||
// This is the first failure, which may be spurious. To avoid unnecessary
|
||||
// connection error warnings we simply retry the /sync immediately. Only
|
||||
// if *that* one fails too do we say the connection has been lost.
|
||||
// Examples of when this may happen are:
|
||||
// - Restarting backend servers. (In an HA world backends may be
|
||||
// restarted all the time, and its easiest just to make the
|
||||
// client retry).
|
||||
// - Intermediate proxies restarting.
|
||||
// - Device network changes.
|
||||
// Should we emit a state like "MAYBE_CONNETION_LOST"?
|
||||
self._syncConnectionLost = true;
|
||||
debuglog("Starting keep-alive");
|
||||
// Note that we do *not* mark the sync connection as
|
||||
// lost yet: we only do this if a keepalive poke
|
||||
// fails, since long lived HTTP connections will
|
||||
// go away sometimes and we shouldn't treat this as
|
||||
// erroneous. We set the state to 'reconnecting'
|
||||
// instead, so that clients can onserve this state
|
||||
// if they wish.
|
||||
self._startKeepAlives().done(function() {
|
||||
self._sync(syncOptions);
|
||||
} else {
|
||||
debuglog("Starting keep-alive");
|
||||
self._syncConnectionLost = true;
|
||||
self._startKeepAlives().done(function() {
|
||||
self._sync(syncOptions);
|
||||
});
|
||||
self._currentSyncRequest = null;
|
||||
self._updateSyncState("ERROR", { error: err });
|
||||
}
|
||||
});
|
||||
self._currentSyncRequest = null;
|
||||
self._updateSyncState("RECONNECTING");
|
||||
});
|
||||
};
|
||||
|
||||
@@ -859,17 +847,21 @@ SyncApi.prototype._processSyncResponse = function(syncToken, data) {
|
||||
*/
|
||||
SyncApi.prototype._startKeepAlives = function(delay) {
|
||||
if (delay === undefined) {
|
||||
delay = 5000 + Math.floor(Math.random() * 5000);
|
||||
delay = 2000 + Math.floor(Math.random() * 5000);
|
||||
}
|
||||
|
||||
if (this._keepAliveTimer !== null) {
|
||||
clearTimeout(this._keepAliveTimer);
|
||||
}
|
||||
var self = this;
|
||||
self._keepAliveTimer = setTimeout(
|
||||
self._pokeKeepAlive.bind(self),
|
||||
delay
|
||||
);
|
||||
if (delay > 0) {
|
||||
self._keepAliveTimer = setTimeout(
|
||||
self._pokeKeepAlive.bind(self),
|
||||
delay
|
||||
);
|
||||
} else {
|
||||
self._pokeKeepAlive();
|
||||
}
|
||||
if (!this._connectionReturnedDefer) {
|
||||
this._connectionReturnedDefer = q.defer();
|
||||
}
|
||||
@@ -889,9 +881,15 @@ SyncApi.prototype._pokeKeepAlive = function() {
|
||||
}
|
||||
}
|
||||
|
||||
this.client._http.requestWithPrefix(
|
||||
undefined, "GET", "/_matrix/client/versions", undefined,
|
||||
undefined, "", 15 * 1000
|
||||
this.client._http.request(
|
||||
undefined, // callback
|
||||
"GET", "/_matrix/client/versions",
|
||||
undefined, // queryParams
|
||||
undefined, // data
|
||||
{
|
||||
prefix: '',
|
||||
localTimeoutMs: 15 * 1000,
|
||||
}
|
||||
).done(function() {
|
||||
success();
|
||||
}, function(err) {
|
||||
@@ -907,6 +905,13 @@ SyncApi.prototype._pokeKeepAlive = function() {
|
||||
self._pokeKeepAlive.bind(self),
|
||||
5000 + Math.floor(Math.random() * 5000)
|
||||
);
|
||||
// A keepalive has failed, so we emit the
|
||||
// error state (whether or not this is the
|
||||
// first failure).
|
||||
// Note we do this after setting the timer:
|
||||
// this lets the unit tests advance the mock
|
||||
// clock when the get the error.
|
||||
self._updateSyncState("ERROR", { error: err });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "matrix-js-sdk",
|
||||
"version": "0.6.2",
|
||||
"version": "0.6.3",
|
||||
"description": "Matrix Client-Server SDK for Javascript",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -37,6 +37,117 @@ describe("MatrixClient", function() {
|
||||
httpBackend.verifyNoOutstandingExpectation();
|
||||
});
|
||||
|
||||
describe("uploadContent", function() {
|
||||
var buf = new Buffer('hello world');
|
||||
it("should upload the file", function(done) {
|
||||
httpBackend.when(
|
||||
"POST", "/_matrix/media/v1/upload"
|
||||
).check(function(req) {
|
||||
expect(req.data).toEqual(buf);
|
||||
expect(req.queryParams.filename).toEqual("hi.txt");
|
||||
expect(req.queryParams.access_token).toEqual(accessToken);
|
||||
expect(req.headers["Content-Type"]).toEqual("text/plain");
|
||||
expect(req.opts.json).toBeFalsy();
|
||||
expect(req.opts.timeout).toBe(undefined);
|
||||
}).respond(200, "content");
|
||||
|
||||
var prom = client.uploadContent({
|
||||
stream: buf,
|
||||
name: "hi.txt",
|
||||
type: "text/plain",
|
||||
});
|
||||
|
||||
expect(prom).toBeDefined();
|
||||
|
||||
var uploads = client.getCurrentUploads();
|
||||
expect(uploads.length).toEqual(1);
|
||||
expect(uploads[0].promise).toBe(prom);
|
||||
expect(uploads[0].loaded).toEqual(0);
|
||||
|
||||
prom.then(function(response) {
|
||||
// for backwards compatibility, we return the raw JSON
|
||||
expect(response).toEqual("content");
|
||||
|
||||
var uploads = client.getCurrentUploads();
|
||||
expect(uploads.length).toEqual(0);
|
||||
}).catch(utils.failTest).done(done);
|
||||
|
||||
httpBackend.flush();
|
||||
});
|
||||
|
||||
it("should parse the response if rawResponse=false", function(done) {
|
||||
httpBackend.when(
|
||||
"POST", "/_matrix/media/v1/upload"
|
||||
).check(function(req) {
|
||||
expect(req.opts.json).toBeFalsy();
|
||||
}).respond(200, JSON.stringify({ "content_uri": "uri" }));
|
||||
|
||||
client.uploadContent({
|
||||
stream: buf,
|
||||
name: "hi.txt",
|
||||
type: "text/plain",
|
||||
}, {
|
||||
rawResponse: false,
|
||||
}).then(function(response) {
|
||||
expect(response.content_uri).toEqual("uri");
|
||||
}).catch(utils.failTest).done(done);
|
||||
|
||||
httpBackend.flush();
|
||||
});
|
||||
|
||||
it("should parse errors into a MatrixError", function(done) {
|
||||
// opts.json is false, so request returns unparsed json.
|
||||
httpBackend.when(
|
||||
"POST", "/_matrix/media/v1/upload"
|
||||
).check(function(req) {
|
||||
expect(req.data).toEqual(buf);
|
||||
expect(req.opts.json).toBeFalsy();
|
||||
}).respond(400, JSON.stringify({
|
||||
"errcode": "M_SNAFU",
|
||||
"error": "broken",
|
||||
}));
|
||||
|
||||
client.uploadContent({
|
||||
stream: buf,
|
||||
name: "hi.txt",
|
||||
type: "text/plain",
|
||||
}).then(function(response) {
|
||||
throw Error("request not failed");
|
||||
}, function(error) {
|
||||
expect(error.httpStatus).toEqual(400);
|
||||
expect(error.errcode).toEqual("M_SNAFU");
|
||||
expect(error.message).toEqual("broken");
|
||||
}).catch(utils.failTest).done(done);
|
||||
|
||||
httpBackend.flush();
|
||||
});
|
||||
|
||||
it("should return a promise which can be cancelled", function(done) {
|
||||
var prom = client.uploadContent({
|
||||
stream: buf,
|
||||
name: "hi.txt",
|
||||
type: "text/plain",
|
||||
});
|
||||
|
||||
var uploads = client.getCurrentUploads();
|
||||
expect(uploads.length).toEqual(1);
|
||||
expect(uploads[0].promise).toBe(prom);
|
||||
expect(uploads[0].loaded).toEqual(0);
|
||||
|
||||
prom.then(function(response) {
|
||||
throw Error("request not aborted");
|
||||
}, function(error) {
|
||||
expect(error).toEqual("aborted");
|
||||
|
||||
var uploads = client.getCurrentUploads();
|
||||
expect(uploads.length).toEqual(0);
|
||||
}).catch(utils.failTest).done(done);
|
||||
|
||||
var r = client.cancelUpload(prom);
|
||||
expect(r).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("joinRoom", function() {
|
||||
it("should no-op if you've already joined a room", function() {
|
||||
var roomId = "!foo:bar";
|
||||
|
||||
@@ -11,18 +11,17 @@ function HttpBackend() {
|
||||
var self = this;
|
||||
// the request function dependency that the SDK needs.
|
||||
this.requestFn = function(opts, callback) {
|
||||
var realReq = new Request(opts.method, opts.uri, opts.body, opts.qs);
|
||||
realReq.callback = callback;
|
||||
console.log("HTTP backend received request: %s %s", opts.method, opts.uri);
|
||||
self.requests.push(realReq);
|
||||
var req = new Request(opts, callback);
|
||||
console.log("HTTP backend received request: %s", req);
|
||||
self.requests.push(req);
|
||||
|
||||
var abort = function() {
|
||||
var idx = self.requests.indexOf(realReq);
|
||||
var idx = self.requests.indexOf(req);
|
||||
if (idx >= 0) {
|
||||
console.log("Aborting HTTP request: %s %s", opts.method,
|
||||
opts.uri);
|
||||
self.requests.splice(idx, 1);
|
||||
realReq.callback("aborted");
|
||||
req.callback("aborted");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -161,22 +160,32 @@ HttpBackend.prototype = {
|
||||
* @return {Request} An expected request.
|
||||
*/
|
||||
when: function(method, path, data) {
|
||||
var pendingReq = new Request(method, path, data);
|
||||
var pendingReq = new ExpectedRequest(method, path, data);
|
||||
this.expectedRequests.push(pendingReq);
|
||||
return pendingReq;
|
||||
}
|
||||
};
|
||||
|
||||
function Request(method, path, data, queryParams) {
|
||||
/**
|
||||
* Represents the expectation of a request.
|
||||
*
|
||||
* <p>Includes the conditions to be matched against, the checks to be made,
|
||||
* and the response to be returned.
|
||||
*
|
||||
* @constructor
|
||||
* @param {string} method
|
||||
* @param {string} path
|
||||
* @param {object?} data
|
||||
*/
|
||||
function ExpectedRequest(method, path, data) {
|
||||
this.method = method;
|
||||
this.path = path;
|
||||
this.data = data;
|
||||
this.queryParams = queryParams;
|
||||
this.callback = null;
|
||||
this.response = null;
|
||||
this.checks = [];
|
||||
}
|
||||
Request.prototype = {
|
||||
|
||||
ExpectedRequest.prototype = {
|
||||
/**
|
||||
* Execute a check when this request has been satisfied.
|
||||
* @param {Function} fn The function to execute.
|
||||
@@ -221,6 +230,44 @@ Request.prototype = {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a request made by the app.
|
||||
*
|
||||
* @constructor
|
||||
* @param {object} opts opts passed to request()
|
||||
* @param {function} callback
|
||||
*/
|
||||
function Request(opts, callback) {
|
||||
this.opts = opts;
|
||||
this.callback = callback;
|
||||
|
||||
Object.defineProperty(this, 'method', {
|
||||
get: function() { return opts.method; }
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'path', {
|
||||
get: function() { return opts.uri; }
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'data', {
|
||||
get: function() { return opts.body; }
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'queryParams', {
|
||||
get: function() { return opts.qs; }
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'headers', {
|
||||
get: function() { return opts.headers || {}; }
|
||||
});
|
||||
}
|
||||
|
||||
Request.prototype = {
|
||||
toString: function() {
|
||||
return this.method + " " + this.path;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* The HttpBackend class.
|
||||
*/
|
||||
|
||||
@@ -51,9 +51,10 @@ describe("MatrixClient", function() {
|
||||
// }
|
||||
// items are popped off when processed and block if no items left.
|
||||
];
|
||||
var accept_keepalives;
|
||||
var pendingLookup = null;
|
||||
function httpReq(cb, method, path, qp, data, prefix) {
|
||||
if (path === KEEP_ALIVE_PATH) {
|
||||
if (path === KEEP_ALIVE_PATH && accept_keepalives) {
|
||||
return q();
|
||||
}
|
||||
var next = httpLookups.shift();
|
||||
@@ -144,8 +145,10 @@ describe("MatrixClient", function() {
|
||||
client._http.authedRequest.andCallFake(httpReq);
|
||||
client._http.authedRequestWithPrefix.andCallFake(httpReq);
|
||||
client._http.requestWithPrefix.andCallFake(httpReq);
|
||||
client._http.request.andCallFake(httpReq);
|
||||
|
||||
// set reasonable working defaults
|
||||
accept_keepalives = true;
|
||||
pendingLookup = null;
|
||||
httpLookups = [];
|
||||
httpLookups.push(PUSH_RULES_RESPONSE);
|
||||
@@ -289,6 +292,8 @@ describe("MatrixClient", function() {
|
||||
true, "retryImmediately returned false"
|
||||
);
|
||||
jasmine.Clock.tick(1);
|
||||
} else if (state === "RECONNECTING" && httpLookups.length > 0) {
|
||||
jasmine.Clock.tick(10000);
|
||||
} else if (state === "SYNCING" && httpLookups.length === 0) {
|
||||
client.removeListener("sync", syncListener);
|
||||
done();
|
||||
@@ -368,21 +373,25 @@ describe("MatrixClient", function() {
|
||||
it("should transition ERROR -> PREPARED after /sync if prev failed",
|
||||
function(done) {
|
||||
var expectedStates = [];
|
||||
accept_keepalives = false;
|
||||
httpLookups = [];
|
||||
httpLookups.push(PUSH_RULES_RESPONSE);
|
||||
httpLookups.push(FILTER_RESPONSE);
|
||||
// We fail twice since the SDK ignores the first error.
|
||||
httpLookups.push({
|
||||
method: "GET", path: "/sync", error: { errcode: "NOPE_NOPE_NOPE" }
|
||||
});
|
||||
httpLookups.push({
|
||||
method: "GET", path: "/sync", error: { errcode: "NOPE_NOPE_NOPE2" }
|
||||
method: "GET", path: KEEP_ALIVE_PATH, error: { errcode: "KEEPALIVE_FAIL" }
|
||||
});
|
||||
httpLookups.push({
|
||||
method: "GET", path: KEEP_ALIVE_PATH, data: {}
|
||||
});
|
||||
httpLookups.push({
|
||||
method: "GET", path: "/sync", data: SYNC_DATA
|
||||
});
|
||||
|
||||
expectedStates.push(["ERROR", null]);
|
||||
expectedStates.push(["RECONNECTING", null]);
|
||||
expectedStates.push(["ERROR", "RECONNECTING"]);
|
||||
expectedStates.push(["PREPARED", "ERROR"]);
|
||||
client.on("sync", syncChecker(expectedStates, done));
|
||||
client.startClient();
|
||||
@@ -397,18 +406,19 @@ describe("MatrixClient", function() {
|
||||
});
|
||||
|
||||
it("should transition SYNCING -> ERROR after a failed /sync", function(done) {
|
||||
accept_keepalives = false;
|
||||
var expectedStates = [];
|
||||
// We fail twice since the SDK ignores the first error.
|
||||
httpLookups.push({
|
||||
method: "GET", path: "/sync", error: { errcode: "NONONONONO" }
|
||||
});
|
||||
httpLookups.push({
|
||||
method: "GET", path: "/sync", error: { errcode: "NONONONONO2" }
|
||||
method: "GET", path: KEEP_ALIVE_PATH, error: { errcode: "KEEPALIVE_FAIL" }
|
||||
});
|
||||
|
||||
expectedStates.push(["PREPARED", null]);
|
||||
expectedStates.push(["SYNCING", "PREPARED"]);
|
||||
expectedStates.push(["ERROR", "SYNCING"]);
|
||||
expectedStates.push(["RECONNECTING", "SYNCING"]);
|
||||
expectedStates.push(["ERROR", "RECONNECTING"]);
|
||||
client.on("sync", syncChecker(expectedStates, done));
|
||||
client.startClient();
|
||||
});
|
||||
@@ -441,22 +451,23 @@ describe("MatrixClient", function() {
|
||||
client.startClient();
|
||||
});
|
||||
|
||||
it("should transition ERROR -> ERROR if multiple /sync fails", function(done) {
|
||||
it("should transition ERROR -> ERROR if keepalive keeps failing", function(done) {
|
||||
accept_keepalives = false;
|
||||
var expectedStates = [];
|
||||
// We fail twice since the SDK ignores the first error.
|
||||
httpLookups.push({
|
||||
method: "GET", path: "/sync", error: { errcode: "NONONONONO" }
|
||||
});
|
||||
httpLookups.push({
|
||||
method: "GET", path: "/sync", error: { errcode: "NONONONONO" }
|
||||
method: "GET", path: KEEP_ALIVE_PATH, error: { errcode: "KEEPALIVE_FAIL" }
|
||||
});
|
||||
httpLookups.push({
|
||||
method: "GET", path: "/sync", error: { errcode: "NONONONONO" }
|
||||
method: "GET", path: KEEP_ALIVE_PATH, error: { errcode: "KEEPALIVE_FAIL" }
|
||||
});
|
||||
|
||||
expectedStates.push(["PREPARED", null]);
|
||||
expectedStates.push(["SYNCING", "PREPARED"]);
|
||||
expectedStates.push(["ERROR", "SYNCING"]);
|
||||
expectedStates.push(["RECONNECTING", "SYNCING"]);
|
||||
expectedStates.push(["ERROR", "RECONNECTING"]);
|
||||
expectedStates.push(["ERROR", "ERROR"]);
|
||||
client.on("sync", syncChecker(expectedStates, done));
|
||||
client.startClient();
|
||||
|
||||
Reference in New Issue
Block a user