Bug 1102841: implement Cancel and Block a call for incoming direct calls r=abr
authorMike de Boer <mdeboer@mozilla.com>
Fri, 21 Nov 2014 15:38:42 +0100
changeset 241397 39a1305d71191fcad3289f3799354275493fc723
parent 241396 8525554ff2ecc9c4c1ac7febd6c97a272901e952
child 241398 96046c85765face71e42b5c85fbe7a16ddc27a2c
push id4311
push userraliiev@mozilla.com
push dateMon, 12 Jan 2015 19:37:41 +0000
treeherdermozilla-beta@150c9fed433b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersabr
bugs1102841
milestone36.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 1102841: implement Cancel and Block a call for incoming direct calls r=abr
browser/components/loop/LoopCalls.jsm
browser/components/loop/content/js/conversation.js
browser/components/loop/content/js/conversation.jsx
browser/components/loop/content/shared/js/conversationStore.js
--- a/browser/components/loop/LoopCalls.jsm
+++ b/browser/components/loop/LoopCalls.jsm
@@ -16,16 +16,18 @@ const EMAIL_OR_PHONE_RE = /^(:?\S+@\S+|\
 XPCOMUtils.defineLazyModuleGetter(this, "MozLoopService",
                                   "resource:///modules/loop/MozLoopService.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "LOOP_SESSION_TYPE",
                                   "resource:///modules/loop/MozLoopService.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "LoopContacts",
                                   "resource:///modules/loop/LoopContacts.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+                                  "resource://gre/modules/Task.jsm");
 
  /**
  * Attempts to open a websocket.
  *
  * A new websocket interface is used each time. If an onStop callback
  * was received, calling asyncOpen() on the same interface will
  * trigger a "alreay open socket" exception even though the channel
  * is logically closed.
@@ -321,16 +323,60 @@ let LoopCallsInternal = {
       callType: callType,
       type: "outgoing"
     };
 
     this._startCall(callData);
     return true;
   },
 
+  /**
+   * Block a caller so it will show up in the contacts list as a blocked contact.
+   * If the contact is not yet part of the users' contacts list, it will be added
+   * as a blocked contact directly.
+   *
+   * @param {String}   callerId Email address or phone number that may identify
+   *                            the caller as an existing contact
+   * @param {Function} callback Function that will be invoked once the operation
+   *                            has completed. When an error occurs, it will be
+   *                            passed as its first argument
+   */
+  blockDirectCaller: function(callerId, callback) {
+    let field = callerId.contains("@") ? "email" : "tel";
+    Task.spawn(function* () {
+      // See if we can find the caller in our database.
+      let contacts = yield LoopContacts.promise("search", {
+        q: callerId,
+        field: field
+      });
+
+      let contact;
+      if (contacts.length) {
+        for (contact of contacts) {
+          yield LoopContacts.promise("block", contact._guid);
+        }
+      } else {
+        // If the contact doesn't exist yet, add it as a blocked contact.
+        contact = {
+          id: MozLoopService.generateUUID(),
+          name: [callerId],
+          category: ["local"],
+          blocked: true
+        };
+        // Add the phone OR email field to the contact.
+        contact[field] = [{
+          pref: true,
+          value: callerId
+        }];
+
+        yield LoopContacts.promise("add", contact);
+      }
+    }).then(callback, callback);
+  },
+
    /**
    * Open call progress websocket and terminate with a reason of busy
    * the server.
    *
    * @param {callData} Must contain the progressURL, callId and websocketToken
    *                   returned by the LoopService.
    */
   _returnBusy: function(callData) {
@@ -395,11 +441,18 @@ this.LoopCalls = {
      * Starts a direct call to the contact addresses.
      *
      * @param {Object} contact The contact to call
      * @param {String} callType The type of call, e.g. "audio-video" or "audio-only"
      * @return true if the call is opened, false if it is not opened (i.e. busy)
      */
   startDirectCall: function(contact, callType) {
     LoopCallsInternal.startDirectCall(contact, callType);
+  },
+
+  /**
+   * @see LoopCallsInternal#blockDirectCaller
+   */
+  blockDirectCaller: function(callerId, callback) {
+    return LoopCallsInternal.blockDirectCaller(callerId, callback);
   }
 };
 Object.freeze(LoopCalls);
--- a/browser/components/loop/content/js/conversation.js
+++ b/browser/components/loop/content/js/conversation.js
@@ -15,16 +15,19 @@ loop.conversation = (function(mozL10n) {
   var sharedMixins = loop.shared.mixins;
   var sharedModels = loop.shared.models;
   var sharedActions = loop.shared.actions;
 
   var OutgoingConversationView = loop.conversationViews.OutgoingConversationView;
   var CallIdentifierView = loop.conversationViews.CallIdentifierView;
   var DesktopRoomConversationView = loop.roomViews.DesktopRoomConversationView;
 
+  // Matches strings of the form "<nonspaces>@<nonspaces>" or "+<digits>"
+  var EMAIL_OR_PHONE_RE = /^(:?\S+@\S+|\+\d+)$/;
+
   var IncomingCallView = React.createClass({displayName: 'IncomingCallView',
     mixins: [sharedMixins.DropdownMenuMixin, sharedMixins.AudioMixin],
 
     propTypes: {
       model: React.PropTypes.object.isRequired,
       video: React.PropTypes.bool.isRequired
     },
 
@@ -500,24 +503,37 @@ loop.conversation = (function(mozL10n) {
      * Decline and block an incoming call
      * @note:
      * - loopToken is the callUrl identifier. It gets set in the panel
      *   after a callUrl is received
      */
     declineAndBlock: function() {
       navigator.mozLoop.stopAlerting();
       var token = this.props.conversation.get("callToken");
-      this.props.client.deleteCallUrl(token,
-        this.props.conversation.get("sessionType"),
-        function(error) {
+      var callerId = this.props.conversation.get("callerId");
+
+      // If this is a direct call, we'll need to block the caller directly.
+      if (callerId && EMAIL_OR_PHONE_RE.test(callerId)) {
+        navigator.mozLoop.calls.blockDirectCaller(callerId, function(err) {
           // XXX The conversation window will be closed when this cb is triggered
           // figure out if there is a better way to report the error to the user
-          // (bug 1048909).
-          console.log(error);
+          // (bug 1103150).
+          console.log(err.fileName + ":" + err.lineNumber + ": " + err.message);
         });
+      } else {
+        this.props.client.deleteCallUrl(token,
+          this.props.conversation.get("sessionType"),
+          function(error) {
+            // XXX The conversation window will be closed when this cb is triggered
+            // figure out if there is a better way to report the error to the user
+            // (bug 1048909).
+            console.log(error);
+          });
+      }
+
       this._declineCall();
     },
 
     /**
      * Handles a error starting the session
      */
     _handleSessionError: function() {
       // XXX Not the ideal response, but bug 1047410 will be replacing
--- a/browser/components/loop/content/js/conversation.jsx
+++ b/browser/components/loop/content/js/conversation.jsx
@@ -15,16 +15,19 @@ loop.conversation = (function(mozL10n) {
   var sharedMixins = loop.shared.mixins;
   var sharedModels = loop.shared.models;
   var sharedActions = loop.shared.actions;
 
   var OutgoingConversationView = loop.conversationViews.OutgoingConversationView;
   var CallIdentifierView = loop.conversationViews.CallIdentifierView;
   var DesktopRoomConversationView = loop.roomViews.DesktopRoomConversationView;
 
+  // Matches strings of the form "<nonspaces>@<nonspaces>" or "+<digits>"
+  var EMAIL_OR_PHONE_RE = /^(:?\S+@\S+|\+\d+)$/;
+
   var IncomingCallView = React.createClass({
     mixins: [sharedMixins.DropdownMenuMixin, sharedMixins.AudioMixin],
 
     propTypes: {
       model: React.PropTypes.object.isRequired,
       video: React.PropTypes.bool.isRequired
     },
 
@@ -500,24 +503,37 @@ loop.conversation = (function(mozL10n) {
      * Decline and block an incoming call
      * @note:
      * - loopToken is the callUrl identifier. It gets set in the panel
      *   after a callUrl is received
      */
     declineAndBlock: function() {
       navigator.mozLoop.stopAlerting();
       var token = this.props.conversation.get("callToken");
-      this.props.client.deleteCallUrl(token,
-        this.props.conversation.get("sessionType"),
-        function(error) {
+      var callerId = this.props.conversation.get("callerId");
+
+      // If this is a direct call, we'll need to block the caller directly.
+      if (callerId && EMAIL_OR_PHONE_RE.test(callerId)) {
+        navigator.mozLoop.calls.blockDirectCaller(callerId, function(err) {
           // XXX The conversation window will be closed when this cb is triggered
           // figure out if there is a better way to report the error to the user
-          // (bug 1048909).
-          console.log(error);
+          // (bug 1103150).
+          console.log(err.fileName + ":" + err.lineNumber + ": " + err.message);
         });
+      } else {
+        this.props.client.deleteCallUrl(token,
+          this.props.conversation.get("sessionType"),
+          function(error) {
+            // XXX The conversation window will be closed when this cb is triggered
+            // figure out if there is a better way to report the error to the user
+            // (bug 1048909).
+            console.log(error);
+          });
+      }
+
       this._declineCall();
     },
 
     /**
      * Handles a error starting the session
      */
     _handleSessionError: function() {
       // XXX Not the ideal response, but bug 1047410 will be replacing
--- a/browser/components/loop/content/shared/js/conversationStore.js
+++ b/browser/components/loop/content/shared/js/conversationStore.js
@@ -70,16 +70,18 @@ loop.store.ConversationStore = (function
       contact: undefined,
       // The call type for the call.
       // XXX Don't hard-code, this comes from the data in bug 1072323
       callType: CALL_TYPES.AUDIO_VIDEO,
 
       // Call Connection information
       // The call id from the loop-server
       callId: undefined,
+      // The caller id of the contacting side
+      callerId: undefined,
       // The connection progress url to connect the websocket
       progressURL: undefined,
       // The websocket token that allows connection to the progress url
       websocketToken: undefined,
       // SDK API key
       apiKey: undefined,
       // SDK session ID
       sessionId: undefined,