/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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/. */ /** * This is a small wrapper around XMLHttpRequest, which solves various * inadequacies of the API, e.g. error handling. It is entirely generic and * can be used for purposes outside of even mail. * * It does not provide download progress, but assumes that the * fetched resource is so small (<1 10 KB) that the roundtrip and * response generation is far more significant than the * download time of the response. In other words, it's fine for RPC, * but not for bigger file downloads. */ Components.utils.import("resource://gre/modules/JXON.js"); /** * Set up a fetch. * * @param url {String} URL of the server function. * ATTENTION: The caller needs to make sure that the URL is secure to call. * @param urlArgs {Object, associative array} Parameters to add * to the end of the URL as query string. E.g. * { foo: "bla", bar: "blub blub" } will add "?foo=bla&bar=blub%20blub" * to the URL * (unless the URL already has a "?", then it adds "&foo..."). * The values will be urlComponentEncoded, so pass them unencoded. * @param post {Boolean} HTTP GET or POST * Only influences the HTTP request method, * i.e. first line of the HTTP request, not the body or parameters. * Use POST when you modify server state, * GET when you only request information. * * @param successCallback {Function(result {String})} * Called when the server call worked (no errors). * |result| will contain the body of the HTTP reponse, as string. * @param errorCallback {Function(ex)} * Called in case of error. ex contains the error * with a user-displayable but not localized |.message| and maybe a * |.code|, which can be either * - an nsresult error code, * - an HTTP result error code (0...1000) or * - negative: 0...-100 : * -2 = can't resolve server in DNS etc. * -4 = response body (e.g. XML) malformed */ /* not yet supported: * @param headers {Object, associative array} Like urlArgs, * just that the params will be added as HTTP headers. * { foo: "blub blub" } will add "Foo: Blub blub" * The values will be urlComponentEncoded, apart from space, * so pass them unencoded. * @param headerArgs {Object, associative array} Like urlArgs, * just that the params will be added as HTTP headers. * { foo: "blub blub" } will add "X-Moz-Arg-Foo: Blub blub" * The values will be urlComponentEncoded, apart from space, * so pass them unencoded. * @param bodyArgs {Object, associative array} Like urlArgs, * just that the params will be sent x-url-encoded in the body, * like a HTML form post. * The values will be urlComponentEncoded, so pass them unencoded. * This cannot be used together with |uploadBody|. * @param uploadbody {Object} Arbitrary object, which to use as * body of the HTTP request. Will also set the mimetype accordingly. * Only supported object types, currently only E4X is supported * (sending XML). * Usually, you have nothing to upload, so just pass |null|. */ function FetchHTTP(url, urlArgs, post, successCallback, errorCallback) { assert(typeof(successCallback) == "function", "BUG: successCallback"); assert(typeof(errorCallback) == "function", "BUG: errorCallback"); this._url = sanitize.string(url); if (!urlArgs) urlArgs = {}; this._urlArgs = urlArgs; this._post = sanitize.boolean(post); this._successCallback = successCallback; this._errorCallback = errorCallback; } FetchHTTP.prototype = { __proto__: Abortable.prototype, _url : null, // URL as passed to ctor, without arguments _urlArgs : null, _post : null, _successCallback : null, _errorCallback : null, _request : null, // the XMLHttpRequest object result : null, start : function() { var url = this._url; for (var name in this._urlArgs) { url += (!url.includes("?") ? "?" : "&") + name + "=" + encodeURIComponent(this._urlArgs[name]); } this._request = new XMLHttpRequest(); let request = this._request; request.open(this._post ? "POST" : "GET", url); request.channel.loadGroup = null; // needs bug 407190 patch v4 (or higher) - uncomment if that lands. // try { // var channel = request.channel.QueryInterface(Ci.nsIHttpChannel2); // channel.connectTimeout = 5; // channel.requestTimeout = 5; // } catch (e) { dump(e + "\n"); } var me = this; request.onload = function() { me._response(true); } request.onerror = function() { me._response(false); } request.send(null); }, _response : function(success, exStored) { try { var errorCode = null; var errorStr = null; if (success && this._request.status >= 200 && this._request.status < 300) // HTTP level success { try { // response var mimetype = this._request.getResponseHeader("Content-Type"); if (!mimetype) mimetype = ""; mimetype = mimetype.split(";")[0]; if (mimetype == "text/xml" || mimetype == "application/xml" || mimetype == "text/rdf") { this.result = JXON.build(this._request.responseXML); } else { //ddump("mimetype: " + mimetype + " only supported as text"); this.result = this._request.responseText; } //ddump("result:\n" + this.result); } catch (e) { success = false; errorStr = getStringBundle( "chrome://messenger/locale/accountCreationUtil.properties") .GetStringFromName("bad_response_content.error"); errorCode = -4; } } else { success = false; errorCode = this._request.status; if (errorCode == 0) { errorStr = getStringBundle( "chrome://messenger/locale/accountCreationUtil.properties") .GetStringFromName("cannot_contact_server.error"); ddump(errorStr); } else { errorStr = this._request.statusText; } } // Callbacks if (success) { try { this._successCallback(this.result); } catch (e) { logException(e); this._error(e); } } else if (exStored) this._error(exStored); else this._error(new ServerException(errorStr, errorCode, this._url)); if (this._finishedCallback) { try { this._finishedCallback(this); } catch (e) { logException(e); this._error(e); } } } catch (e) { // error in our fetchhttp._response() code logException(e); this._error(e); } }, _error : function(e) { try { this._errorCallback(e); } catch (e) { // error in errorCallback, too! logException(e); alertPrompt("Error in errorCallback for fetchhttp", e); } }, /** * Call this between start() and finishedCallback fired. */ cancel : function(ex) { assert(!this.result, "Call already returned"); this._request.abort(); // Need to manually call error handler // this._response(false, ex ? ex : new UserCancelledException()); }, /** * Allows caller or lib to be notified when the call is done. * This is useful to enable and disable a Cancel button in the UI, * which allows to cancel the network request. */ setFinishedCallback : function(finishedCallback) { this._finishedCallback = finishedCallback; } } function CancelledException(msg) { Exception.call(this, msg); } CancelledException.prototype = Object.create(Exception.prototype); CancelledException.prototype.constructor = CancelledException; function UserCancelledException(msg) { // The user knows they cancelled so I don't see a need // for a message to that effect. if (!msg) msg = "User cancelled"; CancelledException.call(this, msg); } UserCancelledException.prototype = Object.create(CancelledException.prototype); UserCancelledException.prototype.constructor = UserCancelledException; function ServerException(msg, code, uri) { Exception.call(this, msg); this.code = code; this.uri = uri; } ServerException.prototype = Object.create(Exception.prototype); ServerException.prototype.constructor = ServerException;