Bug 1277026 - Disconnect Sync and show a notification on FxA remote disconnect. r?markh draft
authorEdouard Oger <eoger@fastmail.com>
Wed, 08 Jun 2016 10:40:42 -0700
changeset 376756 4a8269fdaa3de613fba0c85e438f85ad719cb9c8
parent 376755 8934547b6295315f49a51330d19a5c9fa0b77b0b
child 523230 325a0431eb74a62e3f79d7c6abaa818f4073e25b
push id20662
push userbmo:edouard.oger@gmail.com
push dateWed, 08 Jun 2016 17:47:36 +0000
reviewersmarkh
bugs1277026
milestone50.0a1
Bug 1277026 - Disconnect Sync and show a notification on FxA remote disconnect. r?markh MozReview-Commit-ID: Hxz1j5QDkfM
browser/components/nsBrowserGlue.js
browser/locales/en-US/chrome/browser/accounts.properties
services/fxaccounts/FxAccounts.jsm
services/fxaccounts/FxAccountsCommon.js
services/fxaccounts/FxAccountsPush.js
services/fxaccounts/tests/xpcshell/test_push_service.js
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -307,16 +307,19 @@ BrowserGlue.prototype = {
         }
         break;
       case "weave:service:ready":
         this._setSyncAutoconnectDelay();
         break;
       case "fxaccounts:onverified":
         this._showSyncStartedDoorhanger();
         break;
+      case "fxaccounts:device_disconnected":
+        this._onDeviceDisconnected();
+        break;
       case "weave:engine:clients:display-uri":
         this._onDisplaySyncURI(subject);
         break;
       case "session-save":
         this._setPrefToSaveSession(true);
         subject.QueryInterface(Ci.nsISupportsPRBool);
         subject.data = true;
         break;
@@ -525,16 +528,17 @@ BrowserGlue.prototype = {
     os.addObserver(this, "quit-application-requested", false);
     os.addObserver(this, "quit-application-granted", false);
     if (OBSERVE_LASTWINDOW_CLOSE_TOPICS) {
       os.addObserver(this, "browser-lastwindow-close-requested", false);
       os.addObserver(this, "browser-lastwindow-close-granted", false);
     }
     os.addObserver(this, "weave:service:ready", false);
     os.addObserver(this, "fxaccounts:onverified", false);
+    os.addObserver(this, "fxaccounts:device_disconnected", false);
     os.addObserver(this, "weave:engine:clients:display-uri", false);
     os.addObserver(this, "session-save", false);
     os.addObserver(this, "places-init-complete", false);
     this._isPlacesInitObserver = true;
     os.addObserver(this, "places-database-locked", false);
     this._isPlacesLockedObserver = true;
     os.addObserver(this, "distribution-customization-complete", false);
     os.addObserver(this, "places-shutdown", false);
@@ -591,16 +595,17 @@ BrowserGlue.prototype = {
     os.removeObserver(this, "quit-application-granted");
     os.removeObserver(this, "restart-in-safe-mode");
     if (OBSERVE_LASTWINDOW_CLOSE_TOPICS) {
       os.removeObserver(this, "browser-lastwindow-close-requested");
       os.removeObserver(this, "browser-lastwindow-close-granted");
     }
     os.removeObserver(this, "weave:service:ready");
     os.removeObserver(this, "fxaccounts:onverified");
+    os.removeObserver(this, "fxaccounts:device_disconnected");
     os.removeObserver(this, "weave:engine:clients:display-uri");
     os.removeObserver(this, "session-save");
     if (this._bookmarksBackupIdleTime) {
       this._idleService.removeIdleObserver(this, this._bookmarksBackupIdleTime);
       delete this._bookmarksBackupIdleTime;
     }
     if (this._isPlacesInitObserver)
       os.removeObserver(this, "places-init-complete");
@@ -2499,16 +2504,29 @@ BrowserGlue.prototype = {
 
       // The payload is wrapped weirdly because of how Sync does notifications.
       tabbrowser.addTab(data.wrappedJSObject.object.uri);
     } catch (ex) {
       Cu.reportError("Error displaying tab received by Sync: " + ex);
     }
   },
 
+  _onDeviceDisconnected() {
+    let bundle = Services.strings.createBundle("chrome://browser/locale/accounts.properties");
+    let title = bundle.GetStringFromName("deviceDisconnectedNotification.title");
+    let body = bundle.GetStringFromName("deviceDisconnectedNotification.body");
+
+    let clickCallback = (subject, topic, data) => {
+      if (topic != "alertclickcallback")
+        return;
+      this._openPreferences("sync");
+    }
+    AlertsService.showAlertNotification(null, title, body, true, null, clickCallback);
+  },
+
   _handleFlashHang: function() {
     ++this._flashHangCount;
     if (this._flashHangCount < 2) {
       return;
     }
     // protected mode only applies to win32
     if (Services.appinfo.XPCOMABI != "x86-msvc") {
       return;
--- a/browser/locales/en-US/chrome/browser/accounts.properties
+++ b/browser/locales/en-US/chrome/browser/accounts.properties
@@ -23,8 +23,13 @@ verificationSentTitle = Verification Sen
 verificationSentBody = A verification link has been sent to %S.
 verificationNotSentTitle = Unable to Send Verification
 verificationNotSentBody = We are unable to send a verification mail at this time, please try again later.
 
 # LOCALIZATION NOTE (syncStartNotification.title, syncStartNotification.body)
 # These strings are used in a notification shown after Sync is connected.
 syncStartNotification.title = Sync enabled
 syncStartNotification.body = Firefox will begin syncing momentarily.
+
+# LOCALIZATION NOTE (deviceDisconnectedNotification.title, deviceDisconnectedNotification.body)
+# These strings are used in a notification shown after Sync was disconnected remotely.
+deviceDisconnectedNotification.title = Sync disconnected
+deviceDisconnectedNotification.body = This computer has been successfully disconnected from Firefox Sync.
--- a/services/fxaccounts/FxAccounts.jsm
+++ b/services/fxaccounts/FxAccounts.jsm
@@ -54,16 +54,17 @@ var publicProperties = [
   "promiseAccountsChangeProfileURI",
   "promiseAccountsManageURI",
   "removeCachedOAuthToken",
   "resendVerificationEmail",
   "setSignedInUser",
   "signOut",
   "updateUserAccountData",
   "updateDeviceRegistration",
+  "handleDeviceDisconnection",
   "whenVerified"
 ];
 
 // An AccountState object holds all state related to one specific account.
 // Only one AccountState is ever "current" in the FxAccountsInternal object -
 // whenever a user logs out or logs in, the current AccountState is discarded,
 // making it impossible for the wrong state or state data to be accidentally
 // used.
@@ -1486,16 +1487,30 @@ FxAccountsInternal.prototype = {
   updateDeviceRegistration() {
     return this.getSignedInUser().then(signedInUser => {
       if (signedInUser) {
         return this._registerOrUpdateDevice(signedInUser);
       }
     }).catch(error => this._logErrorAndResetDeviceRegistrationVersion(error));
   },
 
+  handleDeviceDisconnection(deviceId) {
+    return this.currentAccountState.getUserAccountData()
+      .then(data => data ? data.deviceId : null)
+      .then(localDeviceId => {
+        if (deviceId == localDeviceId) {
+          this.notifyObservers(ON_DEVICE_DISCONNECTED_NOTIFICATION, deviceId);
+          return this.signOut(true);
+        }
+        log.error(
+          "The device ID to disconnect doesn't match with the local device ID.\n"
+          + "Local: " + localDeviceId + ", ID to disconnect: " + deviceId);
+    });
+  },
+
   // If you change what we send to the FxA servers during device registration,
   // you'll have to bump the DEVICE_REGISTRATION_VERSION number to force older
   // devices to re-register when Firefox updates
   _registerOrUpdateDevice(signedInUser) {
     try {
       // Allow tests to skip device registration because:
       //   1. It makes remote requests to the auth server.
       //   2. _getDeviceName does not work from xpcshell.
--- a/services/fxaccounts/FxAccountsCommon.js
+++ b/services/fxaccounts/FxAccountsCommon.js
@@ -84,16 +84,17 @@ exports.KEY_LIFETIME       = 1000 * 3600
 exports.POLL_SESSION       = 1000 * 60 * 20;   // 20 minutes
 
 // Observer notifications.
 exports.ONLOGIN_NOTIFICATION = "fxaccounts:onlogin";
 exports.ONVERIFIED_NOTIFICATION = "fxaccounts:onverified";
 exports.ONLOGOUT_NOTIFICATION = "fxaccounts:onlogout";
 // Internal to services/fxaccounts only
 exports.ON_FXA_UPDATE_NOTIFICATION = "fxaccounts:update";
+exports.ON_DEVICE_DISCONNECTED_NOTIFICATION = "fxaccounts:device_disconnected";
 
 exports.FXA_PUSH_SCOPE_ACCOUNT_UPDATE = "chrome://fxa-device-update";
 
 exports.ON_PROFILE_CHANGE_NOTIFICATION = "fxaccounts:profilechange";
 
 // UI Requests.
 exports.UI_REQUEST_SIGN_IN_FLOW = "signInFlow";
 exports.UI_REQUEST_REFRESH_AUTH = "refreshAuthentication";
--- a/services/fxaccounts/FxAccountsPush.js
+++ b/services/fxaccounts/FxAccountsPush.js
@@ -147,16 +147,19 @@ FxAccountsPushService.prototype = {
     this.log.trace("FxAccountsPushService _onPushMessage");
     if (!message.data) {
       // Use the empty signal to check the verification state of the account right away
       this.fxAccounts.checkVerificationStatus();
       return;
     }
     let payload = message.data.json();
     switch (payload.command) {
+      case ON_DEVICE_DISCONNECTED_NOTIFICATION:
+        this.fxAccounts.handleDeviceDisconnection(payload.data.id);
+        break;
       default:
         this.log.warn("FxA Push command unrecognized: " + payload.command);
     }
   },
   /**
    * Fired when the Push server drops a subscription, or the subscription identifier changes.
    *
    * https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIPushService#Receiving_Push_Messages
--- a/services/fxaccounts/tests/xpcshell/test_push_service.js
+++ b/services/fxaccounts/tests/xpcshell/test_push_service.js
@@ -138,16 +138,46 @@ add_test(function observePushTopicVerify
   let pushService = new FxAccountsPushService({
     pushService: mockPushService,
     fxAccounts: customAccounts,
   });
 
   pushService.observe(emptyMsg, mockPushService.pushTopic, FXA_PUSH_SCOPE_ACCOUNT_UPDATE);
 });
 
+add_test(function observePushTopicDeviceDisconnected() {
+  const deviceId = "bogusid";
+  let msg = {
+    data: {
+      json: () => ({
+        command: ON_DEVICE_DISCONNECTED_NOTIFICATION,
+        data: {
+          id: deviceId
+        }
+      })
+    },
+    QueryInterface: function() {
+      return this;
+    }
+  };
+  let customAccounts = Object.assign(mockFxAccounts, {
+    handleDeviceDisconnection: function () {
+      // checking verification status on push messages without data
+      run_next_test();
+    }
+  });
+
+  let pushService = new FxAccountsPushService({
+    pushService: mockPushService,
+    fxAccounts: customAccounts,
+  });
+
+  pushService.observe(msg, mockPushService.pushTopic, FXA_PUSH_SCOPE_ACCOUNT_UPDATE);
+});
+
 add_test(function observeSubscriptionChangeTopic() {
   let customAccounts = Object.assign(mockFxAccounts, {
     updateDeviceRegistration: function () {
       // subscription change means updating the device registration
       run_next_test();
     }
   });