Bug 1121071 Part 1 - Remove old call url code from the Loop panel and related UI areas. r=mikedeboer
authorMark Banner <standard8@mozilla.com>
Fri, 16 Jan 2015 18:34:30 +0000
changeset 224333 37ca802819327f29e4b7e7d2822b47cdf235bd81
parent 224332 68fbd452c2d95cf47a4d48297a0a77a28853eaa7
child 224334 2234aaa1655a16174d3425733331c9bf16915f58
push id54190
push userkwierso@gmail.com
push dateSat, 17 Jan 2015 02:06:29 +0000
treeherdermozilla-inbound@369a8f14ccf8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmikedeboer
bugs1121071
milestone38.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1121071 Part 1 - Remove old call url code from the Loop panel and related UI areas. r=mikedeboer
browser/app/profile/firefox.js
browser/components/loop/MozLoopAPI.jsm
browser/components/loop/MozLoopService.jsm
browser/components/loop/content/js/client.js
browser/components/loop/content/js/panel.js
browser/components/loop/content/js/panel.jsx
browser/components/loop/content/panel.html
browser/components/loop/test/desktop-local/client_test.js
browser/components/loop/test/desktop-local/panel_test.js
browser/components/loop/test/xpcshell/test_loopservice_expiry.js
browser/components/loop/test/xpcshell/test_loopservice_locales.js
browser/components/loop/test/xpcshell/xpcshell.ini
browser/components/loop/ui/fake-mozLoop.js
browser/components/loop/ui/ui-showcase.js
browser/components/loop/ui/ui-showcase.jsx
browser/locales/en-US/chrome/browser/loop/loop.properties
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1672,17 +1672,16 @@ pref("loop.debug.websocket", false);
 pref("loop.debug.sdk", false);
 #ifdef DEBUG
 pref("loop.CSP", "default-src 'self' about: file: chrome: http://localhost:*; img-src 'self' data: http://www.gravatar.com/ about: file: chrome:; font-src 'none'; connect-src wss://*.tokbox.com https://*.opentok.com https://*.tokbox.com wss://*.mozilla.com https://*.mozilla.org wss://*.mozaws.net http://localhost:* ws://localhost:*; media-src blob:");
 #else
 pref("loop.CSP", "default-src 'self' about: file: chrome:; img-src 'self' data: http://www.gravatar.com/ about: file: chrome:; font-src 'none'; connect-src wss://*.tokbox.com https://*.opentok.com https://*.tokbox.com wss://*.mozilla.com https://*.mozilla.org wss://*.mozaws.net; media-src blob:");
 #endif
 pref("loop.oauth.google.redirect_uri", "urn:ietf:wg:oauth:2.0:oob:auto");
 pref("loop.oauth.google.scope", "https://www.google.com/m8/feeds");
-pref("loop.rooms.enabled", true);
 pref("loop.fxa_oauth.tokendata", "");
 pref("loop.fxa_oauth.profile", "");
 pref("loop.support_url", "https://support.mozilla.org/kb/group-conversations-firefox-hello-webrtc");
 
 // serverURL to be assigned by services team
 pref("services.push.serverURL", "wss://push.services.mozilla.com/");
 
 pref("social.sidebar.unload_timeout_ms", 10000);
--- a/browser/components/loop/MozLoopAPI.jsm
+++ b/browser/components/loop/MozLoopAPI.jsm
@@ -412,36 +412,16 @@ function injectLoopAPI(targetWindow) {
           callback(null, chosenButton == 0);
         } catch (ex) {
           callback(cloneValueInto(ex, targetWindow));
         }
       }
     },
 
     /**
-     * Used to note a call url expiry time. If the time is later than the current
-     * latest expiry time, then the stored expiry time is increased. For times
-     * sooner, this function is a no-op; this ensures we always have the latest
-     * expiry time for a url.
-     *
-     * This is used to determine whether or not we should be registering with the
-     * push server on start.
-     *
-     * @param {Integer} expiryTimeSeconds The seconds since epoch of the expiry time
-     *                                    of the url.
-     */
-    noteCallUrlExpiry: {
-      enumerable: true,
-      writable: true,
-      value: function(expiryTimeSeconds) {
-        MozLoopService.noteCallUrlExpiry(expiryTimeSeconds);
-      }
-    },
-
-    /**
      * Set any preference under "loop."
      *
      * @param {String} prefName The name of the pref without the preceding "loop."
      * @param {*} value The value to set.
      * @param {Enum} prefType Type of preference, defined at Ci.nsIPrefBranch. Optional.
      *
      * Any errors thrown by the Mozilla pref API are logged to the console
      * and cause false to be returned.
--- a/browser/components/loop/MozLoopService.jsm
+++ b/browser/components/loop/MozLoopService.jsm
@@ -1239,32 +1239,16 @@ this.MozLoopService = {
   /**
    * @see MozLoopServiceInternal.promiseRegisteredWithServers
    */
   promiseRegisteredWithServers: function(sessionType = LOOP_SESSION_TYPE.GUEST) {
     return MozLoopServiceInternal.promiseRegisteredWithServers(sessionType);
   },
 
   /**
-   * Used to note a call url expiry time. If the time is later than the current
-   * latest expiry time, then the stored expiry time is increased. For times
-   * sooner, this function is a no-op; this ensures we always have the latest
-   * expiry time for a url.
-   *
-   * This is used to determine whether or not we should be registering with the
-   * push server on start.
-   *
-   * @param {Integer} expiryTimeSeconds The seconds since epoch of the expiry time
-   *                                    of the url.
-   */
-  noteCallUrlExpiry: function(expiryTimeSeconds) {
-    MozLoopServiceInternal.expiryTimeSeconds = expiryTimeSeconds;
-  },
-
-  /**
    * Returns the strings for the specified element. Designed for use with l10n.js.
    *
    * @param {key} The element id to get strings for.
    * @return {String} A JSON string containing the localized attribute/value pairs
    *                  for the element.
    */
   getStrings: function(key) {
     var stringData = MozLoopServiceInternal.localizedStrings;
--- a/browser/components/loop/content/js/client.js
+++ b/browser/components/loop/content/js/client.js
@@ -4,22 +4,16 @@
 
 /* jshint esnext:true */
 /* global loop:true, hawk, deriveHawkCredentials */
 
 var loop = loop || {};
 loop.Client = (function($) {
   "use strict";
 
-  // The expected properties to be returned from the POST /call-url/ request.
-  var expectedCallUrlProperties = ["callUrl", "expiresAt"];
-
-  // The expected properties to be returned from the GET /calls request.
-  var expectedCallProperties = ["calls"];
-
   // THe expected properties to be returned from the POST /calls request.
   var expectedPostCallProperties = [
     "apiKey", "callId", "progressURL",
     "sessionId", "sessionToken", "websocketToken"
   ];
 
   /**
    * Loop server client.
@@ -77,66 +71,16 @@ loop.Client = (function($) {
      */
     _failureHandler: function(cb, error) {
       var message = "HTTP " + error.code + " " + error.error + "; " + error.message;
       console.error(message);
       cb(error);
     },
 
     /**
-     * Requests a call URL from the Loop server. It will note the
-     * expiry time for the url with the mozLoop api.  It will select the
-     * appropriate hawk session to use based on whether or not the user
-     * is currently logged into a Firefox account profile.
-     *
-     * Callback parameters:
-     * - err null on successful request, non-null otherwise.
-     * - callUrlData an object of the obtained call url data if successful:
-     * -- callUrl: The url of the call
-     * -- expiresAt: The amount of hours until expiry of the url
-     *
-     * @param  {String} simplepushUrl a registered Simple Push URL
-     * @param  {string} nickname the nickname of the future caller
-     * @param  {Function} cb Callback(err, callUrlData)
-     */
-    requestCallUrl: function(nickname, cb) {
-      var sessionType;
-      if (this.mozLoop.userProfile) {
-        sessionType = this.mozLoop.LOOP_SESSION_TYPE.FXA;
-      } else {
-        sessionType = this.mozLoop.LOOP_SESSION_TYPE.GUEST;
-      }
-
-      this.mozLoop.hawkRequest(sessionType, "/call-url/", "POST",
-                               {callerId: nickname},
-        function (error, responseText) {
-          if (error) {
-            this._telemetryAdd("LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS", false);
-            this._failureHandler(cb, error);
-            return;
-          }
-
-          try {
-            var urlData = JSON.parse(responseText);
-
-            // This throws if the data is invalid, in which case only the failure
-            // telemetry will be recorded.
-            var returnData = this._validate(urlData, expectedCallUrlProperties);
-
-            this._telemetryAdd("LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS", true);
-            cb(null, returnData);
-          } catch (err) {
-            this._telemetryAdd("LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS", false);
-            console.log("Error requesting call info", err);
-            cb(err);
-          }
-        }.bind(this));
-    },
-
-    /**
      * Block call URL based on the token identifier
      *
      * @param {string} token Conversation identifier used to block the URL
      * @param {mozLoop.LOOP_SESSION_TYPE} sessionType The type of session which
      *                                                the url belongs to.
      * @param {function} cb Callback function used for handling an error
      *                      response. XXX The incoming call panel does not
      *                      exist after the block button is clicked therefore
@@ -198,26 +142,12 @@ loop.Client = (function($) {
             cb(null, outgoingCallData);
           } catch (err) {
             console.log("Error requesting call info", err);
             cb(err);
           }
         }.bind(this)
       );
     },
-
-    /**
-     * Adds a value to a telemetry histogram, ignoring errors.
-     *
-     * @param  {string}  histogramId Name of the telemetry histogram to update.
-     * @param  {integer} value       Value to add to the histogram.
-     */
-    _telemetryAdd: function(histogramId, value) {
-      try {
-        this.mozLoop.telemetryAdd(histogramId, value);
-      } catch (err) {
-        console.error("Error recording telemetry", err);
-      }
-    },
   };
 
   return Client;
 })(jQuery);
--- a/browser/components/loop/content/js/panel.js
+++ b/browser/components/loop/content/js/panel.js
@@ -34,19 +34,17 @@ loop.panel = (function(_, mozL10n) {
       };
     },
 
     getInitialState: function() {
       // XXX Work around props.selectedTab being undefined initially.
       // When we don't need to rely on the pref, this can move back to
       // getDefaultProps (bug 1100258).
       return {
-        selectedTab: this.props.selectedTab ||
-          (navigator.mozLoop.getLoopPref("rooms.enabled") ?
-            "rooms" : "call")
+        selectedTab: this.props.selectedTab || "rooms"
       };
     },
 
     handleSelectTab: function(event) {
       var tabName = event.target.dataset.tabName;
       this.setState({selectedTab: tabName});
     },
 
@@ -354,167 +352,16 @@ loop.panel = (function(_, mozL10n) {
                                    icon: "help"})
           )
         )
       );
     }
   });
 
   /**
-   * Call url result view.
-   */
-  var CallUrlResult = React.createClass({displayName: "CallUrlResult",
-    mixins: [sharedMixins.DocumentVisibilityMixin],
-
-    propTypes: {
-      callUrl:        React.PropTypes.string,
-      callUrlExpiry:  React.PropTypes.number,
-      notifications:  React.PropTypes.object.isRequired,
-      client:         React.PropTypes.object.isRequired
-    },
-
-    getInitialState: function() {
-      return {
-        pending: false,
-        copied: false,
-        callUrl: this.props.callUrl || "",
-        callUrlExpiry: 0
-      };
-    },
-
-    /**
-     * Provided by DocumentVisibilityMixin. Schedules retrieval of a new call
-     * URL everytime the panel is reopened.
-     */
-    onDocumentVisible: function() {
-      this._fetchCallUrl();
-    },
-
-    componentDidMount: function() {
-      // If we've already got a callURL, don't bother requesting a new one.
-      // As of this writing, only used for visual testing in the UI showcase.
-      if (this.state.callUrl.length) {
-        return;
-      }
-
-      this._fetchCallUrl();
-    },
-
-    /**
-     * Fetches a call URL.
-     */
-    _fetchCallUrl: function() {
-      this.setState({pending: true});
-      // XXX This is an empty string as a conversation identifier. Bug 1015938 implements
-      // a user-set string.
-      this.props.client.requestCallUrl("",
-                                       this._onCallUrlReceived);
-    },
-
-    _onCallUrlReceived: function(err, callUrlData) {
-      if (err) {
-        if (err.code != 401) {
-          // 401 errors are already handled in hawkRequest and show an error
-          // message about the session.
-          this.props.notifications.errorL10n("unable_retrieve_url");
-        }
-        this.setState(this.getInitialState());
-      } else {
-        try {
-          var callUrl = new window.URL(callUrlData.callUrl);
-          // XXX the current server vers does not implement the callToken field
-          // but it exists in the API. This workaround should be removed in the future
-          var token = callUrlData.callToken ||
-                      callUrl.pathname.split('/').pop();
-
-          // Now that a new URL is available, indicate it has not been shared.
-          this.linkExfiltrated = false;
-
-          this.setState({pending: false, copied: false,
-                         callUrl: callUrl.href,
-                         callUrlExpiry: callUrlData.expiresAt});
-        } catch(e) {
-          console.log(e);
-          this.props.notifications.errorL10n("unable_retrieve_url");
-          this.setState(this.getInitialState());
-        }
-      }
-    },
-
-    handleEmailButtonClick: function(event) {
-      this.handleLinkExfiltration(event);
-
-      sharedUtils.composeCallUrlEmail(this.state.callUrl);
-    },
-
-    handleCopyButtonClick: function(event) {
-      this.handleLinkExfiltration(event);
-      // XXX the mozLoop object should be passed as a prop, to ease testing and
-      //     using a fake implementation in UI components showcase.
-      navigator.mozLoop.copyString(this.state.callUrl);
-      this.setState({copied: true});
-    },
-
-    linkExfiltrated: false,
-
-    handleLinkExfiltration: function(event) {
-      // Update the count of shared URLs only once per generated URL.
-      if (!this.linkExfiltrated) {
-        this.linkExfiltrated = true;
-        try {
-          navigator.mozLoop.telemetryAdd("LOOP_CLIENT_CALL_URL_SHARED", true);
-        } catch (err) {
-          console.error("Error recording telemetry", err);
-        }
-      }
-
-      // Note URL expiration every time it is shared.
-      if (this.state.callUrlExpiry) {
-        navigator.mozLoop.noteCallUrlExpiry(this.state.callUrlExpiry);
-      }
-    },
-
-    render: function() {
-      // XXX setting elem value from a state (in the callUrl input)
-      // makes it immutable ie read only but that is fine in our case.
-      // readOnly attr will suppress a warning regarding this issue
-      // from the react lib.
-      var cx = React.addons.classSet;
-      return (
-        React.createElement("div", {className: "generate-url"}, 
-          React.createElement("header", {id: "share-link-header"}, mozL10n.get("share_link_header_text")), 
-          React.createElement("div", {className: "generate-url-stack"}, 
-            React.createElement("input", {type: "url", value: this.state.callUrl, readOnly: "true", 
-                   onCopy: this.handleLinkExfiltration, 
-                   className: cx({"generate-url-input": true,
-                                  pending: this.state.pending,
-                                  // Used in functional testing, signals that
-                                  // call url was received from loop server
-                                  callUrl: !this.state.pending})}), 
-            React.createElement("div", {className: cx({"generate-url-spinner": true,
-                                spinner: true,
-                                busy: this.state.pending})})
-          ), 
-          React.createElement(ButtonGroup, {additionalClass: "url-actions"}, 
-            React.createElement(Button, {additionalClass: "button-email", 
-                    disabled: !this.state.callUrl, 
-                    onClick: this.handleEmailButtonClick, 
-                    caption: mozL10n.get("share_button")}), 
-            React.createElement(Button, {additionalClass: "button-copy", 
-                    disabled: !this.state.callUrl, 
-                    onClick: this.handleCopyButtonClick, 
-                    caption: this.state.copied ? mozL10n.get("copied_url_button") :
-                                                 mozL10n.get("copy_url_button")})
-          )
-        )
-      );
-    }
-  });
-
-  /**
    * FxA sign in/up link component.
    */
   var AuthLink = React.createClass({displayName: "AuthLink",
     mixins: [sharedMixins.WindowCloseMixin],
 
     handleSignUpLinkClick: function() {
       navigator.mozLoop.logInToFxA();
       this.closeWindow();
@@ -815,19 +662,17 @@ loop.panel = (function(_, mozL10n) {
   });
 
   /**
    * Panel view.
    */
   var PanelView = React.createClass({displayName: "PanelView",
     propTypes: {
       notifications: React.PropTypes.object.isRequired,
-      client: React.PropTypes.object.isRequired,
       // Mostly used for UI components showcase and unit tests
-      callUrl: React.PropTypes.string,
       userProfile: React.PropTypes.object,
       // Used only for unit tests.
       showTabButtons: React.PropTypes.bool,
       selectedTab: React.PropTypes.string,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       mozLoop: React.PropTypes.object,
       roomStore:
         React.PropTypes.instanceOf(loop.store.RoomStore).isRequired
@@ -864,27 +709,23 @@ loop.panel = (function(_, mozL10n) {
           detailsButtonLabel: serviceError.error.friendlyDetailsButtonLabel,
           detailsButtonCallback: serviceError.error.friendlyDetailsButtonCallback,
         });
       } else {
         this.props.notifications.remove(this.props.notifications.get("service-error"));
       }
     },
 
-    _roomsEnabled: function() {
-      return this.props.mozLoop.getLoopPref("rooms.enabled");
-    },
-
     _onStatusChanged: function() {
       var profile = this.props.mozLoop.userProfile;
       var currUid = this.state.userProfile ? this.state.userProfile.uid : null;
       var newUid = profile ? profile.uid : null;
       if (currUid != newUid) {
         // On profile change (login, logout), switch back to the default tab.
-        this.selectTab(this._roomsEnabled() ? "rooms" : "call");
+        this.selectTab("rooms");
         this.setState({userProfile: profile});
       }
       this.updateServiceErrors();
     },
 
     _gettingStartedSeen: function() {
       this.setState({
         gettingStartedSeen: this.props.mozLoop.getLoopPref("gettingStarted.seen"),
@@ -897,44 +738,16 @@ loop.panel = (function(_, mozL10n) {
           this.selectTab(e.detail.tab);
           break;
         default:
           console.error("Invalid action", e.detail.action);
           break;
       }
     },
 
-    /**
-     * The rooms feature is hidden by default for now. Once it gets mainstream,
-     * this method can be simplified.
-     */
-    _renderRoomsOrCallTab: function() {
-      if (!this._roomsEnabled()) {
-        return (
-          React.createElement(Tab, {name: "call"}, 
-            React.createElement("div", {className: "content-area"}, 
-              React.createElement(CallUrlResult, {client: this.props.client, 
-                             notifications: this.props.notifications, 
-                             callUrl: this.props.callUrl}), 
-              React.createElement(ToSView, null)
-            )
-          )
-        );
-      }
-
-      return (
-        React.createElement(Tab, {name: "rooms"}, 
-          React.createElement(RoomList, {dispatcher: this.props.dispatcher, 
-                    store: this.props.roomStore, 
-                    userDisplayName: this._getUserDisplayName()}), 
-          React.createElement(ToSView, null)
-        )
-      );
-    },
-
     startForm: function(name, contact) {
       this.refs[name].initForm(contact);
       this.selectTab(name);
     },
 
     selectTab: function(name) {
       this.refs.tabView.setState({ selectedTab: name });
     },
@@ -981,17 +794,22 @@ loop.panel = (function(_, mozL10n) {
       }
 
       return (
         React.createElement("div", null, 
           React.createElement(NotificationListView, {notifications: this.props.notifications, 
                                 clearOnDocumentHidden: true}), 
           React.createElement(TabView, {ref: "tabView", selectedTab: this.props.selectedTab, 
             buttonsHidden: hideButtons}, 
-            this._renderRoomsOrCallTab(), 
+            React.createElement(Tab, {name: "rooms"}, 
+              React.createElement(RoomList, {dispatcher: this.props.dispatcher, 
+                        store: this.props.roomStore, 
+                        userDisplayName: this._getUserDisplayName()}), 
+              React.createElement(ToSView, null)
+            ), 
             React.createElement(Tab, {name: "contacts"}, 
               React.createElement(ContactsList, {selectTab: this.selectTab, 
                             startForm: this.startForm, 
                             notifications: this.props.notifications})
             ), 
             React.createElement(Tab, {name: "contacts_add", hidden: true}, 
               React.createElement(ContactDetailsForm, {ref: "contacts_add", mode: "add", 
                                   selectTab: this.selectTab})
@@ -1024,26 +842,24 @@ loop.panel = (function(_, mozL10n) {
   /**
    * Panel initialisation.
    */
   function init() {
     // Do the initial L10n setup, we do this before anything
     // else to ensure the L10n environment is setup correctly.
     mozL10n.initialize(navigator.mozLoop);
 
-    var client = new loop.Client();
     var notifications = new sharedModels.NotificationCollection();
     var dispatcher = new loop.Dispatcher();
     var roomStore = new loop.store.RoomStore(dispatcher, {
       mozLoop: navigator.mozLoop,
       notifications: notifications
     });
 
     React.render(React.createElement(PanelView, {
-      client: client, 
       notifications: notifications, 
       roomStore: roomStore, 
       mozLoop: navigator.mozLoop, 
       dispatcher: dispatcher}
     ), document.querySelector("#main"));
 
     document.body.setAttribute("dir", mozL10n.getDirection());
 
@@ -1052,17 +868,16 @@ loop.panel = (function(_, mozL10n) {
     evtObject.initEvent('loopPanelInitialized', true, false);
     window.dispatchEvent(evtObject);
   }
 
   return {
     init: init,
     AuthLink: AuthLink,
     AvailabilityDropdown: AvailabilityDropdown,
-    CallUrlResult: CallUrlResult,
     GettingStartedView: GettingStartedView,
     PanelView: PanelView,
     RoomEntry: RoomEntry,
     RoomList: RoomList,
     SettingsDropdown: SettingsDropdown,
     ToSView: ToSView,
     UserIdentity: UserIdentity,
   };
--- a/browser/components/loop/content/js/panel.jsx
+++ b/browser/components/loop/content/js/panel.jsx
@@ -34,19 +34,17 @@ loop.panel = (function(_, mozL10n) {
       };
     },
 
     getInitialState: function() {
       // XXX Work around props.selectedTab being undefined initially.
       // When we don't need to rely on the pref, this can move back to
       // getDefaultProps (bug 1100258).
       return {
-        selectedTab: this.props.selectedTab ||
-          (navigator.mozLoop.getLoopPref("rooms.enabled") ?
-            "rooms" : "call")
+        selectedTab: this.props.selectedTab || "rooms"
       };
     },
 
     handleSelectTab: function(event) {
       var tabName = event.target.dataset.tabName;
       this.setState({selectedTab: tabName});
     },
 
@@ -354,167 +352,16 @@ loop.panel = (function(_, mozL10n) {
                                    icon="help" />
           </ul>
         </div>
       );
     }
   });
 
   /**
-   * Call url result view.
-   */
-  var CallUrlResult = React.createClass({
-    mixins: [sharedMixins.DocumentVisibilityMixin],
-
-    propTypes: {
-      callUrl:        React.PropTypes.string,
-      callUrlExpiry:  React.PropTypes.number,
-      notifications:  React.PropTypes.object.isRequired,
-      client:         React.PropTypes.object.isRequired
-    },
-
-    getInitialState: function() {
-      return {
-        pending: false,
-        copied: false,
-        callUrl: this.props.callUrl || "",
-        callUrlExpiry: 0
-      };
-    },
-
-    /**
-     * Provided by DocumentVisibilityMixin. Schedules retrieval of a new call
-     * URL everytime the panel is reopened.
-     */
-    onDocumentVisible: function() {
-      this._fetchCallUrl();
-    },
-
-    componentDidMount: function() {
-      // If we've already got a callURL, don't bother requesting a new one.
-      // As of this writing, only used for visual testing in the UI showcase.
-      if (this.state.callUrl.length) {
-        return;
-      }
-
-      this._fetchCallUrl();
-    },
-
-    /**
-     * Fetches a call URL.
-     */
-    _fetchCallUrl: function() {
-      this.setState({pending: true});
-      // XXX This is an empty string as a conversation identifier. Bug 1015938 implements
-      // a user-set string.
-      this.props.client.requestCallUrl("",
-                                       this._onCallUrlReceived);
-    },
-
-    _onCallUrlReceived: function(err, callUrlData) {
-      if (err) {
-        if (err.code != 401) {
-          // 401 errors are already handled in hawkRequest and show an error
-          // message about the session.
-          this.props.notifications.errorL10n("unable_retrieve_url");
-        }
-        this.setState(this.getInitialState());
-      } else {
-        try {
-          var callUrl = new window.URL(callUrlData.callUrl);
-          // XXX the current server vers does not implement the callToken field
-          // but it exists in the API. This workaround should be removed in the future
-          var token = callUrlData.callToken ||
-                      callUrl.pathname.split('/').pop();
-
-          // Now that a new URL is available, indicate it has not been shared.
-          this.linkExfiltrated = false;
-
-          this.setState({pending: false, copied: false,
-                         callUrl: callUrl.href,
-                         callUrlExpiry: callUrlData.expiresAt});
-        } catch(e) {
-          console.log(e);
-          this.props.notifications.errorL10n("unable_retrieve_url");
-          this.setState(this.getInitialState());
-        }
-      }
-    },
-
-    handleEmailButtonClick: function(event) {
-      this.handleLinkExfiltration(event);
-
-      sharedUtils.composeCallUrlEmail(this.state.callUrl);
-    },
-
-    handleCopyButtonClick: function(event) {
-      this.handleLinkExfiltration(event);
-      // XXX the mozLoop object should be passed as a prop, to ease testing and
-      //     using a fake implementation in UI components showcase.
-      navigator.mozLoop.copyString(this.state.callUrl);
-      this.setState({copied: true});
-    },
-
-    linkExfiltrated: false,
-
-    handleLinkExfiltration: function(event) {
-      // Update the count of shared URLs only once per generated URL.
-      if (!this.linkExfiltrated) {
-        this.linkExfiltrated = true;
-        try {
-          navigator.mozLoop.telemetryAdd("LOOP_CLIENT_CALL_URL_SHARED", true);
-        } catch (err) {
-          console.error("Error recording telemetry", err);
-        }
-      }
-
-      // Note URL expiration every time it is shared.
-      if (this.state.callUrlExpiry) {
-        navigator.mozLoop.noteCallUrlExpiry(this.state.callUrlExpiry);
-      }
-    },
-
-    render: function() {
-      // XXX setting elem value from a state (in the callUrl input)
-      // makes it immutable ie read only but that is fine in our case.
-      // readOnly attr will suppress a warning regarding this issue
-      // from the react lib.
-      var cx = React.addons.classSet;
-      return (
-        <div className="generate-url">
-          <header id="share-link-header">{mozL10n.get("share_link_header_text")}</header>
-          <div className="generate-url-stack">
-            <input type="url" value={this.state.callUrl} readOnly="true"
-                   onCopy={this.handleLinkExfiltration}
-                   className={cx({"generate-url-input": true,
-                                  pending: this.state.pending,
-                                  // Used in functional testing, signals that
-                                  // call url was received from loop server
-                                  callUrl: !this.state.pending})} />
-            <div className={cx({"generate-url-spinner": true,
-                                spinner: true,
-                                busy: this.state.pending})} />
-          </div>
-          <ButtonGroup additionalClass="url-actions">
-            <Button additionalClass="button-email"
-                    disabled={!this.state.callUrl}
-                    onClick={this.handleEmailButtonClick}
-                    caption={mozL10n.get("share_button")} />
-            <Button additionalClass="button-copy"
-                    disabled={!this.state.callUrl}
-                    onClick={this.handleCopyButtonClick}
-                    caption={this.state.copied ? mozL10n.get("copied_url_button") :
-                                                 mozL10n.get("copy_url_button")} />
-          </ButtonGroup>
-        </div>
-      );
-    }
-  });
-
-  /**
    * FxA sign in/up link component.
    */
   var AuthLink = React.createClass({
     mixins: [sharedMixins.WindowCloseMixin],
 
     handleSignUpLinkClick: function() {
       navigator.mozLoop.logInToFxA();
       this.closeWindow();
@@ -815,19 +662,17 @@ loop.panel = (function(_, mozL10n) {
   });
 
   /**
    * Panel view.
    */
   var PanelView = React.createClass({
     propTypes: {
       notifications: React.PropTypes.object.isRequired,
-      client: React.PropTypes.object.isRequired,
       // Mostly used for UI components showcase and unit tests
-      callUrl: React.PropTypes.string,
       userProfile: React.PropTypes.object,
       // Used only for unit tests.
       showTabButtons: React.PropTypes.bool,
       selectedTab: React.PropTypes.string,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       mozLoop: React.PropTypes.object,
       roomStore:
         React.PropTypes.instanceOf(loop.store.RoomStore).isRequired
@@ -864,27 +709,23 @@ loop.panel = (function(_, mozL10n) {
           detailsButtonLabel: serviceError.error.friendlyDetailsButtonLabel,
           detailsButtonCallback: serviceError.error.friendlyDetailsButtonCallback,
         });
       } else {
         this.props.notifications.remove(this.props.notifications.get("service-error"));
       }
     },
 
-    _roomsEnabled: function() {
-      return this.props.mozLoop.getLoopPref("rooms.enabled");
-    },
-
     _onStatusChanged: function() {
       var profile = this.props.mozLoop.userProfile;
       var currUid = this.state.userProfile ? this.state.userProfile.uid : null;
       var newUid = profile ? profile.uid : null;
       if (currUid != newUid) {
         // On profile change (login, logout), switch back to the default tab.
-        this.selectTab(this._roomsEnabled() ? "rooms" : "call");
+        this.selectTab("rooms");
         this.setState({userProfile: profile});
       }
       this.updateServiceErrors();
     },
 
     _gettingStartedSeen: function() {
       this.setState({
         gettingStartedSeen: this.props.mozLoop.getLoopPref("gettingStarted.seen"),
@@ -897,44 +738,16 @@ loop.panel = (function(_, mozL10n) {
           this.selectTab(e.detail.tab);
           break;
         default:
           console.error("Invalid action", e.detail.action);
           break;
       }
     },
 
-    /**
-     * The rooms feature is hidden by default for now. Once it gets mainstream,
-     * this method can be simplified.
-     */
-    _renderRoomsOrCallTab: function() {
-      if (!this._roomsEnabled()) {
-        return (
-          <Tab name="call">
-            <div className="content-area">
-              <CallUrlResult client={this.props.client}
-                             notifications={this.props.notifications}
-                             callUrl={this.props.callUrl} />
-              <ToSView />
-            </div>
-          </Tab>
-        );
-      }
-
-      return (
-        <Tab name="rooms">
-          <RoomList dispatcher={this.props.dispatcher}
-                    store={this.props.roomStore}
-                    userDisplayName={this._getUserDisplayName()}/>
-          <ToSView />
-        </Tab>
-      );
-    },
-
     startForm: function(name, contact) {
       this.refs[name].initForm(contact);
       this.selectTab(name);
     },
 
     selectTab: function(name) {
       this.refs.tabView.setState({ selectedTab: name });
     },
@@ -981,17 +794,22 @@ loop.panel = (function(_, mozL10n) {
       }
 
       return (
         <div>
           <NotificationListView notifications={this.props.notifications}
                                 clearOnDocumentHidden={true} />
           <TabView ref="tabView" selectedTab={this.props.selectedTab}
             buttonsHidden={hideButtons}>
-            {this._renderRoomsOrCallTab()}
+            <Tab name="rooms">
+              <RoomList dispatcher={this.props.dispatcher}
+                        store={this.props.roomStore}
+                        userDisplayName={this._getUserDisplayName()}/>
+              <ToSView />
+            </Tab>
             <Tab name="contacts">
               <ContactsList selectTab={this.selectTab}
                             startForm={this.startForm}
                             notifications={this.props.notifications} />
             </Tab>
             <Tab name="contacts_add" hidden={true}>
               <ContactDetailsForm ref="contacts_add" mode="add"
                                   selectTab={this.selectTab} />
@@ -1024,26 +842,24 @@ loop.panel = (function(_, mozL10n) {
   /**
    * Panel initialisation.
    */
   function init() {
     // Do the initial L10n setup, we do this before anything
     // else to ensure the L10n environment is setup correctly.
     mozL10n.initialize(navigator.mozLoop);
 
-    var client = new loop.Client();
     var notifications = new sharedModels.NotificationCollection();
     var dispatcher = new loop.Dispatcher();
     var roomStore = new loop.store.RoomStore(dispatcher, {
       mozLoop: navigator.mozLoop,
       notifications: notifications
     });
 
     React.render(<PanelView
-      client={client}
       notifications={notifications}
       roomStore={roomStore}
       mozLoop={navigator.mozLoop}
       dispatcher={dispatcher}
     />, document.querySelector("#main"));
 
     document.body.setAttribute("dir", mozL10n.getDirection());
 
@@ -1052,17 +868,16 @@ loop.panel = (function(_, mozL10n) {
     evtObject.initEvent('loopPanelInitialized', true, false);
     window.dispatchEvent(evtObject);
   }
 
   return {
     init: init,
     AuthLink: AuthLink,
     AvailabilityDropdown: AvailabilityDropdown,
-    CallUrlResult: CallUrlResult,
     GettingStartedView: GettingStartedView,
     PanelView: PanelView,
     RoomEntry: RoomEntry,
     RoomList: RoomList,
     SettingsDropdown: SettingsDropdown,
     ToSView: ToSView,
     UserIdentity: UserIdentity,
   };
--- a/browser/components/loop/content/panel.html
+++ b/browser/components/loop/content/panel.html
@@ -27,13 +27,12 @@
     <script type="text/javascript" src="loop/shared/js/validate.js"></script>
     <script type="text/javascript" src="loop/shared/js/actions.js"></script>
     <script type="text/javascript" src="loop/shared/js/dispatcher.js"></script>
     <script type="text/javascript" src="loop/shared/js/store.js"></script>
     <script type="text/javascript" src="loop/shared/js/roomStore.js"></script>
     <script type="text/javascript" src="loop/shared/js/roomStates.js"></script>
     <script type="text/javascript" src="loop/shared/js/fxOSActiveRoomStore.js"></script>
     <script type="text/javascript" src="loop/shared/js/activeRoomStore.js"></script>
-    <script type="text/javascript" src="loop/js/client.js"></script>
     <script type="text/javascript;version=1.8" src="loop/js/contacts.js"></script>
     <script type="text/javascript" src="loop/js/panel.js"></script>
  </body>
 </html>
--- a/browser/components/loop/test/desktop-local/client_test.js
+++ b/browser/components/loop/test/desktop-local/client_test.js
@@ -27,17 +27,16 @@ describe("loop.Client", function() {
     sandbox = sinon.sandbox.create();
     callback = sinon.spy();
     fakeToken = "fakeTokenText";
     mozLoop = {
       getLoopPref: sandbox.stub()
         .returns(null)
         .withArgs("hawk-session-token")
         .returns(fakeToken),
-      noteCallUrlExpiry: sinon.spy(),
       hawkRequest: sinon.stub(),
       LOOP_SESSION_TYPE: {
         GUEST: 1,
         FXA: 2
       },
       userProfile: null,
       telemetryAdd: sinon.spy()
     };
@@ -84,150 +83,16 @@ describe("loop.Client", function() {
 
         sinon.assert.calledOnce(callback);
         sinon.assert.calledWithMatch(callback, sinon.match(function(err) {
           return err.code == 400 && "invalid token" == err.message;
         }));
       });
     });
 
-    describe("#requestCallUrl", function() {
-      it("should post to /call-url/", function() {
-        client.requestCallUrl("foo", callback);
-
-        sinon.assert.calledOnce(hawkRequestStub);
-        sinon.assert.calledWithExactly(hawkRequestStub, sinon.match.number,
-          "/call-url/", "POST", {callerId: "foo"}, sinon.match.func);
-      });
-
-      it("should send a sessionType of LOOP_SESSION_TYPE.GUEST when " +
-         "mozLoop.userProfile returns null", function() {
-        mozLoop.userProfile = null;
-
-        client.requestCallUrl("foo", callback);
-
-        sinon.assert.calledOnce(hawkRequestStub);
-        sinon.assert.calledWithExactly(hawkRequestStub,
-          mozLoop.LOOP_SESSION_TYPE.GUEST, "/call-url/", "POST",
-          {callerId: "foo"}, sinon.match.func);
-      });
-
-      it("should send a sessionType of LOOP_SESSION_TYPE.FXA when " +
-         "mozLoop.userProfile returns an object", function () {
-        mozLoop.userProfile = {};
-
-        client.requestCallUrl("foo", callback);
-
-        sinon.assert.calledOnce(hawkRequestStub);
-        sinon.assert.calledWithExactly(hawkRequestStub,
-          mozLoop.LOOP_SESSION_TYPE.FXA, "/call-url/", "POST",
-          {callerId: "foo"}, sinon.match.func);
-      });
-
-      it("should call the callback with the url when the request succeeds",
-        function() {
-          var callUrlData = {
-            "callUrl": "fakeCallUrl",
-            "expiresAt": 60
-          };
-
-          // Sets up the hawkRequest stub to trigger the callback with no error
-          // and the url.
-          hawkRequestStub.callsArgWith(4, null, JSON.stringify(callUrlData));
-
-          client.requestCallUrl("foo", callback);
-
-          sinon.assert.calledWithExactly(callback, null, callUrlData);
-        });
-
-      it("should not update call url expiry when the request succeeds",
-        function() {
-          var callUrlData = {
-            "callUrl": "fakeCallUrl",
-            "expiresAt": 6000
-          };
-
-          // Sets up the hawkRequest stub to trigger the callback with no error
-          // and the url.
-          hawkRequestStub.callsArgWith(4, null, JSON.stringify(callUrlData));
-
-          client.requestCallUrl("foo", callback);
-
-          sinon.assert.notCalled(mozLoop.noteCallUrlExpiry);
-        });
-
-      it("should call mozLoop.telemetryAdd when the request succeeds",
-        function(done) {
-          var callUrlData = {
-            "callUrl": "fakeCallUrl",
-            "expiresAt": 60
-          };
-
-          // Sets up the hawkRequest stub to trigger the callback with no error
-          // and the url.
-          hawkRequestStub.callsArgWith(4, null,
-            JSON.stringify(callUrlData));
-
-          client.requestCallUrl("foo", function(err) {
-            expect(err).to.be.null;
-
-            sinon.assert.calledOnce(mozLoop.telemetryAdd);
-            sinon.assert.calledWith(mozLoop.telemetryAdd,
-                                    "LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS",
-                                    true);
-
-            done();
-          });
-        });
-
-      it("should send an error when the request fails", function() {
-        // Sets up the hawkRequest stub to trigger the callback with
-        // an error
-        hawkRequestStub.callsArgWith(4, fakeErrorRes);
-
-        client.requestCallUrl("foo", callback);
-
-        sinon.assert.calledOnce(callback);
-        sinon.assert.calledWithMatch(callback, sinon.match(function(err) {
-          return err.code == 400 && "invalid token" == err.message;
-        }));
-      });
-
-      it("should send an error if the data is not valid", function() {
-        // Sets up the hawkRequest stub to trigger the callback with
-        // an error
-        hawkRequestStub.callsArgWith(4, null, "{}");
-
-        client.requestCallUrl("foo", callback);
-
-        sinon.assert.calledOnce(callback);
-        sinon.assert.calledWithMatch(callback, sinon.match(function(err) {
-          return /Invalid data received/.test(err.message);
-        }));
-      });
-
-      it("should call mozLoop.telemetryAdd when the request fails",
-        function(done) {
-          // Sets up the hawkRequest stub to trigger the callback with
-          // an error
-          hawkRequestStub.callsArgWith(4, fakeErrorRes);
-
-          client.requestCallUrl("foo", function(err) {
-            expect(err).not.to.be.null;
-
-            sinon.assert.calledOnce(mozLoop.telemetryAdd);
-            sinon.assert.calledWith(mozLoop.telemetryAdd,
-                                    "LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS",
-                                    false);
-
-            done();
-          });
-        });
-    });
-
     describe("#setupOutgoingCall", function() {
       var calleeIds, callType;
 
       beforeEach(function() {
         calleeIds = [
           "fakeemail", "fake phone"
         ];
         callType = "audio";
--- a/browser/components/loop/test/desktop-local/panel_test.js
+++ b/browser/components/loop/test/desktop-local/panel_test.js
@@ -43,20 +43,16 @@ describe("loop.panel", function() {
       get locale() {
         return "en-US";
       },
       setLoopPref: sandbox.stub(),
       getLoopPref: sandbox.stub().returns("unseen"),
       getPluralForm: function() {
         return "fakeText";
       },
-      copyString: sandbox.stub(),
-      noteCallUrlExpiry: sinon.spy(),
-      composeEmail: sinon.spy(),
-      telemetryAdd: sinon.spy(),
       contacts: {
         getAll: function(callback) {
           callback(null, []);
         },
         on: sandbox.stub()
       },
       rooms: {
         getAll: function(version, callback) {
@@ -181,79 +177,43 @@ describe("loop.panel", function() {
           dispatcher: dispatcher,
           roomStore: roomStore
         }));
     }
 
     describe('TabView', function() {
       var view, callTab, roomsTab, contactsTab;
 
-      describe("loop.rooms.enabled on", function() {
-        beforeEach(function() {
-          navigator.mozLoop.getLoopPref = function(pref) {
-            if (pref === "rooms.enabled" ||
-                pref === "gettingStarted.seen") {
-              return true;
-            }
-          };
-
-          view = createTestPanelView();
-
-          [roomsTab, contactsTab] =
-            TestUtils.scryRenderedDOMComponentsWithClass(view, "tab");
-        });
+      beforeEach(function() {
+        navigator.mozLoop.getLoopPref = function(pref) {
+          if (pref === "gettingStarted.seen") {
+            return true;
+          }
+        };
 
-        it("should select contacts tab when clicking tab button", function() {
-          TestUtils.Simulate.click(
-            view.getDOMNode().querySelector("li[data-tab-name=\"contacts\"]"));
-
-          expect(contactsTab.getDOMNode().classList.contains("selected"))
-            .to.be.true;
-        });
+        view = createTestPanelView();
 
-        it("should select rooms tab when clicking tab button", function() {
-          TestUtils.Simulate.click(
-            view.getDOMNode().querySelector("li[data-tab-name=\"rooms\"]"));
-
-          expect(roomsTab.getDOMNode().classList.contains("selected"))
-            .to.be.true;
-        });
+        [roomsTab, contactsTab] =
+          TestUtils.scryRenderedDOMComponentsWithClass(view, "tab");
       });
 
-      describe("loop.rooms.enabled off", function() {
-        beforeEach(function() {
-          navigator.mozLoop.getLoopPref = function(pref) {
-            if (pref === "rooms.enabled") {
-              return false;
-            } else if (pref === "gettingStarted.seen") {
-              return true;
-            }
-          };
-
-          view = createTestPanelView();
-
-          [callTab, contactsTab] =
-            TestUtils.scryRenderedDOMComponentsWithClass(view, "tab");
-        });
+      it("should select contacts tab when clicking tab button", function() {
+        TestUtils.Simulate.click(
+          view.getDOMNode().querySelector("li[data-tab-name=\"contacts\"]"));
 
-        it("should select contacts tab when clicking tab button", function() {
-          TestUtils.Simulate.click(
-            view.getDOMNode().querySelector("li[data-tab-name=\"contacts\"]"));
-
-          expect(contactsTab.getDOMNode().classList.contains("selected"))
-            .to.be.true;
-        });
+        expect(contactsTab.getDOMNode().classList.contains("selected"))
+          .to.be.true;
+      });
 
-        it("should select call tab when clicking tab button", function() {
-          TestUtils.Simulate.click(
-            view.getDOMNode().querySelector("li[data-tab-name=\"call\"]"));
+      it("should select rooms tab when clicking tab button", function() {
+        TestUtils.Simulate.click(
+          view.getDOMNode().querySelector("li[data-tab-name=\"rooms\"]"));
 
-          expect(callTab.getDOMNode().classList.contains("selected"))
-            .to.be.true;
-        });
+        expect(roomsTab.getDOMNode().classList.contains("selected"))
+          .to.be.true;
       });
     });
 
     describe("AuthLink", function() {
 
       beforeEach(function() {
         navigator.mozLoop.calls = { clearCallInProgress: function() {} };
       });
@@ -463,294 +423,16 @@ describe("loop.panel", function() {
           TestUtils.findRenderedComponentWithType(view, loop.panel.GettingStartedView);
           sinon.assert.fail("Should not find the GettingStartedView if it has been seen");
         } catch (ex) {}
       });
 
     });
   });
 
-  describe("loop.panel.CallUrlResult", function() {
-    var fakeClient, callUrlData, view;
-
-    beforeEach(function() {
-      callUrlData = {
-        callUrl: "http://call.invalid/fakeToken",
-        expiresAt: 1000
-      };
-
-      fakeClient = {
-        requestCallUrl: function(_, cb) {
-          cb(null, callUrlData);
-        }
-      };
-
-      sandbox.stub(notifications, "reset");
-      view = TestUtils.renderIntoDocument(
-        React.createElement(loop.panel.CallUrlResult, {
-          notifications: notifications,
-          client: fakeClient
-        }));
-    });
-
-    describe("Rendering the component should generate a call URL", function() {
-
-      beforeEach(function() {
-        document.mozL10n.initialize({
-          getStrings: function(key) {
-            var text;
-
-            if (key === "share_email_subject4")
-              text = "email-subject";
-            else if (key === "share_email_body4")
-              text = "{{callUrl}}";
-
-            return JSON.stringify({textContent: text});
-          }
-        });
-      });
-
-      it("should make a request to requestCallUrl", function() {
-        sandbox.stub(fakeClient, "requestCallUrl");
-        var view = TestUtils.renderIntoDocument(
-          React.createElement(loop.panel.CallUrlResult, {
-            notifications: notifications,
-            client: fakeClient
-          }));
-
-        sinon.assert.calledOnce(view.props.client.requestCallUrl);
-        sinon.assert.calledWithExactly(view.props.client.requestCallUrl,
-                                       sinon.match.string, sinon.match.func);
-      });
-
-      it("should set the call url form in a pending state", function() {
-        // Cancel requestCallUrl effect to keep the state pending
-        fakeClient.requestCallUrl = sandbox.stub();
-        var view = TestUtils.renderIntoDocument(
-          React.createElement(loop.panel.CallUrlResult, {
-          notifications: notifications,
-          client: fakeClient
-        }));
-
-        expect(view.state.pending).eql(true);
-      });
-
-      it("should update state with the call url received", function() {
-        expect(view.state.pending).eql(false);
-        expect(view.state.callUrl).eql(callUrlData.callUrl);
-      });
-
-      it("should clear the pending state when a response is received",
-        function() {
-          expect(view.state.pending).eql(false);
-        });
-
-      it("should update CallUrlResult with the call url", function() {
-        var urlField = view.getDOMNode().querySelector("input[type='url']");
-
-        expect(urlField.value).eql(callUrlData.callUrl);
-      });
-
-      it("should have 0 pending notifications", function() {
-        expect(view.props.notifications.length).eql(0);
-      });
-
-      it("should display a share button for email", function() {
-        fakeClient.requestCallUrl = sandbox.stub();
-        var composeCallUrlEmail = sandbox.stub(sharedUtils, "composeCallUrlEmail");
-        var view = TestUtils.renderIntoDocument(
-          React.createElement(loop.panel.CallUrlResult, {
-          notifications: notifications,
-          client: fakeClient
-        }));
-        view.setState({pending: false, callUrl: "http://example.com"});
-
-        TestUtils.findRenderedDOMComponentWithClass(view, "button-email");
-        TestUtils.Simulate.click(view.getDOMNode().querySelector(".button-email"));
-
-        sinon.assert.calledOnce(composeCallUrlEmail);
-        sinon.assert.calledWithExactly(composeCallUrlEmail, "http://example.com");
-      });
-
-      it("should feature a copy button capable of copying the call url when clicked", function() {
-        fakeClient.requestCallUrl = sandbox.stub();
-        var view = TestUtils.renderIntoDocument(
-          React.createElement(loop.panel.CallUrlResult, {
-          notifications: notifications,
-          client: fakeClient
-        }));
-        view.setState({
-          pending: false,
-          copied: false,
-          callUrl: "http://example.com",
-          callUrlExpiry: 6000
-        });
-
-        TestUtils.Simulate.click(view.getDOMNode().querySelector(".button-copy"));
-
-        sinon.assert.calledOnce(navigator.mozLoop.copyString);
-        sinon.assert.calledWithExactly(navigator.mozLoop.copyString,
-          view.state.callUrl);
-      });
-
-      it("should note the call url expiry when the url is copied via button",
-        function() {
-          var view = TestUtils.renderIntoDocument(
-            React.createElement(loop.panel.CallUrlResult, {
-            notifications: notifications,
-            client: fakeClient
-          }));
-          view.setState({
-            pending: false,
-            copied: false,
-            callUrl: "http://example.com",
-            callUrlExpiry: 6000
-          });
-
-          TestUtils.Simulate.click(view.getDOMNode().querySelector(".button-copy"));
-
-          sinon.assert.calledOnce(navigator.mozLoop.noteCallUrlExpiry);
-          sinon.assert.calledWithExactly(navigator.mozLoop.noteCallUrlExpiry,
-            6000);
-        });
-
-      it("should call mozLoop.telemetryAdd when the url is copied via button",
-        function() {
-          var view = TestUtils.renderIntoDocument(
-            React.createElement(loop.panel.CallUrlResult, {
-            notifications: notifications,
-            client: fakeClient
-          }));
-          view.setState({
-            pending: false,
-            copied: false,
-            callUrl: "http://example.com",
-            callUrlExpiry: 6000
-          });
-
-          // Multiple clicks should result in the URL being counted only once.
-          TestUtils.Simulate.click(view.getDOMNode().querySelector(".button-copy"));
-          TestUtils.Simulate.click(view.getDOMNode().querySelector(".button-copy"));
-
-          sinon.assert.calledOnce(navigator.mozLoop.telemetryAdd);
-          sinon.assert.calledWith(navigator.mozLoop.telemetryAdd,
-                                  "LOOP_CLIENT_CALL_URL_SHARED",
-                                  true);
-        });
-
-      it("should note the call url expiry when the url is emailed",
-        function() {
-          var view = TestUtils.renderIntoDocument(
-            React.createElement(loop.panel.CallUrlResult, {
-            notifications: notifications,
-            client: fakeClient
-          }));
-          view.setState({
-            pending: false,
-            copied: false,
-            callUrl: "http://example.com",
-            callUrlExpiry: 6000
-          });
-
-          TestUtils.Simulate.click(view.getDOMNode().querySelector(".button-email"));
-
-          sinon.assert.calledOnce(navigator.mozLoop.noteCallUrlExpiry);
-          sinon.assert.calledWithExactly(navigator.mozLoop.noteCallUrlExpiry,
-            6000);
-        });
-
-      it("should call mozLoop.telemetryAdd when the url is emailed",
-        function() {
-          var view = TestUtils.renderIntoDocument(
-            React.createElement(loop.panel.CallUrlResult, {
-            notifications: notifications,
-            client: fakeClient
-          }));
-          view.setState({
-            pending: false,
-            copied: false,
-            callUrl: "http://example.com",
-            callUrlExpiry: 6000
-          });
-
-          // Multiple clicks should result in the URL being counted only once.
-          TestUtils.Simulate.click(view.getDOMNode().querySelector(".button-email"));
-          TestUtils.Simulate.click(view.getDOMNode().querySelector(".button-email"));
-
-          sinon.assert.calledOnce(navigator.mozLoop.telemetryAdd);
-          sinon.assert.calledWith(navigator.mozLoop.telemetryAdd,
-                                  "LOOP_CLIENT_CALL_URL_SHARED",
-                                  true);
-        });
-
-      it("should note the call url expiry when the url is copied manually",
-        function() {
-          var view = TestUtils.renderIntoDocument(
-            React.createElement(loop.panel.CallUrlResult, {
-            notifications: notifications,
-            client: fakeClient
-          }));
-          view.setState({
-            pending: false,
-            copied: false,
-            callUrl: "http://example.com",
-            callUrlExpiry: 6000
-          });
-
-          var urlField = view.getDOMNode().querySelector("input[type='url']");
-          TestUtils.Simulate.copy(urlField);
-
-          sinon.assert.calledOnce(navigator.mozLoop.noteCallUrlExpiry);
-          sinon.assert.calledWithExactly(navigator.mozLoop.noteCallUrlExpiry,
-            6000);
-        });
-
-      it("should call mozLoop.telemetryAdd when the url is copied manually",
-        function() {
-          var view = TestUtils.renderIntoDocument(
-            React.createElement(loop.panel.CallUrlResult, {
-            notifications: notifications,
-            client: fakeClient
-          }));
-          view.setState({
-            pending: false,
-            copied: false,
-            callUrl: "http://example.com",
-            callUrlExpiry: 6000
-          });
-
-          // Multiple copies should result in the URL being counted only once.
-          var urlField = view.getDOMNode().querySelector("input[type='url']");
-          TestUtils.Simulate.copy(urlField);
-          TestUtils.Simulate.copy(urlField);
-
-          sinon.assert.calledOnce(navigator.mozLoop.telemetryAdd);
-          sinon.assert.calledWith(navigator.mozLoop.telemetryAdd,
-                                  "LOOP_CLIENT_CALL_URL_SHARED",
-                                  true);
-        });
-
-      it("should notify the user when the operation failed", function() {
-        fakeClient.requestCallUrl = function(_, cb) {
-          cb("fake error");
-        };
-        sandbox.stub(notifications, "errorL10n");
-        TestUtils.renderIntoDocument(
-          React.createElement(loop.panel.CallUrlResult, {
-            notifications: notifications,
-            client: fakeClient
-          }));
-
-        sinon.assert.calledOnce(notifications.errorL10n);
-        sinon.assert.calledWithExactly(notifications.errorL10n,
-                                       "unable_retrieve_url");
-      });
-    });
-  });
-
   describe("loop.panel.RoomEntry", function() {
     var dispatcher, roomData;
 
     beforeEach(function() {
       dispatcher = new loop.Dispatcher();
       roomData = {
         roomToken: "QzBbvGmIZWU",
         roomUrl: "http://sample/QzBbvGmIZWU",
deleted file mode 100644
--- a/browser/components/loop/test/xpcshell/test_loopservice_expiry.js
+++ /dev/null
@@ -1,25 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-function expiryTimePref() {
-  return Services.prefs.getIntPref("loop.urlsExpiryTimeSeconds");
-}
-
-function run_test()
-{
-  setupFakeLoopServer();
-
-  Services.prefs.setIntPref("loop.urlsExpiryTimeSeconds", 0);
-
-  MozLoopService.noteCallUrlExpiry(1000);
-
-  Assert.equal(expiryTimePref(), 1000, "should be equal to set value");
-
-  MozLoopService.noteCallUrlExpiry(900);
-
-  Assert.equal(expiryTimePref(), 1000, "should remain the same value");
-
-  MozLoopService.noteCallUrlExpiry(1500);
-
-  Assert.equal(expiryTimePref(), 1500, "should be the increased value");
-}
--- a/browser/components/loop/test/xpcshell/test_loopservice_locales.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_locales.js
@@ -11,18 +11,18 @@ function test_locale() {
 }
 
 function test_getStrings() {
   // Try an invalid string
   Assert.equal(MozLoopService.getStrings("invalid_not_found_string"), "");
 
   // XXX This depends on the L10n values, which I'd prefer not to do, but is the
   // simplest way for now.
-  Assert.equal(MozLoopService.getStrings("share_link_header_text"),
-               '{"textContent":"Share this link to invite someone to talk:"}');
+  Assert.equal(MozLoopService.getStrings("display_name_guest"),
+               '{"textContent":"Guest"}');
 }
 
 function run_test()
 {
   setupFakeLoopServer();
 
   test_locale();
   test_getStrings();
--- a/browser/components/loop/test/xpcshell/xpcshell.ini
+++ b/browser/components/loop/test/xpcshell/xpcshell.ini
@@ -4,17 +4,16 @@ tail =
 firefox-appdir = browser
 skip-if = toolkit == 'gonk'
 
 [test_loopapi_hawk_request.js]
 [test_looppush_initialize.js]
 [test_looprooms.js]
 [test_loopservice_directcall.js]
 [test_loopservice_dnd.js]
-[test_loopservice_expiry.js]
 [test_loopservice_hawk_errors.js]
 [test_loopservice_hawk_request.js]
 [test_loopservice_loop_prefs.js]
 [test_loopservice_initialize.js]
 [test_loopservice_locales.js]
 [test_loopservice_notification.js]
 [test_loopservice_registration.js]
 [test_loopservice_registration_retry.js]
--- a/browser/components/loop/ui/fake-mozLoop.js
+++ b/browser/components/loop/ui/fake-mozLoop.js
@@ -43,24 +43,20 @@ var fakeRooms = [
   }
 ];
 
 /**
  * Faking the mozLoop object which doesn't exist in regular web pages.
  * @type {Object}
  */
 navigator.mozLoop = {
-  roomsEnabled: false,
   ensureRegistered: function() {},
   getAudioBlob: function(){},
   getLoopPref: function(pref) {
     switch(pref) {
-      // Ensure UI for rooms is displayed in the showcase.
-      case "rooms.enabled":
-        return this.roomsEnabled;
       // Ensure we skip FTE completely.
       case "gettingStarted.seen":
         return true;
     }
   },
   setLoopPref: function(){},
   releaseCallData: function() {},
   copyString: function() {},
--- a/browser/components/loop/ui/ui-showcase.js
+++ b/browser/components/loop/ui/ui-showcase.js
@@ -78,27 +78,25 @@
     client: {},
     mozLoop: navigator.mozLoop,
     sdkDriver: {}
   });
 
   // Local mocks
 
   var mockMozLoopRooms = _.extend({}, navigator.mozLoop);
-  mockMozLoopRooms.roomsEnabled = true;
 
   var mockContact = {
     name: ["Mr Smith"],
     email: [{
       value: "smith@invalid.com"
     }]
   };
 
   var mockClient = {
-    requestCallUrl: noop,
     requestCallUrlInfo: noop
   };
 
   var mockSDK = {};
 
   var mockConversationModel = new loop.shared.models.ConversationModel({
     callerId: "Mrs Jones",
     urlCreationDate: (new Date() / 1000).toString()
@@ -215,65 +213,45 @@
   var App = React.createClass({displayName: "App",
     render: function() {
       return (
         React.createElement(ShowCase, null, 
           React.createElement(Section, {name: "PanelView"}, 
             React.createElement("p", {className: "note"}, 
               React.createElement("strong", null, "Note:"), " 332px wide."
             ), 
-            React.createElement(Example, {summary: "Call URL retrieved", dashed: "true", style: {width: "332px"}}, 
-              React.createElement(PanelView, {client: mockClient, notifications: notifications, 
-                         callUrl: "http://invalid.example.url/", 
-                         mozLoop: navigator.mozLoop, 
-                         dispatcher: dispatcher, 
-                         roomStore: roomStore})
-            ), 
-            React.createElement(Example, {summary: "Call URL retrieved - authenticated", dashed: "true", style: {width: "332px"}}, 
-              React.createElement(PanelView, {client: mockClient, notifications: notifications, 
-                         callUrl: "http://invalid.example.url/", 
-                         userProfile: {email: "test@example.com"}, 
-                         mozLoop: navigator.mozLoop, 
-                         dispatcher: dispatcher, 
-                         roomStore: roomStore})
-            ), 
-            React.createElement(Example, {summary: "Pending call url retrieval", dashed: "true", style: {width: "332px"}}, 
-              React.createElement(PanelView, {client: mockClient, notifications: notifications, 
-                         mozLoop: navigator.mozLoop, 
-                         dispatcher: dispatcher, 
-                         roomStore: roomStore})
-            ), 
-            React.createElement(Example, {summary: "Pending call url retrieval - authenticated", dashed: "true", style: {width: "332px"}}, 
+            React.createElement(Example, {summary: "Room list tab", dashed: "true", style: {width: "332px"}}, 
               React.createElement(PanelView, {client: mockClient, notifications: notifications, 
                          userProfile: {email: "test@example.com"}, 
-                         mozLoop: navigator.mozLoop, 
+                         mozLoop: mockMozLoopRooms, 
                          dispatcher: dispatcher, 
-                         roomStore: roomStore})
+                         roomStore: roomStore, 
+                         selectedTab: "rooms"})
+            ), 
+            React.createElement(Example, {summary: "Contact list tab", dashed: "true", style: {width: "332px"}}, 
+              React.createElement(PanelView, {client: mockClient, notifications: notifications, 
+                         userProfile: {email: "test@example.com"}, 
+                         mozLoop: mockMozLoopRooms, 
+                         dispatcher: dispatcher, 
+                         roomStore: roomStore, 
+                         selectedTab: "contacts"})
             ), 
             React.createElement(Example, {summary: "Error Notification", dashed: "true", style: {width: "332px"}}, 
               React.createElement(PanelView, {client: mockClient, notifications: errNotifications, 
                          mozLoop: navigator.mozLoop, 
                          dispatcher: dispatcher, 
                          roomStore: roomStore})
             ), 
             React.createElement(Example, {summary: "Error Notification - authenticated", dashed: "true", style: {width: "332px"}}, 
               React.createElement(PanelView, {client: mockClient, notifications: errNotifications, 
                          userProfile: {email: "test@example.com"}, 
                          mozLoop: navigator.mozLoop, 
                          dispatcher: dispatcher, 
                          roomStore: roomStore})
             ), 
-            React.createElement(Example, {summary: "Room list tab", dashed: "true", style: {width: "332px"}}, 
-              React.createElement(PanelView, {client: mockClient, notifications: notifications, 
-                         userProfile: {email: "test@example.com"}, 
-                         mozLoop: mockMozLoopRooms, 
-                         dispatcher: dispatcher, 
-                         roomStore: roomStore, 
-                         selectedTab: "rooms"})
-            ), 
             React.createElement(Example, {summary: "Contact import success", dashed: "true", style: {width: "332px"}}, 
               React.createElement(PanelView, {notifications: new loop.shared.models.NotificationCollection([{level: "success", message: "Import success"}]), 
                          userProfile: {email: "test@example.com"}, 
                          mozLoop: mockMozLoopRooms, 
                          dispatcher: dispatcher, 
                          roomStore: roomStore, 
                          selectedTab: "contacts"})
             ), 
--- a/browser/components/loop/ui/ui-showcase.jsx
+++ b/browser/components/loop/ui/ui-showcase.jsx
@@ -78,27 +78,25 @@
     client: {},
     mozLoop: navigator.mozLoop,
     sdkDriver: {}
   });
 
   // Local mocks
 
   var mockMozLoopRooms = _.extend({}, navigator.mozLoop);
-  mockMozLoopRooms.roomsEnabled = true;
 
   var mockContact = {
     name: ["Mr Smith"],
     email: [{
       value: "smith@invalid.com"
     }]
   };
 
   var mockClient = {
-    requestCallUrl: noop,
     requestCallUrlInfo: noop
   };
 
   var mockSDK = {};
 
   var mockConversationModel = new loop.shared.models.ConversationModel({
     callerId: "Mrs Jones",
     urlCreationDate: (new Date() / 1000).toString()
@@ -215,65 +213,45 @@
   var App = React.createClass({
     render: function() {
       return (
         <ShowCase>
           <Section name="PanelView">
             <p className="note">
               <strong>Note:</strong> 332px wide.
             </p>
-            <Example summary="Call URL retrieved" dashed="true" style={{width: "332px"}}>
-              <PanelView client={mockClient} notifications={notifications}
-                         callUrl="http://invalid.example.url/"
-                         mozLoop={navigator.mozLoop}
-                         dispatcher={dispatcher}
-                         roomStore={roomStore} />
-            </Example>
-            <Example summary="Call URL retrieved - authenticated" dashed="true" style={{width: "332px"}}>
-              <PanelView client={mockClient} notifications={notifications}
-                         callUrl="http://invalid.example.url/"
-                         userProfile={{email: "test@example.com"}}
-                         mozLoop={navigator.mozLoop}
-                         dispatcher={dispatcher}
-                         roomStore={roomStore} />
-            </Example>
-            <Example summary="Pending call url retrieval" dashed="true" style={{width: "332px"}}>
-              <PanelView client={mockClient} notifications={notifications}
-                         mozLoop={navigator.mozLoop}
-                         dispatcher={dispatcher}
-                         roomStore={roomStore} />
-            </Example>
-            <Example summary="Pending call url retrieval - authenticated" dashed="true" style={{width: "332px"}}>
+            <Example summary="Room list tab" dashed="true" style={{width: "332px"}}>
               <PanelView client={mockClient} notifications={notifications}
                          userProfile={{email: "test@example.com"}}
-                         mozLoop={navigator.mozLoop}
+                         mozLoop={mockMozLoopRooms}
                          dispatcher={dispatcher}
-                         roomStore={roomStore} />
+                         roomStore={roomStore}
+                         selectedTab="rooms" />
+            </Example>
+            <Example summary="Contact list tab" dashed="true" style={{width: "332px"}}>
+              <PanelView client={mockClient} notifications={notifications}
+                         userProfile={{email: "test@example.com"}}
+                         mozLoop={mockMozLoopRooms}
+                         dispatcher={dispatcher}
+                         roomStore={roomStore}
+                         selectedTab="contacts" />
             </Example>
             <Example summary="Error Notification" dashed="true" style={{width: "332px"}}>
               <PanelView client={mockClient} notifications={errNotifications}
                          mozLoop={navigator.mozLoop}
                          dispatcher={dispatcher}
                          roomStore={roomStore} />
             </Example>
             <Example summary="Error Notification - authenticated" dashed="true" style={{width: "332px"}}>
               <PanelView client={mockClient} notifications={errNotifications}
                          userProfile={{email: "test@example.com"}}
                          mozLoop={navigator.mozLoop}
                          dispatcher={dispatcher}
                          roomStore={roomStore} />
             </Example>
-            <Example summary="Room list tab" dashed="true" style={{width: "332px"}}>
-              <PanelView client={mockClient} notifications={notifications}
-                         userProfile={{email: "test@example.com"}}
-                         mozLoop={mockMozLoopRooms}
-                         dispatcher={dispatcher}
-                         roomStore={roomStore}
-                         selectedTab="rooms" />
-            </Example>
             <Example summary="Contact import success" dashed="true" style={{width: "332px"}}>
               <PanelView notifications={new loop.shared.models.NotificationCollection([{level: "success", message: "Import success"}])}
                          userProfile={{email: "test@example.com"}}
                          mozLoop={mockMozLoopRooms}
                          dispatcher={dispatcher}
                          roomStore={roomStore}
                          selectedTab="contacts" />
             </Example>
--- a/browser/locales/en-US/chrome/browser/loop/loop.properties
+++ b/browser/locales/en-US/chrome/browser/loop/loop.properties
@@ -8,40 +8,18 @@
 ## should remain "Firefox Hello" for all locales.
 clientShortname2=Firefox Hello
 
 ## LOCALIZATION_NOTE(first_time_experience.title): clientShortname will be
 ## replaced by the brand name
 first_time_experience_title={{clientShortname}} — Join the conversation
 first_time_experience_button_label=Get Started
 
-share_link_header_text=Share this link to invite someone to talk:
 invite_header_text=Invite someone to join you.
 
-## LOCALIZATION NOTE(invitee_name_label): Displayed when obtaining a url.
-## See https://people.mozilla.org/~dhenein/labs/loop-mvp-spec/#precall-firstrun
-## Click the label icon at the end of the url field.
-invitee_name_label=Who are you inviting?
-## LOCALIZATION NOTE(invitee_expire_days_label): Allows the user to adjust
-## the expiry time. Click the label icon at the end of the url field to see where
-## this is:
-## https://people.mozilla.org/~dhenein/labs/loop-mvp-spec/#precall-firstrun
-## Semicolon-separated list of plural forms. See:
-## http://developer.mozilla.org/en/docs/Localization_and_Plurals
-## In this item, don't translate the part between {{..}}
-invitee_expire_days_label=Invitation will expire in {{expiry_time}} day;Invitation will expire in {{expiry_time}} days
-## LOCALIZATION NOTE(invitee_expire_hours_label): Allows the user to adjust
-## the expiry time. Click the label icon are the end of the url field to see where
-## this is:
-## https://people.mozilla.org/~dhenein/labs/loop-mvp-spec/#precall-firstrun
-## Semicolon-separated list of plural forms. See:
-## http://developer.mozilla.org/en/docs/Localization_and_Plurals
-## In this item, don't translate the part between {{..}}
-invitee_expire_hours_label=Invitation will expire in {{expiry_time}} hour;Invitation will expire in {{expiry_time}} hours
-
 # Status text
 display_name_guest=Guest
 display_name_dnd_status=Do Not Disturb
 display_name_available_status=Available
 
 # Error bars
 ## LOCALIZATION NOTE(unable_retrieve_url,session_expired_error_description,could_not_authenticate,password_changed_question,try_again_later,could_not_connect,check_internet_connection,login_expired,service_not_available,problem_accessing_account):
 ## These may be displayed at the top of the panel here:
@@ -61,19 +39,17 @@ problem_accessing_account=There Was A Pr
 ## the appropriate action.
 ## See https://people.mozilla.org/~dhenein/labs/loop-mvp-spec/#error for location
 retry_button=Retry
 
 share_email_subject4={{clientShortname}} — Join the conversation
 ## LOCALIZATION NOTE (share_email_body4): In this item, don't translate the
 ## part between {{..}} and leave the \r\n\r\n part alone
 share_email_body4=Hello!\r\n\r\nJoin me for a video conversation using {{clientShortname}}:\r\n\r\nYou don't have to download or install anything. Just copy and paste this URL into your browser:\r\n\r\n{{callUrl}}\r\n\r\nIf you want, you can also learn more about {{clientShortname}} at {{learnMoreUrl}}\r\n\r\nTalk to you soon!
-share_button=Email
 share_button2=Email Link
-copy_url_button=Copy
 copy_url_button2=Copy Link
 copied_url_button=Copied!
 
 panel_footer_signin_or_signup_link=Sign In or Sign Up
 
 settings_menu_item_account=Account
 settings_menu_item_settings=Settings
 settings_menu_item_signout=Sign Out