diff --git a/browser/base/content/browser-syncui.js b/browser/base/content/browser-syncui.js index f7fde1b5a613..1c415861177d 100644 --- a/browser/base/content/browser-syncui.js +++ b/browser/base/content/browser-syncui.js @@ -287,9 +287,8 @@ let gSyncUI = { // Commands doSync: function SUI_doSync() { let needsSetup = this._needsSetup(); - let loginFailed = this._loginFailed(); - if (!(loginFailed || needsSetup)) { + if (!needsSetup) { setTimeout(function () Weave.Service.errorHandler.syncAndReportErrors(), 0); } diff --git a/browser/components/loop/GoogleImporter.jsm b/browser/components/loop/GoogleImporter.jsm index 56e5cbe545ff..fb8680c14892 100644 --- a/browser/components/loop/GoogleImporter.jsm +++ b/browser/components/loop/GoogleImporter.jsm @@ -448,10 +448,13 @@ this.GoogleImporter.prototype = { if (phoneNodes.length) { contact.tel = []; for (let [,phoneNode] of Iterator(phoneNodes)) { + let phoneNumber = phoneNode.hasAttribute("uri") ? + phoneNode.getAttribute("uri").replace("tel:", "") : + phoneNode.firstChild.nodeValue; contact.tel.push({ pref: (phoneNode.getAttribute("primary") == "true"), type: [getFieldType(phoneNode)], - value: phoneNode.getAttribute("uri").replace("tel:", "") + value: phoneNumber }); } } diff --git a/browser/components/loop/LoopContacts.jsm b/browser/components/loop/LoopContacts.jsm index b5537a0f5e36..9b0334d7284c 100644 --- a/browser/components/loop/LoopContacts.jsm +++ b/browser/components/loop/LoopContacts.jsm @@ -790,16 +790,85 @@ let LoopContactsInternal = Object.freeze({ /** * Search through the data store for contacts that match a certain (sub-)string. + * NB: The current implementation is very simple, naive if you will; we fetch + * _all_ the contacts via `getAll()` and iterate over all of them to find + * the contacts matching the supplied query (brute-force search in + * exponential time). * - * @param {String} query Needle to search for in our haystack of contacts + * @param {Object} query Needle to search for in our haystack of contacts * @param {Function} callback Function that will be invoked once the operation * finished. The first argument passed will be an * `Error` object or `null`. The second argument will * be an `Array` of contact objects, if successfull. + * + * Example: + * LoopContacts.search({ + * q: "foo@bar.com", + * field: "email" // 'email' is the default. + * }, function(err, contacts) { + * if (err) { + * throw err; + * } + * console.dir(contacts); + * }); */ search: function(query, callback) { - //TODO in bug 1037114. - callback(new Error("Not implemented yet!")); + if (!("q" in query) || !query.q) { + callback(new Error("Nothing to search for. 'q' is required.")); + return; + } + if (!("field" in query)) { + query.field = "email"; + } + let queryValue = query.q; + if (query.field == "tel") { + queryValue = queryValue.replace(/[\D]+/g, ""); + } + + const checkForMatch = function(fieldValue) { + if (typeof fieldValue == "string") { + if (query.field == "tel") { + return fieldValue.replace(/[\D]+/g, "").endsWith(queryValue); + } + return fieldValue == queryValue; + } + if (typeof fieldValue == "number" || typeof fieldValue == "boolean") { + return fieldValue == queryValue; + } + if ("value" in fieldValue) { + return checkForMatch(fieldValue.value); + } + return false; + }; + + let foundContacts = []; + this.getAll((err, contacts) => { + if (err) { + callback(err); + return; + } + + for (let contact of contacts) { + let matchWith = contact[query.field]; + if (!matchWith) { + continue; + } + + // Many fields are defined as Arrays. + if (Array.isArray(matchWith)) { + for (let fieldValue of matchWith) { + if (checkForMatch(fieldValue)) { + foundContacts.push(contact); + break; + } + } + } else if (checkForMatch(matchWith)) { + foundContacts.push(contact); + } + } + + callback(null, foundContacts); + }); } }); diff --git a/browser/components/loop/MozLoopService.jsm b/browser/components/loop/MozLoopService.jsm index 1118a130c677..309946f80cbb 100644 --- a/browser/components/loop/MozLoopService.jsm +++ b/browser/components/loop/MozLoopService.jsm @@ -23,6 +23,8 @@ const LOOP_SESSION_TYPE = { // See LOG_LEVELS in Console.jsm. Common examples: "All", "Info", "Warn", & "Error". const PREF_LOG_LEVEL = "loop.debug.loglevel"; +const EMAIL_OR_PHONE_RE = /^(:?\S+@\S+|\+\d+)$/; + Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Promise.jsm"); @@ -56,6 +58,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "HawkClient", XPCOMUtils.defineLazyModuleGetter(this, "deriveHawkCredentials", "resource://services-common/hawkrequest.js"); +XPCOMUtils.defineLazyModuleGetter(this, "LoopContacts", + "resource:///modules/loop/LoopContacts.jsm"); + XPCOMUtils.defineLazyModuleGetter(this, "LoopStorage", "resource:///modules/loop/LoopStorage.jsm"); @@ -786,17 +791,46 @@ let MozLoopServiceInternal = { * Starts a call, saves the call data, and opens a chat window. * * @param {Object} callData The data associated with the call including an id. - * @param {Boolean} conversationType Whether or not the call is "incoming" - * or "outgoing" + * @param {String} conversationType Whether or not the call is "incoming" + * or "outgoing" */ _startCall: function(callData, conversationType) { - this.callsData.inUse = true; - this.callsData.data = callData; - this.openChatWindow( - null, - // No title, let the page set that, to avoid flickering. - "", - "about:loopconversation#" + conversationType + "/" + callData.callId); + const openChat = () => { + this.callsData.inUse = true; + this.callsData.data = callData; + + this.openChatWindow( + null, + // No title, let the page set that, to avoid flickering. + "", + "about:loopconversation#" + conversationType + "/" + callData.callId); + }; + + if (conversationType == "incoming" && ("callerId" in callData) && + EMAIL_OR_PHONE_RE.test(callData.callerId)) { + LoopContacts.search({ + q: callData.callerId, + field: callData.callerId.contains("@") ? "email" : "tel" + }, (err, contacts) => { + if (err) { + // Database error, helas! + openChat(); + return; + } + + for (let contact of contacts) { + if (contact.blocked) { + // Blocked! Send a busy signal back to the caller. + this._returnBusy(callData); + return; + } + } + + openChat(); + }) + } else { + openChat(); + } }, /** diff --git a/browser/components/loop/content/js/contacts.js b/browser/components/loop/content/js/contacts.js index 4dba2cf07691..a2a8054a7265 100644 --- a/browser/components/loop/content/js/contacts.js +++ b/browser/components/loop/content/js/contacts.js @@ -86,12 +86,15 @@ loop.contacts = (function(_, mozL10n) { return ( React.DOM.ul({className: cx({ "dropdown-menu": true, "dropdown-menu-up": this.state.openDirUp })}, - React.DOM.li({className: cx({ "dropdown-menu-item": true }), - onClick: this.onItemClick, 'data-action': "video-call"}, + React.DOM.li({className: cx({ "dropdown-menu-item": true, + "disabled": this.props.blocked }), + onClick: this.onItemClick, + 'data-action': "video-call"}, React.DOM.i({className: "icon icon-video-call"}), mozL10n.get("video_call_menu_button") ), - React.DOM.li({className: cx({ "dropdown-menu-item": true }), + React.DOM.li({className: cx({ "dropdown-menu-item": true, + "disabled": this.props.blocked }), onClick: this.onItemClick, 'data-action': "audio-call"}, React.DOM.i({className: "icon icon-audio-call"}), mozL10n.get("audio_call_menu_button") @@ -388,10 +391,14 @@ loop.contacts = (function(_, mozL10n) { }); break; case "video-call": - navigator.mozLoop.startDirectCall(contact, CALL_TYPES.AUDIO_VIDEO); + if (!contact.blocked) { + navigator.mozLoop.startDirectCall(contact, CALL_TYPES.AUDIO_VIDEO); + } break; case "audio-call": - navigator.mozLoop.startDirectCall(contact, CALL_TYPES.AUDIO_ONLY); + if (!contact.blocked) { + navigator.mozLoop.startDirectCall(contact, CALL_TYPES.AUDIO_ONLY); + } break; default: console.error("Unrecognized action: " + actionName); diff --git a/browser/components/loop/content/js/contacts.jsx b/browser/components/loop/content/js/contacts.jsx index 56dc27d995b9..ae534547761b 100644 --- a/browser/components/loop/content/js/contacts.jsx +++ b/browser/components/loop/content/js/contacts.jsx @@ -86,12 +86,15 @@ loop.contacts = (function(_, mozL10n) { return (