File: client/lib/request.js
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
define(['./hawk', './errors'], function (hawk, ERRORS) {
'use strict';
/* global XMLHttpRequest */
/**
* @class Request
* @constructor
* @param {String} baseUri Base URI
* @param {Object} xhr XMLHttpRequest constructor
* @param {Object} [options={}] Options
* @param {Number} [options.localtimeOffsetMsec]
* Local time offset with the remote auth server's clock
*/
function Request (baseUri, xhr, options) {
if (!options) {
options = {};
}
this.baseUri = baseUri;
this._localtimeOffsetMsec = options.localtimeOffsetMsec;
this.xhr = xhr || XMLHttpRequest;
this.timeout = options.timeout || 30 * 1000;
}
/**
* @method send
* @param {String} path Request path
* @param {String} method HTTP Method
* @param {Object} credentials HAWK Headers
* @param {Object} jsonPayload JSON Payload
* @param {Object} [options={}] Options
* @param {String} [options.retrying]
* Flag indicating if the request is a retry
* @param {Array} [options.headers]
* A set of extra headers to add to the request
* @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request
*/
Request.prototype.send = function request(path, method, credentials, jsonPayload, options) {
/*eslint complexity: [2, 8] */
var xhr = new this.xhr();
var uri = this.baseUri + path;
var payload = null;
var self = this;
options = options || {};
if (jsonPayload) {
payload = JSON.stringify(jsonPayload);
}
try {
xhr.open(method, uri);
} catch (e) {
return Promise.reject({ error: 'Unknown error', message: e.toString(), errno: 999 });
}
return new Promise(function (resolve, reject) {
xhr.timeout = self.timeout;
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
var result = xhr.responseText;
try {
result = JSON.parse(xhr.responseText);
} catch (e) { }
if (result.errno) {
// Try to recover from a timeskew error and not already tried
if (result.errno === ERRORS.INVALID_TIMESTAMP && !options.retrying) {
var serverTime = result.serverTime;
self._localtimeOffsetMsec = (serverTime * 1000) - new Date().getTime();
// add to options that the request is retrying
options.retrying = true;
return self.send(path, method, credentials, jsonPayload, options)
.then(resolve, reject);
} else {
return reject(result);
}
}
if (typeof xhr.status === 'undefined' || xhr.status !== 200) {
if (result.length === 0) {
return reject({ error: 'Timeout error', errno: 999 });
} else {
return reject({ error: 'Unknown error', message: result, errno: 999, code: xhr.status });
}
}
resolve(result);
}
};
// calculate Hawk header if credentials are supplied
if (credentials) {
var hawkHeader = hawk.client.header(uri, method, {
credentials: credentials,
payload: payload,
contentType: 'application/json',
localtimeOffsetMsec: self._localtimeOffsetMsec || 0
});
xhr.setRequestHeader('authorization', hawkHeader.field);
}
xhr.setRequestHeader('Content-Type', 'application/json');
if (options && options.headers) {
// set extra headers for this request
for (var header in options.headers) {
xhr.setRequestHeader(header, options.headers[header]);
}
}
xhr.send(payload);
});
};
return Request;
});