Bug 1199213 - Part 1. Remove the old standalone call-url handling code from Loop. r=mikedeboer
authorMark Banner <standard8@mozilla.com>
Thu, 17 Sep 2015 16:19:35 +0200
changeset 295599 0159f007b4028f5394e14c6792f213c15a375c24
parent 295598 cfb78d51b8f5270fa069d21676c04615e81f0b26
child 295600 87008f7ea9d077df7e318d636a804d1c52203ef3
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmikedeboer
bugs1199213
milestone43.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 1199213 - Part 1. Remove the old standalone call-url handling code from Loop. r=mikedeboer
browser/components/loop/.eslintrc
browser/components/loop/content/js/conversation.js
browser/components/loop/content/js/conversation.jsx
browser/components/loop/content/shared/js/models.js
browser/components/loop/content/shared/js/views.js
browser/components/loop/content/shared/js/views.jsx
browser/components/loop/standalone/content/index.html
browser/components/loop/standalone/content/js/multiplexGum.js
browser/components/loop/standalone/content/js/standaloneAppStore.js
browser/components/loop/standalone/content/js/standaloneClient.js
browser/components/loop/standalone/content/js/webapp.js
browser/components/loop/standalone/content/js/webapp.jsx
browser/components/loop/standalone/content/l10n/en-US/loop.properties
browser/components/loop/test/desktop-local/conversation_test.js
browser/components/loop/test/karma/karma.coverage.shared_standalone.js
browser/components/loop/test/shared/models_test.js
browser/components/loop/test/shared/views_test.js
browser/components/loop/test/standalone/index.html
browser/components/loop/test/standalone/multiplexGum_test.js
browser/components/loop/test/standalone/standaloneAppStore_test.js
browser/components/loop/test/standalone/standalone_client_test.js
browser/components/loop/test/standalone/webapp_test.js
browser/components/loop/ui/index.html
--- a/browser/components/loop/.eslintrc
+++ b/browser/components/loop/.eslintrc
@@ -14,17 +14,16 @@
   },
   "extends": "eslint:recommended",
   "globals": {
     "_": false,
     "Backbone": false,
     "chai": false,
     "console": false,
     "loop": true,
-    "MozActivity": false,
     "mozRTCSessionDescription": false,
     "OT": false,
     "performance": false,
     "Promise": false,
     "React": false,
     "sinon": false
   },
   "rules": {
--- a/browser/components/loop/content/js/conversation.js
+++ b/browser/components/loop/content/js/conversation.js
@@ -129,19 +129,16 @@ loop.conversation = (function(mozL10n) {
     var sdkDriver = new loop.OTSdkDriver({
       isDesktop: true,
       useDataChannels: useDataChannels,
       dispatcher: dispatcher,
       sdk: OT,
       mozLoop: navigator.mozLoop
     });
 
-    // expose for functional tests
-    loop.conversation._sdkDriver = sdkDriver;
-
     // Create the stores.
     var conversationAppStore = new loop.store.ConversationAppStore({
       dispatcher: dispatcher,
       mozLoop: navigator.mozLoop
     });
     var conversationStore = new loop.store.ConversationStore(dispatcher, {
       client: client,
       isDesktop: true,
--- a/browser/components/loop/content/js/conversation.jsx
+++ b/browser/components/loop/content/js/conversation.jsx
@@ -129,19 +129,16 @@ loop.conversation = (function(mozL10n) {
     var sdkDriver = new loop.OTSdkDriver({
       isDesktop: true,
       useDataChannels: useDataChannels,
       dispatcher: dispatcher,
       sdk: OT,
       mozLoop: navigator.mozLoop
     });
 
-    // expose for functional tests
-    loop.conversation._sdkDriver = sdkDriver;
-
     // Create the stores.
     var conversationAppStore = new loop.store.ConversationAppStore({
       dispatcher: dispatcher,
       mozLoop: navigator.mozLoop
     });
     var conversationStore = new loop.store.ConversationStore(dispatcher, {
       client: client,
       isDesktop: true,
--- a/browser/components/loop/content/shared/js/models.js
+++ b/browser/components/loop/content/shared/js/models.js
@@ -3,373 +3,16 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 var loop = loop || {};
 loop.shared = loop.shared || {};
 loop.shared.models = (function(l10n) {
   "use strict";
 
   /**
-   * Conversation model.
-   */
-  var ConversationModel = Backbone.Model.extend({
-    defaults: {
-      connected: false,            // Session connected flag
-      ongoing: false,              // Ongoing call flag
-      callerId: undefined,         // Loop caller id
-      loopToken: undefined,        // Loop conversation token
-      sessionId: undefined,        // OT session id
-      sessionToken: undefined,     // OT session token
-      sessionType: undefined,      // Hawk session type
-      apiKey: undefined,           // OT api key
-      windowId: undefined,         // The window id
-      callId: undefined,           // The callId on the server
-      progressURL: undefined,      // The websocket url to use for progress
-      websocketToken: undefined,   // The token to use for websocket auth, this is
-                                   // stored as a hex string which is what the server
-                                   // requires.
-      callType: undefined,         // The type of incoming call selected by
-                                   // other peer ("audio" or "audio-video")
-      selectedCallType: "audio-video", // The selected type for the call that was
-                                       // initiated ("audio" or "audio-video")
-      callToken: undefined,        // Incoming call token.
-      callUrl: undefined,          // Incoming call url
-                                   // Used for blocking a call url
-      subscribedStream: false,     // Used to indicate that a stream has been
-                                   // subscribed to
-      publishedStream: false       // Used to indicate that a stream has been
-                                   // published
-    },
-
-    /**
-     * SDK object.
-     * @type {OT}
-     */
-    sdk: undefined,
-
-    /**
-     * SDK session object.
-     * @type {XXX}
-     */
-    session: undefined,
-
-    /**
-     * Constructor.
-     *
-     * Options:
-     * - {OT} mozLoop: browser mozLoop service object.
-     *
-     * Required:
-     * - {OT} sdk: OT SDK object.
-     *
-     * @param  {Object} attributes Attributes object.
-     * @param  {Object} options    Options object.
-     */
-    initialize: function(attributes, options) {
-      options = options || {};
-      this.mozLoop = options.mozLoop;
-      if (!options.sdk) {
-        throw new Error("missing required sdk");
-      }
-      this.sdk = options.sdk;
-
-      // Set loop.debug.sdk to true in the browser, or standalone:
-      // localStorage.setItem("debug.sdk", true);
-      if (loop.shared.utils.getBoolPreference("debug.sdk")) {
-        this.sdk.setLogLevel(this.sdk.DEBUG);
-      }
-    },
-
-    /**
-     * Indicates an incoming conversation has been accepted.
-     */
-    accepted: function() {
-      this.trigger("call:accepted");
-    },
-
-    /**
-     * Used to indicate that an outgoing call should start any necessary
-     * set-up.
-     *
-     * @param {String} selectedCallType Call type ("audio" or "audio-video")
-     */
-    setupOutgoingCall: function(selectedCallType) {
-      if (selectedCallType) {
-        this.set("selectedCallType", selectedCallType);
-      }
-      this.trigger("call:outgoing:get-media-privs");
-    },
-
-    /**
-     * Used to indicate that media privileges have been accepted.
-     */
-    gotMediaPrivs: function() {
-      this.trigger("call:outgoing:setup");
-    },
-
-    /**
-     * Starts an outgoing conversation.
-     *
-     * @param {Object} sessionData The session data received from the
-     *                             server for the outgoing call.
-     */
-    outgoing: function(sessionData) {
-      this.setOutgoingSessionData(sessionData);
-      this.trigger("call:outgoing");
-    },
-
-    /**
-     * Checks that the session is ready.
-     *
-     * @return {Boolean}
-     */
-    isSessionReady: function() {
-      return !!this.get("sessionId");
-    },
-
-    /**
-     * Sets session information.
-     * Session data received by creating an outgoing call.
-     *
-     * @param {Object} sessionData Conversation session information.
-     */
-    setOutgoingSessionData: function(sessionData) {
-      // Explicit property assignment to prevent later "surprises"
-      this.set({
-        sessionId: sessionData.sessionId,
-        sessionToken: sessionData.sessionToken,
-        apiKey: sessionData.apiKey,
-        callId: sessionData.callId,
-        progressURL: sessionData.progressURL,
-        websocketToken: sessionData.websocketToken.toString(16)
-      });
-    },
-
-    /**
-     * Sets session information about the incoming call.
-     *
-     * @param {Object} sessionData Conversation session information.
-     */
-    setIncomingSessionData: function(sessionData) {
-      // Explicit property assignment to prevent later "surprises"
-      this.set({
-        sessionId: sessionData.sessionId,
-        sessionToken: sessionData.sessionToken,
-        sessionType: sessionData.sessionType,
-        apiKey: sessionData.apiKey,
-        callId: sessionData.callId,
-        callerId: sessionData.callerId,
-        urlCreationDate: sessionData.urlCreationDate,
-        progressURL: sessionData.progressURL,
-        websocketToken: sessionData.websocketToken.toString(16),
-        callType: sessionData.callType || "audio-video",
-        callToken: sessionData.callToken,
-        callUrl: sessionData.callUrl
-      });
-    },
-
-    /**
-     * Starts a SDK session and subscribe to call events.
-     */
-    startSession: function() {
-      if (!this.isSessionReady()) {
-        throw new Error("Can't start session as it's not ready");
-      }
-      this.set({
-        publishedStream: false,
-        subscribedStream: false
-      });
-
-      this.session = this.sdk.initSession(this.get("sessionId"));
-      this.listenTo(this.session, "streamCreated", this._streamCreated);
-      this.listenTo(this.session, "connectionDestroyed",
-                                  this._connectionDestroyed);
-      this.listenTo(this.session, "sessionDisconnected",
-                                  this._sessionDisconnected);
-      this.session.connect(this.get("apiKey"), this.get("sessionToken"),
-                           this._onConnectCompletion.bind(this));
-
-      // We store the call credentials for debugging purposes.
-      if (this.mozLoop) {
-        this.mozLoop.addConversationContext(this.get("windowId"),
-                                            this.get("sessionId"),
-                                            this.get("callId"));
-      }
-    },
-
-    /**
-     * Ends current session.
-     */
-    endSession: function() {
-      this.session.disconnect();
-      this.set({
-        publishedStream: false,
-        subscribedStream: false,
-        ongoing: false
-      }).once("session:ended", this.stopListening, this);
-    },
-
-    /**
-     * Helper function to determine if video stream is available for the
-     * incoming or outgoing call
-     *
-     * @param {string} callType Incoming or outgoing call
-     */
-    hasVideoStream: function(callType) {
-      if (callType === "incoming") {
-        return this.get("callType") === "audio-video";
-      }
-      if (callType === "outgoing") {
-        return this.get("selectedCallType") === "audio-video";
-      }
-      return undefined;
-    },
-
-    /**
-     * Used to remove the scheme from a url.
-     */
-    _removeScheme: function(url) {
-      if (!url) {
-        return "";
-      }
-      return url.replace(/^https?:\/\//, "");
-    },
-
-    /**
-     * Returns a conversation identifier for the incoming call view
-     */
-    getCallIdentifier: function() {
-      return this.get("callerId") || this._removeScheme(this.get("callUrl"));
-    },
-
-    /**
-     * Publishes a local stream.
-     *
-     * @param {Publisher} publisher The publisher object to publish
-     *                              to the session.
-     */
-    publish: function(publisher) {
-      this.session.publish(publisher);
-      this.set("publishedStream", true);
-    },
-
-    /**
-     * Subscribes to a remote stream.
-     *
-     * @param {Stream} stream The remote stream to subscribe to.
-     * @param {DOMElement} element The element to display the stream in.
-     * @param {Object} config The display properties to set on the stream as
-     *                        documented in:
-     * https://tokbox.com/opentok/libraries/client/js/reference/Session.html#subscribe
-     */
-    subscribe: function(stream, element, config) {
-      this.session.subscribe(stream, element, config);
-      this.set("subscribedStream", true);
-    },
-
-    /**
-     * Returns true if a stream has been published and a stream has been
-     * subscribed to.
-     */
-    streamsConnected: function() {
-      return this.get("publishedStream") && this.get("subscribedStream");
-    },
-
-    /**
-     * Handle a loop-server error, which has an optional `errno` property which
-     * is server error identifier.
-     *
-     * Triggers the following events:
-     *
-     * - `session:expired` for expired call urls
-     * - `session:error` for other generic errors
-     *
-     * @param  {Error} err Error object.
-     */
-    _handleServerError: function(err) {
-      switch (err.errno) {
-        // loop-server sends 404 + INVALID_TOKEN (errno 105) whenever a token is
-        // missing OR expired; we treat this information as if the url is always
-        // expired.
-        case 105:
-          this.trigger("session:expired", err);
-          break;
-        default:
-          this.trigger("session:error", err);
-          break;
-      }
-    },
-
-    /**
-     * Manages connection status
-     * triggers apropriate event for connection error/success
-     * http://tokbox.com/opentok/tutorials/connect-session/js/
-     * http://tokbox.com/opentok/tutorials/hello-world/js/
-     * http://tokbox.com/opentok/libraries/client/js/reference/SessionConnectEvent.html
-     *
-     * @param {error|null} error
-     */
-    _onConnectCompletion: function(error) {
-      if (error) {
-        this.trigger("session:connection-error", error);
-        this.endSession();
-      } else {
-        this.trigger("session:connected");
-        this.set("connected", true);
-      }
-    },
-
-    /**
-     * New created streams are available.
-     * http://tokbox.com/opentok/libraries/client/js/reference/StreamEvent.html
-     *
-     * @param  {StreamEvent} event
-     */
-    _streamCreated: function(event) {
-      this.set("ongoing", true)
-          .trigger("session:stream-created", event);
-    },
-
-    /**
-     * Local user hung up.
-     * http://tokbox.com/opentok/libraries/client/js/reference/SessionDisconnectEvent.html
-     *
-     * @param  {SessionDisconnectEvent} event
-     */
-    _sessionDisconnected: function(event) {
-      if(event.reason === "networkDisconnected") {
-        this._signalEnd("session:network-disconnected", event);
-      } else {
-        this._signalEnd("session:ended", event);
-      }
-    },
-
-    _signalEnd: function(eventName, event) {
-      this.set("connected", false)
-          .set("ongoing", false)
-          .trigger(eventName, event);
-    },
-
-    /**
-     * Peer hung up. Disconnects local session.
-     * http://tokbox.com/opentok/libraries/client/js/reference/ConnectionEvent.html
-     *
-     * @param  {ConnectionEvent} event
-     */
-    _connectionDestroyed: function(event) {
-      if (event.reason === "networkDisconnected") {
-        this._signalEnd("session:network-disconnected", event);
-      } else {
-        this._signalEnd("session:peer-hungup", event);
-      }
-      this.endSession();
-    }
-  });
-
-  /**
    * Notification model.
    */
   var NotificationModel = Backbone.Model.extend({
     defaults: {
       details: "",
       detailsButtonLabel: "",
       detailsButtonCallback: null,
       level: "info",
@@ -440,13 +83,12 @@ loop.shared.models = (function(l10n) {
      *                  strings or numbers.
      */
     successL10n: function(messageId, l10nProps) {
       this.success(l10n.get(messageId, l10nProps));
     }
   });
 
   return {
-    ConversationModel: ConversationModel,
     NotificationCollection: NotificationCollection,
     NotificationModel: NotificationModel
   };
 })(navigator.mozL10n || document.mozL10n);
--- a/browser/components/loop/content/shared/js/views.js
+++ b/browser/components/loop/content/shared/js/views.js
@@ -481,242 +481,16 @@ loop.shared.views = (function(_, mozL10n
                                    mozLoop: this.props.mozLoop})
           )
         )
       );
     }
   });
 
   /**
-   * Conversation view.
-   */
-  var ConversationView = React.createClass({displayName: "ConversationView",
-    mixins: [
-      Backbone.Events,
-      sharedMixins.AudioMixin,
-      sharedMixins.MediaSetupMixin
-    ],
-
-    propTypes: {
-      audio: React.PropTypes.object,
-      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
-      initiate: React.PropTypes.bool,
-      isDesktop: React.PropTypes.bool,
-      model: React.PropTypes.object.isRequired,
-      mozLoop: React.PropTypes.object,
-      sdk: React.PropTypes.object.isRequired,
-      video: React.PropTypes.object
-    },
-
-    getDefaultProps: function() {
-      return {
-        initiate: true,
-        isDesktop: false,
-        video: {enabled: true, visible: true},
-        audio: {enabled: true, visible: true}
-      };
-    },
-
-    getInitialState: function() {
-      return {
-        video: this.props.video,
-        audio: this.props.audio
-      };
-    },
-
-    componentDidMount: function() {
-      if (this.props.initiate) {
-        /**
-         * XXX This is a workaround for desktop machines that do not have a
-         * camera installed. As we don't yet have device enumeration, when
-         * we do, this can be removed (bug 1138851), and the sdk should handle it.
-         */
-        if (this.props.isDesktop &&
-            !window.MediaStreamTrack.getSources) {
-          // If there's no getSources function, the sdk defines its own and caches
-          // the result. So here we define the "normal" one which doesn't get cached, so
-          // we can change it later.
-          window.MediaStreamTrack.getSources = function(callback) {
-            callback([{kind: "audio"}, {kind: "video"}]);
-          };
-        }
-
-        this.listenTo(this.props.sdk, "exception", this._handleSdkException);
-
-        this.listenTo(this.props.model, "session:connected",
-                                        this._onSessionConnected);
-        this.listenTo(this.props.model, "session:stream-created",
-                                        this._streamCreated);
-        this.listenTo(this.props.model, ["session:peer-hungup",
-                                         "session:network-disconnected",
-                                         "session:ended"].join(" "),
-                                         this.stopPublishing);
-        this.props.model.startSession();
-      }
-    },
-
-    componentWillUnmount: function() {
-      // Unregister all local event listeners
-      this.stopListening();
-      this.hangup();
-    },
-
-    hangup: function() {
-      this.stopPublishing();
-      this.props.model.endSession();
-    },
-
-    _onSessionConnected: function(event) {
-      this.startPublishing(event);
-      this.play("connected");
-    },
-
-    /**
-     * Subscribes and attaches each created stream to a DOM element.
-     *
-     * XXX: for now we only support a single remote stream, hence a single DOM
-     *      element.
-     *
-     * http://tokbox.com/opentok/libraries/client/js/reference/StreamEvent.html
-     *
-     * @param  {StreamEvent} event
-     */
-    _streamCreated: function(event) {
-      var incoming = this.getDOMNode().querySelector(".remote");
-      this.props.model.subscribe(event.stream, incoming,
-        this.getDefaultPublisherConfig({
-          publishVideo: this.props.video.enabled
-        }));
-    },
-
-    /**
-     * Handles the SDK Exception event.
-     *
-     * https://tokbox.com/opentok/libraries/client/js/reference/ExceptionEvent.html
-     *
-     * @param {ExceptionEvent} event
-     */
-    _handleSdkException: function(event) {
-      /**
-       * XXX This is a workaround for desktop machines that do not have a
-       * camera installed. As we don't yet have device enumeration, when
-       * we do, this can be removed (bug 1138851), and the sdk should handle it.
-       */
-      if (this.publisher &&
-          event.code === OT.ExceptionCodes.UNABLE_TO_PUBLISH &&
-          event.message === "GetUserMedia" &&
-          this.state.video.enabled) {
-        this.state.video.enabled = false;
-
-        window.MediaStreamTrack.getSources = function(callback) {
-          callback([{kind: "audio"}]);
-        };
-
-        this.stopListening(this.publisher);
-        this.publisher.destroy();
-        this.startPublishing();
-      }
-    },
-
-    /**
-     * Publishes remote streams available once a session is connected.
-     *
-     * http://tokbox.com/opentok/libraries/client/js/reference/SessionConnectEvent.html
-     *
-     * @param  {SessionConnectEvent} event
-     */
-    startPublishing: function(event) {
-      var outgoing = this.getDOMNode().querySelector(".local");
-
-      // XXX move this into its StreamingVideo component?
-      this.publisher = this.props.sdk.initPublisher(
-        outgoing, this.getDefaultPublisherConfig({publishVideo: this.props.video.enabled}));
-
-      // Suppress OT GuM custom dialog, see bug 1018875
-      this.listenTo(this.publisher, "accessDialogOpened accessDenied",
-                    function(ev) {
-                      ev.preventDefault();
-                    });
-
-      this.listenTo(this.publisher, "streamCreated", function(ev) {
-        this.setState({
-          audio: {enabled: ev.stream.hasAudio},
-          video: {enabled: ev.stream.hasVideo}
-        });
-      });
-
-      this.listenTo(this.publisher, "streamDestroyed", function() {
-        this.setState({
-          audio: {enabled: false},
-          video: {enabled: false}
-        });
-      });
-
-      this.props.model.publish(this.publisher);
-    },
-
-    /**
-     * Toggles streaming status for a given stream type.
-     *
-     * @param  {String}  type     Stream type ("audio" or "video").
-     * @param  {Boolean} enabled  Enabled stream flag.
-     */
-    publishStream: function(type, enabled) {
-      if (type === "audio") {
-        this.publisher.publishAudio(enabled);
-        this.setState({audio: {enabled: enabled}});
-      } else {
-        this.publisher.publishVideo(enabled);
-        this.setState({video: {enabled: enabled}});
-      }
-    },
-
-    /**
-     * Unpublishes local stream.
-     */
-    stopPublishing: function() {
-      if (this.publisher) {
-        // Unregister listeners for publisher events
-        this.stopListening(this.publisher);
-
-        this.props.model.session.unpublish(this.publisher);
-      }
-    },
-
-    render: function() {
-      var localStreamClasses = React.addons.classSet({
-        local: true,
-        "local-stream": true,
-        "local-stream-audio": !this.state.video.enabled
-      });
-      return (
-        React.createElement("div", {className: "video-layout-wrapper"}, 
-          React.createElement("div", {className: "conversation in-call"}, 
-            React.createElement("div", {className: "media nested"}, 
-              React.createElement("div", {className: "video_wrapper remote_wrapper"}, 
-                React.createElement("div", {className: "video_inner remote focus-stream"}, 
-                  React.createElement(ConversationToolbar, {
-                    audio: this.state.audio, 
-                    dispatcher: this.props.dispatcher, 
-                    hangup: this.hangup, 
-                    mozLoop: this.props.mozLoop, 
-                    publishStream: this.publishStream, 
-                    video: this.state.video})
-                )
-              ), 
-              React.createElement("div", {className: localStreamClasses})
-
-            )
-          )
-        )
-      );
-    }
-  });
-
-  /**
    * Notification view.
    */
   var NotificationView = React.createClass({displayName: "NotificationView",
     mixins: [Backbone.Events],
 
     propTypes: {
       notification: React.PropTypes.object.isRequired
     },
@@ -1317,17 +1091,16 @@ loop.shared.views = (function(_, mozL10n
   });
 
   return {
     AvatarView: AvatarView,
     Button: Button,
     ButtonGroup: ButtonGroup,
     Checkbox: Checkbox,
     ContextUrlView: ContextUrlView,
-    ConversationView: ConversationView,
     ConversationToolbar: ConversationToolbar,
     MediaControlButton: MediaControlButton,
     MediaLayoutView: MediaLayoutView,
     MediaView: MediaView,
     LoadingView: LoadingView,
     SettingsControlButton: SettingsControlButton,
     ScreenShareControlButton: ScreenShareControlButton,
     NotificationListView: NotificationListView
--- a/browser/components/loop/content/shared/js/views.jsx
+++ b/browser/components/loop/content/shared/js/views.jsx
@@ -481,242 +481,16 @@ loop.shared.views = (function(_, mozL10n
                                    mozLoop={this.props.mozLoop} />
           </li>
         </ul>
       );
     }
   });
 
   /**
-   * Conversation view.
-   */
-  var ConversationView = React.createClass({
-    mixins: [
-      Backbone.Events,
-      sharedMixins.AudioMixin,
-      sharedMixins.MediaSetupMixin
-    ],
-
-    propTypes: {
-      audio: React.PropTypes.object,
-      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
-      initiate: React.PropTypes.bool,
-      isDesktop: React.PropTypes.bool,
-      model: React.PropTypes.object.isRequired,
-      mozLoop: React.PropTypes.object,
-      sdk: React.PropTypes.object.isRequired,
-      video: React.PropTypes.object
-    },
-
-    getDefaultProps: function() {
-      return {
-        initiate: true,
-        isDesktop: false,
-        video: {enabled: true, visible: true},
-        audio: {enabled: true, visible: true}
-      };
-    },
-
-    getInitialState: function() {
-      return {
-        video: this.props.video,
-        audio: this.props.audio
-      };
-    },
-
-    componentDidMount: function() {
-      if (this.props.initiate) {
-        /**
-         * XXX This is a workaround for desktop machines that do not have a
-         * camera installed. As we don't yet have device enumeration, when
-         * we do, this can be removed (bug 1138851), and the sdk should handle it.
-         */
-        if (this.props.isDesktop &&
-            !window.MediaStreamTrack.getSources) {
-          // If there's no getSources function, the sdk defines its own and caches
-          // the result. So here we define the "normal" one which doesn't get cached, so
-          // we can change it later.
-          window.MediaStreamTrack.getSources = function(callback) {
-            callback([{kind: "audio"}, {kind: "video"}]);
-          };
-        }
-
-        this.listenTo(this.props.sdk, "exception", this._handleSdkException);
-
-        this.listenTo(this.props.model, "session:connected",
-                                        this._onSessionConnected);
-        this.listenTo(this.props.model, "session:stream-created",
-                                        this._streamCreated);
-        this.listenTo(this.props.model, ["session:peer-hungup",
-                                         "session:network-disconnected",
-                                         "session:ended"].join(" "),
-                                         this.stopPublishing);
-        this.props.model.startSession();
-      }
-    },
-
-    componentWillUnmount: function() {
-      // Unregister all local event listeners
-      this.stopListening();
-      this.hangup();
-    },
-
-    hangup: function() {
-      this.stopPublishing();
-      this.props.model.endSession();
-    },
-
-    _onSessionConnected: function(event) {
-      this.startPublishing(event);
-      this.play("connected");
-    },
-
-    /**
-     * Subscribes and attaches each created stream to a DOM element.
-     *
-     * XXX: for now we only support a single remote stream, hence a single DOM
-     *      element.
-     *
-     * http://tokbox.com/opentok/libraries/client/js/reference/StreamEvent.html
-     *
-     * @param  {StreamEvent} event
-     */
-    _streamCreated: function(event) {
-      var incoming = this.getDOMNode().querySelector(".remote");
-      this.props.model.subscribe(event.stream, incoming,
-        this.getDefaultPublisherConfig({
-          publishVideo: this.props.video.enabled
-        }));
-    },
-
-    /**
-     * Handles the SDK Exception event.
-     *
-     * https://tokbox.com/opentok/libraries/client/js/reference/ExceptionEvent.html
-     *
-     * @param {ExceptionEvent} event
-     */
-    _handleSdkException: function(event) {
-      /**
-       * XXX This is a workaround for desktop machines that do not have a
-       * camera installed. As we don't yet have device enumeration, when
-       * we do, this can be removed (bug 1138851), and the sdk should handle it.
-       */
-      if (this.publisher &&
-          event.code === OT.ExceptionCodes.UNABLE_TO_PUBLISH &&
-          event.message === "GetUserMedia" &&
-          this.state.video.enabled) {
-        this.state.video.enabled = false;
-
-        window.MediaStreamTrack.getSources = function(callback) {
-          callback([{kind: "audio"}]);
-        };
-
-        this.stopListening(this.publisher);
-        this.publisher.destroy();
-        this.startPublishing();
-      }
-    },
-
-    /**
-     * Publishes remote streams available once a session is connected.
-     *
-     * http://tokbox.com/opentok/libraries/client/js/reference/SessionConnectEvent.html
-     *
-     * @param  {SessionConnectEvent} event
-     */
-    startPublishing: function(event) {
-      var outgoing = this.getDOMNode().querySelector(".local");
-
-      // XXX move this into its StreamingVideo component?
-      this.publisher = this.props.sdk.initPublisher(
-        outgoing, this.getDefaultPublisherConfig({publishVideo: this.props.video.enabled}));
-
-      // Suppress OT GuM custom dialog, see bug 1018875
-      this.listenTo(this.publisher, "accessDialogOpened accessDenied",
-                    function(ev) {
-                      ev.preventDefault();
-                    });
-
-      this.listenTo(this.publisher, "streamCreated", function(ev) {
-        this.setState({
-          audio: {enabled: ev.stream.hasAudio},
-          video: {enabled: ev.stream.hasVideo}
-        });
-      });
-
-      this.listenTo(this.publisher, "streamDestroyed", function() {
-        this.setState({
-          audio: {enabled: false},
-          video: {enabled: false}
-        });
-      });
-
-      this.props.model.publish(this.publisher);
-    },
-
-    /**
-     * Toggles streaming status for a given stream type.
-     *
-     * @param  {String}  type     Stream type ("audio" or "video").
-     * @param  {Boolean} enabled  Enabled stream flag.
-     */
-    publishStream: function(type, enabled) {
-      if (type === "audio") {
-        this.publisher.publishAudio(enabled);
-        this.setState({audio: {enabled: enabled}});
-      } else {
-        this.publisher.publishVideo(enabled);
-        this.setState({video: {enabled: enabled}});
-      }
-    },
-
-    /**
-     * Unpublishes local stream.
-     */
-    stopPublishing: function() {
-      if (this.publisher) {
-        // Unregister listeners for publisher events
-        this.stopListening(this.publisher);
-
-        this.props.model.session.unpublish(this.publisher);
-      }
-    },
-
-    render: function() {
-      var localStreamClasses = React.addons.classSet({
-        local: true,
-        "local-stream": true,
-        "local-stream-audio": !this.state.video.enabled
-      });
-      return (
-        <div className="video-layout-wrapper">
-          <div className="conversation in-call">
-            <div className="media nested">
-              <div className="video_wrapper remote_wrapper">
-                <div className="video_inner remote focus-stream">
-                  <ConversationToolbar
-                    audio={this.state.audio}
-                    dispatcher={this.props.dispatcher}
-                    hangup={this.hangup}
-                    mozLoop={this.props.mozLoop}
-                    publishStream={this.publishStream}
-                    video={this.state.video} />
-                </div>
-              </div>
-              <div className={localStreamClasses}></div>
-
-            </div>
-          </div>
-        </div>
-      );
-    }
-  });
-
-  /**
    * Notification view.
    */
   var NotificationView = React.createClass({
     mixins: [Backbone.Events],
 
     propTypes: {
       notification: React.PropTypes.object.isRequired
     },
@@ -1317,17 +1091,16 @@ loop.shared.views = (function(_, mozL10n
   });
 
   return {
     AvatarView: AvatarView,
     Button: Button,
     ButtonGroup: ButtonGroup,
     Checkbox: Checkbox,
     ContextUrlView: ContextUrlView,
-    ConversationView: ConversationView,
     ConversationToolbar: ConversationToolbar,
     MediaControlButton: MediaControlButton,
     MediaLayoutView: MediaLayoutView,
     MediaView: MediaView,
     LoadingView: LoadingView,
     SettingsControlButton: SettingsControlButton,
     ScreenShareControlButton: ScreenShareControlButton,
     NotificationListView: NotificationListView
--- a/browser/components/loop/standalone/content/index.html
+++ b/browser/components/loop/standalone/content/index.html
@@ -115,51 +115,40 @@
         cdnURL: "shared/libs/"
       };
       window.OTProperties.assetURL = window.OTProperties.cdnURL + "sdk-content/";
       window.OTProperties.configURL = window.OTProperties.assetURL + "js/dynamic_config.min.js";
 
       // We don't use the SDK's CSS. This will prevent spurious 404 errors.
       window.OTProperties.cssURL = "about:blank";
     </script>
-    <script type="text/javascript" src="js/multiplexGum.js"></script>
     <script type="text/javascript" src="shared/libs/sdk.js"></script>
-    <script>
-      // multiplexGum needs evaluation before sdk.js, but TBPlugin is not
-      // defined until after sdk.js has been evaluated. This updates the
-      // navigator object to reference TBPlugin if it was defined by sdk.js.
-      if (!navigator.originalGum) {
-        navigator.originalGum = (window.TBPlugin && window.TBPlugin.getUserMedia);
-      }
-    </script>
     <script type="text/javascript" src="libs/l10n-gaia-02ca67948fe8.js"></script>
     <script type="text/javascript" src="shared/libs/react-0.12.2.js"></script>
     <script type="text/javascript" src="shared/libs/lodash-3.9.3.js"></script>
     <script type="text/javascript" src="shared/libs/backbone-1.2.1.js"></script>
 
     <!-- app scripts -->
     <script type="text/javascript" src="config.js"></script>
     <script type="text/javascript" src="shared/js/utils.js"></script>
     <script type="text/javascript" src="shared/js/crypto.js"></script>
-    <script type="text/javascript" src="shared/js/models.js"></script>
     <script type="text/javascript" src="shared/js/mixins.js"></script>
     <script type="text/javascript" src="shared/js/actions.js"></script>
     <script type="text/javascript" src="shared/js/validate.js"></script>
     <script type="text/javascript" src="shared/js/dispatcher.js"></script>
     <script type="text/javascript" src="shared/js/websocket.js"></script>
     <script type="text/javascript" src="shared/js/otSdkDriver.js"></script>
     <script type="text/javascript" src="shared/js/store.js"></script>
     <script type="text/javascript" src="shared/js/activeRoomStore.js"></script>
     <script type="text/javascript" src="shared/js/views.js"></script>
     <script type="text/javascript" src="shared/js/textChatStore.js"></script>
     <script type="text/javascript" src="shared/js/textChatView.js"></script>
     <script type="text/javascript" src="shared/js/urlRegExps.js"></script>
     <script type="text/javascript" src="shared/js/linkifiedTextView.js"></script>
     <script type="text/javascript" src="js/standaloneAppStore.js"></script>
-    <script type="text/javascript" src="js/standaloneClient.js"></script>
     <script type="text/javascript" src="js/standaloneMozLoop.js"></script>
     <script type="text/javascript" src="js/standaloneRoomViews.js"></script>
     <script type="text/javascript" src="js/standaloneMetricsStore.js"></script>
     <script type="text/javascript" src="js/webapp.js"></script>
 
     <script>
       // Wait for all the localization notes to load
       window.addEventListener("localized", function() {
deleted file mode 100644
--- a/browser/components/loop/standalone/content/js/multiplexGum.js
+++ /dev/null
@@ -1,149 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-var loop = loop || {};
-
-/**
- * Monkeypatch getUserMedia in a way that prevents additional camera and
- * microphone prompts, at the cost of ignoring all constraints other than
- * the first set passed in.
- *
- * The first call to navigator.getUserMedia (also now aliased to
- * multiplexGum.getPermsAndCacheMedia to allow for explicit calling code)
- * will cause the underlying gUM implementation to be called.
- *
- * While permission is pending, subsequent calls will result in the callbacks
- * being queued. Once the call succeeds or fails, all queued success or
- * failure callbacks will be invoked.  Subsequent calls to either function will
- * cause the success or failure callback to be invoked immediately.
- */
-loop.standaloneMedia = (function() {
-  "use strict";
-
-  function patchSymbolIfExtant(objectName, propertyName, replacement) {
-    var object;
-    if (window[objectName]) {
-      object = window[objectName];
-    }
-    if (object && object[propertyName]) {
-      object[propertyName] = replacement;
-    }
-  }
-
-  // originalGum _must_ be on navigator; otherwise things blow up.
-  // For TBPlugin users, navigator.originalGum is set after the TB SDK is loaded.
-  navigator.originalGum = navigator.getUserMedia ||
-                          navigator.mozGetUserMedia ||
-                          navigator.webkitGetUserMedia;
-
-  function _MultiplexGum() {
-    this.reset();
-  }
-
-  _MultiplexGum.prototype = {
-    /**
-     * @see The docs at the top of this file for overall semantics,
-     * & http://developer.mozilla.org/en-US/docs/NavigatorUserMedia.getUserMedia
-     * for params, since this is intended to be purely a passthrough to gUM.
-     */
-    getPermsAndCacheMedia: function(constraints, onSuccess, onError) {
-      function handleResult(callbacks, param) {
-        // Operate on a copy of the array in case any of the callbacks
-        // calls reset, which would cause an infinite-recursion.
-        this.userMedia.successCallbacks = [];
-        this.userMedia.errorCallbacks = [];
-        callbacks.forEach(function(cb) {
-          if (typeof cb == "function") {
-            cb(param);
-          }
-        });
-      }
-      function handleSuccess(localStream) {
-        this.userMedia.pending = false;
-        this.userMedia.localStream = localStream;
-        this.userMedia.error = null;
-        handleResult.call(this, this.userMedia.successCallbacks.slice(0), localStream);
-      }
-
-      function handleError(error) {
-        this.userMedia.pending = false;
-        this.userMedia.error = error;
-        handleResult.call(this, this.userMedia.errorCallbacks.slice(0), error);
-        this.error = null;
-      }
-
-      if (this.userMedia.localStream &&
-          this.userMedia.localStream.ended) {
-        this.userMedia.localStream = null;
-      }
-
-      this.userMedia.errorCallbacks.push(onError);
-      this.userMedia.successCallbacks.push(onSuccess);
-
-      if (this.userMedia.localStream) {
-        handleSuccess.call(this, this.userMedia.localStream);
-        return;
-      } else if (this.userMedia.error) {
-        handleError.call(this, this.userMedia.error);
-        return;
-      }
-
-      if (this.userMedia.pending) {
-        return;
-      }
-      this.userMedia.pending = true;
-
-      navigator.originalGum(constraints, handleSuccess.bind(this),
-        handleError.bind(this));
-    },
-
-    /**
-     * Reset the cached permissions, callbacks, and media to their default
-     * state and call any error callbacks to let any waiting callers know
-     * not to ever expect any more callbacks.  We use "PERMISSION_DENIED",
-     * for lack of a better, more specific gUM code that callers are likely
-     * to be prepared to handle.
-     */
-    reset: function() {
-      // When called from the ctor, userMedia is not created yet.
-      if (this.userMedia) {
-        this.userMedia.errorCallbacks.forEach(function(cb) {
-          if (typeof cb == "function") {
-            cb("PERMISSION_DENIED");
-          }
-        });
-        if (this.userMedia.localStream &&
-            typeof this.userMedia.localStream.stop == "function") {
-          this.userMedia.localStream.stop();
-        }
-      }
-      this.userMedia = {
-        error: null,
-        localStream: null,
-        pending: false,
-        errorCallbacks: [],
-        successCallbacks: []
-      };
-    }
-  };
-
-  var singletonMultiplexGum = new _MultiplexGum();
-  function myGetUserMedia() {
-    // This function is needed to pull in the instance
-    // of the singleton for tests to overwrite the used instance.
-    singletonMultiplexGum.getPermsAndCacheMedia.apply(singletonMultiplexGum, arguments);
-  }
-  patchSymbolIfExtant("navigator", "mozGetUserMedia", myGetUserMedia);
-  patchSymbolIfExtant("navigator", "webkitGetUserMedia", myGetUserMedia);
-  patchSymbolIfExtant("navigator", "getUserMedia", myGetUserMedia);
-  patchSymbolIfExtant("TBPlugin", "getUserMedia", myGetUserMedia);
-
-  return {
-    multiplexGum: singletonMultiplexGum,
-    _MultiplexGum: _MultiplexGum,
-    setSingleton: function(singleton) {
-      singletonMultiplexGum = singleton;
-    }
-  };
-})();
--- a/browser/components/loop/standalone/content/js/standaloneAppStore.js
+++ b/browser/components/loop/standalone/content/js/standaloneAppStore.js
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 var loop = loop || {};
 loop.store = loop.store || {};
 
 /**
- * Manages the conversation window app controller view. Used to get
+ * Manages the standalone app controller view. Used to get
  * the window data and store the window type.
  */
 loop.store.StandaloneAppStore = (function() {
   "use strict";
 
   var sharedActions = loop.shared.actions;
   var sharedUtils = loop.shared.utils;
 
@@ -26,24 +26,20 @@ loop.store.StandaloneAppStore = (functio
    */
   var StandaloneAppStore = function(options) {
     if (!options.dispatcher) {
       throw new Error("Missing option dispatcher");
     }
     if (!options.sdk) {
       throw new Error("Missing option sdk");
     }
-    if (!options.conversation) {
-      throw new Error("Missing option conversation");
-    }
 
     this._dispatcher = options.dispatcher;
     this._storeState = {};
     this._sdk = options.sdk;
-    this._conversation = options.conversation;
 
     this._dispatcher.register(this, [
       "extractTokenInfo"
     ]);
   };
 
   StandaloneAppStore.prototype = _.extend({
     /**
@@ -127,20 +123,16 @@ loop.store.StandaloneAppStore = (functio
       } else if (actionData.windowPath) {
         // ES6 not used in standalone yet.
         var result = this._extractWindowDataFromPath(actionData.windowPath);
         windowType = result[0];
         token = result[1];
       }
       // Else type is home.
 
-      if (token) {
-        this._conversation.set({loopToken: token});
-      }
-
       this.setStoreState({
         windowType: windowType,
         isFirefox: sharedUtils.isFirefox(navigator.userAgent),
         unsupportedPlatform: unsupportedPlatform
       });
 
       // If we've not got a window ID, don't dispatch the action, as we don't
       // need it.
deleted file mode 100644
--- a/browser/components/loop/standalone/content/js/standaloneClient.js
+++ /dev/null
@@ -1,155 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-var loop = loop || {};
-loop.StandaloneClient = (function() {
-  "use strict";
-
-  // The expected properties to be returned from the POST /calls request.
-  var expectedCallsProperties = [ "sessionId", "sessionToken", "apiKey" ];
-
-  /**
-   * Loop server standalone client.
-   *
-   * @param {Object} settings Settings object.
-   */
-  function StandaloneClient(settings) {
-    settings = settings || {};
-    if (!settings.baseServerUrl) {
-      throw new Error("missing required baseServerUrl");
-    }
-
-    this.settings = settings;
-  }
-
-  StandaloneClient.prototype = {
-    /**
-     * Validates a data object to confirm it has the specified properties.
-     *
-     * @param  {Object} data        The data object to verify
-     * @param  {Array}  properties  The list of properties to verify within the object
-     * @return This returns either the specific property if only one
-     *         property is specified, or it returns all properties
-     */
-    _validate: function(data, properties) {
-      if (typeof data !== "object") {
-        throw new Error("Invalid data received from server");
-      }
-
-      properties.forEach(function (property) {
-        if (!data.hasOwnProperty(property)) {
-          throw new Error("Invalid data received from server - missing " +
-            property);
-        }
-      });
-
-      if (properties.length === 1) {
-        return data[properties[0]];
-      }
-
-      return data;
-    },
-
-    /**
-     * Generic handler for XHR failures.
-     *
-     * @param {Function} cb Callback(err)
-     * @param xhrReq
-     */
-    _failureHandler: function(cb, xhrReq) {
-      var jsonErr = JSON.parse(xhrReq.responseText && xhrReq.responseText || "{}");
-      var message = "HTTP " + xhrReq.status + " " + xhrReq.statusText;
-
-      // Logging the technical error to the console
-      console.error("Server error", message, jsonErr);
-
-      // Create an error with server error `errno` code attached as a property
-      var err = new Error(message);
-      err.errno = jsonErr.errno;
-
-      cb(err);
-    },
-
-    /**
-     * Makes a request for url creation date for standalone UI
-     *
-     * @param {String} loopToken The loopToken representing the call
-     * @param {Function} cb Callback(err, callUrlInfo)
-     *
-     **/
-    requestCallUrlInfo: function(loopToken, cb) {
-      if (!loopToken) {
-        throw new Error("Missing required parameter loopToken");
-      }
-      if (!cb) {
-        throw new Error("Missing required callback function");
-      }
-
-      var url = this.settings.baseServerUrl + "/calls/" + loopToken;
-      var xhrReq = new XMLHttpRequest();
-
-      xhrReq.open("GET", url, true);
-      xhrReq.setRequestHeader("Content-type", "application/json");
-
-      xhrReq.onload = function() {
-        var request = xhrReq;
-        var responseJSON = JSON.parse(request.responseText || null);
-
-        if (request.readyState === 4 && request.status >= 200 && request.status < 300) {
-          try {
-            cb(null, responseJSON);
-          } catch (err) {
-            console.error("Error requesting call info", err.message);
-            cb(err);
-          }
-        } else {
-          this._failureHandler(cb, request);
-        }
-      }.bind(this, xhrReq);
-
-      xhrReq.send();
-    },
-
-    /**
-     * Posts a call request to the server for a call represented by the
-     * loopToken. Will return the session data for the call.
-     *
-     * @param  {String} loopToken The loopToken representing the call
-     * @param  {String} callType The type of media in the call, e.g.
-     *                           "audio" or "audio-video"
-     * @param  {Function} cb Callback(err, sessionData)
-     */
-    requestCallInfo: function(loopToken, callType, cb) {
-      if (!loopToken) {
-        throw new Error("missing required parameter loopToken");
-      }
-
-      var url = this.settings.baseServerUrl + "/calls/" + loopToken;
-      var xhrReq = new XMLHttpRequest();
-
-      xhrReq.open("POST", url, true);
-      xhrReq.setRequestHeader("Content-type", "application/json");
-
-      xhrReq.onload = function() {
-        var request = xhrReq;
-        var responseJSON = JSON.parse(request.responseText || null);
-
-        if (request.readyState === 4 && request.status >= 200 && request.status < 300) {
-          try {
-            cb(null, this._validate(responseJSON, expectedCallsProperties));
-          } catch (err) {
-            console.error("Error requesting call info", err.message);
-            cb(err);
-          }
-        } else {
-          this._failureHandler(cb, request);
-        }
-      }.bind(this, xhrReq);
-
-      xhrReq.send(JSON.stringify({callType: callType, channel: "standalone"}));
-    }
-  };
-
-  return StandaloneClient;
-})();
--- a/browser/components/loop/standalone/content/js/webapp.js
+++ b/browser/components/loop/standalone/content/js/webapp.js
@@ -11,24 +11,21 @@ loop.webapp = (function(_, OT, mozL10n) 
 
   var sharedActions = loop.shared.actions;
   var sharedMixins = loop.shared.mixins;
   var sharedModels = loop.shared.models;
   var sharedViews = loop.shared.views;
   var sharedUtils = loop.shared.utils;
   var WEBSOCKET_REASONS = loop.shared.utils.WEBSOCKET_REASONS;
 
-  var multiplexGum = loop.standaloneMedia.multiplexGum;
-
   /**
    * Homepage view.
    */
   var HomeView = React.createClass({displayName: "HomeView",
     render: function() {
-      multiplexGum.reset();
       return (
         React.createElement("p", null, mozL10n.get("welcome", {clientShortname: mozL10n.get("clientShortname2")}))
       );
     }
   });
 
   /**
    * Unsupported Browsers view.
@@ -107,759 +104,28 @@ loop.webapp = (function(_, OT, mozL10n) 
             )
           )
         )
       );
     }
   });
 
   /**
-   * Expired call URL view.
-   */
-  var CallUrlExpiredView = React.createClass({displayName: "CallUrlExpiredView",
-    propTypes: {
-      isFirefox: React.PropTypes.bool.isRequired
-    },
-
-    render: function() {
-      return (
-        React.createElement("div", {className: "highlight-issue-box"}, 
-          React.createElement("div", {className: "info-panel"}, 
-            React.createElement("div", {className: "firefox-logo"}), 
-            React.createElement("h1", null, mozL10n.get("call_url_unavailable_notification_heading")), 
-            React.createElement("h4", null, mozL10n.get("call_url_unavailable_notification_message2"))
-          ), 
-          React.createElement(PromoteFirefoxView, {isFirefox: this.props.isFirefox})
-        )
-      );
-    }
-  });
-
-  var ConversationBranding = React.createClass({displayName: "ConversationBranding",
-    render: function() {
-      return (
-        React.createElement("h1", {className: "standalone-header-title"}, 
-          React.createElement("strong", null, mozL10n.get("clientShortname2"))
-        )
-      );
-    }
-  });
-
-  var ConversationHeader = React.createClass({displayName: "ConversationHeader",
-    propTypes: {
-      urlCreationDateString: React.PropTypes.string.isRequired
-    },
-
-    render: function() {
-      var cx = React.addons.classSet;
-      var conversationUrl = location.href;
-
-      var urlCreationDateClasses = cx({
-        "light-color-font": true,
-        "call-url-date": true, /* Used as a handler in the tests */
-        // Hidden until date is available.
-        "hide": !this.props.urlCreationDateString.length
-      });
-
-      var callUrlCreationDateString = mozL10n.get("call_url_creation_date_label", {
-        "call_url_creation_date": this.props.urlCreationDateString
-      });
-
-      return (
-        React.createElement("header", {className: "standalone-header header-box container-box"}, 
-          React.createElement(ConversationBranding, null), 
-          React.createElement("div", {className: "loop-logo", 
-               title: mozL10n.get("client_alttext",
-                                  {clientShortname: mozL10n.get("clientShortname2")})}), 
-          React.createElement("h3", {className: "call-url"}, 
-            conversationUrl
-          ), 
-          React.createElement("h4", {className: urlCreationDateClasses}, 
-            callUrlCreationDateString
-          )
-        )
-      );
-    }
-  });
-
-  var ConversationFooter = React.createClass({displayName: "ConversationFooter",
-    render: function() {
-      return (
-        React.createElement("div", {className: "standalone-footer container-box"}, 
-          React.createElement("div", {className: "footer-logo", 
-               title: mozL10n.get("vendor_alttext",
-                                  {vendorShortname: mozL10n.get("vendorShortname")})}), 
-          React.createElement("div", {className: "footer-external-links"}, 
-            React.createElement("a", {href: loop.config.generalSupportUrl, target: "_blank"}, 
-              mozL10n.get("support_link")
-            )
-          )
-        )
-      );
-    }
-  });
-
-  /**
-   * A view for when conversations are pending, displays any messages
-   * and an option cancel button.
-   */
-  var PendingConversationView = React.createClass({displayName: "PendingConversationView",
-    propTypes: {
-      callState: React.PropTypes.string.isRequired,
-      // If not supplied, the cancel button is not displayed.
-      cancelCallback: React.PropTypes.func
-    },
-
-    render: function() {
-      var cancelButtonClasses = React.addons.classSet({
-        btn: true,
-        "btn-large": true,
-        "btn-cancel": true,
-        hide: !this.props.cancelCallback
-      });
-
-      return (
-        React.createElement("div", {className: "container"}, 
-          React.createElement("div", {className: "container-box"}, 
-            React.createElement("header", {className: "pending-header header-box"}, 
-              React.createElement(ConversationBranding, null)
-            ), 
-
-            React.createElement("div", {id: "cameraPreview"}), 
-
-            React.createElement("div", {id: "messages"}), 
-
-            React.createElement("p", {className: "standalone-btn-label"}, 
-              this.props.callState
-            ), 
-
-            React.createElement("div", {className: "btn-pending-cancel-group btn-group"}, 
-              React.createElement("div", {className: "flex-padding-1"}), 
-              React.createElement("button", {className: cancelButtonClasses, 
-                      onClick: this.props.cancelCallback}, 
-                React.createElement("span", {className: "standalone-call-btn-text"}, 
-                  mozL10n.get("initiate_call_cancel_button")
-                )
-              ), 
-              React.createElement("div", {className: "flex-padding-1"})
-            )
-          ), 
-          React.createElement(ConversationFooter, null)
-        )
-      );
-    }
-  });
-
-  /**
-   * View displayed whilst the get user media prompt is being displayed. Indicates
-   * to the user to accept the prompt.
-   */
-  var GumPromptConversationView = React.createClass({displayName: "GumPromptConversationView",
-    render: function() {
-      var callState = mozL10n.get("call_progress_getting_media_description", {
-        clientShortname: mozL10n.get("clientShortname2")
-      });
-      document.title = mozL10n.get("standalone_title_with_status", {
-        clientShortname: mozL10n.get("clientShortname2"),
-        currentStatus: mozL10n.get("call_progress_getting_media_title")
-      });
-
-      return React.createElement(PendingConversationView, {callState: callState});
-    }
-  });
-
-  /**
-   * View displayed waiting for a call to be connected. Updates the display
-   * once the websocket shows that the callee is being alerted.
-   */
-  var WaitingConversationView = React.createClass({displayName: "WaitingConversationView",
-    mixins: [sharedMixins.AudioMixin],
-
-    getInitialState: function() {
-      return {
-        callState: "connecting"
-      };
-    },
-
-    propTypes: {
-      websocket: React.PropTypes.instanceOf(loop.CallConnectionWebSocket)
-                      .isRequired
-    },
-
-    componentDidMount: function() {
-      this.play("connecting", {loop: true});
-      this.props.websocket.listenTo(this.props.websocket, "progress:alerting",
-                                    this._handleRingingProgress);
-    },
-
-    _handleRingingProgress: function() {
-      this.play("ringtone", {loop: true});
-      this.setState({callState: "ringing"});
-    },
-
-    _cancelOutgoingCall: function() {
-      multiplexGum.reset();
-      this.props.websocket.cancel();
-    },
-
-    render: function() {
-      var callStateStringEntityName = "call_progress_" + this.state.callState + "_description";
-      var callState = mozL10n.get(callStateStringEntityName);
-      document.title = mozL10n.get("standalone_title_with_status",
-                                   {clientShortname: mozL10n.get("clientShortname2"),
-                                    currentStatus: mozL10n.get(callStateStringEntityName)});
-
-      return (
-        React.createElement(PendingConversationView, {
-          callState: callState, 
-          cancelCallback: this._cancelOutgoingCall})
-      );
-    }
-  });
-
-  var InitiateCallButton = React.createClass({displayName: "InitiateCallButton",
-    mixins: [sharedMixins.DropdownMenuMixin()],
-
-    propTypes: {
-      caption: React.PropTypes.string.isRequired,
-      disabled: React.PropTypes.bool,
-      startCall: React.PropTypes.func.isRequired
-    },
-
-    getDefaultProps: function() {
-      return {disabled: false};
-    },
-
-    render: function() {
-      var dropdownMenuClasses = React.addons.classSet({
-        "native-dropdown-large-parent": true,
-        "standalone-dropdown-menu": true,
-        "visually-hidden": !this.state.showMenu
-      });
-      var chevronClasses = React.addons.classSet({
-        "btn-chevron": true,
-        "disabled": this.props.disabled
-      });
-      return (
-        React.createElement("div", {className: "standalone-btn-chevron-menu-group"}, 
-          React.createElement("div", {className: "btn-group-chevron"}, 
-            React.createElement("div", {className: "btn-group"}, 
-              React.createElement("button", {className: "btn btn-constrained btn-large btn-accept", 
-                      disabled: this.props.disabled, 
-                      onClick: this.props.startCall("audio-video"), 
-                      title: mozL10n.get("initiate_audio_video_call_tooltip2")}, 
-                React.createElement("span", {className: "standalone-call-btn-text"}, 
-                  this.props.caption
-                ), 
-                React.createElement("span", {className: "standalone-call-btn-video-icon"})
-              ), 
-              React.createElement("div", {className: chevronClasses, 
-                   onClick: this.toggleDropdownMenu}
-              )
-            ), 
-            React.createElement("ul", {className: dropdownMenuClasses}, 
-              React.createElement("li", null, 
-                React.createElement("button", {className: "start-audio-only-call", 
-                        disabled: this.props.disabled, 
-                        onClick: this.props.startCall("audio")}, 
-                  mozL10n.get("initiate_audio_call_button2")
-                )
-              )
-            )
-          )
-        )
-      );
-    }
-  });
-
-  /**
-   * Initiate conversation view.
-   */
-  var InitiateConversationView = React.createClass({displayName: "InitiateConversationView",
-    mixins: [Backbone.Events],
-
-    propTypes: {
-      callButtonLabel: React.PropTypes.string.isRequired,
-      client: React.PropTypes.object.isRequired,
-      conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel).isRequired,
-      // XXX Check more tightly here when we start injecting window.loop.*
-      notifications: React.PropTypes.object.isRequired,
-      title: React.PropTypes.string.isRequired
-    },
-
-    getInitialState: function() {
-      return {
-        urlCreationDateString: "",
-        disableCallButton: false
-      };
-    },
-
-    componentDidMount: function() {
-      this.listenTo(this.props.conversation,
-                    "session:error", this._onSessionError);
-      this.props.client.requestCallUrlInfo(
-        this.props.conversation.get("loopToken"),
-        this._setConversationTimestamp);
-    },
-
-    componentWillUnmount: function() {
-      this.stopListening(this.props.conversation);
-      localStorage.setItem("has-seen-tos", "true");
-    },
-
-    _onSessionError: function(error, l10nProps) {
-      var errorL10n = error || "unable_retrieve_call_info";
-      this.props.notifications.errorL10n(errorL10n, l10nProps);
-      console.error(errorL10n);
-    },
-
-    /**
-     * Initiates the call.
-     * Takes in a call type parameter "audio" or "audio-video" and returns
-     * a function that initiates the call. React click handler requires a function
-     * to be called when that event happenes.
-     *
-     * @param {string} User call type choice "audio" or "audio-video"
-     */
-    startCall: function(callType) {
-      return function() {
-        this.props.conversation.setupOutgoingCall(callType);
-        this.setState({disableCallButton: true});
-      }.bind(this);
-    },
-
-    _setConversationTimestamp: function(err, callUrlInfo) {
-      if (err) {
-        this.props.notifications.errorL10n("unable_retrieve_call_info");
-      } else {
-        this.setState({
-          urlCreationDateString: sharedUtils.formatDate(callUrlInfo.urlCreationDate)
-        });
-      }
-    },
-
-    render: function() {
-      var tosLinkName = mozL10n.get("terms_of_use_link_text");
-      var privacyNoticeName = mozL10n.get("privacy_notice_link_text");
-
-      var tosHTML = mozL10n.get("legal_text_and_links", {
-        "clientShortname": mozL10n.get("clientShortname2"),
-        "terms_of_use_url": "<a target=_blank href='" +
-          loop.config.legalWebsiteUrl + "'>" +
-          tosLinkName + "</a>",
-        "privacy_notice_url": "<a target=_blank href='" +
-          loop.config.privacyWebsiteUrl + "'>" + privacyNoticeName + "</a>"
-      });
-
-      var tosClasses = React.addons.classSet({
-        "terms-service": true,
-        hide: (localStorage.getItem("has-seen-tos") === "true")
-      });
-
-      return (
-        React.createElement("div", {className: "container"}, 
-          React.createElement("div", {className: "container-box"}, 
-
-            React.createElement(ConversationHeader, {
-              urlCreationDateString: this.state.urlCreationDateString}), 
-
-            React.createElement("p", {className: "standalone-btn-label"}, 
-              this.props.title
-            ), 
-
-            React.createElement("div", {id: "messages"}), 
-
-            React.createElement("div", {className: "btn-group"}, 
-              React.createElement("div", {className: "flex-padding-1"}), 
-              React.createElement(InitiateCallButton, {
-                caption: this.props.callButtonLabel, 
-                disabled: this.state.disableCallButton, 
-                startCall: this.startCall}
-              ), 
-              React.createElement("div", {className: "flex-padding-1"})
-            ), 
-
-            React.createElement("p", {className: tosClasses, 
-               dangerouslySetInnerHTML: {__html: tosHTML}})
-          ), 
-
-          React.createElement(ConversationFooter, null)
-        )
-      );
-    }
-  });
-
-  /**
-   * Ended conversation view.
-   */
-  var EndedConversationView = React.createClass({displayName: "EndedConversationView",
-    propTypes: {
-      conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
-                         .isRequired,
-      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
-      onAfterFeedbackReceived: React.PropTypes.func.isRequired,
-      sdk: React.PropTypes.object.isRequired
-    },
-
-    render: function() {
-      document.title = mozL10n.get("standalone_title_with_status",
-                                   {clientShortname: mozL10n.get("clientShortname2"),
-                                    currentStatus: mozL10n.get("status_conversation_ended")});
-      return (
-        React.createElement("div", {className: "ended-conversation"}, 
-          React.createElement(sharedViews.ConversationView, {
-            audio: {enabled: false, visible: false}, 
-            dispatcher: this.props.dispatcher, 
-            initiate: false, 
-            model: this.props.conversation, 
-            sdk: this.props.sdk, 
-            video: {enabled: false, visible: false}})
-        )
-      );
-    }
-  });
-
-  var StartConversationView = React.createClass({displayName: "StartConversationView",
-    render: function() {
-      document.title = mozL10n.get("clientShortname2");
-      return (
-        React.createElement(InitiateConversationView, React.__spread({}, 
-          this.props, 
-          {callButtonLabel: mozL10n.get("initiate_audio_video_call_button2"), 
-          title: mozL10n.get("initiate_call_button_label2")}))
-      );
-    }
-  });
-
-  var FailedConversationView = React.createClass({displayName: "FailedConversationView",
-    mixins: [sharedMixins.AudioMixin],
-
-    componentDidMount: function() {
-      this.play("failure");
-    },
-
-    render: function() {
-      document.title = mozL10n.get("standalone_title_with_status",
-                                   {clientShortname: mozL10n.get("clientShortname2"),
-                                    currentStatus: mozL10n.get("status_error")});
-      return (
-        React.createElement(InitiateConversationView, React.__spread({}, 
-          this.props, 
-          {callButtonLabel: mozL10n.get("retry_call_button"), 
-          title: mozL10n.get("call_failed_title")}))
-      );
-    }
-  });
-
-  /**
-   * This view manages the outgoing conversation views - from
-   * call initiation through to the actual conversation and call end.
-   *
-   * At the moment, it does more than that, these parts need refactoring out.
-   */
-  var OutgoingConversationView = React.createClass({displayName: "OutgoingConversationView",
-    propTypes: {
-      client: React.PropTypes.instanceOf(loop.StandaloneClient).isRequired,
-      conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel).isRequired,
-      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
-      isFirefox: React.PropTypes.bool.isRequired,
-      notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection).isRequired,
-      sdk: React.PropTypes.object.isRequired
-    },
-
-    getInitialState: function() {
-      return {
-        callStatus: "start"
-      };
-    },
-
-    componentDidMount: function() {
-      this.props.conversation.on("call:outgoing", this.startCall, this);
-      this.props.conversation.on("call:outgoing:get-media-privs", this.getMediaPrivs, this);
-      this.props.conversation.on("call:outgoing:setup", this.setupOutgoingCall, this);
-      this.props.conversation.on("change:publishedStream", this._checkConnected, this);
-      this.props.conversation.on("change:subscribedStream", this._checkConnected, this);
-      this.props.conversation.on("session:ended", this._endCall, this);
-      this.props.conversation.on("session:peer-hungup", this._onPeerHungup, this);
-      this.props.conversation.on("session:network-disconnected", this._onNetworkDisconnected, this);
-      this.props.conversation.on("session:connection-error", this._notifyError, this);
-    },
-
-    componentDidUnmount: function() {
-      this.props.conversation.off(null, null, this);
-    },
-
-    shouldComponentUpdate: function(nextProps, nextState) {
-      // Only rerender if current state has actually changed
-      return nextState.callStatus !== this.state.callStatus;
-    },
-
-    resetCallStatus: function() {
-      return function() {
-        this.setState({callStatus: "start"});
-      }.bind(this);
-    },
-
-    /**
-     * Renders the conversation views.
-     */
-    render: function() {
-      switch (this.state.callStatus) {
-        case "start": {
-          return (
-            React.createElement(StartConversationView, {
-              client: this.props.client, 
-              conversation: this.props.conversation, 
-              notifications: this.props.notifications})
-          );
-        }
-        case "failure": {
-          return (
-            React.createElement(FailedConversationView, {
-              client: this.props.client, 
-              conversation: this.props.conversation, 
-              notifications: this.props.notifications})
-          );
-        }
-        case "gumPrompt": {
-          return React.createElement(GumPromptConversationView, null);
-        }
-        case "pending": {
-          return React.createElement(WaitingConversationView, {websocket: this._websocket});
-        }
-        case "connected": {
-          document.title = mozL10n.get("standalone_title_with_status",
-                                       {clientShortname: mozL10n.get("clientShortname2"),
-                                        currentStatus: mozL10n.get("status_in_conversation")});
-          return (
-            React.createElement(sharedViews.ConversationView, {
-              dispatcher: this.props.dispatcher, 
-              initiate: true, 
-              model: this.props.conversation, 
-              sdk: this.props.sdk, 
-              video: {enabled: this.props.conversation.hasVideoStream("outgoing")}})
-          );
-        }
-        case "end": {
-          return (
-            React.createElement(EndedConversationView, {
-              conversation: this.props.conversation, 
-              dispatcher: this.props.dispatcher, 
-              onAfterFeedbackReceived: this.resetCallStatus(), 
-              sdk: this.props.sdk})
-          );
-        }
-        case "expired": {
-          return (
-            React.createElement(CallUrlExpiredView, {isFirefox: this.props.isFirefox})
-          );
-        }
-        default: {
-          return React.createElement(HomeView, null);
-        }
-      }
-    },
-
-    /**
-     * Notify the user that the connection was not possible
-     * @param {{code: number, message: string}} error
-     */
-    _notifyError: function(error) {
-      console.error(error);
-      this.props.notifications.errorL10n("connection_error_see_console_notification");
-      this.setState({callStatus: "end"});
-    },
-
-    /**
-     * Peer hung up. Notifies the user and ends the call.
-     *
-     * Event properties:
-     * - {String} connectionId: OT session id
-     */
-    _onPeerHungup: function() {
-      this.props.notifications.warnL10n("peer_ended_conversation2");
-      this.setState({callStatus: "end"});
-    },
-
-    /**
-     * Network disconnected. Notifies the user and ends the call.
-     */
-    _onNetworkDisconnected: function() {
-      this.props.notifications.warnL10n("network_disconnected");
-      this.setState({callStatus: "end"});
-    },
-
-    /**
-     * Starts the set up of a call, obtaining the required information from the
-     * server.
-     */
-    setupOutgoingCall: function() {
-      var loopToken = this.props.conversation.get("loopToken");
-      if (!loopToken) {
-        this.props.notifications.errorL10n("missing_conversation_info");
-        this.setState({callStatus: "failure"});
-      } else {
-        var callType = this.props.conversation.get("selectedCallType");
-
-        this.props.client.requestCallInfo(this.props.conversation.get("loopToken"),
-                                          callType, function(err, sessionData) {
-          if (err) {
-            switch (err.errno) {
-              // loop-server sends 404 + INVALID_TOKEN (errno 105) whenever a token is
-              // missing OR expired; we treat this information as if the url is always
-              // expired.
-              case 105:
-                this.setState({callStatus: "expired"});
-                break;
-              default:
-                this.props.notifications.errorL10n("missing_conversation_info");
-                this.setState({callStatus: "failure"});
-                break;
-            }
-            return;
-          }
-          this.props.conversation.outgoing(sessionData);
-        }.bind(this));
-      }
-    },
-
-    /**
-     * Asks the user for the media privileges, handling the result appropriately.
-     */
-    getMediaPrivs: function() {
-      this.setState({callStatus: "gumPrompt"});
-      multiplexGum.getPermsAndCacheMedia({audio: true, video: true},
-        function(localStream) {
-          this.props.conversation.gotMediaPrivs();
-        }.bind(this),
-        function(errorCode) {
-          multiplexGum.reset();
-          this.setState({callStatus: "failure"});
-        }.bind(this)
-      );
-    },
-
-    /**
-     * Actually starts the call.
-     */
-    startCall: function() {
-      var loopToken = this.props.conversation.get("loopToken");
-      if (!loopToken) {
-        this.props.notifications.errorL10n("missing_conversation_info");
-        this.setState({callStatus: "failure"});
-        return;
-      }
-
-      this._setupWebSocket();
-      this.setState({callStatus: "pending"});
-    },
-
-    /**
-     * Used to set up the web socket connection and navigate to the
-     * call view if appropriate.
-     *
-     * @param {string} loopToken The session token to use.
-     */
-    _setupWebSocket: function() {
-      this._websocket = new loop.CallConnectionWebSocket({
-        url: this.props.conversation.get("progressURL"),
-        websocketToken: this.props.conversation.get("websocketToken"),
-        callId: this.props.conversation.get("callId")
-      });
-      this._websocket.promiseConnect().then(function() {
-      }.bind(this), function() {
-        // XXX Not the ideal response, but bug 1047410 will be replacing
-        // this by better "call failed" UI.
-        this.props.notifications.errorL10n("cannot_start_call_session_not_ready");
-        return;
-      }.bind(this));
-
-      this._websocket.on("progress", this._handleWebSocketProgress, this);
-    },
-
-    /**
-     * Checks if the streams have been connected, and notifies the
-     * websocket that the media is now connected.
-     */
-    _checkConnected: function() {
-      // Check we've had both local and remote streams connected before
-      // sending the media up message.
-      if (this.props.conversation.streamsConnected()) {
-        this._websocket.mediaUp();
-      }
-    },
-
-    /**
-     * Used to receive websocket progress and to determine how to handle
-     * it if appropraite.
-     */
-    _handleWebSocketProgress: function(progressData) {
-      switch(progressData.state) {
-        case "connecting": {
-          // We just go straight to the connected view as the media gets set up.
-          this.setState({callStatus: "connected"});
-          break;
-        }
-        case "terminated": {
-          // At the moment, we show the same text regardless
-          // of the terminated reason.
-          this._handleCallTerminated(progressData.reason);
-          break;
-        }
-      }
-    },
-
-    /**
-     * Handles call rejection.
-     *
-     * @param {String} reason The reason the call was terminated (reject, busy,
-     *                        timeout, cancel, media-fail, user-unknown, closed)
-     */
-    _handleCallTerminated: function(reason) {
-      multiplexGum.reset();
-
-      if (reason === WEBSOCKET_REASONS.CANCEL) {
-        this.setState({callStatus: "start"});
-        return;
-      }
-      // XXX later, we'll want to display more meaningfull messages (needs UX)
-      this.props.notifications.errorL10n("call_timeout_notification_text");
-      this.setState({callStatus: "failure"});
-    },
-
-    /**
-     * Handles ending a call by resetting the view to the start state.
-     */
-    _endCall: function() {
-      multiplexGum.reset();
-
-      if (this.state.callStatus !== "failure") {
-        this.setState({callStatus: "end"});
-      }
-    }
-  });
-
-  /**
    * Webapp Root View. This is the main, single, view that controls the display
    * of the webapp page.
    */
   var WebappRootView = React.createClass({displayName: "WebappRootView",
 
     mixins: [sharedMixins.UrlHashChangeMixin,
              sharedMixins.DocumentLocationMixin,
              Backbone.Events],
 
     propTypes: {
       activeRoomStore: React.PropTypes.instanceOf(loop.store.ActiveRoomStore).isRequired,
-      client: React.PropTypes.instanceOf(loop.StandaloneClient).isRequired,
-      conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel).isRequired,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
-      notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
-                          .isRequired,
-      sdk: React.PropTypes.object.isRequired,
       standaloneAppStore: React.PropTypes.instanceOf(
         loop.store.StandaloneAppStore).isRequired
     },
 
     getInitialState: function() {
       return this.props.standaloneAppStore.getStoreState();
     },
 
@@ -880,27 +146,16 @@ loop.webapp = (function(_, OT, mozL10n) 
     render: function() {
       switch (this.state.windowType) {
         case "unsupportedDevice": {
           return React.createElement(UnsupportedDeviceView, {platform: this.state.unsupportedPlatform});
         }
         case "unsupportedBrowser": {
           return React.createElement(UnsupportedBrowserView, {isFirefox: this.state.isFirefox});
         }
-        case "outgoing": {
-          return (
-            React.createElement(OutgoingConversationView, {
-               client: this.props.client, 
-               conversation: this.props.conversation, 
-               dispatcher: this.props.dispatcher, 
-               isFirefox: this.state.isFirefox, 
-               notifications: this.props.notifications, 
-               sdk: this.props.sdk})
-          );
-        }
         case "room": {
           return (
             React.createElement(loop.standaloneRoomViews.StandaloneRoomView, {
               activeRoomStore: this.props.activeRoomStore, 
               dispatcher: this.props.dispatcher, 
               isFirefox: this.state.isFirefox})
           );
         }
@@ -919,44 +174,34 @@ loop.webapp = (function(_, OT, mozL10n) 
   /**
    * App initialization.
    */
   function init() {
     var standaloneMozLoop = new loop.StandaloneMozLoop({
       baseServerUrl: loop.config.serverUrl
     });
 
-    // Older non-flux based items.
-    var notifications = new sharedModels.NotificationCollection();
-
     // New flux items.
     var dispatcher = new loop.Dispatcher();
-    var client = new loop.StandaloneClient({
-      baseServerUrl: loop.config.serverUrl
-    });
     var sdkDriver = new loop.OTSdkDriver({
       // For the standalone, always request data channels. If they aren't
       // implemented on the client, there won't be a similar message to us, and
       // we won't display the UI.
       useDataChannels: true,
       dispatcher: dispatcher,
       sdk: OT
     });
 
-    var conversation = new sharedModels.ConversationModel({}, {
-        sdk: OT
-    });
     var activeRoomStore = new loop.store.ActiveRoomStore(dispatcher, {
         mozLoop: standaloneMozLoop,
         sdkDriver: sdkDriver
     });
 
     // Stores
     var standaloneAppStore = new loop.store.StandaloneAppStore({
-      conversation: conversation,
       dispatcher: dispatcher,
       sdk: OT
     });
     var standaloneMetricsStore = new loop.store.StandaloneMetricsStore(dispatcher, {
       activeRoomStore: activeRoomStore
     });
     var textChatStore = new loop.store.TextChatStore(dispatcher, {
       sdkDriver: sdkDriver
@@ -971,45 +216,33 @@ loop.webapp = (function(_, OT, mozL10n) 
     });
 
     window.addEventListener("unload", function() {
       dispatcher.dispatch(new sharedActions.WindowUnload());
     });
 
     React.render(React.createElement(WebappRootView, {
       activeRoomStore: activeRoomStore, 
-      client: client, 
-      conversation: conversation, 
       dispatcher: dispatcher, 
-      notifications: notifications, 
-      sdk: OT, 
       standaloneAppStore: standaloneAppStore}), document.querySelector("#main"));
 
     // Set the 'lang' and 'dir' attributes to <html> when the page is translated
     document.documentElement.lang = mozL10n.language.code;
     document.documentElement.dir = mozL10n.language.direction;
     document.title = mozL10n.get("clientShortname2");
 
     var locationData = sharedUtils.locationData();
 
     dispatcher.dispatch(new sharedActions.ExtractTokenInfo({
       windowPath: locationData.pathname,
       windowHash: locationData.hash
     }));
   }
 
   return {
-    CallUrlExpiredView: CallUrlExpiredView,
-    PendingConversationView: PendingConversationView,
-    GumPromptConversationView: GumPromptConversationView,
-    WaitingConversationView: WaitingConversationView,
-    StartConversationView: StartConversationView,
-    FailedConversationView: FailedConversationView,
-    OutgoingConversationView: OutgoingConversationView,
-    EndedConversationView: EndedConversationView,
     HomeView: HomeView,
     UnsupportedBrowserView: UnsupportedBrowserView,
     UnsupportedDeviceView: UnsupportedDeviceView,
     init: init,
     PromoteFirefoxView: PromoteFirefoxView,
     WebappRootView: WebappRootView
   };
 })(_, window.OT, navigator.mozL10n);
--- a/browser/components/loop/standalone/content/js/webapp.jsx
+++ b/browser/components/loop/standalone/content/js/webapp.jsx
@@ -11,24 +11,21 @@ loop.webapp = (function(_, OT, mozL10n) 
 
   var sharedActions = loop.shared.actions;
   var sharedMixins = loop.shared.mixins;
   var sharedModels = loop.shared.models;
   var sharedViews = loop.shared.views;
   var sharedUtils = loop.shared.utils;
   var WEBSOCKET_REASONS = loop.shared.utils.WEBSOCKET_REASONS;
 
-  var multiplexGum = loop.standaloneMedia.multiplexGum;
-
   /**
    * Homepage view.
    */
   var HomeView = React.createClass({
     render: function() {
-      multiplexGum.reset();
       return (
         <p>{mozL10n.get("welcome", {clientShortname: mozL10n.get("clientShortname2")})}</p>
       );
     }
   });
 
   /**
    * Unsupported Browsers view.
@@ -107,759 +104,28 @@ loop.webapp = (function(_, OT, mozL10n) 
             </a>
           </p>
         </div>
       );
     }
   });
 
   /**
-   * Expired call URL view.
-   */
-  var CallUrlExpiredView = React.createClass({
-    propTypes: {
-      isFirefox: React.PropTypes.bool.isRequired
-    },
-
-    render: function() {
-      return (
-        <div className="highlight-issue-box">
-          <div className="info-panel">
-            <div className="firefox-logo" />
-            <h1>{mozL10n.get("call_url_unavailable_notification_heading")}</h1>
-            <h4>{mozL10n.get("call_url_unavailable_notification_message2")}</h4>
-          </div>
-          <PromoteFirefoxView isFirefox={this.props.isFirefox}/>
-        </div>
-      );
-    }
-  });
-
-  var ConversationBranding = React.createClass({
-    render: function() {
-      return (
-        <h1 className="standalone-header-title">
-          <strong>{mozL10n.get("clientShortname2")}</strong>
-        </h1>
-      );
-    }
-  });
-
-  var ConversationHeader = React.createClass({
-    propTypes: {
-      urlCreationDateString: React.PropTypes.string.isRequired
-    },
-
-    render: function() {
-      var cx = React.addons.classSet;
-      var conversationUrl = location.href;
-
-      var urlCreationDateClasses = cx({
-        "light-color-font": true,
-        "call-url-date": true, /* Used as a handler in the tests */
-        // Hidden until date is available.
-        "hide": !this.props.urlCreationDateString.length
-      });
-
-      var callUrlCreationDateString = mozL10n.get("call_url_creation_date_label", {
-        "call_url_creation_date": this.props.urlCreationDateString
-      });
-
-      return (
-        <header className="standalone-header header-box container-box">
-          <ConversationBranding />
-          <div className="loop-logo"
-               title={mozL10n.get("client_alttext",
-                                  {clientShortname: mozL10n.get("clientShortname2")})}></div>
-          <h3 className="call-url">
-            {conversationUrl}
-          </h3>
-          <h4 className={urlCreationDateClasses}>
-            {callUrlCreationDateString}
-          </h4>
-        </header>
-      );
-    }
-  });
-
-  var ConversationFooter = React.createClass({
-    render: function() {
-      return (
-        <div className="standalone-footer container-box">
-          <div className="footer-logo"
-               title={mozL10n.get("vendor_alttext",
-                                  {vendorShortname: mozL10n.get("vendorShortname")})} />
-          <div className="footer-external-links">
-            <a href={loop.config.generalSupportUrl} target="_blank">
-              {mozL10n.get("support_link")}
-            </a>
-          </div>
-        </div>
-      );
-    }
-  });
-
-  /**
-   * A view for when conversations are pending, displays any messages
-   * and an option cancel button.
-   */
-  var PendingConversationView = React.createClass({
-    propTypes: {
-      callState: React.PropTypes.string.isRequired,
-      // If not supplied, the cancel button is not displayed.
-      cancelCallback: React.PropTypes.func
-    },
-
-    render: function() {
-      var cancelButtonClasses = React.addons.classSet({
-        btn: true,
-        "btn-large": true,
-        "btn-cancel": true,
-        hide: !this.props.cancelCallback
-      });
-
-      return (
-        <div className="container">
-          <div className="container-box">
-            <header className="pending-header header-box">
-              <ConversationBranding />
-            </header>
-
-            <div id="cameraPreview" />
-
-            <div id="messages" />
-
-            <p className="standalone-btn-label">
-              {this.props.callState}
-            </p>
-
-            <div className="btn-pending-cancel-group btn-group">
-              <div className="flex-padding-1" />
-              <button className={cancelButtonClasses}
-                      onClick={this.props.cancelCallback} >
-                <span className="standalone-call-btn-text">
-                  {mozL10n.get("initiate_call_cancel_button")}
-                </span>
-              </button>
-              <div className="flex-padding-1" />
-            </div>
-          </div>
-          <ConversationFooter />
-        </div>
-      );
-    }
-  });
-
-  /**
-   * View displayed whilst the get user media prompt is being displayed. Indicates
-   * to the user to accept the prompt.
-   */
-  var GumPromptConversationView = React.createClass({
-    render: function() {
-      var callState = mozL10n.get("call_progress_getting_media_description", {
-        clientShortname: mozL10n.get("clientShortname2")
-      });
-      document.title = mozL10n.get("standalone_title_with_status", {
-        clientShortname: mozL10n.get("clientShortname2"),
-        currentStatus: mozL10n.get("call_progress_getting_media_title")
-      });
-
-      return <PendingConversationView callState={callState}/>;
-    }
-  });
-
-  /**
-   * View displayed waiting for a call to be connected. Updates the display
-   * once the websocket shows that the callee is being alerted.
-   */
-  var WaitingConversationView = React.createClass({
-    mixins: [sharedMixins.AudioMixin],
-
-    getInitialState: function() {
-      return {
-        callState: "connecting"
-      };
-    },
-
-    propTypes: {
-      websocket: React.PropTypes.instanceOf(loop.CallConnectionWebSocket)
-                      .isRequired
-    },
-
-    componentDidMount: function() {
-      this.play("connecting", {loop: true});
-      this.props.websocket.listenTo(this.props.websocket, "progress:alerting",
-                                    this._handleRingingProgress);
-    },
-
-    _handleRingingProgress: function() {
-      this.play("ringtone", {loop: true});
-      this.setState({callState: "ringing"});
-    },
-
-    _cancelOutgoingCall: function() {
-      multiplexGum.reset();
-      this.props.websocket.cancel();
-    },
-
-    render: function() {
-      var callStateStringEntityName = "call_progress_" + this.state.callState + "_description";
-      var callState = mozL10n.get(callStateStringEntityName);
-      document.title = mozL10n.get("standalone_title_with_status",
-                                   {clientShortname: mozL10n.get("clientShortname2"),
-                                    currentStatus: mozL10n.get(callStateStringEntityName)});
-
-      return (
-        <PendingConversationView
-          callState={callState}
-          cancelCallback={this._cancelOutgoingCall} />
-      );
-    }
-  });
-
-  var InitiateCallButton = React.createClass({
-    mixins: [sharedMixins.DropdownMenuMixin()],
-
-    propTypes: {
-      caption: React.PropTypes.string.isRequired,
-      disabled: React.PropTypes.bool,
-      startCall: React.PropTypes.func.isRequired
-    },
-
-    getDefaultProps: function() {
-      return {disabled: false};
-    },
-
-    render: function() {
-      var dropdownMenuClasses = React.addons.classSet({
-        "native-dropdown-large-parent": true,
-        "standalone-dropdown-menu": true,
-        "visually-hidden": !this.state.showMenu
-      });
-      var chevronClasses = React.addons.classSet({
-        "btn-chevron": true,
-        "disabled": this.props.disabled
-      });
-      return (
-        <div className="standalone-btn-chevron-menu-group">
-          <div className="btn-group-chevron">
-            <div className="btn-group">
-              <button className="btn btn-constrained btn-large btn-accept"
-                      disabled={this.props.disabled}
-                      onClick={this.props.startCall("audio-video")}
-                      title={mozL10n.get("initiate_audio_video_call_tooltip2")}>
-                <span className="standalone-call-btn-text">
-                  {this.props.caption}
-                </span>
-                <span className="standalone-call-btn-video-icon" />
-              </button>
-              <div className={chevronClasses}
-                   onClick={this.toggleDropdownMenu}>
-              </div>
-            </div>
-            <ul className={dropdownMenuClasses}>
-              <li>
-                <button className="start-audio-only-call"
-                        disabled={this.props.disabled}
-                        onClick={this.props.startCall("audio")}>
-                  {mozL10n.get("initiate_audio_call_button2")}
-                </button>
-              </li>
-            </ul>
-          </div>
-        </div>
-      );
-    }
-  });
-
-  /**
-   * Initiate conversation view.
-   */
-  var InitiateConversationView = React.createClass({
-    mixins: [Backbone.Events],
-
-    propTypes: {
-      callButtonLabel: React.PropTypes.string.isRequired,
-      client: React.PropTypes.object.isRequired,
-      conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel).isRequired,
-      // XXX Check more tightly here when we start injecting window.loop.*
-      notifications: React.PropTypes.object.isRequired,
-      title: React.PropTypes.string.isRequired
-    },
-
-    getInitialState: function() {
-      return {
-        urlCreationDateString: "",
-        disableCallButton: false
-      };
-    },
-
-    componentDidMount: function() {
-      this.listenTo(this.props.conversation,
-                    "session:error", this._onSessionError);
-      this.props.client.requestCallUrlInfo(
-        this.props.conversation.get("loopToken"),
-        this._setConversationTimestamp);
-    },
-
-    componentWillUnmount: function() {
-      this.stopListening(this.props.conversation);
-      localStorage.setItem("has-seen-tos", "true");
-    },
-
-    _onSessionError: function(error, l10nProps) {
-      var errorL10n = error || "unable_retrieve_call_info";
-      this.props.notifications.errorL10n(errorL10n, l10nProps);
-      console.error(errorL10n);
-    },
-
-    /**
-     * Initiates the call.
-     * Takes in a call type parameter "audio" or "audio-video" and returns
-     * a function that initiates the call. React click handler requires a function
-     * to be called when that event happenes.
-     *
-     * @param {string} User call type choice "audio" or "audio-video"
-     */
-    startCall: function(callType) {
-      return function() {
-        this.props.conversation.setupOutgoingCall(callType);
-        this.setState({disableCallButton: true});
-      }.bind(this);
-    },
-
-    _setConversationTimestamp: function(err, callUrlInfo) {
-      if (err) {
-        this.props.notifications.errorL10n("unable_retrieve_call_info");
-      } else {
-        this.setState({
-          urlCreationDateString: sharedUtils.formatDate(callUrlInfo.urlCreationDate)
-        });
-      }
-    },
-
-    render: function() {
-      var tosLinkName = mozL10n.get("terms_of_use_link_text");
-      var privacyNoticeName = mozL10n.get("privacy_notice_link_text");
-
-      var tosHTML = mozL10n.get("legal_text_and_links", {
-        "clientShortname": mozL10n.get("clientShortname2"),
-        "terms_of_use_url": "<a target=_blank href='" +
-          loop.config.legalWebsiteUrl + "'>" +
-          tosLinkName + "</a>",
-        "privacy_notice_url": "<a target=_blank href='" +
-          loop.config.privacyWebsiteUrl + "'>" + privacyNoticeName + "</a>"
-      });
-
-      var tosClasses = React.addons.classSet({
-        "terms-service": true,
-        hide: (localStorage.getItem("has-seen-tos") === "true")
-      });
-
-      return (
-        <div className="container">
-          <div className="container-box">
-
-            <ConversationHeader
-              urlCreationDateString={this.state.urlCreationDateString} />
-
-            <p className="standalone-btn-label">
-              {this.props.title}
-            </p>
-
-            <div id="messages"></div>
-
-            <div className="btn-group">
-              <div className="flex-padding-1" />
-              <InitiateCallButton
-                caption={this.props.callButtonLabel}
-                disabled={this.state.disableCallButton}
-                startCall={this.startCall}
-              />
-              <div className="flex-padding-1" />
-            </div>
-
-            <p className={tosClasses}
-               dangerouslySetInnerHTML={{__html: tosHTML}}></p>
-          </div>
-
-          <ConversationFooter />
-        </div>
-      );
-    }
-  });
-
-  /**
-   * Ended conversation view.
-   */
-  var EndedConversationView = React.createClass({
-    propTypes: {
-      conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
-                         .isRequired,
-      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
-      onAfterFeedbackReceived: React.PropTypes.func.isRequired,
-      sdk: React.PropTypes.object.isRequired
-    },
-
-    render: function() {
-      document.title = mozL10n.get("standalone_title_with_status",
-                                   {clientShortname: mozL10n.get("clientShortname2"),
-                                    currentStatus: mozL10n.get("status_conversation_ended")});
-      return (
-        <div className="ended-conversation">
-          <sharedViews.ConversationView
-            audio={{enabled: false, visible: false}}
-            dispatcher={this.props.dispatcher}
-            initiate={false}
-            model={this.props.conversation}
-            sdk={this.props.sdk}
-            video={{enabled: false, visible: false}} />
-        </div>
-      );
-    }
-  });
-
-  var StartConversationView = React.createClass({
-    render: function() {
-      document.title = mozL10n.get("clientShortname2");
-      return (
-        <InitiateConversationView
-          {...this.props}
-          callButtonLabel={mozL10n.get("initiate_audio_video_call_button2")}
-          title={mozL10n.get("initiate_call_button_label2")} />
-      );
-    }
-  });
-
-  var FailedConversationView = React.createClass({
-    mixins: [sharedMixins.AudioMixin],
-
-    componentDidMount: function() {
-      this.play("failure");
-    },
-
-    render: function() {
-      document.title = mozL10n.get("standalone_title_with_status",
-                                   {clientShortname: mozL10n.get("clientShortname2"),
-                                    currentStatus: mozL10n.get("status_error")});
-      return (
-        <InitiateConversationView
-          {...this.props}
-          callButtonLabel={mozL10n.get("retry_call_button")}
-          title={mozL10n.get("call_failed_title")} />
-      );
-    }
-  });
-
-  /**
-   * This view manages the outgoing conversation views - from
-   * call initiation through to the actual conversation and call end.
-   *
-   * At the moment, it does more than that, these parts need refactoring out.
-   */
-  var OutgoingConversationView = React.createClass({
-    propTypes: {
-      client: React.PropTypes.instanceOf(loop.StandaloneClient).isRequired,
-      conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel).isRequired,
-      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
-      isFirefox: React.PropTypes.bool.isRequired,
-      notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection).isRequired,
-      sdk: React.PropTypes.object.isRequired
-    },
-
-    getInitialState: function() {
-      return {
-        callStatus: "start"
-      };
-    },
-
-    componentDidMount: function() {
-      this.props.conversation.on("call:outgoing", this.startCall, this);
-      this.props.conversation.on("call:outgoing:get-media-privs", this.getMediaPrivs, this);
-      this.props.conversation.on("call:outgoing:setup", this.setupOutgoingCall, this);
-      this.props.conversation.on("change:publishedStream", this._checkConnected, this);
-      this.props.conversation.on("change:subscribedStream", this._checkConnected, this);
-      this.props.conversation.on("session:ended", this._endCall, this);
-      this.props.conversation.on("session:peer-hungup", this._onPeerHungup, this);
-      this.props.conversation.on("session:network-disconnected", this._onNetworkDisconnected, this);
-      this.props.conversation.on("session:connection-error", this._notifyError, this);
-    },
-
-    componentDidUnmount: function() {
-      this.props.conversation.off(null, null, this);
-    },
-
-    shouldComponentUpdate: function(nextProps, nextState) {
-      // Only rerender if current state has actually changed
-      return nextState.callStatus !== this.state.callStatus;
-    },
-
-    resetCallStatus: function() {
-      return function() {
-        this.setState({callStatus: "start"});
-      }.bind(this);
-    },
-
-    /**
-     * Renders the conversation views.
-     */
-    render: function() {
-      switch (this.state.callStatus) {
-        case "start": {
-          return (
-            <StartConversationView
-              client={this.props.client}
-              conversation={this.props.conversation}
-              notifications={this.props.notifications} />
-          );
-        }
-        case "failure": {
-          return (
-            <FailedConversationView
-              client={this.props.client}
-              conversation={this.props.conversation}
-              notifications={this.props.notifications} />
-          );
-        }
-        case "gumPrompt": {
-          return <GumPromptConversationView />;
-        }
-        case "pending": {
-          return <WaitingConversationView websocket={this._websocket} />;
-        }
-        case "connected": {
-          document.title = mozL10n.get("standalone_title_with_status",
-                                       {clientShortname: mozL10n.get("clientShortname2"),
-                                        currentStatus: mozL10n.get("status_in_conversation")});
-          return (
-            <sharedViews.ConversationView
-              dispatcher={this.props.dispatcher}
-              initiate={true}
-              model={this.props.conversation}
-              sdk={this.props.sdk}
-              video={{enabled: this.props.conversation.hasVideoStream("outgoing")}} />
-          );
-        }
-        case "end": {
-          return (
-            <EndedConversationView
-              conversation={this.props.conversation}
-              dispatcher={this.props.dispatcher}
-              onAfterFeedbackReceived={this.resetCallStatus()}
-              sdk={this.props.sdk} />
-          );
-        }
-        case "expired": {
-          return (
-            <CallUrlExpiredView isFirefox={this.props.isFirefox}/>
-          );
-        }
-        default: {
-          return <HomeView />;
-        }
-      }
-    },
-
-    /**
-     * Notify the user that the connection was not possible
-     * @param {{code: number, message: string}} error
-     */
-    _notifyError: function(error) {
-      console.error(error);
-      this.props.notifications.errorL10n("connection_error_see_console_notification");
-      this.setState({callStatus: "end"});
-    },
-
-    /**
-     * Peer hung up. Notifies the user and ends the call.
-     *
-     * Event properties:
-     * - {String} connectionId: OT session id
-     */
-    _onPeerHungup: function() {
-      this.props.notifications.warnL10n("peer_ended_conversation2");
-      this.setState({callStatus: "end"});
-    },
-
-    /**
-     * Network disconnected. Notifies the user and ends the call.
-     */
-    _onNetworkDisconnected: function() {
-      this.props.notifications.warnL10n("network_disconnected");
-      this.setState({callStatus: "end"});
-    },
-
-    /**
-     * Starts the set up of a call, obtaining the required information from the
-     * server.
-     */
-    setupOutgoingCall: function() {
-      var loopToken = this.props.conversation.get("loopToken");
-      if (!loopToken) {
-        this.props.notifications.errorL10n("missing_conversation_info");
-        this.setState({callStatus: "failure"});
-      } else {
-        var callType = this.props.conversation.get("selectedCallType");
-
-        this.props.client.requestCallInfo(this.props.conversation.get("loopToken"),
-                                          callType, function(err, sessionData) {
-          if (err) {
-            switch (err.errno) {
-              // loop-server sends 404 + INVALID_TOKEN (errno 105) whenever a token is
-              // missing OR expired; we treat this information as if the url is always
-              // expired.
-              case 105:
-                this.setState({callStatus: "expired"});
-                break;
-              default:
-                this.props.notifications.errorL10n("missing_conversation_info");
-                this.setState({callStatus: "failure"});
-                break;
-            }
-            return;
-          }
-          this.props.conversation.outgoing(sessionData);
-        }.bind(this));
-      }
-    },
-
-    /**
-     * Asks the user for the media privileges, handling the result appropriately.
-     */
-    getMediaPrivs: function() {
-      this.setState({callStatus: "gumPrompt"});
-      multiplexGum.getPermsAndCacheMedia({audio: true, video: true},
-        function(localStream) {
-          this.props.conversation.gotMediaPrivs();
-        }.bind(this),
-        function(errorCode) {
-          multiplexGum.reset();
-          this.setState({callStatus: "failure"});
-        }.bind(this)
-      );
-    },
-
-    /**
-     * Actually starts the call.
-     */
-    startCall: function() {
-      var loopToken = this.props.conversation.get("loopToken");
-      if (!loopToken) {
-        this.props.notifications.errorL10n("missing_conversation_info");
-        this.setState({callStatus: "failure"});
-        return;
-      }
-
-      this._setupWebSocket();
-      this.setState({callStatus: "pending"});
-    },
-
-    /**
-     * Used to set up the web socket connection and navigate to the
-     * call view if appropriate.
-     *
-     * @param {string} loopToken The session token to use.
-     */
-    _setupWebSocket: function() {
-      this._websocket = new loop.CallConnectionWebSocket({
-        url: this.props.conversation.get("progressURL"),
-        websocketToken: this.props.conversation.get("websocketToken"),
-        callId: this.props.conversation.get("callId")
-      });
-      this._websocket.promiseConnect().then(function() {
-      }.bind(this), function() {
-        // XXX Not the ideal response, but bug 1047410 will be replacing
-        // this by better "call failed" UI.
-        this.props.notifications.errorL10n("cannot_start_call_session_not_ready");
-        return;
-      }.bind(this));
-
-      this._websocket.on("progress", this._handleWebSocketProgress, this);
-    },
-
-    /**
-     * Checks if the streams have been connected, and notifies the
-     * websocket that the media is now connected.
-     */
-    _checkConnected: function() {
-      // Check we've had both local and remote streams connected before
-      // sending the media up message.
-      if (this.props.conversation.streamsConnected()) {
-        this._websocket.mediaUp();
-      }
-    },
-
-    /**
-     * Used to receive websocket progress and to determine how to handle
-     * it if appropraite.
-     */
-    _handleWebSocketProgress: function(progressData) {
-      switch(progressData.state) {
-        case "connecting": {
-          // We just go straight to the connected view as the media gets set up.
-          this.setState({callStatus: "connected"});
-          break;
-        }
-        case "terminated": {
-          // At the moment, we show the same text regardless
-          // of the terminated reason.
-          this._handleCallTerminated(progressData.reason);
-          break;
-        }
-      }
-    },
-
-    /**
-     * Handles call rejection.
-     *
-     * @param {String} reason The reason the call was terminated (reject, busy,
-     *                        timeout, cancel, media-fail, user-unknown, closed)
-     */
-    _handleCallTerminated: function(reason) {
-      multiplexGum.reset();
-
-      if (reason === WEBSOCKET_REASONS.CANCEL) {
-        this.setState({callStatus: "start"});
-        return;
-      }
-      // XXX later, we'll want to display more meaningfull messages (needs UX)
-      this.props.notifications.errorL10n("call_timeout_notification_text");
-      this.setState({callStatus: "failure"});
-    },
-
-    /**
-     * Handles ending a call by resetting the view to the start state.
-     */
-    _endCall: function() {
-      multiplexGum.reset();
-
-      if (this.state.callStatus !== "failure") {
-        this.setState({callStatus: "end"});
-      }
-    }
-  });
-
-  /**
    * Webapp Root View. This is the main, single, view that controls the display
    * of the webapp page.
    */
   var WebappRootView = React.createClass({
 
     mixins: [sharedMixins.UrlHashChangeMixin,
              sharedMixins.DocumentLocationMixin,
              Backbone.Events],
 
     propTypes: {
       activeRoomStore: React.PropTypes.instanceOf(loop.store.ActiveRoomStore).isRequired,
-      client: React.PropTypes.instanceOf(loop.StandaloneClient).isRequired,
-      conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel).isRequired,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
-      notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
-                          .isRequired,
-      sdk: React.PropTypes.object.isRequired,
       standaloneAppStore: React.PropTypes.instanceOf(
         loop.store.StandaloneAppStore).isRequired
     },
 
     getInitialState: function() {
       return this.props.standaloneAppStore.getStoreState();
     },
 
@@ -880,27 +146,16 @@ loop.webapp = (function(_, OT, mozL10n) 
     render: function() {
       switch (this.state.windowType) {
         case "unsupportedDevice": {
           return <UnsupportedDeviceView platform={this.state.unsupportedPlatform}/>;
         }
         case "unsupportedBrowser": {
           return <UnsupportedBrowserView isFirefox={this.state.isFirefox}/>;
         }
-        case "outgoing": {
-          return (
-            <OutgoingConversationView
-               client={this.props.client}
-               conversation={this.props.conversation}
-               dispatcher={this.props.dispatcher}
-               isFirefox={this.state.isFirefox}
-               notifications={this.props.notifications}
-               sdk={this.props.sdk} />
-          );
-        }
         case "room": {
           return (
             <loop.standaloneRoomViews.StandaloneRoomView
               activeRoomStore={this.props.activeRoomStore}
               dispatcher={this.props.dispatcher}
               isFirefox={this.state.isFirefox} />
           );
         }
@@ -919,44 +174,34 @@ loop.webapp = (function(_, OT, mozL10n) 
   /**
    * App initialization.
    */
   function init() {
     var standaloneMozLoop = new loop.StandaloneMozLoop({
       baseServerUrl: loop.config.serverUrl
     });
 
-    // Older non-flux based items.
-    var notifications = new sharedModels.NotificationCollection();
-
     // New flux items.
     var dispatcher = new loop.Dispatcher();
-    var client = new loop.StandaloneClient({
-      baseServerUrl: loop.config.serverUrl
-    });
     var sdkDriver = new loop.OTSdkDriver({
       // For the standalone, always request data channels. If they aren't
       // implemented on the client, there won't be a similar message to us, and
       // we won't display the UI.
       useDataChannels: true,
       dispatcher: dispatcher,
       sdk: OT
     });
 
-    var conversation = new sharedModels.ConversationModel({}, {
-        sdk: OT
-    });
     var activeRoomStore = new loop.store.ActiveRoomStore(dispatcher, {
         mozLoop: standaloneMozLoop,
         sdkDriver: sdkDriver
     });
 
     // Stores
     var standaloneAppStore = new loop.store.StandaloneAppStore({
-      conversation: conversation,
       dispatcher: dispatcher,
       sdk: OT
     });
     var standaloneMetricsStore = new loop.store.StandaloneMetricsStore(dispatcher, {
       activeRoomStore: activeRoomStore
     });
     var textChatStore = new loop.store.TextChatStore(dispatcher, {
       sdkDriver: sdkDriver
@@ -971,45 +216,33 @@ loop.webapp = (function(_, OT, mozL10n) 
     });
 
     window.addEventListener("unload", function() {
       dispatcher.dispatch(new sharedActions.WindowUnload());
     });
 
     React.render(<WebappRootView
       activeRoomStore={activeRoomStore}
-      client={client}
-      conversation={conversation}
       dispatcher={dispatcher}
-      notifications={notifications}
-      sdk={OT}
       standaloneAppStore={standaloneAppStore} />, document.querySelector("#main"));
 
     // Set the 'lang' and 'dir' attributes to <html> when the page is translated
     document.documentElement.lang = mozL10n.language.code;
     document.documentElement.dir = mozL10n.language.direction;
     document.title = mozL10n.get("clientShortname2");
 
     var locationData = sharedUtils.locationData();
 
     dispatcher.dispatch(new sharedActions.ExtractTokenInfo({
       windowPath: locationData.pathname,
       windowHash: locationData.hash
     }));
   }
 
   return {
-    CallUrlExpiredView: CallUrlExpiredView,
-    PendingConversationView: PendingConversationView,
-    GumPromptConversationView: GumPromptConversationView,
-    WaitingConversationView: WaitingConversationView,
-    StartConversationView: StartConversationView,
-    FailedConversationView: FailedConversationView,
-    OutgoingConversationView: OutgoingConversationView,
-    EndedConversationView: EndedConversationView,
     HomeView: HomeView,
     UnsupportedBrowserView: UnsupportedBrowserView,
     UnsupportedDeviceView: UnsupportedDeviceView,
     init: init,
     PromoteFirefoxView: PromoteFirefoxView,
     WebappRootView: WebappRootView
   };
 })(_, window.OT, navigator.mozL10n);
--- a/browser/components/loop/standalone/content/l10n/en-US/loop.properties
+++ b/browser/components/loop/standalone/content/l10n/en-US/loop.properties
@@ -1,97 +1,57 @@
 ## LOCALIZATION NOTE: In this file, don't translate the part between {{..}}
-restart_call=Rejoin
 conversation_has_ended=Your conversation has ended.
-call_timeout_notification_text=Your call did not go through.
-missing_conversation_info=Missing conversation information.
-network_disconnected=The network connection terminated abruptly.
-peer_ended_conversation2=The person you were calling has ended the conversation.
-call_failed_title=Call failed.
 generic_failure_message=We're having technical difficulties…
 generic_failure_with_reason2=You can try again or email a link to be reached at later.
 generic_failure_no_reason2=Would you like to try again?
 retry_call_button=Retry
 unable_retrieve_call_info=Unable to retrieve conversation information.
 hangup_button_title=Hang up
 hangup_button_caption2=Exit
 mute_local_audio_button_title=Mute your audio
 unmute_local_audio_button_title=Unmute your audio
 mute_local_video_button_title2=Disable video
 unmute_local_video_button_title2=Enable video
 active_screenshare_button_title=Stop sharing
 inactive_screenshare_button_title=Share your screen
 
-outgoing_call_title=Start conversation?
-call_with_contact_title=Conversation with {{incomingCallIdentity}}
 welcome=Welcome to the {{clientShortname}} web client.
 incompatible_browser_heading=Oops!
 incompatible_browser_message=Firefox Hello only works in browsers that support WebRTC
-powered_by_webrtc=The audio and video components of {{clientShortname}} are powered by WebRTC.
-use_latest_firefox=Please try this link in a WebRTC-enabled browser, such as {{firefoxBrandNameLink}}.
 unsupported_platform_heading=Sorry!
 unsupported_platform_message={{platform}} does not currently support {{clientShortname}}
 unsupported_platform_ios=iOS
 unsupported_platform_windows_phone=Windows Phone
 unsupported_platform_blackberry=Blackberry
 unsupported_platform_learn_more_link=Learn more about why your platform doesn't support {{clientShortname}}
-connection_error_see_console_notification=Call failed; see console for details.
-call_url_unavailable_notification_heading=Oops!
-call_url_unavailable_notification_message2=Sorry, this URL is not available. It may be expired or entered incorrectly.
 promote_firefox_hello_heading=Download {{brandShortname}} to make free audio and video calls!
 get_firefox_button=Get {{brandShortname}}
-initiate_call_button_label2=Ready to start your conversation?
-initiate_audio_video_call_button2=Start
-initiate_audio_video_call_tooltip2=Start a video conversation
-initiate_audio_call_button2=Voice conversation
 initiate_call_cancel_button=Cancel
 legal_text_and_links=By using {{clientShortname}} you agree to the {{terms_of_use_url}} and {{privacy_notice_url}}
 terms_of_use_link_text=Terms of use
 privacy_notice_link_text=Privacy notice
-invite_header_text=Invite someone to join you.
 self_view_hidden_message=Self-view hidden but still being sent; resize window \
   to show
 
 ## LOCALIZATION NOTE(brandShortname): This should not be localized and
 ## should remain "Firefox" for all locales.
 brandShortname=Firefox
 ## LOCALIZATION NOTE(clientShortname2): This should not be localized and
 ## should remain "Firefox Hello" for all locales.
 clientShortname2=Firefox Hello
 ## LOCALIZATION NOTE(vendorShortname): This should not be localized and
 ## should remain "Mozilla" for all locales.
 vendorShortname=Mozilla
 
-## LOCALIZATION NOTE(client_alttext): {{clientShortname}} will be replaced with the
-## value of the clientShortname2 string above.
-client_alttext={{clientShortname}} logo
-vendor_alttext={{vendorShortname}} logo
-
-## LOCALIZATION NOTE (call_url_creation_date_label): Example output: (from May 26, 2014)
-call_url_creation_date_label=(from {{call_url_creation_date}})
 call_progress_getting_media_description={{clientShortname}} requires access to your camera and microphone.
-call_progress_getting_media_title=Waiting for media…
 call_progress_connecting_description=Connecting…
 call_progress_ringing_description=Ringing…
 
-## LOCALIZATION_NOTE (feedback_rejoin_button): Displayed on the feedback form after
-## a signed-in to signed-in user call.
-## https://people.mozilla.org/~dhenein/labs/loop-mvp-spec/#feedback
-feedback_rejoin_button=Rejoin
-## LOCALIZATION NOTE (feedback_report_user_button): Used to report a user in the case of
-## an abusive user.
-feedback_report_user_button=Report User
-
-## 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
-
 help_label=Help
-tour_label=Tour
 
 rooms_default_room_name_template=Conversation {{conversationLabel}}
 ## LOCALIZATION_NOTE(rooms_welcome_title): {{conversationName}} will be replaced
 ## by the user specified conversation name.
 rooms_welcome_title=Welcome to {{conversationName}}
 rooms_leave_button_label=Leave
 rooms_list_copy_url_tooltip=Copy Link
 rooms_list_delete_tooltip=Delete conversation
@@ -110,28 +70,21 @@ rooms_media_denied_message=We could not 
 room_information_failure_not_available=No information about this conversation is available. Please request a new link from the person who sent it to you.
 room_information_failure_unsupported_browser=Your browser cannot access any information about this conversation. Please make sure you're using the latest version.
 
 ## LOCALIZATION_NOTE(rooms_read_while_wait_offer): This string is followed by a
 # tile/offer image and title that are provided by a separate service that has
 # localized content.
 rooms_read_while_wait_offer=Want something to read while you wait?
 
-## LOCALIZATION_NOTE(standalone_title_with_status): {{clientShortname}} will be
-## replaced by the brand name and {{currentStatus}} will be replaced
-## by the current call status (Connecting, Ringing, etc.)
-standalone_title_with_status={{clientShortname}} — {{currentStatus}}
 ## LOCALIZATION_NOTE(standalone_title_with_room_name): {{roomName}} will be replaced
 ## by the name of the conversation and {{clientShortname}} will be
 ## replaced by the brand name.
 standalone_title_with_room_name={{roomName}} — {{clientShortname}}
-status_in_conversation=In conversation
-status_conversation_ended=Conversation ended
 status_error=Something went wrong
-support_link=Get Help
 
 # Text chat strings
 
 chat_textbox_placeholder=Type here…
 # LOCALIZATION NOTE (context_inroom_label): this string is followed by the
 # title/URL of the website you are having a conversation about, displayed on a
 # separate line. If this structure doesn't work for your locale, you might want
 # to consider this as a stand-alone title. See example screenshot:
--- a/browser/components/loop/test/desktop-local/conversation_test.js
+++ b/browser/components/loop/test/desktop-local/conversation_test.js
@@ -82,19 +82,16 @@ describe("loop.conversation", function()
   });
 
   describe("#init", function() {
     var OTRestore;
     beforeEach(function() {
       sandbox.stub(React, "render");
       sandbox.stub(document.mozL10n, "initialize");
 
-      sandbox.stub(loop.shared.models.ConversationModel.prototype,
-        "initialize");
-
       sandbox.stub(loop.Dispatcher.prototype, "dispatch");
 
       sandbox.stub(loop.shared.utils,
         "locationData").returns({
           hash: "#42",
           pathname: "/"
         });
 
--- a/browser/components/loop/test/karma/karma.coverage.shared_standalone.js
+++ b/browser/components/loop/test/karma/karma.coverage.shared_standalone.js
@@ -29,19 +29,17 @@ module.exports = function(config) {
     "content/shared/js/otSdkDriver.js",
     "content/shared/js/activeRoomStore.js",
     "content/shared/js/conversationStore.js",
     "content/shared/js/views.js",
     "content/shared/js/textChatStore.js",
     "content/shared/js/textChatView.js",
     "content/shared/js/urlRegExps.js",
     "content/shared/js/linkifiedTextView.js",
-    "standalone/content/js/multiplexGum.js",
     "standalone/content/js/standaloneAppStore.js",
-    "standalone/content/js/standaloneClient.js",
     "standalone/content/js/standaloneMozLoop.js",
     "standalone/content/js/standaloneRoomViews.js",
     "standalone/content/js/standaloneMetricsStore.js",
     "standalone/content/js/webapp.js",
     "test/shared/*.js",
     "test/standalone/*.js"
   ]);
 
--- a/browser/components/loop/test/shared/models_test.js
+++ b/browser/components/loop/test/shared/models_test.js
@@ -1,452 +1,28 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 describe("loop.shared.models", function() {
   "use strict";
 
   var expect = chai.expect;
-  var l10n = navigator.mozL10n || document.mozL10n;
-  var sharedModels = loop.shared.models, sandbox, fakeXHR,
-      requests = [], fakeSDK, fakeMozLoop, fakeSession, fakeSessionData;
+  var l10n = navigator.mozL10n;
+  var sharedModels = loop.shared.models;
+  var sandbox;
 
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
-    sandbox.useFakeTimers();
-    fakeXHR = sandbox.useFakeXMLHttpRequest();
-    requests = [];
-    // https://github.com/cjohansen/Sinon.JS/issues/393
-    fakeXHR.xhr.onCreate = function(xhr) {
-      requests.push(xhr);
-    };
-    fakeSessionData = {
-      sessionId: "sessionId",
-      sessionToken: "sessionToken",
-      apiKey: "apiKey",
-      callType: "callType",
-      websocketToken: 123,
-      callToken: "callToken",
-      callUrl: "http://invalid/callToken",
-      callerId: "mrssmith"
-    };
-    fakeSession = _.extend({
-      connect: function () {},
-      endSession: sandbox.stub(),
-      set: sandbox.stub(),
-      disconnect: sandbox.spy(),
-      unpublish: sandbox.spy()
-    }, Backbone.Events);
-    fakeSDK = {
-      initPublisher: sandbox.spy(),
-      initSession: sandbox.stub().returns(fakeSession)
-    };
-    fakeMozLoop = {
-      addConversationContext: sinon.spy()
-    };
   });
 
   afterEach(function() {
     sandbox.restore();
   });
 
-  describe("ConversationModel", function() {
-    describe("#initialize", function() {
-      it("should require a sdk option", function() {
-        expect(function() {
-          new sharedModels.ConversationModel({}, {});
-        }).to.Throw(Error, /missing required sdk/);
-      });
-    });
-
-    describe("constructed", function() {
-      var conversation;
-
-      beforeEach(function() {
-        conversation = new sharedModels.ConversationModel({}, {
-          sdk: fakeSDK,
-          mozLoop: fakeMozLoop
-        });
-        conversation.set("loopToken", "fakeToken");
-      });
-
-      describe("#accepted", function() {
-        it("should trigger a `call:accepted` event", function(done) {
-          conversation.once("call:accepted", function() {
-            done();
-          });
-
-          conversation.accepted();
-        });
-      });
-
-      describe("#setupOutgoingCall", function() {
-        it("should set the a custom selected call type", function() {
-          conversation.setupOutgoingCall("audio");
-
-          expect(conversation.get("selectedCallType")).eql("audio");
-        });
-
-        it("should respect the default selected call type when none is passed",
-          function() {
-            conversation.setupOutgoingCall();
-
-            expect(conversation.get("selectedCallType")).eql("audio-video");
-          });
-
-        it("should trigger a `call:outgoing:get-media-privs` event", function(done) {
-          conversation.once("call:outgoing:get-media-privs", function() {
-            done();
-          });
-
-          conversation.setupOutgoingCall();
-        });
-      });
-
-      describe("#gotMediaPrivs", function() {
-        it("should trigger a `call:outgoing:setup` event", function(done) {
-          conversation.once("call:outgoing:setup", function() {
-            done();
-          });
-
-          conversation.gotMediaPrivs();
-        });
-      });
-
-      describe("#outgoing", function() {
-        beforeEach(function() {
-          sandbox.stub(conversation, "endSession");
-          sandbox.stub(conversation, "setOutgoingSessionData");
-          sandbox.stub(conversation, "setIncomingSessionData");
-        });
-
-        it("should save the outgoing sessionData", function() {
-          conversation.outgoing(fakeSessionData);
-
-          sinon.assert.calledOnce(conversation.setOutgoingSessionData);
-        });
-
-        it("should trigger a `call:outgoing` event", function(done) {
-          conversation.once("call:outgoing", function() {
-            done();
-          });
-
-          conversation.outgoing();
-        });
-      });
-
-      describe("#setSessionData", function() {
-        it("should update outgoing conversation session information",
-           function() {
-             conversation.setOutgoingSessionData(fakeSessionData);
-
-             expect(conversation.get("sessionId")).eql("sessionId");
-             expect(conversation.get("sessionToken")).eql("sessionToken");
-             expect(conversation.get("apiKey")).eql("apiKey");
-           });
-
-        it("should update incoming conversation session information",
-           function() {
-             conversation.setIncomingSessionData(fakeSessionData);
-
-             expect(conversation.get("sessionId")).eql("sessionId");
-             expect(conversation.get("sessionToken")).eql("sessionToken");
-             expect(conversation.get("apiKey")).eql("apiKey");
-             expect(conversation.get("callType")).eql("callType");
-             expect(conversation.get("callToken")).eql("callToken");
-           });
-      });
-
-      describe("#startSession", function() {
-        var model;
-
-        beforeEach(function() {
-          model = new sharedModels.ConversationModel(fakeSessionData, {
-            sdk: fakeSDK,
-            mozLoop: fakeMozLoop
-          });
-          model.set({
-            publishedStream: true,
-            subscribedStream: true
-          });
-          model.startSession();
-        });
-
-        it("should start a session", function() {
-          sinon.assert.calledOnce(fakeSDK.initSession);
-        });
-
-        it("should reset the stream flags", function() {
-          expect(model.get("publishedStream")).eql(false);
-          expect(model.get("subscribedStream")).eql(false);
-        });
-
-        it("should call addConversationContext", function() {
-          fakeMozLoop.addConversationContext = sandbox.stub();
-
-          model.set({
-            windowId: "28",
-            sessionId: "321456",
-            callId: "142536"
-          });
-          model.startSession();
-
-          sinon.assert.calledOnce(fakeMozLoop.addConversationContext);
-          sinon.assert.calledWithExactly(fakeMozLoop.addConversationContext,
-                                         "28", "321456", "142536");
-        });
-
-        it("should call connect", function() {
-          fakeSession.connect = sandbox.stub();
-
-          model.startSession();
-
-          sinon.assert.calledOnce(fakeSession.connect);
-          sinon.assert.calledWithExactly(fakeSession.connect,
-                        sinon.match.string, sinon.match.string,
-                        sinon.match.func);
-        });
-
-        it("should set connected to true when no error is called back",
-            function() {
-              fakeSession.connect = function(key, token, cb) {
-                cb(null);
-              };
-              sandbox.stub(model, "set");
-
-              model.startSession();
-
-              sinon.assert.calledWith(model.set, "connected", true);
-            });
-
-        it("should trigger session:connected when no error is called back",
-            function() {
-              fakeSession.connect = function(key, token, cb) {
-                cb(null);
-              };
-              sandbox.stub(model, "trigger");
-
-              model.startSession();
-
-              sinon.assert.calledWithExactly(model.trigger, "session:connected");
-            });
-
-        describe("Session events", function() {
-
-          it("should trigger a fail event when an error is called back",
-            function() {
-              fakeSession.connect = function(key, token, cb) {
-                cb({
-                  error: true
-                });
-              };
-              sandbox.stub(model, "endSession");
-
-              model.startSession();
-
-              sinon.assert.calledOnce(model.endSession);
-              sinon.assert.calledWithExactly(model.endSession);
-            });
-
-          it("should trigger session:connection-error event when an error is" +
-            " called back", function() {
-              fakeSession.connect = function(key, token, cb) {
-                cb({
-                  error: true
-                });
-              };
-              sandbox.stub(model, "trigger");
-
-              model.startSession();
-
-              sinon.assert.called(model.trigger);
-              sinon.assert.calledWithExactly(model.trigger,
-                          "session:connection-error", sinon.match.object);
-            });
-
-          it("should set the connected attr to true on connection completed",
-            function() {
-              fakeSession.connect = function(key, token, cb) {
-                cb();
-              };
-
-              model.startSession();
-
-              expect(model.get("connected")).eql(true);
-            });
-
-          it("should trigger a session:ended event on sessionDisconnected",
-            function(done) {
-              model.once("session:ended", function(){ done(); });
-
-              fakeSession.trigger("sessionDisconnected", {reason: "ko"});
-            });
-
-          it("should trigger network-disconnected on networkDisconnect reason",
-             function(done) {
-               model.once("session:network-disconnected", function() {
-                 done();
-               });
-
-               var fakeEvent = {
-                 connectionId: 42,
-                 reason: "networkDisconnected"
-               };
-
-               fakeSession.trigger("sessionDisconnected", fakeEvent);
-            });
-
-          it("should set the connected attribute to false on sessionDisconnected",
-            function() {
-              fakeSession.trigger("sessionDisconnected", {reason: "ko"});
-
-              expect(model.get("connected")).eql(false);
-            });
-
-          it("should set the ongoing attribute to false on sessionDisconnected",
-            function() {
-              fakeSession.trigger("sessionDisconnected", {reason: "ko"});
-
-              expect(model.get("ongoing")).eql(false);
-            });
-
-          describe("connectionDestroyed event received", function() {
-            var fakeEvent = {reason: "ko", connection: {connectionId: 42}};
-
-            it("should trigger a session:peer-hungup model event",
-              function(done) {
-                model.once("session:peer-hungup", function(event) {
-                  expect(event.connection.connectionId).eql(42);
-                  done();
-                });
-
-                fakeSession.trigger("connectionDestroyed", fakeEvent);
-              });
-
-            it("should terminate the session", function() {
-              sandbox.stub(model, "endSession");
-
-              fakeSession.trigger("connectionDestroyed", fakeEvent);
-
-              sinon.assert.calledOnce(model.endSession);
-            });
-          });
-        });
-      });
-
-      describe("#endSession", function() {
-        var model;
-
-        beforeEach(function() {
-          model = new sharedModels.ConversationModel(fakeSessionData, {
-            sdk: fakeSDK
-          });
-          model.set("ongoing", true);
-          model.startSession();
-        });
-
-        it("should disconnect current session", function() {
-          model.endSession();
-
-          sinon.assert.calledOnce(fakeSession.disconnect);
-        });
-
-        it("should set the connected attribute to false", function() {
-          model.endSession();
-
-          expect(model.get("connected")).eql(false);
-        });
-
-        it("should set the ongoing attribute to false", function() {
-          model.endSession();
-
-          expect(model.get("ongoing")).eql(false);
-        });
-
-        it("should set the streams to unpublished", function() {
-          model.set({
-            publishedStream: true,
-            subscribedStream: true
-          });
-
-          model.endSession();
-
-          expect(model.get("publishedStream")).eql(false);
-          expect(model.get("subscribedStream")).eql(false);
-        });
-
-        it("should stop listening to session events once the session is " +
-           "actually disconnected", function() {
-            sandbox.stub(model, "stopListening");
-
-            model.endSession();
-            model.trigger("session:ended");
-
-            sinon.assert.calledOnce(model.stopListening);
-          });
-      });
-
-      describe("#hasVideoStream", function() {
-        var model;
-
-        beforeEach(function() {
-          model = new sharedModels.ConversationModel(fakeSessionData, {
-            sdk: fakeSDK
-          });
-          model.startSession();
-        });
-
-        it("should return true for incoming callType", function() {
-          model.set("callType", "audio-video");
-
-          expect(model.hasVideoStream("incoming")).to.eql(true);
-        });
-
-        it("should return true for outgoing callType", function() {
-          model.set("selectedCallType", "audio-video");
-
-          expect(model.hasVideoStream("outgoing")).to.eql(true);
-        });
-      });
-
-      describe("#getCallIdentifier", function() {
-        var model;
-
-        beforeEach(function() {
-          model = new sharedModels.ConversationModel(fakeSessionData, {
-            sdk: fakeSDK
-          });
-          model.startSession();
-        });
-
-        it("should return the callerId", function() {
-          expect(model.getCallIdentifier()).eql("mrssmith");
-        });
-
-        it("should return the shorted callUrl if the callerId does not exist",
-          function() {
-            model.set({callerId: ""});
-
-            expect(model.getCallIdentifier()).eql("invalid/callToken");
-          });
-
-        it("should return an empty string if neither callerId nor callUrl exist",
-          function() {
-            model.set({
-              callerId: undefined,
-              callUrl: undefined
-            });
-
-            expect(model.getCallIdentifier()).eql("");
-          });
-      });
-    });
-  });
-
   describe("NotificationCollection", function() {
     var collection, notifData, testNotif;
 
     beforeEach(function() {
       collection = new sharedModels.NotificationCollection();
       sandbox.stub(l10n, "get", function(x, y) {
         return "translated:" + x + (y ? ":" + y : "");
       });
@@ -497,11 +73,10 @@ describe("loop.shared.models", function(
         function() {
           collection.errorL10n("fakeId", "fakeProp");
 
           expect(collection).to.have.length.of(1);
           expect(collection.at(0).get("level")).eql("error");
           expect(collection.at(0).get("message")).eql("translated:fakeId:fakeProp");
       });
     });
-
   });
 });
--- a/browser/components/loop/test/shared/views_test.js
+++ b/browser/components/loop/test/shared/views_test.js
@@ -569,351 +569,16 @@ describe("loop.shared.views", function()
       TestUtils.Simulate.click(
         comp.getDOMNode().querySelector(".btn-mute-video"));
 
       sinon.assert.calledOnce(publishStream);
       sinon.assert.calledWithExactly(publishStream, "video", true);
     });
   });
 
-  describe("ConversationView", function() {
-    var fakeSDK, fakeSessionData, fakeSession, fakePublisher, model, fakeAudio;
-
-    function mountTestComponent(props) {
-      props = _.extend({
-        dispatcher: dispatcher,
-        mozLoop: {}
-      }, props || {});
-      return TestUtils.renderIntoDocument(
-        React.createElement(sharedViews.ConversationView, props));
-    }
-
-    beforeEach(function() {
-      fakeAudio = {
-        play: sinon.spy(),
-        pause: sinon.spy(),
-        removeAttribute: sinon.spy()
-      };
-      sandbox.stub(window, "Audio").returns(fakeAudio);
-
-      fakeSessionData = {
-        sessionId: "sessionId",
-        sessionToken: "sessionToken",
-        apiKey: "apiKey"
-      };
-      fakeSession = _.extend({
-        connection: {connectionId: 42},
-        connect: sandbox.spy(),
-        disconnect: sandbox.spy(),
-        publish: sandbox.spy(),
-        unpublish: sandbox.spy(),
-        subscribe: sandbox.spy()
-      }, Backbone.Events);
-      fakePublisher = _.extend({
-        publishAudio: sandbox.spy(),
-        publishVideo: sandbox.spy()
-      }, Backbone.Events);
-      fakeSDK = {
-        initPublisher: sandbox.stub().returns(fakePublisher),
-        initSession: sandbox.stub().returns(fakeSession),
-        on: sandbox.stub()
-      };
-      model = new sharedModels.ConversationModel(fakeSessionData, {
-        sdk: fakeSDK
-      });
-    });
-
-    describe("#componentDidMount", function() {
-      it("should start a session by default", function() {
-        sandbox.stub(model, "startSession");
-
-        mountTestComponent({
-          sdk: fakeSDK,
-          model: model,
-          video: {enabled: true}
-        });
-
-        sinon.assert.calledOnce(model.startSession);
-      });
-
-      // Test loop.shared.utils.findParentNode.
-      // Added here to take advantage of having markup.
-      it("should find '.video-layout-wrapper'", function() {
-        var view = mountTestComponent({
-          initiate: false,
-          sdk: fakeSDK,
-          model: model,
-          video: {enabled: true}
-        });
-        var menu = view.getDOMNode().querySelector(".btn-hangup-entry");
-
-        var result = loop.shared.utils.findParentNode(menu,
-                                                      "video-layout-wrapper");
-
-        expect(result.classList.contains("video-layout-wrapper")).to.eql(true);
-      });
-
-      it("shouldn't start a session if initiate is false", function() {
-        sandbox.stub(model, "startSession");
-
-        mountTestComponent({
-          initiate: false,
-          sdk: fakeSDK,
-          model: model,
-          video: {enabled: true}
-        });
-
-        sinon.assert.notCalled(model.startSession);
-      });
-    });
-
-    describe("constructed", function() {
-      var comp;
-
-      beforeEach(function() {
-        comp = mountTestComponent({
-          sdk: fakeSDK,
-          model: model,
-          video: {enabled: false}
-        });
-      });
-
-      describe("#hangup", function() {
-        beforeEach(function() {
-          comp.startPublishing();
-        });
-
-        it("should disconnect the session", function() {
-          sandbox.stub(model, "endSession");
-
-          comp.hangup();
-
-          sinon.assert.calledOnce(model.endSession);
-        });
-
-        it("should stop publishing local streams", function() {
-          comp.hangup();
-
-          sinon.assert.calledOnce(fakeSession.unpublish);
-        });
-      });
-
-      describe("#startPublishing", function() {
-        it("should publish local stream", function() {
-          comp.startPublishing();
-
-          sinon.assert.calledOnce(fakeSDK.initPublisher);
-          sinon.assert.calledOnce(fakeSession.publish);
-        });
-
-        // XXX This test would need reworking, but the code should be going
-        // away after the obsolences of call urls (currently bug 1170150).
-        it("should start listening to OT publisher accessDialogOpened and " +
-          " accessDenied events");
-      });
-
-      describe("#stopPublishing", function() {
-        beforeEach(function() {
-          sandbox.stub(fakePublisher, "off");
-          comp.startPublishing();
-        });
-
-        it("should stop publish local stream", function() {
-          comp.stopPublishing();
-
-          sinon.assert.calledOnce(fakeSession.unpublish);
-        });
-
-        it("should unsubscribe from publisher events",
-          function() {
-            comp.stopPublishing();
-
-            // Note: Backbone.Events#stopListening calls off() on passed object.
-            sinon.assert.calledOnce(fakePublisher.off);
-          });
-      });
-
-      describe("#publishStream", function() {
-        var component;
-
-        beforeEach(function() {
-          component = mountTestComponent({
-            sdk: fakeSDK,
-            model: model,
-            video: {enabled: false}
-          });
-          component.startPublishing();
-        });
-
-        it("should start streaming local audio", function() {
-          component.publishStream("audio", true);
-
-          sinon.assert.calledOnce(fakePublisher.publishAudio);
-          sinon.assert.calledWithExactly(fakePublisher.publishAudio, true);
-        });
-
-        it("should stop streaming local audio", function() {
-          component.publishStream("audio", false);
-
-          sinon.assert.calledOnce(fakePublisher.publishAudio);
-          sinon.assert.calledWithExactly(fakePublisher.publishAudio, false);
-        });
-
-        it("should start streaming local video", function() {
-          component.publishStream("video", true);
-
-          sinon.assert.calledOnce(fakePublisher.publishVideo);
-          sinon.assert.calledWithExactly(fakePublisher.publishVideo, true);
-        });
-
-        it("should stop streaming local video", function() {
-          component.publishStream("video", false);
-
-          sinon.assert.calledOnce(fakePublisher.publishVideo);
-          sinon.assert.calledWithExactly(fakePublisher.publishVideo, false);
-        });
-      });
-
-      describe("Model events", function() {
-
-        describe("for standalone", function() {
-
-          beforeEach(function() {
-            // In standalone, navigator.mozLoop does not exists
-            if (navigator.hasOwnProperty("mozLoop")) {
-              sandbox.stub(navigator, "mozLoop", undefined);
-            }
-          });
-
-          it("should play a connected sound, once, on session:connected",
-             function() {
-               var url = "shared/sounds/connected.ogg";
-               sandbox.stub(window, "XMLHttpRequest").returns(fakeAudioXHR);
-               model.trigger("session:connected");
-
-               fakeAudioXHR.onload();
-
-               sinon.assert.called(fakeAudioXHR.open);
-               sinon.assert.calledWithExactly(fakeAudioXHR.open, "GET", url, true);
-
-               sinon.assert.calledOnce(fakeAudio.play);
-               expect(fakeAudio.loop).to.not.equal(true);
-             });
-        });
-
-        describe("for desktop", function() {
-          var origMozLoop;
-
-          beforeEach(function() {
-            origMozLoop = navigator.mozLoop;
-            navigator.mozLoop = {
-              getAudioBlob: sinon.spy(function(name, callback) {
-                var data = new ArrayBuffer(10);
-                callback(null, new Blob([data], {type: "audio/ogg"}));
-              })
-            };
-          });
-
-          afterEach(function() {
-            navigator.mozLoop = origMozLoop;
-          });
-
-          it("should play a connected sound, once, on session:connected",
-             function() {
-               var url = "chrome://browser/content/loop/shared/sounds/connected.ogg";
-               model.trigger("session:connected");
-
-               sinon.assert.calledOnce(navigator.mozLoop.getAudioBlob);
-               sinon.assert.calledWithExactly(navigator.mozLoop.getAudioBlob,
-                                              "connected", sinon.match.func);
-               sinon.assert.calledOnce(fakeAudio.play);
-               expect(fakeAudio.loop).to.not.equal(true);
-             });
-        });
-
-        describe("for both (standalone and desktop)", function() {
-          beforeEach(function() {
-            sandbox.stub(window, "XMLHttpRequest").returns(fakeAudioXHR);
-          });
-
-          it("should start streaming on session:connected", function() {
-            model.trigger("session:connected");
-
-            sinon.assert.calledOnce(fakeSDK.initPublisher);
-          });
-
-          it("should publish remote stream on session:stream-created",
-             function() {
-               var s1 = {connection: {connectionId: 42}};
-
-               model.trigger("session:stream-created", {stream: s1});
-
-               sinon.assert.calledOnce(fakeSession.subscribe);
-               sinon.assert.calledWith(fakeSession.subscribe, s1);
-             });
-
-          it("should unpublish local stream on session:ended", function() {
-            comp.startPublishing();
-
-            model.trigger("session:ended");
-
-            sinon.assert.calledOnce(fakeSession.unpublish);
-          });
-
-          it("should unpublish local stream on session:peer-hungup", function() {
-            comp.startPublishing();
-
-            model.trigger("session:peer-hungup");
-
-            sinon.assert.calledOnce(fakeSession.unpublish);
-          });
-
-          it("should unpublish local stream on session:network-disconnected",
-             function() {
-               comp.startPublishing();
-
-               model.trigger("session:network-disconnected");
-
-               sinon.assert.calledOnce(fakeSession.unpublish);
-             });
-        });
-
-      });
-
-      describe("Publisher events", function() {
-        beforeEach(function() {
-          comp.startPublishing();
-        });
-
-        it("should set audio state on streamCreated", function() {
-          fakePublisher.trigger("streamCreated", {stream: {hasAudio: true}});
-          expect(comp.state.audio.enabled).eql(true);
-
-          fakePublisher.trigger("streamCreated", {stream: {hasAudio: false}});
-          expect(comp.state.audio.enabled).eql(false);
-        });
-
-        it("should set video state on streamCreated", function() {
-          fakePublisher.trigger("streamCreated", {stream: {hasVideo: true}});
-          expect(comp.state.video.enabled).eql(true);
-
-          fakePublisher.trigger("streamCreated", {stream: {hasVideo: false}});
-          expect(comp.state.video.enabled).eql(false);
-        });
-
-        it("should set media state on streamDestroyed", function() {
-          fakePublisher.trigger("streamDestroyed");
-
-          expect(comp.state.audio.enabled).eql(false);
-          expect(comp.state.video.enabled).eql(false);
-        });
-      });
-    });
-  });
-
   describe("NotificationListView", function() {
     var coll, view, testNotif;
 
     function mountTestComponent(props) {
       props = _.extend({
         key: 0
       }, props || {});
       return TestUtils.renderIntoDocument(
--- a/browser/components/loop/test/standalone/index.html
+++ b/browser/components/loop/test/standalone/index.html
@@ -40,44 +40,39 @@
   <script src="../shared/vendor/sinon-1.16.1.js"></script>
   <script src="../shared/sdk_mock.js"></script>
   <script>
     chai.config.includeStack = true;
     mocha.setup({ui: 'bdd', timeout: 10000});
   </script>
   <!-- App scripts -->
   <script src="../../content/shared/js/utils.js"></script>
-  <script src="../../content/shared/js/models.js"></script>
   <script src="../../content/shared/js/mixins.js"></script>
   <script src="../../content/shared/js/websocket.js"></script>
   <script src="../../content/shared/js/actions.js"></script>
   <script src="../../content/shared/js/validate.js"></script>
   <script src="../../content/shared/js/dispatcher.js"></script>
   <script src="../../content/shared/js/store.js"></script>
   <script src="../../content/shared/js/roomStates.js"></script>
   <script src="../../content/shared/js/activeRoomStore.js"></script>
   <script src="../../content/shared/js/views.js"></script>
   <script src="../../content/shared/js/textChatStore.js"></script>
   <script src="../../content/shared/js/textChatView.js"></script>
   <script src="../../content/shared/js/otSdkDriver.js"></script>
-  <script src="../../standalone/content/js/multiplexGum.js"></script>
   <script src="../../standalone/content/js/standaloneAppStore.js"></script>
-  <script src="../../standalone/content/js/standaloneClient.js"></script>
   <script src="../../standalone/content/js/standaloneMozLoop.js"></script>
   <script src="../../standalone/content/js/standaloneRoomViews.js"></script>
   <script src="../../standalone/content/js/standaloneMetricsStore.js"></script>
   <script src="../../standalone/content/js/webapp.js"></script>
   <!-- Test scripts -->
-  <script src="standalone_client_test.js"></script>
   <script src="standaloneAppStore_test.js"></script>
   <script src="standaloneMozLoop_test.js"></script>
   <script src="standaloneRoomViews_test.js"></script>
   <script src="standaloneMetricsStore_test.js"></script>
   <script src="webapp_test.js"></script>
-  <script src="multiplexGum_test.js"></script>
   <script>
     describe("Uncaught Error Check", function() {
       it("should load the tests without errors", function() {
         chai.expect(uncaughtError && uncaughtError.message).to.be.undefined;
       });
     });
 
     describe("Unexpected Warnings Check", function() {
deleted file mode 100644
--- a/browser/components/loop/test/standalone/multiplexGum_test.js
+++ /dev/null
@@ -1,363 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-describe("loop.standaloneMedia._MultiplexGum", function() {
-  "use strict";
-
-  var expect = chai.expect;
-  var defaultGum =
-    navigator.getUserMedia ||
-    navigator.mozGetUserMedia ||
-    navigator.webkitGetUserMedia ||
-    (window.TBPlugin && window.TBPlugin.getUserMedia);
-
-  var sandbox;
-  var multiplexGum;
-
-  beforeEach(function() {
-    sandbox = sinon.sandbox.create();
-    multiplexGum = new loop.standaloneMedia._MultiplexGum();
-    loop.standaloneMedia.setSingleton(multiplexGum);
-  });
-
-  afterEach(function() {
-    sandbox.restore();
-  });
-
-  describe("#constructor", function() {
-    it("pending should default to false", function() {
-      expect(multiplexGum.userMedia.pending).to.equal(false);
-    });
-  });
-
-  describe("default getUserMedia", function() {
-    it("should call getPermsAndCacheMedia", function() {
-      var fakeOptions = {audio: true, video: true};
-      var successCB = function() {};
-      var errorCB = function() {};
-      sandbox.stub(navigator, "originalGum");
-      sandbox.stub(loop.standaloneMedia._MultiplexGum.prototype,
-        "getPermsAndCacheMedia");
-      multiplexGum = new loop.standaloneMedia._MultiplexGum();
-
-      defaultGum(fakeOptions, successCB, errorCB);
-
-      sinon.assert.calledOnce(multiplexGum.getPermsAndCacheMedia);
-      sinon.assert.calledWithExactly(multiplexGum.getPermsAndCacheMedia,
-        fakeOptions, successCB, errorCB);
-    });
-  });
-
-  describe("#getPermsAndCacheMedia", function() {
-    beforeEach(function() {
-      sandbox.stub(navigator, "originalGum");
-    });
-
-    it("should change pending to true", function() {
-      multiplexGum.getPermsAndCacheMedia();
-
-      expect(multiplexGum.userMedia.pending).to.equal(true);
-    });
-
-    it("should call originalGum", function() {
-      multiplexGum.getPermsAndCacheMedia();
-
-      sinon.assert.calledOnce(navigator.originalGum);
-    });
-
-    it("should reset the pending state when the error callback is called",
-      function(done) {
-        var fakeError = new Error();
-        navigator.originalGum.callsArgWith(2, fakeError);
-
-        multiplexGum.getPermsAndCacheMedia(null, null, function onError(error) {
-          expect(multiplexGum.userMedia.pending).to.equal(false);
-          done();
-        });
-      });
-
-    it("should reset the pending state when the success callback is called",
-      function(done) {
-        var fakeLocalStream = {};
-        navigator.originalGum.callsArgWith(1, fakeLocalStream);
-
-        multiplexGum.getPermsAndCacheMedia(null,
-          function onSuccess(localStream) {
-            expect(multiplexGum.userMedia.pending).to.equal(false);
-            done();
-          }, null);
-      });
-
-    it("should call the error callback when originalGum calls back an error",
-      function(done) {
-        var fakeError = new Error();
-        navigator.originalGum.callsArgWith(2, fakeError);
-
-        multiplexGum.getPermsAndCacheMedia(null, null, function onError(error) {
-          expect(error).to.eql(fakeError);
-          done();
-        });
-      });
-
-    it("should propagate the success callback when originalGum succeeds",
-      function(done) {
-        var fakeLocalStream = {};
-        navigator.originalGum.callsArgWith(1, fakeLocalStream);
-
-        multiplexGum.getPermsAndCacheMedia(null,
-          function onSuccess(localStream) {
-            expect(localStream).to.eql(fakeLocalStream);
-            done();
-          }, null);
-      });
-
-    it("should call the success callback when the stream is cached",
-      function(done) {
-        var fakeLocalStream = {};
-        multiplexGum.userMedia.localStream = fakeLocalStream;
-        sinon.assert.notCalled(navigator.originalGum);
-
-        multiplexGum.getPermsAndCacheMedia(null,
-          function onSuccess(localStream) {
-            expect(localStream).to.eql(fakeLocalStream);
-            done();
-          }, null);
-      });
-
-    it("should call the error callback when an error is cached",
-      function(done) {
-        var fakeError = new Error();
-        multiplexGum.userMedia.error = fakeError;
-        sinon.assert.notCalled(navigator.originalGum);
-
-        multiplexGum.getPermsAndCacheMedia(null, null, function onError(error) {
-          expect(error).to.eql(fakeError);
-          done();
-        });
-      });
-
-    it("should clear the error when success is called back", function(done) {
-      var fakeError = new Error();
-      var fakeLocalStream = {};
-      multiplexGum.userMedia.localStream = fakeLocalStream;
-      multiplexGum.userMedia.error = fakeError;
-
-      multiplexGum.getPermsAndCacheMedia(null, function onSuccess(localStream) {
-        expect(multiplexGum.userMedia.error).to.not.eql(fakeError);
-        expect(localStream).to.eql(fakeLocalStream);
-        done();
-      }, null);
-    });
-
-    it("should call all success callbacks when success is achieved",
-      function(done) {
-        var fakeLocalStream = {};
-        var calls = 0;
-        // Async is needed so that the callbacks can be queued up.
-        navigator.originalGum.callsArgWithAsync(1, fakeLocalStream);
-
-        multiplexGum.getPermsAndCacheMedia(null, function onSuccess(localStream) {
-          calls += 1;
-          expect(localStream).to.eql(fakeLocalStream);
-        }, null);
-
-        expect(multiplexGum.userMedia).to.have.property("pending", true);
-
-        multiplexGum.getPermsAndCacheMedia(null, function onSuccess(localStream) {
-          calls += 10;
-          expect(localStream).to.eql(fakeLocalStream);
-          expect(calls).to.equal(11);
-          done();
-        }, null);
-      });
-
-    it("should call all error callbacks when error is encountered",
-      function(done) {
-        var fakeError = new Error();
-        var calls = 0;
-        // Async is needed so that the callbacks can be queued up.
-        navigator.originalGum.callsArgWithAsync(2, fakeError);
-
-        multiplexGum.getPermsAndCacheMedia(null, null, function onError(error) {
-          calls += 1;
-          expect(error).to.eql(fakeError);
-        });
-
-        expect(multiplexGum.userMedia).to.have.property("pending", true);
-
-        multiplexGum.getPermsAndCacheMedia(null, null, function onError(error) {
-          calls += 10;
-          expect(error).to.eql(fakeError);
-          expect(calls).to.eql(11);
-          done();
-        });
-      });
-
-    it("should not call a getPermsAndCacheMedia success callback at the time" +
-       " of gUM success callback fires",
-      function() {
-        var fakeLocalStream = {};
-        multiplexGum.userMedia.localStream = fakeLocalStream;
-        navigator.originalGum.callsArgWith(1, fakeLocalStream);
-        var calledOnce = false;
-        var promiseCalledOnce = new Promise(function(resolve, reject) {
-
-          multiplexGum.getPermsAndCacheMedia(null,
-            function gPACMSuccess(localStream) {
-              expect(localStream).to.eql(fakeLocalStream);
-              expect(multiplexGum.userMedia).to.have.property("pending", false);
-              expect(multiplexGum.userMedia.successCallbacks.length).to.equal(0);
-              if (calledOnce) {
-                sinon.assert.fail("original callback was called twice");
-              }
-              calledOnce = true;
-              resolve();
-            }, function() {
-              sinon.assert.fail("error callback should not have fired");
-              reject();
-            });
-        });
-
-        return promiseCalledOnce.then(function() {
-          defaultGum(null, function gUMSuccess(localStream2) {
-            expect(localStream2).to.eql(fakeLocalStream);
-            expect(multiplexGum.userMedia).to.have.property("pending", false);
-            expect(multiplexGum.userMedia.successCallbacks.length).to.equal(0);
-          });
-        });
-      });
-
-    it("should not call a getPermsAndCacheMedia error callback when the " +
-      " gUM error callback fires",
-      function() {
-        var fakeError = "monkeys ate the stream";
-        multiplexGum.userMedia.error = fakeError;
-        navigator.originalGum.callsArgWith(2, fakeError);
-        var calledOnce = false;
-        var promiseCalledOnce = new Promise(function(resolve, reject) {
-          multiplexGum.getPermsAndCacheMedia(null, function() {
-            sinon.assert.fail("success callback should not have fired");
-            reject();
-          }, function gPACMError(errString) {
-            expect(errString).to.eql(fakeError);
-            expect(multiplexGum.userMedia).to.have.property("pending", false);
-            if (calledOnce) {
-              sinon.assert.fail("original error callback was called twice");
-            }
-            calledOnce = true;
-            resolve();
-          });
-        });
-
-        return promiseCalledOnce.then(function() {
-          defaultGum(null, function() {},
-            function gUMError(errString) {
-              expect(errString).to.eql(fakeError);
-              expect(multiplexGum.userMedia).to.have.property("pending", false);
-            });
-        });
-      });
-
-    it("should call the success callback with a new stream, " +
-       " when a new stream is available",
-      function(done) {
-        var endedStream = {ended: true};
-        var newStream = {};
-        multiplexGum.userMedia.localStream = endedStream;
-        navigator.originalGum.callsArgWith(1, newStream);
-
-        multiplexGum.getPermsAndCacheMedia(null, function onSuccess(localStream) {
-          expect(localStream).to.eql(newStream);
-          done();
-        }, null);
-      });
-  });
-
-  describe("#reset", function () {
-    it("should reset all userMedia state to default", function() {
-      // If userMedia is defined, then it needs to have all of
-      // the properties that multipleGum will depend on. It is
-      // easier to simply delete the object than to setup a fake
-      // state of the object.
-      delete multiplexGum.userMedia;
-
-      multiplexGum.reset();
-
-      expect(multiplexGum.userMedia).to.deep.equal({
-          error: null,
-          localStream: null,
-          pending: false,
-          errorCallbacks: [],
-          successCallbacks: []
-      });
-    });
-
-    it("should call all queued error callbacks with 'PERMISSION_DENIED'",
-      function(done) {
-        sandbox.stub(navigator, "originalGum");
-        multiplexGum.getPermsAndCacheMedia(null, function(localStream) {
-          sinon.assert.fail(
-            "The success callback shouldn't be called due to reset");
-        }, function(error) {
-          expect(error).to.equal("PERMISSION_DENIED");
-          done();
-        });
-        multiplexGum.reset();
-      });
-
-    it("should call MST.stop() on the stream tracks", function() {
-      var stopStub = sandbox.stub();
-      multiplexGum.userMedia.localStream = {stop: stopStub};
-
-      multiplexGum.reset();
-
-      sinon.assert.calledOnce(stopStub);
-    });
-
-    it("should not call MST.stop() on the stream tracks if .stop() doesn't exist",
-      function() {
-        multiplexGum.userMedia.localStream = {};
-
-        try {
-          multiplexGum.reset();
-        } catch (ex) {
-          sinon.assert.fail(
-            "reset shouldn't throw when a stream doesn't implement stop(): "
-            + ex);
-        }
-      });
-
-    it("should not get stuck in recursion if the error callback calls 'reset'",
-      function() {
-        sandbox.stub(navigator, "originalGum");
-        navigator.originalGum.callsArgWith(2, "PERMISSION_DENIED");
-
-        var calledOnce = false;
-        multiplexGum.getPermsAndCacheMedia(null, null, function() {
-          if (calledOnce) {
-            sinon.assert.fail("reset should only be called once");
-          }
-          calledOnce = true;
-          multiplexGum.reset.bind(multiplexGum)();
-        });
-      });
-
-    it("should not get stuck in recursion if the success callback calls 'reset'",
-      function() {
-        sandbox.stub(navigator, "originalGum");
-        navigator.originalGum.callsArgWith(1, {});
-
-        var calledOnce = false;
-        multiplexGum.getPermsAndCacheMedia(null, function() {
-          calledOnce = true;
-          multiplexGum.reset.bind(multiplexGum)();
-        }, function() {
-          if (calledOnce) {
-            sinon.assert.fail("reset should only be called once");
-          }
-          calledOnce = true;
-        });
-      });
-  });
-});
--- a/browser/components/loop/test/standalone/standaloneAppStore_test.js
+++ b/browser/components/loop/test/standalone/standaloneAppStore_test.js
@@ -17,71 +17,54 @@ describe("loop.store.StandaloneAppStore"
   afterEach(function() {
     sandbox.restore();
   });
 
   describe("#constructor", function() {
     it("should throw an error if the dispatcher is missing", function() {
       expect(function() {
         new loop.store.StandaloneAppStore({
-          sdk: {},
           helper: {},
-          conversation: {}
+          sdk: {}
         });
       }).to.Throw(/dispatcher/);
     });
 
     it("should throw an error if sdk is missing", function() {
       expect(function() {
         new loop.store.StandaloneAppStore({
           dispatcher: dispatcher,
-          helper: {},
-          conversation: {}
+          helper: {}
         });
       }).to.Throw(/sdk/);
     });
-
-    it("should throw an error if conversation is missing", function() {
-      expect(function() {
-        new loop.store.StandaloneAppStore({
-          dispatcher: dispatcher,
-          sdk: {},
-          helper: {}
-        });
-      }).to.Throw(/conversation/);
-    });
   });
 
   describe("#extractTokenInfo", function() {
-    var store, fakeGetWindowData, fakeSdk, fakeConversation, helper;
+    var store, fakeGetWindowData, fakeSdk, helper;
 
     beforeEach(function() {
       fakeGetWindowData = {
         windowPath: "",
         windowHash: ""
       };
 
       sandbox.stub(loop.shared.utils, "getUnsupportedPlatform").returns();
       sandbox.stub(loop.shared.utils, "isFirefox").returns(true);
 
       fakeSdk = {
         checkSystemRequirements: sinon.stub().returns(true)
       };
 
-      fakeConversation = {
-        set: sinon.spy()
-      };
-
       sandbox.stub(dispatcher, "dispatch");
 
       store = new loop.store.StandaloneAppStore({
         dispatcher: dispatcher,
-        sdk: fakeSdk,
         helper: helper,
-        conversation: fakeConversation
+        sdk: fakeSdk
       });
     });
 
     it("should set isFirefox to true for Firefox", function() {
       store.extractTokenInfo(
         new sharedActions.ExtractTokenInfo(fakeGetWindowData));
 
       expect(store.getStoreState().isFirefox).eql(true);
@@ -148,42 +131,16 @@ describe("loop.store.StandaloneAppStore"
       fakeGetWindowData.windowPath = "/";
 
       store.extractTokenInfo(
         new sharedActions.ExtractTokenInfo(fakeGetWindowData));
 
       expect(store.getStoreState().windowType).eql("home");
     });
 
-    it("should set the loopToken on the conversation for call paths",
-      function() {
-        fakeGetWindowData.windowPath = "/c/fakecalltoken";
-
-        store.extractTokenInfo(
-          new sharedActions.ExtractTokenInfo(fakeGetWindowData));
-
-        sinon.assert.calledOnce(fakeConversation.set);
-        sinon.assert.calledWithExactly(fakeConversation.set, {
-          loopToken: "fakecalltoken"
-        });
-      });
-
-    it("should set the loopToken on the conversation for room paths",
-      function() {
-        fakeGetWindowData.windowPath = "/c/fakeroomtoken";
-
-        store.extractTokenInfo(
-          new sharedActions.ExtractTokenInfo(fakeGetWindowData));
-
-        sinon.assert.calledOnce(fakeConversation.set);
-        sinon.assert.calledWithExactly(fakeConversation.set, {
-          loopToken: "fakeroomtoken"
-        });
-      });
-
     it("should dispatch a FetchServerData action for call paths",
       function() {
         fakeGetWindowData.windowPath = "/c/fakecalltoken";
 
         store.extractTokenInfo(
           new sharedActions.ExtractTokenInfo(fakeGetWindowData));
 
         sinon.assert.calledOnce(dispatcher.dispatch);
deleted file mode 100644
--- a/browser/components/loop/test/standalone/standalone_client_test.js
+++ /dev/null
@@ -1,179 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-describe("loop.StandaloneClient", function() {
-  "use strict";
-
-  var expect = chai.expect;
-  var sandbox,
-      fakeXHR,
-      requests = [],
-      callback,
-      fakeToken;
-
-  beforeEach(function() {
-    sandbox = sinon.sandbox.create();
-    fakeXHR = sandbox.useFakeXMLHttpRequest();
-    requests = [];
-    // https://github.com/cjohansen/Sinon.JS/issues/393
-    fakeXHR.xhr.onCreate = function (xhr) {
-      requests.push(xhr);
-    };
-    callback = sinon.spy();
-    fakeToken = "fakeTokenText";
-  });
-
-  afterEach(function() {
-    sandbox.restore();
-  });
-
-  describe("loop.StandaloneClient", function() {
-    describe("#constructor", function() {
-      it("should require a baseServerUrl setting", function() {
-        expect(function() {
-          new loop.StandaloneClient();
-        }).to.Throw(Error, /required/);
-      });
-    });
-
-    describe("#requestCallUrlInfo", function() {
-      var client, fakeServerErrorDescription;
-
-      beforeEach(function() {
-        client = new loop.StandaloneClient(
-          {baseServerUrl: "http://fake.api"}
-        );
-      });
-
-      describe("should make the requests to the server", function() {
-
-        it("should throw if loopToken is missing", function() {
-          expect(client.requestCallUrlInfo).to
-                                .throw(/Missing required parameter loopToken/);
-        });
-
-        it("should make a GET request for the call url creation date", function() {
-          client.requestCallUrlInfo("fakeCallUrlToken", function() {});
-
-          expect(requests).to.have.length.of(1);
-          expect(requests[0].url)
-                            .to.eql("http://fake.api/calls/fakeCallUrlToken");
-          expect(requests[0].method).to.eql("GET");
-        });
-
-        it("should call the callback with (null, serverResponse)", function() {
-          var successCallback = sandbox.spy(function() {});
-          var serverResponse = {
-            calleeFriendlyName: "Andrei",
-            urlCreationDate: 0
-          };
-
-          client.requestCallUrlInfo("fakeCallUrlToken", successCallback);
-          requests[0].respond(200, {"Content-Type": "application/json"},
-                              JSON.stringify(serverResponse));
-
-          sinon.assert.calledWithExactly(successCallback,
-                                         null,
-                                         serverResponse);
-        });
-
-        it("should log the error if the requests fails", function() {
-          sinon.stub(console, "error");
-          var serverResponse = {error: true};
-          var error = JSON.stringify(serverResponse);
-
-          client.requestCallUrlInfo("fakeCallUrlToken", sandbox.stub());
-          requests[0].respond(404, {"Content-Type": "application/json"},
-                              error);
-
-          sinon.assert.calledOnce(console.error);
-          sinon.assert.calledWithExactly(console.error, "Server error",
-                                        "HTTP 404 Not Found", serverResponse);
-        });
-     });
-    });
-
-
-
-    describe("requestCallInfo", function() {
-      var client, fakeServerErrorDescription;
-
-      beforeEach(function() {
-        client = new loop.StandaloneClient(
-          {baseServerUrl: "http://fake.api"}
-        );
-        fakeServerErrorDescription = {
-          code: 401,
-          errno: 101,
-          error: "error",
-          message: "invalid token",
-          info: "error info"
-        };
-      });
-
-      it("should prevent launching a conversation when token is missing",
-        function() {
-          expect(function() {
-            client.requestCallInfo();
-          }).to.Throw(Error, /missing.*[Tt]oken/);
-        });
-
-      it("should post data for the given call", function() {
-        client.requestCallInfo("fake", "audio", callback);
-
-        expect(requests).to.have.length.of(1);
-        expect(requests[0].url).to.be.equal("http://fake.api/calls/fake");
-        expect(requests[0].method).to.be.equal("POST");
-        expect(requests[0].requestBody).to.be.equal('{"callType":"audio","channel":"standalone"}');
-      });
-
-      it("should receive call data for the given call", function() {
-        client.requestCallInfo("fake", "audio-video", callback);
-
-        var sessionData = {
-          sessionId: "one",
-          sessionToken: "two",
-          apiKey: "three"
-        };
-
-        requests[0].respond(200, {"Content-Type": "application/json"},
-                            JSON.stringify(sessionData));
-        sinon.assert.calledWithExactly(callback, null, sessionData);
-      });
-
-      it("should send an error when the request fails", function() {
-        client.requestCallInfo("fake", "audio", callback);
-
-        requests[0].respond(401, {"Content-Type": "application/json"},
-                            JSON.stringify(fakeServerErrorDescription));
-        sinon.assert.calledWithMatch(callback, sinon.match(function(err) {
-          return /HTTP 401 Unauthorized/.test(err.message);
-        }));
-      });
-
-      it("should attach the server error description object to the error " +
-         "passed to the callback",
-        function() {
-          client.requestCallInfo("fake", "audio", callback);
-
-          requests[0].respond(401, {"Content-Type": "application/json"},
-                              JSON.stringify(fakeServerErrorDescription));
-
-          sinon.assert.calledWithMatch(callback, sinon.match(function(err) {
-            return err.errno === fakeServerErrorDescription.errno;
-          }));
-        });
-
-      it("should send an error if the data is not valid", function() {
-        client.requestCallInfo("fake", "audio", callback);
-
-        requests[0].respond(200, {"Content-Type": "application/json"},
-                            '{"bad": "one"}');
-        sinon.assert.calledWithMatch(callback, sinon.match(function(err) {
-          return /Invalid data received/.test(err.message);
-        }));
-      });
-    });
-  });
-});
--- a/browser/components/loop/test/standalone/webapp_test.js
+++ b/browser/components/loop/test/standalone/webapp_test.js
@@ -3,53 +3,28 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 describe("loop.webapp", function() {
   "use strict";
 
   var expect = chai.expect;
   var TestUtils = React.addons.TestUtils;
   var sharedActions = loop.shared.actions;
-  var sharedModels = loop.shared.models,
-      sharedViews = loop.shared.views,
-      sharedUtils = loop.shared.utils,
-      standaloneMedia = loop.standaloneMedia,
+  var sharedUtils = loop.shared.utils,
       sandbox,
-      notifications,
-      stubGetPermsAndCacheMedia,
-      fakeAudioXHR,
       dispatcher,
-      mozL10nGet,
-      WEBSOCKET_REASONS = loop.shared.utils.WEBSOCKET_REASONS;
+      mozL10nGet;
 
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
     dispatcher = new loop.Dispatcher();
-    notifications = new sharedModels.NotificationCollection();
-
-    stubGetPermsAndCacheMedia = sandbox.stub(
-      loop.standaloneMedia._MultiplexGum.prototype, "getPermsAndCacheMedia");
 
     mozL10nGet = sandbox.stub(navigator.mozL10n, "get", function(x) {
       return "translated:" + x;
     });
-
-    fakeAudioXHR = {
-      open: sinon.spy(),
-      send: function() {},
-      abort: function() {},
-      getResponseHeader: function(header) {
-        if (header === "Content-Type") {
-          return "audio/ogg";
-        }
-      },
-      responseType: null,
-      response: new ArrayBuffer(10),
-      onload: null
-    };
   });
 
   afterEach(function() {
     sandbox.restore();
   });
 
   describe("#init", function() {
     var loopConfigRestore;
@@ -76,614 +51,58 @@ describe("loop.webapp", function() {
         sinon.match(function(value) {
           return TestUtils.isCompositeComponentElement(value,
             loop.webapp.WebappRootView);
       }));
     });
 
     it("should dispatch a ExtractTokenInfo action with the path and hash",
       function() {
-        sandbox.stub(loop.shared.utils, "locationData").returns({
+        sandbox.stub(sharedUtils, "locationData").returns({
           hash: "#fakeKey",
           pathname: "/c/faketoken"
         });
 
       loop.webapp.init();
 
       sinon.assert.calledOnce(loop.Dispatcher.prototype.dispatch);
       sinon.assert.calledWithExactly(loop.Dispatcher.prototype.dispatch,
         new sharedActions.ExtractTokenInfo({
           windowPath: "/c/faketoken",
           windowHash: "#fakeKey"
         }));
     });
   });
 
-  describe("OutgoingConversationView", function() {
-    var ocView, conversation, client;
-
-    function mountTestComponent(props) {
-      return TestUtils.renderIntoDocument(
-        React.createElement(
-          loop.webapp.OutgoingConversationView, props));
-    }
-
-    beforeEach(function() {
-      client = new loop.StandaloneClient({
-        baseServerUrl: "http://fake.example.com"
-      });
-      sandbox.stub(client, "requestCallInfo");
-      sandbox.stub(client, "requestCallUrlInfo");
-      conversation = new sharedModels.ConversationModel({}, {
-        sdk: {}
-      });
-      conversation.set("loopToken", "fakeToken");
-      ocView = mountTestComponent({
-        client: client,
-        conversation: conversation,
-        isFirefox: true,
-        notifications: notifications,
-        sdk: {
-          on: sandbox.stub()
-        },
-        dispatcher: dispatcher
-      });
-    });
-
-    describe("start", function() {
-      it("should display the StartConversationView", function() {
-        TestUtils.findRenderedComponentWithType(ocView,
-          loop.webapp.StartConversationView);
-      });
-    });
-
-    // This is tested separately to ease testing, although it isn't really a
-    // public API. This will probably be refactored soon anyway.
-    describe("#_setupWebSocket", function() {
-      beforeEach(function() {
-        conversation.setOutgoingSessionData({
-          sessionId: "sessionId",
-          sessionToken: "sessionToken",
-          apiKey: "apiKey",
-          callId: "Hello",
-          progressURL: "http://invalid/url",
-          websocketToken: 123
-        });
-      });
-
-      describe("Websocket connection successful", function() {
-        var promise;
-
-        beforeEach(function() {
-          sandbox.stub(loop, "CallConnectionWebSocket").returns({
-            promiseConnect: function() {
-              promise = new Promise(function(resolve, reject) {
-                resolve();
-              });
-              return promise;
-            },
-
-            on: sandbox.spy()
-          });
-        });
-
-        it("should create a CallConnectionWebSocket", function(done) {
-          ocView._setupWebSocket();
-
-          promise.then(function () {
-            sinon.assert.calledOnce(loop.CallConnectionWebSocket);
-            sinon.assert.calledWithExactly(loop.CallConnectionWebSocket, {
-              callId: "Hello",
-              url: "http://invalid/url",
-              // The websocket token is converted to a hex string.
-              websocketToken: "7b"
-            });
-            done();
-          });
-        });
-      });
-
-      describe("Websocket connection failed", function() {
-        var promise;
-
-        beforeEach(function() {
-          sandbox.stub(loop, "CallConnectionWebSocket").returns({
-            promiseConnect: function() {
-              promise = new Promise(function(resolve, reject) {
-                reject();
-              });
-              return promise;
-            },
-
-            on: sandbox.spy()
-          });
-        });
-
-        it("should display an error", function(done) {
-          sandbox.stub(notifications, "errorL10n");
-          ocView._setupWebSocket();
-
-          promise.then(function() {
-          }, function () {
-            sinon.assert.calledOnce(notifications.errorL10n);
-            sinon.assert.calledWithExactly(notifications.errorL10n,
-              "cannot_start_call_session_not_ready");
-            done();
-          });
-        });
-      });
-
-      describe("Websocket Events", function() {
-        beforeEach(function() {
-          conversation.setOutgoingSessionData({
-            sessionId: "sessionId",
-            sessionToken: "sessionToken",
-            apiKey: "apiKey",
-            callId: "Hello",
-            progressURL: "http://progress.example.com",
-            websocketToken: 123
-          });
-
-          sandbox.stub(loop.CallConnectionWebSocket.prototype,
-                       "promiseConnect").returns({
-            then: sandbox.spy()
-          });
-
-          ocView._setupWebSocket();
-        });
-
-        describe("Progress", function() {
-          describe("state: terminate, reason: reject", function() {
-            beforeEach(function() {
-              sandbox.stub(notifications, "errorL10n");
-              sandbox.stub(window, "XMLHttpRequest").returns(fakeAudioXHR);
-            });
-
-            it("should display the FailedConversationView", function() {
-              ocView._websocket.trigger("progress", {
-                state: "terminated",
-                reason: WEBSOCKET_REASONS.REJECT
-              });
-
-              TestUtils.findRenderedComponentWithType(ocView,
-                loop.webapp.FailedConversationView);
-            });
-
-            it("should reset multiplexGum when a call is rejected",
-              function() {
-                var multiplexGum = new standaloneMedia._MultiplexGum();
-                standaloneMedia.setSingleton(multiplexGum);
-                sandbox.stub(standaloneMedia._MultiplexGum.prototype, "reset");
-
-                ocView._websocket.trigger("progress", {
-                  state: "terminated",
-                  reason: WEBSOCKET_REASONS.REJECT
-                });
-
-                sinon.assert.calledOnce(multiplexGum.reset);
-              });
-
-            it("should display an error message if the reason is not WEBSOCKET_REASONS.CANCEL",
-              function() {
-                ocView._websocket.trigger("progress", {
-                  state: "terminated",
-                  reason: WEBSOCKET_REASONS.REJECT
-                });
-
-                sinon.assert.calledOnce(notifications.errorL10n);
-                sinon.assert.calledWithExactly(notifications.errorL10n,
-                  "call_timeout_notification_text");
-              });
-
-            it("should not display an error message if the reason is WEBSOCKET_REASONS.CANCEL",
-              function() {
-                ocView._websocket.trigger("progress", {
-                  state: "terminated",
-                  reason: WEBSOCKET_REASONS.CANCEL
-                });
-
-                sinon.assert.notCalled(notifications.errorL10n);
-              });
-          });
-
-          describe("state: connecting", function() {
-            it("should set display the ConversationView", function() {
-              // Prevent the conversation trying to start the session for
-              // this test.
-              sandbox.stub(conversation, "startSession");
-
-              conversation.set({"loopToken": "fakeToken"});
-
-              ocView._websocket.trigger("progress", {
-                state: "connecting"
-              });
-
-              TestUtils.findRenderedComponentWithType(ocView,
-                sharedViews.ConversationView);
-            });
-          });
-        });
-      });
-    });
-
-    describe("Events", function() {
-      var fakeSessionData, promiseConnectStub;
-
-      beforeEach(function() {
-        fakeSessionData = {
-          sessionId: "sessionId",
-          sessionToken: "sessionToken",
-          apiKey: "apiKey",
-          websocketToken: 123,
-          progressURL: "fakeUrl",
-          callId: "fakeCallId"
-        };
-        conversation.set(fakeSessionData);
-        conversation.set("loopToken", "fakeToken");
-        sandbox.stub(notifications, "errorL10n");
-        sandbox.stub(notifications, "warnL10n");
-        promiseConnectStub =
-          sandbox.stub(loop.CallConnectionWebSocket.prototype, "promiseConnect");
-        promiseConnectStub.returns(new Promise(function(resolve, reject) {}));
-        sandbox.stub(window, "XMLHttpRequest").returns(fakeAudioXHR);
-      });
-
-      describe("call:outgoing", function() {
-        it("should display FailedConversationView if session token is missing",
-          function() {
-            conversation.set("loopToken", "");
-
-            ocView.startCall();
-
-            TestUtils.findRenderedComponentWithType(ocView,
-              loop.webapp.FailedConversationView);
-          });
-
-        it("should notify the user if session token is missing", function() {
-          conversation.set("loopToken", "");
-
-          ocView.startCall();
-
-          sinon.assert.calledOnce(notifications.errorL10n);
-          sinon.assert.calledWithExactly(notifications.errorL10n,
-                                         "missing_conversation_info");
-        });
-
-        it("should setup the websocket if session token is available",
-          function() {
-            ocView.startCall();
-
-            sinon.assert.calledOnce(promiseConnectStub);
-          });
-
-        it("should show the PendingConversationView if session token is available",
-          function() {
-            ocView.startCall();
-
-            TestUtils.findRenderedComponentWithType(ocView,
-              loop.webapp.PendingConversationView);
-          });
-      });
-
-      describe("session:ended", function() {
-        it("should call multiplexGum.reset", function() {
-          var multiplexGum = new standaloneMedia._MultiplexGum();
-          standaloneMedia.setSingleton(multiplexGum);
-          sandbox.stub(standaloneMedia._MultiplexGum.prototype, "reset");
-
-          conversation.trigger("session:ended");
-
-          sinon.assert.calledOnce(multiplexGum.reset);
-        });
-
-        it("should display the StartConversationView", function() {
-          conversation.trigger("session:ended");
-
-          TestUtils.findRenderedComponentWithType(ocView,
-            loop.webapp.EndedConversationView);
-        });
-
-        it("should display the FailedConversationView if callStatus is failure",
-          function() {
-            ocView.setState({
-              callStatus: "failure"
-            });
-            conversation.trigger("session:ended");
-
-            var failedView = TestUtils.findRenderedComponentWithType(ocView,
-                loop.webapp.FailedConversationView);
-            expect(failedView).to.not.equal(null);
-          });
-      });
-
-      describe("session:peer-hungup", function() {
-        it("should set display the StartConversationView", function() {
-          conversation.trigger("session:peer-hungup");
-
-          TestUtils.findRenderedComponentWithType(ocView,
-            loop.webapp.EndedConversationView);
-        });
-
-        it("should notify the user", function() {
-          conversation.trigger("session:peer-hungup");
-
-          sinon.assert.calledOnce(notifications.warnL10n);
-          sinon.assert.calledWithExactly(notifications.warnL10n,
-                                         "peer_ended_conversation2");
-        });
-
-      });
-
-      describe("session:network-disconnected", function() {
-        it("should display the StartConversationView",
-          function() {
-            conversation.trigger("session:network-disconnected");
-
-            TestUtils.findRenderedComponentWithType(ocView,
-              loop.webapp.EndedConversationView);
-          });
-
-        it("should notify the user", function() {
-          conversation.trigger("session:network-disconnected");
-
-          sinon.assert.calledOnce(notifications.warnL10n);
-          sinon.assert.calledWithExactly(notifications.warnL10n,
-                                         "network_disconnected");
-        });
-      });
-
-      describe("Published and Subscribed Streams", function() {
-        beforeEach(function() {
-          ocView._websocket = {
-            mediaUp: sinon.spy()
-          };
-        });
-
-        describe("publishStream", function() {
-          it("should not notify the websocket if only one stream is up",
-            function() {
-              conversation.set("publishedStream", true);
-
-              sinon.assert.notCalled(ocView._websocket.mediaUp);
-            });
-
-          it("should notify the websocket that media is up if both streams" +
-             "are connected", function() {
-              conversation.set("subscribedStream", true);
-              conversation.set("publishedStream", true);
-
-              sinon.assert.calledOnce(ocView._websocket.mediaUp);
-            });
-        });
-
-        describe("subscribedStream", function() {
-          it("should not notify the websocket if only one stream is up",
-            function() {
-              conversation.set("subscribedStream", true);
-
-              sinon.assert.notCalled(ocView._websocket.mediaUp);
-            });
-
-          it("should notify tloadhe websocket that media is up if both streams" +
-             "are connected", function() {
-              conversation.set("publishedStream", true);
-              conversation.set("subscribedStream", true);
-
-              sinon.assert.calledOnce(ocView._websocket.mediaUp);
-            });
-        });
-      });
-
-      describe("#setupOutgoingCall", function() {
-        describe("No loop token", function() {
-          beforeEach(function() {
-            conversation.set("loopToken", "");
-          });
-
-          it("should display the FailedConversationView", function() {
-            ocView.setupOutgoingCall();
-
-            TestUtils.findRenderedComponentWithType(ocView,
-              loop.webapp.FailedConversationView);
-          });
-
-          it("should display an error", function() {
-            ocView.setupOutgoingCall();
-
-            sinon.assert.calledOnce(notifications.errorL10n);
-          });
-        });
-
-        describe("Has loop token", function() {
-          beforeEach(function() {
-            sandbox.stub(conversation, "outgoing");
-          });
-
-          it("should call requestCallInfo on the client",
-            function() {
-              conversation.set("selectedCallType", "audio-video");
-              ocView.setupOutgoingCall();
-
-              sinon.assert.calledOnce(client.requestCallInfo);
-              sinon.assert.calledWith(client.requestCallInfo, "fakeToken",
-                                      "audio-video");
-            });
-
-          describe("requestCallInfo response handling", function() {
-            it("should set display the CallUrlExpiredView if the call has expired",
-               function() {
-                client.requestCallInfo.callsArgWith(2, {errno: 105});
-
-                ocView.setupOutgoingCall();
-
-                TestUtils.findRenderedComponentWithType(ocView,
-                  loop.webapp.CallUrlExpiredView);
-              });
-
-            it("should set display the FailedConversationView on any other error",
-               function() {
-                client.requestCallInfo.callsArgWith(2, {errno: 104});
-
-                ocView.setupOutgoingCall();
-
-                TestUtils.findRenderedComponentWithType(ocView,
-                  loop.webapp.FailedConversationView);
-              });
-
-            it("should notify the user on any other error", function() {
-              client.requestCallInfo.callsArgWith(2, {errno: 104});
-
-              ocView.setupOutgoingCall();
-
-              sinon.assert.calledOnce(notifications.errorL10n);
-            });
-
-            it("should call outgoing on the conversation model when details " +
-               "are successfully received", function() {
-                client.requestCallInfo.callsArgWith(2, null, fakeSessionData);
-
-                ocView.setupOutgoingCall();
-
-                sinon.assert.calledOnce(conversation.outgoing);
-                sinon.assert.calledWithExactly(conversation.outgoing, fakeSessionData);
-              });
-          });
-        });
-      });
-
-      describe("getMediaPrivs", function() {
-        var multiplexGum;
-
-        beforeEach(function() {
-          multiplexGum = new standaloneMedia._MultiplexGum();
-          standaloneMedia.setSingleton(multiplexGum);
-          sandbox.stub(standaloneMedia._MultiplexGum.prototype, "reset");
-
-          sandbox.stub(conversation, "gotMediaPrivs");
-        });
-
-        it("should call getPermsAndCacheMedia", function() {
-          conversation.trigger("call:outgoing:get-media-privs");
-
-          sinon.assert.calledOnce(stubGetPermsAndCacheMedia);
-        });
-
-        it("should call gotMediaPrevs on the model when successful", function() {
-          stubGetPermsAndCacheMedia.callsArgWith(1, {});
-
-          conversation.trigger("call:outgoing:get-media-privs");
-
-          sinon.assert.calledOnce(conversation.gotMediaPrivs);
-        });
-
-        it("should call multiplexGum.reset when getPermsAndCacheMedia fails",
-          function() {
-            stubGetPermsAndCacheMedia.callsArgWith(2, "FAKE_ERROR");
-
-            conversation.trigger("call:outgoing:get-media-privs");
-
-            sinon.assert.calledOnce(multiplexGum.reset);
-          });
-
-        it("should set state to `failure` when getPermsAndCacheMedia fails",
-          function() {
-            stubGetPermsAndCacheMedia.callsArgWith(2, "FAKE_ERROR");
-
-            conversation.trigger("call:outgoing:get-media-privs");
-
-            expect(ocView.state.callStatus).eql("failure");
-          });
-      });
-
-
-    });
-
-    describe("FailedConversationView", function() {
-      var view, fakeConversation, fakeClient, fakeAudio;
-
-      beforeEach(function() {
-        sandbox.stub(window, "XMLHttpRequest").returns(fakeAudioXHR);
-
-        fakeAudio = {
-          play: sinon.spy(),
-          pause: sinon.spy(),
-          removeAttribute: sinon.spy()
-        };
-        sandbox.stub(window, "Audio").returns(fakeAudio);
-
-        fakeClient = new loop.StandaloneClient({
-          baseServerUrl: "http://fake.example.com"
-        });
-        fakeConversation = new sharedModels.ConversationModel({}, {
-          sdk: {}
-        });
-        fakeConversation.set("loopToken", "fakeToken");
-
-        sandbox.stub(fakeClient, "requestCallUrlInfo");
-        view = React.addons.TestUtils.renderIntoDocument(
-          React.createElement(
-            loop.webapp.FailedConversationView, {
-              conversation: fakeConversation,
-              client: fakeClient,
-              notifications: notifications
-            }));
-      });
-
-      it("should play a failure sound, once", function() {
-        fakeAudioXHR.onload();
-
-        sinon.assert.called(fakeAudioXHR.open);
-        sinon.assert.calledWithExactly(
-          fakeAudioXHR.open, "GET", "shared/sounds/failure.ogg", true);
-        sinon.assert.calledOnce(fakeAudio.play);
-        expect(fakeAudio.loop).to.equal(false);
-      });
-    });
-  });
-
   describe("WebappRootView", function() {
     var sdk, conversationModel, client, props, standaloneAppStore;
     var activeRoomStore;
 
     function mountTestComponent() {
       return TestUtils.renderIntoDocument(
         React.createElement(
           loop.webapp.WebappRootView, {
-            client: client,
+            activeRoomStore: activeRoomStore,
             dispatcher: dispatcher,
-            notifications: notifications,
-            sdk: sdk,
-            conversation: conversationModel,
-            standaloneAppStore: standaloneAppStore,
-            activeRoomStore: activeRoomStore
+            standaloneAppStore: standaloneAppStore
           }));
     }
 
     beforeEach(function() {
       sdk = {
         checkSystemRequirements: function() { return true; }
       };
-      conversationModel = new sharedModels.ConversationModel({}, {
-        sdk: sdk
-      });
-      client = new loop.StandaloneClient({
-        baseServerUrl: "fakeUrl"
-      });
       activeRoomStore = new loop.store.ActiveRoomStore(dispatcher, {
         mozLoop: {},
         sdkDriver: {}
       });
       standaloneAppStore = new loop.store.StandaloneAppStore({
         dispatcher: dispatcher,
-        sdk: sdk,
-        conversation: conversationModel
+        sdk: sdk
       });
-      // Stub this to stop the StartConversationView kicking in the request and
-      // follow-ups.
-      sandbox.stub(client, "requestCallUrlInfo");
     });
 
     it("should display the UnsupportedDeviceView for `unsupportedDevice` window type",
       function() {
         standaloneAppStore.setStoreState({windowType: "unsupportedDevice", unsupportedPlatform: "ios"});
         var webappRootView = mountTestComponent();
 
         TestUtils.findRenderedComponentWithType(webappRootView,
@@ -695,26 +114,16 @@ describe("loop.webapp", function() {
         standaloneAppStore.setStoreState({windowType: "unsupportedBrowser", isFirefox: false});
 
         var webappRootView = mountTestComponent();
 
         TestUtils.findRenderedComponentWithType(webappRootView,
           loop.webapp.UnsupportedBrowserView);
       });
 
-    it("should display the OutgoingConversationView for `outgoing` window type",
-      function() {
-        standaloneAppStore.setStoreState({windowType: "outgoing", isFirefox: true});
-
-        var webappRootView = mountTestComponent();
-
-        TestUtils.findRenderedComponentWithType(webappRootView,
-          loop.webapp.OutgoingConversationView);
-      });
-
     it("should display the StandaloneRoomView for `room` window type",
       function() {
         standaloneAppStore.setStoreState({windowType: "room", isFirefox: true});
 
         var webappRootView = mountTestComponent();
 
         TestUtils.findRenderedComponentWithType(webappRootView,
           loop.standaloneRoomViews.StandaloneRoomView);
@@ -726,354 +135,21 @@ describe("loop.webapp", function() {
         var webappRootView = mountTestComponent();
 
         TestUtils.findRenderedComponentWithType(webappRootView,
           loop.webapp.HomeView);
     });
   });
 
   describe("HomeView", function() {
-    it("should call loop.standaloneMedia.reset", function() {
-      var multiplexGum = new standaloneMedia._MultiplexGum();
-      standaloneMedia.setSingleton(multiplexGum);
-      sandbox.stub(standaloneMedia._MultiplexGum.prototype, "reset");
-
-      TestUtils.renderIntoDocument(
+    it("should display a welcome", function() {
+      var homeView = TestUtils.renderIntoDocument(
         React.createElement(loop.webapp.HomeView));
 
-      sinon.assert.calledOnce(multiplexGum.reset);
-      sinon.assert.calledWithExactly(multiplexGum.reset);
-    });
-  });
-
-  describe("WaitingConversationView", function() {
-    var view, websocket, fakeAudio;
-
-    beforeEach(function() {
-      websocket = new loop.CallConnectionWebSocket({
-        url: "wss://fake/",
-        callId: "callId",
-        websocketToken: "7b"
-      });
-
-      sinon.stub(websocket, "cancel");
-      fakeAudio = {
-        play: sinon.spy(),
-        pause: sinon.spy(),
-        removeAttribute: sinon.spy()
-      };
-      sandbox.stub(window, "Audio").returns(fakeAudio);
-      sandbox.stub(window, "XMLHttpRequest").returns(fakeAudioXHR);
-
-      view = React.addons.TestUtils.renderIntoDocument(
-        React.createElement(
-          loop.webapp.WaitingConversationView, {
-            websocket: websocket
-          })
-      );
-    });
-
-    describe("#componentDidMount", function() {
-
-      it("should play a looped connecting sound", function() {
-        fakeAudioXHR.onload();
-
-        sinon.assert.called(fakeAudioXHR.open);
-        sinon.assert.calledWithExactly(
-          fakeAudioXHR.open, "GET", "shared/sounds/connecting.ogg", true);
-        sinon.assert.calledOnce(fakeAudio.play);
-        expect(fakeAudio.loop).to.equal(true);
-      });
-
-    });
-
-    describe("#_cancelOutgoingCall", function() {
-      it("should inform the websocket to cancel the setup", function() {
-        var button = view.getDOMNode().querySelector(".btn-cancel");
-        React.addons.TestUtils.Simulate.click(button);
-
-        sinon.assert.calledOnce(websocket.cancel);
-      });
-
-      it("should call multiplexGum.reset to release the camera", function() {
-        var multiplexGum = new standaloneMedia._MultiplexGum();
-        standaloneMedia.setSingleton(multiplexGum);
-        sandbox.stub(standaloneMedia._MultiplexGum.prototype, "reset");
-
-        var button = view.getDOMNode().querySelector(".btn-cancel");
-        React.addons.TestUtils.Simulate.click(button);
-
-        sinon.assert.calledOnce(multiplexGum.reset);
-        sinon.assert.calledWithExactly(multiplexGum.reset);
-      });
-    });
-
-    describe("Events", function() {
-      describe("progress:alerting", function() {
-        it("should update the callstate to ringing", function () {
-          websocket.trigger("progress:alerting");
-
-          expect(view.state.callState).to.be.equal("ringing");
-        });
-
-        it("should play a looped ringing sound", function() {
-          websocket.trigger("progress:alerting");
-          fakeAudioXHR.onload();
-
-          sinon.assert.called(fakeAudioXHR.open);
-          sinon.assert.calledWithExactly(
-            fakeAudioXHR.open, "GET", "shared/sounds/ringtone.ogg", true);
-
-          sinon.assert.called(fakeAudio.play);
-          expect(fakeAudio.loop).to.equal(true);
-        });
-      });
-    });
-  });
-
-  describe("StartConversationView", function() {
-    describe("#initiate", function() {
-      var conversation, view, fakeSubmitEvent, requestCallUrlInfo;
-
-      beforeEach(function() {
-        conversation = new sharedModels.ConversationModel({}, {
-          sdk: {}
-        });
-
-        fakeSubmitEvent = {preventDefault: sinon.spy()};
-
-        var standaloneClientStub = {
-          requestCallUrlInfo: function(token, cb) {
-            cb(null, {urlCreationDate: 0});
-          },
-          settings: {baseServerUrl: loop.webapp.baseServerUrl}
-        };
-
-        view = React.addons.TestUtils.renderIntoDocument(
-          React.createElement(
-            loop.webapp.StartConversationView, {
-              conversation: conversation,
-              notifications: notifications,
-              client: standaloneClientStub
-            }));
-      });
-
-      it("should start the audio-video conversation establishment process",
-        function() {
-          var setupOutgoingCall = sinon.stub(conversation, "setupOutgoingCall");
-
-          var button = view.getDOMNode().querySelector(".btn-accept");
-          React.addons.TestUtils.Simulate.click(button);
-
-          sinon.assert.calledOnce(setupOutgoingCall);
-          sinon.assert.calledWithExactly(setupOutgoingCall, "audio-video");
-      });
-
-      it("should start the audio-only conversation establishment process",
-        function() {
-          var setupOutgoingCall = sinon.stub(conversation, "setupOutgoingCall");
-
-          var button = view.getDOMNode().querySelector(".start-audio-only-call");
-          React.addons.TestUtils.Simulate.click(button);
-
-          sinon.assert.calledOnce(setupOutgoingCall);
-          sinon.assert.calledWithExactly(setupOutgoingCall, "audio");
-        });
-
-      it("should disable audio-video button once session is initiated",
-         function() {
-           conversation.set("loopToken", "fake");
-
-           var button = view.getDOMNode().querySelector(".btn-accept");
-           React.addons.TestUtils.Simulate.click(button);
-
-           expect(button.disabled).to.eql(true);
-         });
-
-      it("should disable audio-only button once session is initiated",
-         function() {
-           conversation.set("loopToken", "fake");
-
-           var button = view.getDOMNode().querySelector(".start-audio-only-call");
-           React.addons.TestUtils.Simulate.click(button);
-
-           expect(button.disabled).to.eql(true);
-         });
-
-      it("should set selectedCallType to audio", function() {
-        conversation.set("loopToken", "fake");
-
-         var button = view.getDOMNode().querySelector(".start-audio-only-call");
-         React.addons.TestUtils.Simulate.click(button);
-
-         expect(conversation.get("selectedCallType")).to.eql("audio");
-       });
-
-       it("should set selectedCallType to audio-video", function() {
-         conversation.set("loopToken", "fake");
-
-         var button = view.getDOMNode().querySelector(".standalone-call-btn-video-icon");
-         React.addons.TestUtils.Simulate.click(button);
-
-         expect(conversation.get("selectedCallType")).to.eql("audio-video");
-      });
-
-      // XXX this test breaks while the feature actually works; find a way to
-      // test this properly.
-      it.skip("should set state.urlCreationDateString to a locale date string",
-        function() {
-          var date = new Date();
-          var options = {year: "numeric", month: "long", day: "numeric"};
-          var timestamp = date.toLocaleDateString(navigator.language, options);
-          var dateElem = view.getDOMNode().querySelector(".call-url-date");
-
-          expect(dateElem.textContent).to.eql(timestamp);
-        });
-    });
-
-    describe("Events", function() {
-      var conversation, view, StandaloneClient, requestCallUrlInfo;
-
-      beforeEach(function() {
-        conversation = new sharedModels.ConversationModel({
-          loopToken: "fake"
-        }, {
-          sdk: {}
-        });
-
-        sandbox.stub(notifications, "errorL10n");
-        requestCallUrlInfo = sandbox.stub();
-
-        view = React.addons.TestUtils.renderIntoDocument(
-            React.createElement(
-              loop.webapp.StartConversationView, {
-                conversation: conversation,
-                notifications: notifications,
-                client: {requestCallUrlInfo: requestCallUrlInfo}
-              }));
-      });
-
-      it("should call requestCallUrlInfo", function() {
-        sinon.assert.calledOnce(requestCallUrlInfo);
-        sinon.assert.calledWithExactly(requestCallUrlInfo,
-                                       sinon.match.string,
-                                       sinon.match.func);
-      });
-
-      it("should add a notification when a session:error model event is " +
-         " received without an argument", function() {
-        conversation.trigger("session:error");
-
-        sinon.assert.calledOnce(notifications.errorL10n);
-        sinon.assert.calledWithExactly(notifications.errorL10n,
-          sinon.match.string, undefined);
-      });
-
-      it("should add a notification with the custom message id when a " +
-         "session:error event is fired with an argument", function() {
-        conversation.trigger("session:error", "tech_error");
-
-        sinon.assert.calledOnce(notifications.errorL10n);
-        sinon.assert.calledWithExactly(notifications.errorL10n,
-                                       "tech_error", undefined);
-      });
-
-      it("should add a notification with the custom message id when a " +
-         "session:error event is fired with an argument and parameters",
-         function() {
-          conversation.trigger("session:error", "tech_error", {param: "value"});
-
-          sinon.assert.calledOnce(notifications.errorL10n);
-          sinon.assert.calledWithExactly(notifications.errorL10n,
-                                         "tech_error", { param: "value" });
-      });
-    });
-
-    describe("#render", function() {
-      var conversation, view, requestCallUrlInfo, oldLocalStorageValue;
-
-      beforeEach(function() {
-        oldLocalStorageValue = localStorage.getItem("has-seen-tos");
-        localStorage.removeItem("has-seen-tos");
-
-        conversation = new sharedModels.ConversationModel({
-          loopToken: "fake"
-        }, {
-          sdk: {}
-        });
-
-        requestCallUrlInfo = sandbox.stub();
-      });
-
-      afterEach(function() {
-        if (oldLocalStorageValue !== null) {
-          localStorage.setItem("has-seen-tos", oldLocalStorageValue);
-        }
-      });
-
-      it("should show the TOS", function() {
-        var tos;
-
-        view = React.addons.TestUtils.renderIntoDocument(
-          React.createElement(
-            loop.webapp.StartConversationView, {
-              conversation: conversation,
-              notifications: notifications,
-              client: {requestCallUrlInfo: requestCallUrlInfo}
-            }));
-        tos = view.getDOMNode().querySelector(".terms-service");
-
-        expect(tos.classList.contains("hide")).to.equal(false);
-      });
-
-      it("should not show the TOS if it has already been seen", function() {
-        var tos;
-
-        localStorage.setItem("has-seen-tos", "true");
-        view = React.addons.TestUtils.renderIntoDocument(
-          React.createElement(
-            loop.webapp.StartConversationView, {
-              conversation: conversation,
-              notifications: notifications,
-              client: {requestCallUrlInfo: requestCallUrlInfo}
-            }));
-        tos = view.getDOMNode().querySelector(".terms-service");
-
-        expect(tos.classList.contains("hide")).to.equal(true);
-      });
-    });
-  });
-
-  describe("EndedConversationView", function() {
-    var view, conversation, fakeAudio;
-
-    beforeEach(function() {
-      fakeAudio = {
-        play: sinon.spy(),
-        pause: sinon.spy(),
-        removeAttribute: sinon.spy()
-      };
-      sandbox.stub(window, "Audio").returns(fakeAudio);
-
-      conversation = new sharedModels.ConversationModel({}, {
-        sdk: {}
-      });
-      sandbox.stub(window, "XMLHttpRequest").returns(fakeAudioXHR);
-      view = React.addons.TestUtils.renderIntoDocument(
-        React.createElement(
-          loop.webapp.EndedConversationView, {
-            conversation: conversation,
-            dispatcher: dispatcher,
-            sdk: {},
-            onAfterFeedbackReceived: function(){}
-          }));
-    });
-
-    it("should render a ConversationView", function() {
-      TestUtils.findRenderedComponentWithType(view, sharedViews.ConversationView);
+      expect(homeView.getDOMNode().textContent.includes("welcome")).eql(true);
     });
   });
 
   describe("PromoteFirefoxView", function() {
     describe("#render", function() {
       it("should not render when using Firefox", function() {
         var comp = TestUtils.renderIntoDocument(
           React.createElement(loop.webapp.PromoteFirefoxView, {
--- a/browser/components/loop/ui/index.html
+++ b/browser/components/loop/ui/index.html
@@ -24,17 +24,16 @@
         uncaughtError = error;
       });
     </script>
 
     <div id="main"></div>
     <div id="results"></div>
     <script src="fake-mozLoop.js"></script>
     <script src="fake-l10n.js"></script>
-    <script src="../content/js/multiplexGum.js"></script>
     <script src="../content/shared/libs/react-0.12.2.js"></script>
     <script src="../content/shared/libs/lodash-3.9.3.js"></script>
     <script src="../content/shared/libs/backbone-1.2.1.js"></script>
     <script src="../content/shared/js/actions.js"></script>
     <script src="../content/shared/js/utils.js"></script>
     <script src="../content/shared/js/models.js"></script>
     <script src="../content/shared/js/mixins.js"></script>
     <script src="../content/shared/js/websocket.js"></script>
@@ -48,17 +47,16 @@
     <script src="../content/js/feedbackViews.js"></script>
     <script src="../content/shared/js/textChatView.js"></script>
     <script src="../content/shared/js/urlRegExps.js"></script>
     <script src="../content/shared/js/linkifiedTextView.js"></script>
     <script src="../content/js/roomStore.js"></script>
     <script src="../content/js/roomViews.js"></script>
     <script src="../content/js/conversationViews.js"></script>
     <script src="../content/js/client.js"></script>
-    <script src="../standalone/content/js/multiplexGum.js"></script>
     <script src="../standalone/content/js/webapp.js"></script>
     <script src="../standalone/content/js/standaloneRoomViews.js"></script>
     <script type="text/javascript;version=1.8" src="../content/js/contacts.js"></script>
     <script>
       if (!loop.contacts) {
         // For browsers that don't support ES6 without special flags (all but Fx
         // at the moment), we shim the contacts namespace with its most barebone
         // implementation.