From 1f94c8885f4fb83bd666675d4389effc2346c3f6 Mon Sep 17 00:00:00 2001 From: Mike Connor Date: Wed, 19 Aug 2009 23:27:22 -0400 Subject: [PATCH] bug 481733 - provide better error messages, handle errors better, make autoconnect more robust, r=edilee --HG-- extra : rebase_source : 3eb68a7b4be88c7bd7b9c2b02c96218298cce2f9 --- services/sync/locales/en-US/login.properties | 1 + services/sync/locales/en-US/sync.properties | 4 +- services/sync/modules/constants.js | 7 +- services/sync/modules/service.js | 140 ++++++++++++++----- 4 files changed, 112 insertions(+), 40 deletions(-) diff --git a/services/sync/locales/en-US/login.properties b/services/sync/locales/en-US/login.properties index c465c9c5956..777d01fd9ff 100644 --- a/services/sync/locales/en-US/login.properties +++ b/services/sync/locales/en-US/login.properties @@ -7,3 +7,4 @@ loginStart.label = Signing in, please wait... loginError.label = Invalid username, password or passphrase. loginSuccess.label = Signed In hide.label = Hide +error.login.description = Error: %1$S diff --git a/services/sync/locales/en-US/sync.properties b/services/sync/locales/en-US/sync.properties index 146c59dec72..203acb17132 100644 --- a/services/sync/locales/en-US/sync.properties +++ b/services/sync/locales/en-US/sync.properties @@ -6,7 +6,7 @@ weaveButtonOnline.label = Weave shareBookmark.menuItem = Share This Folder... unShareBookmark.menuItem = Stop Sharing This Folder -status.offline = Sign in +status.offline = Not Signed In # The next two are not normally used, as we now display the username # when the user is logged in. But if for some reason we can't get the username, @@ -18,6 +18,8 @@ error.login.title = Error While Signing In error.login.description = Weave encountered an error while signing you in: %1$S. Please try again. error.login.reason.password = Your username/password failed error.login.reason.unknown = Unknown error +error.login.reason.passphrase = Invalid passphrase +error.login.reason.network = Network error error.logout.title = Error While Signing Out error.logout.description = Weave encountered an error while signing you out. It's probably ok, and you don't have to do anything about it (then why are we bugging you with this info?). error.sync.title = Error While Syncing diff --git a/services/sync/modules/constants.js b/services/sync/modules/constants.js index 162cb9ccfce..7055b0630db 100644 --- a/services/sync/modules/constants.js +++ b/services/sync/modules/constants.js @@ -45,7 +45,8 @@ const EXPORTED_SYMBOLS = ["WEAVE_VERSION", "COMPATIBLE_VERSION", 'WEAVE_STATUS_PARTIAL', 'SERVER_LOW_QUOTA', 'SERVER_DOWNTIME', 'SERVER_UNREACHABLE', 'LOGIN_FAILED_NO_USERNAME', 'LOGIN_FAILED_NO_PASSWORD', - 'LOGIN_FAILED_REJECTED', 'METARECORD_DOWNLOAD_FAIL', + 'LOGIN_FAILED_NETWORK_ERROR','LOGIN_FAILED_INVALID_PASSPHRASE', + 'LOGIN_FAILED_LOGIN_REJECTED', 'METARECORD_DOWNLOAD_FAIL', 'VERSION_OUT_OF_DATE', 'DESKTOP_VERSION_OUT_OF_DATE', 'KEYS_DOWNLOAD_FAIL', 'NO_KEYS_NO_KEYGEN', 'KEYS_UPLOAD_FAIL', 'SETUP_FAILED_NO_PASSPHRASE', 'ABORT_SYNC_COMMAND', @@ -97,7 +98,9 @@ const SERVER_UNREACHABLE = "Weave server is unreachable."; // Ways that a sync can fail during setup or login: const LOGIN_FAILED_NO_USERNAME = "No username set, login failed."; const LOGIN_FAILED_NO_PASSWORD = "No password set, login failed."; -const LOGIN_FAILED_REJECTED = "Incorrect username or password."; +const LOGIN_FAILED_NETWORK_ERROR = "Weave failed to connect to the server."; +const LOGIN_FAILED_INVALID_PASSPHRASE = "Incorrect passphrase given."; +const LOGIN_FAILED_LOGIN_REJECTED = "Incorrect username or password."; const METARECORD_DOWNLOAD_FAIL = "Can't download metadata record, HTTP error."; const VERSION_OUT_OF_DATE = "This copy of Weave needs to be updated."; const DESKTOP_VERSION_OUT_OF_DATE = "Weave needs updating on your desktop browser."; diff --git a/services/sync/modules/service.js b/services/sync/modules/service.js index f75be400b53..cafcc40741d 100644 --- a/services/sync/modules/service.js +++ b/services/sync/modules/service.js @@ -340,12 +340,8 @@ WeaveSvc.prototype = { this._genKeyURLs(); - if (Svc.Prefs.get("autoconnect") && this.username) { - try { - if (this.login()) - this.syncOnIdle(); - } catch (e) {} - } + if (Svc.Prefs.get("autoconnect")) + this._autoConnect(); }, _initLogs: function WeaveSvc__initLogs() { @@ -476,24 +472,30 @@ WeaveSvc.prototype = { let res = new Resource(this.baseURL + "api/register/chknode/" + username); try { res.get(); - } - catch(ex) { /* we check status below */ } - if (res.lastChannel.responseStatus == 404) { - this._log.debug("Using serverURL as data cluster (multi-cluster support disabled)"); - return Svc.Prefs.get("serverURL"); - } + switch (res.lastChannel.responseStatus) { + case 404: + this._log.debug("Using serverURL as data cluster (multi-cluster support disabled)"); + return Svc.Prefs.get("serverURL"); + case 200: + return "https://" + res.data + "/"; + default: + this._log.debug("Unexpected response code trying to find cluster: " + res.lastChannel.responseStatus); + break; + } + } catch(ex) { /* if the channel failed to start we'll get here, just return false */} - if (res.lastChannel.responseStatus == 200) - return "https://" + res.data + "/"; - - return null; + return false; }, // gets cluster from central LDAP server and sets this.clusterURL setCluster: function WeaveSvc_setCluster(username) { let cluster = this.findCluster(username); + if (cluster) { + if (cluster == this.clusterURL) + return false; + this._log.debug("Saving cluster setting"); this.clusterURL = cluster; return true; @@ -502,19 +504,20 @@ WeaveSvc.prototype = { this._log.debug("Error setting cluster for user " + username); return false; }, - - // update cluster if required. returns false if the update was not required + + // update cluster if required. returns false if the update was not required updateCluster: function WeaveSvc_updateCluster(username) { let cTime = Date.now(); let lastUp = parseFloat(Svc.Prefs.get("lastClusterUpdate")); if (!lastUp || ((cTime - lastUp) >= CLUSTER_BACKOFF)) { - this.setCluster(username); - Svc.Prefs.set("lastClusterUpdate", cTime.toString()); - return true; + if (this.setCluster(username)) { + Svc.Prefs.set("lastClusterUpdate", cTime.toString()); + return true; + } } return false; }, - + verifyLogin: function WeaveSvc_verifyLogin(username, password, passphrase, isLogin) this._catch(this._notify("verify-login", "", function() { this._log.debug("Verifying login for user " + username); @@ -534,22 +537,36 @@ WeaveSvc.prototype = { return headers; } }; - + // login may fail because of cluster change try { res.get(); - } catch (e) { - if (res.lastChannel.responseStatus == 401) { - if (this.updateCluster(username)) - return this.verifyLogin(username, password, passphrase, isLogin); + } catch (e) {} + + try { + switch (res.lastChannel.responseStatus) { + case 200: + if (passphrase && !this.verifyPassphrase(username, password, passphrase)) { + this._setSyncFailure(LOGIN_FAILED_INVALID_PASSPHRASE); + return false; + } + return true; + case 401: + if (this.updateCluster(username)) + return this.verifyLogin(username, password, passphrase, isLogin); + + this._setSyncFailure(LOGIN_FAILED_LOGIN_REJECTED); + this._log.debug("verifyLogin failed: login failed") + return false; + default: + throw "unexpected HTTP response: " + res.lastChannel.responseStatus; } + } catch (e) { + // if we get here, we have either a busted channel or a network error + this._log.debug("verifyLogin failed: " + e) + this._setSyncFailure(LOGIN_FAILED_NETWORK_ERROR); throw e; } - - if (passphrase) - return this.verifyPassphrase(username, password, passphrase); - else - return true; }))(), verifyPassphrase: function WeaveSvc_verifyPassphrase(username, password, passphrase) @@ -636,10 +653,50 @@ WeaveSvc.prototype = { return true; }))(), + _autoConnectAttempts: 0, + _autoConnect: function WeaveSvc__attemptAutoConnect() { + try { + if (!this.username || !this.password || !this.passphrase) + return; + let failureReason; + if (Svc.IO.offline) + failureReason = "Application is offline"; + else if (this.login()) { + this.syncOnIdle(); + return; + } + + failureReason = this.detailedStatus.sync; + } + catch (ex) { + failureReason = ex; + } + + this._log.debug("Autoconnect failed: " + failureReason); + + let listener = new Utils.EventListener(Utils.bind2(this, + function WeaveSvc__autoConnectCallback(timer) { + this._autoConnectTimer = null; + this._autoConnect(); + })); + this._autoConnectTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + + // back off slowly, with some random fuzz to avoid repeated collisions + var interval = Math.floor(Math.random() * SCHEDULED_SYNC_INTERVAL + + SCHEDULED_SYNC_INTERVAL * this._autoConnectAttempts); + this._autoConnectAttempts++; + this._autoConnectTimer.initWithCallback(listener, interval, + Ci.nsITimer.TYPE_ONE_SHOT); + this._log.debug("Scheduling next autoconnect attempt in " + + interval / 1000 + " seconds."); + }, + login: function WeaveSvc_login(username, password, passphrase) this._catch(this._lock(this._notify("login", "", function() { this._loggedIn = false; this._detailedStatus = new StatusRecord(); + if (Svc.IO.offline) + throw "Application is offline, login should not be called"; if (typeof(username) != "undefined") this.username = username; @@ -660,13 +717,17 @@ WeaveSvc.prototype = { if (!(this.verifyLogin(this.username, this.password, passphrase, true))) { - this._setSyncFailure(LOGIN_FAILED_REJECTED); - throw "Login failed"; + // verifyLogin sets the failure states here + throw "Login failed: " + this.detailedStatus.sync; } // Try starting the sync timer now that we're logged in this._loggedIn = true; this._checkSyncStatus(); + if (this._autoConnectTimer) { + this._autoConnectTimer.cancel(); + this._autoConnectTimer = null; + } return true; })))(), @@ -1014,9 +1075,14 @@ WeaveSvc.prototype = { // specifcally handle 500, 502, 503, 504 errors // xxxmpc: what else should be in this list? // this is sort of pseudocode, need a way to get at the - if (!shouldBackoff && - Utils.checkStatus(Records.lastResource.lastChannel.responseStatus, null, [500,[502,504]])) { - shouldBackoff = true; + if (!shouldBackoff) { + try { + shouldBackoff = Utils.checkStatus(Records.lastResource.lastChannel.responseStatus, null, [500,[502,504]]); + } + catch (e) { + // if responseStatus throws, we have a network issue in play + shouldBackoff = true; + } } // if this is a client error, do the next sync as normal and return