зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1079941: implement LoopContacts.search to allow searching for contacts by query and use that to find out if a contact who's trying to call you is blocked. r=abr
This commit is contained in:
Родитель
567b06b0f1
Коммит
f05da90fab
|
@ -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);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -86,12 +86,15 @@ loop.contacts = (function(_, mozL10n) {
|
|||
return (
|
||||
<ul className={cx({ "dropdown-menu": true,
|
||||
"dropdown-menu-up": this.state.openDirUp })}>
|
||||
<li className={cx({ "dropdown-menu-item": true })}
|
||||
onClick={this.onItemClick} data-action="video-call">
|
||||
<li className={cx({ "dropdown-menu-item": true,
|
||||
"disabled": this.props.blocked })}
|
||||
onClick={this.onItemClick}
|
||||
data-action="video-call">
|
||||
<i className="icon icon-video-call" />
|
||||
{mozL10n.get("video_call_menu_button")}
|
||||
</li>
|
||||
<li className={cx({ "dropdown-menu-item": true })}
|
||||
<li className={cx({ "dropdown-menu-item": true,
|
||||
"disabled": this.props.blocked })}
|
||||
onClick={this.onItemClick} data-action="audio-call">
|
||||
<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);
|
||||
|
|
|
@ -16,6 +16,11 @@ const kContacts = [{
|
|||
"type": ["work"],
|
||||
"value": "ally@mail.com"
|
||||
}],
|
||||
tel: [{
|
||||
"pref": true,
|
||||
"type": ["mobile"],
|
||||
"value": "+31-6-12345678"
|
||||
}],
|
||||
category: ["google"],
|
||||
published: 1406798311748,
|
||||
updated: 1406798311748
|
||||
|
@ -27,6 +32,11 @@ const kContacts = [{
|
|||
"type": ["work"],
|
||||
"value": "bob@gmail.com"
|
||||
}],
|
||||
tel: [{
|
||||
"pref": true,
|
||||
"type": ["mobile"],
|
||||
"value": "+1-214-5551234"
|
||||
}],
|
||||
category: ["local"],
|
||||
published: 1406798311748,
|
||||
updated: 1406798311748
|
||||
|
@ -425,11 +435,50 @@ add_task(function* () {
|
|||
LoopStorage.switchDatabase();
|
||||
Assert.equal(LoopStorage.databaseName, "default", "The active partition should have changed");
|
||||
|
||||
LoopContacts.getAll(function(err, contacts) {
|
||||
Assert.equal(err, null, "There shouldn't be an error");
|
||||
|
||||
for (let i = 0, l = contacts.length; i < l; ++i) {
|
||||
compareContacts(contacts[i], kContacts[i]);
|
||||
}
|
||||
});
|
||||
contacts = yield LoopContacts.promise("getAll");
|
||||
for (let i = 0, l = contacts.length; i < l; ++i) {
|
||||
compareContacts(contacts[i], kContacts[i]);
|
||||
}
|
||||
});
|
||||
|
||||
// Test searching for contacts.
|
||||
add_task(function* () {
|
||||
yield promiseLoadContacts();
|
||||
|
||||
let contacts = yield LoopContacts.promise("search", {
|
||||
q: "bob@gmail.com"
|
||||
});
|
||||
Assert.equal(contacts.length, 1, "There should be one contact found");
|
||||
compareContacts(contacts[0], kContacts[1]);
|
||||
|
||||
// Test searching by name.
|
||||
contacts = yield LoopContacts.promise("search", {
|
||||
q: "Ally Avocado",
|
||||
field: "name"
|
||||
});
|
||||
Assert.equal(contacts.length, 1, "There should be one contact found");
|
||||
compareContacts(contacts[0], kContacts[0]);
|
||||
|
||||
// Test searching for multiple contacts.
|
||||
contacts = yield LoopContacts.promise("search", {
|
||||
q: "google",
|
||||
field: "category"
|
||||
});
|
||||
Assert.equal(contacts.length, 2, "There should be two contacts found");
|
||||
|
||||
// Test searching for telephone numbers.
|
||||
contacts = yield LoopContacts.promise("search", {
|
||||
q: "+31612345678",
|
||||
field: "tel"
|
||||
});
|
||||
Assert.equal(contacts.length, 1, "There should be one contact found");
|
||||
compareContacts(contacts[0], kContacts[0]);
|
||||
|
||||
// Test searching for telephone numbers without prefixes.
|
||||
contacts = yield LoopContacts.promise("search", {
|
||||
q: "5551234",
|
||||
field: "tel"
|
||||
});
|
||||
Assert.equal(contacts.length, 1, "There should be one contact found");
|
||||
compareContacts(contacts[0], kContacts[1]);
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче