Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
717 lines
23 KiB
JavaScript
Executable file
717 lines
23 KiB
JavaScript
Executable file
/* global SimplePhoneMatcher */
|
|
/* global Normalizer */
|
|
/* exported Matcher */
|
|
|
|
(function(exports) {
|
|
'use strict';
|
|
|
|
function MatcherObj(pdataProvider) {
|
|
var blankRegExp = /\s+/g;
|
|
|
|
var FB_CATEGORY = 'facebook';
|
|
var FB_LINKED = 'fb_linked';
|
|
|
|
var dataProvider = pdataProvider;
|
|
|
|
// Multiple matcher Object. It tries to find a set of Contacts that match at
|
|
// least one of the targets passed as parameters
|
|
// ptargets: They are the targets (telephone numbers, emails) we want to
|
|
// find in the Contacts DB
|
|
// mmatchingOptions: are the matching options to be passed to the
|
|
// mozContacts API. Particularly filterBy and filterOp
|
|
// MultipleMatcher.onmatch is called when there is at least one matching
|
|
// MultipleMatcher.onmismatch is called when there are no matches
|
|
|
|
function MultipleMatcher(ptargets, pmatchingOptions) {
|
|
var next = 0;
|
|
var self = this;
|
|
var targets = ptargets;
|
|
var matchingOptions = pmatchingOptions;
|
|
var finalMatchings = {};
|
|
|
|
function doMatchBy(target, callbacks) {
|
|
var options = {
|
|
filterValue: target,
|
|
filterBy: matchingOptions.filterBy,
|
|
filterOp: matchingOptions.filterOp
|
|
};
|
|
|
|
var req = dataProvider.find(options);
|
|
|
|
req.onsuccess = function() {
|
|
var matchings = req.result.filter(function(aResult) {
|
|
return filterFacebook(aResult, matchingOptions.linkParams);
|
|
});
|
|
|
|
var filterBy = options.filterBy;
|
|
|
|
var sanitizedTarget = SimplePhoneMatcher.sanitizedNumber(target);
|
|
var targetVariants = SimplePhoneMatcher.generateVariants(target);
|
|
|
|
matchings.forEach(function(aMatching) {
|
|
if (matchingOptions.selfContactId === aMatching.id) {
|
|
return;
|
|
}
|
|
|
|
var field = options.filterBy[0];
|
|
var values = aMatching[field];
|
|
|
|
values.forEach(function(aValue) {
|
|
var value = aValue.value;
|
|
var sanitizedValue = SimplePhoneMatcher.sanitizedNumber(value);
|
|
var valueMatched = false;
|
|
|
|
if (targetVariants.length > 1) {
|
|
if (targetVariants.indexOf(sanitizedValue) !== -1) {
|
|
valueMatched = true;
|
|
}
|
|
} else if (SimplePhoneMatcher.generateVariants(sanitizedValue).
|
|
indexOf(sanitizedTarget) !== -1) {
|
|
valueMatched = true;
|
|
}
|
|
|
|
if (valueMatched) {
|
|
var matchings, matchingObj;
|
|
if (!finalMatchings[aMatching.id]) {
|
|
matchingObj = {
|
|
matchings: {},
|
|
matchingContact: aMatching
|
|
};
|
|
finalMatchings[aMatching.id] = matchingObj;
|
|
matchingObj.matchings[filterBy[0]] = [];
|
|
}
|
|
else {
|
|
matchingObj = finalMatchings[aMatching.id];
|
|
}
|
|
matchings = matchingObj.matchings[filterBy[0]];
|
|
|
|
// Avoinding to report multiple matchings due to variants
|
|
var sameValueMatchings = matchings.filter(function(matching) {
|
|
return (matching.matchedValue === value);
|
|
});
|
|
if (sameValueMatchings.length === 0) {
|
|
matchings.push({
|
|
'target': target,
|
|
'matchedValue': value
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}); // matchings.forEach
|
|
|
|
carryOn();
|
|
}; // onsuccess
|
|
|
|
req.onerror = function(e) {
|
|
window.console.error('Error while trying to do the matching',
|
|
e.target.error.name);
|
|
notifyMismatch(self);
|
|
};
|
|
}
|
|
|
|
function carryOn() {
|
|
next++;
|
|
if (next < targets.length) {
|
|
doMatchBy(targets[next], callbacks);
|
|
}
|
|
else if (Object.keys(finalMatchings).length > 0) {
|
|
notifyMatch(self, finalMatchings);
|
|
}
|
|
else {
|
|
notifyMismatch(self);
|
|
}
|
|
}
|
|
|
|
function matched(contacts) {
|
|
carryOn();
|
|
}
|
|
|
|
var callbacks = {
|
|
onmatch: matched,
|
|
onmismatch: carryOn
|
|
};
|
|
|
|
this.start = function() {
|
|
doMatchBy(targets[0], callbacks);
|
|
};
|
|
}
|
|
|
|
function notifyMatch(obj, matchings) {
|
|
typeof obj.onmatch === 'function' && obj.onmatch(matchings);
|
|
}
|
|
|
|
function notifyMismatch(obj) {
|
|
typeof obj.onmismatch === 'function' && obj.onmismatch();
|
|
}
|
|
|
|
// Match a Contact by the field and filter specified as parameters
|
|
// Callbacks is an object that declares onmatch and onmismatch callbacks
|
|
function matchBy(aContact, field, filterOper, callbacks, poptions) {
|
|
var options = poptions || {};
|
|
var values = [];
|
|
|
|
if (Array.isArray(aContact[field])) {
|
|
aContact[field].forEach(function(aField) {
|
|
if (field === 'tel') {
|
|
// Bug 924378 Contacts API does not properly match phone numbers
|
|
// and their variants
|
|
var variants = SimplePhoneMatcher.generateVariants(aField.value);
|
|
variants.forEach(function(aVariant) {
|
|
values.push(aVariant);
|
|
});
|
|
}
|
|
else if (typeof aField.value === 'string') {
|
|
values.push(aField.value.trim());
|
|
}
|
|
});
|
|
}
|
|
|
|
if (values.length > 0) {
|
|
var matcher = new MultipleMatcher(values, {
|
|
filterBy: [field],
|
|
filterOp: filterOper,
|
|
selfContactId: aContact.id,
|
|
linkParams: options
|
|
});
|
|
matcher.onmatch = callbacks.onmatch;
|
|
|
|
matcher.onmismatch = callbacks.onmismatch;
|
|
|
|
matcher.start();
|
|
}
|
|
else {
|
|
notifyMismatch(callbacks);
|
|
}
|
|
}
|
|
|
|
function matchByTel(aContact, callbacks, options) {
|
|
matchBy(aContact, 'tel', 'match', callbacks, options);
|
|
}
|
|
|
|
function matchByEmail(aContact, callbacks, options) {
|
|
matchBy(aContact, 'email', 'startsWith', callbacks, options);
|
|
}
|
|
|
|
// Performs a matching for an incoming contact 'aContact' and the mode
|
|
// ['active', passive'] specified as parameter. Callbacks is an object
|
|
// with the onmatch and onmismatch functions that will be called after
|
|
// the matching process has finished
|
|
function doMatch(aContact, mode, callbacks) {
|
|
if (mode === 'passive') {
|
|
doMatchPassive(aContact, callbacks);
|
|
}
|
|
else if (mode === 'active') {
|
|
doMatchActive(aContact, callbacks);
|
|
}
|
|
}
|
|
|
|
// Implements the active mode 'matching'
|
|
function doMatchTelAndEmail(aContact, callbacks, options) {
|
|
var localCbs = {
|
|
onmatch: function(telMatches) {
|
|
var matchCbs = {
|
|
onmatch: function(mailMatches) {
|
|
// Have a unique set of matches
|
|
var allMatches = telMatches;
|
|
Object.keys(mailMatches).forEach(function(aMatch) {
|
|
if (!allMatches[aMatch]) {
|
|
allMatches[aMatch] = mailMatches[aMatch];
|
|
}
|
|
else {
|
|
allMatches[aMatch].matchings.email =
|
|
mailMatches[aMatch].matchings.email;
|
|
}
|
|
});
|
|
notifyMatch(callbacks, allMatches);
|
|
},
|
|
onmismatch: function() {
|
|
notifyMatch(callbacks, telMatches);
|
|
}
|
|
};
|
|
matchByEmail(aContact, matchCbs, options);
|
|
},
|
|
onmismatch: function() {
|
|
matchByEmail(aContact, callbacks, options);
|
|
}
|
|
};
|
|
matchByTel(aContact, localCbs, options);
|
|
}
|
|
|
|
function getSourceSimUrl(aContact) {
|
|
var out;
|
|
|
|
if (Array.isArray(aContact.category) &&
|
|
aContact.category.indexOf('sim') !== -1 &&
|
|
Array.isArray(aContact.url)) {
|
|
for (var j = 0; j < aContact.url.length; j++) {
|
|
var aUrl = aContact.url[j];
|
|
if (aUrl.type.indexOf('source') !== -1 &&
|
|
aUrl.type.indexOf('sim') !== -1) {
|
|
out = aUrl.value;
|
|
}
|
|
}
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
// Implements the silent mode matching
|
|
function doMatchPassive(aContact, callbacks) {
|
|
if (!hasName(aContact)) {
|
|
notifyMismatch(callbacks);
|
|
return;
|
|
}
|
|
|
|
// SIM Contacts are detected and matched accordingly
|
|
// We try to match by name and then try to see if they correspond to
|
|
// the same SIM contact. In that case matching is reported
|
|
var simUrl = getSourceSimUrl(aContact);
|
|
if (simUrl) {
|
|
performPassiveMatchForSimContact(simUrl, aContact, callbacks);
|
|
}
|
|
else {
|
|
performPassiveMatch(aContact, callbacks);
|
|
}
|
|
}
|
|
|
|
function performPassiveMatchForSimContact(simUrl, aContact, callbacks) {
|
|
var localCbs = {
|
|
onmatch: function(results) {
|
|
var matchingFound = false;
|
|
|
|
Object.keys(results).forEach(function(aResultId) {
|
|
var matchingContact = results[aResultId].matchingContact;
|
|
var matchingUrl = getSourceSimUrl(matchingContact);
|
|
if (matchingUrl && matchingUrl === simUrl) {
|
|
matchingFound = true;
|
|
var matchings = {};
|
|
matchings[aResultId] = {
|
|
matchingContact: matchingContact
|
|
};
|
|
callbacks.onmatch(matchings);
|
|
return;
|
|
}
|
|
});
|
|
if (!matchingFound) {
|
|
performPassiveMatch(aContact, callbacks);
|
|
}
|
|
},
|
|
onmismatch: function() {
|
|
performPassiveMatch(aContact, callbacks);
|
|
}
|
|
};
|
|
// Matching by name and then see if they have the same SIM contact source
|
|
matchByName(aContact, localCbs);
|
|
}
|
|
|
|
// Implements the silent mode matching
|
|
function performPassiveMatch(aContact, callbacks) {
|
|
var matchingsFound = {};
|
|
|
|
var localCbs = {
|
|
onmatch: function(results) {
|
|
// Results will contain contacts that match by tel or email
|
|
// Normalizing the strings
|
|
var names = [];
|
|
Object.keys(results).forEach(function(aResultId) {
|
|
var mContact = results[aResultId].matchingContact;
|
|
|
|
if (!hasName(mContact)) {
|
|
return;
|
|
}
|
|
|
|
// As the number of candidates here will be short a normal search
|
|
// will be conducted
|
|
var targetFN = null;
|
|
if (!isEmptyStr(aContact.familyName)) {
|
|
targetFN = Normalizer.toAscii(
|
|
aContact.familyName[0].trim().toLowerCase()).
|
|
replace(blankRegExp, '');
|
|
}
|
|
var targetGN = null;
|
|
if (!isEmptyStr(aContact.givenName)) {
|
|
targetGN = Normalizer.toAscii(
|
|
aContact.givenName[0].trim().toLowerCase()).
|
|
replace(blankRegExp, '');
|
|
}
|
|
// To support seamless matching of SIM contacts
|
|
var targetName = (targetGN || '') + (targetFN || '');
|
|
|
|
var mFamilyName = null;
|
|
var mGivenName = null;
|
|
|
|
if (!isEmptyStr(mContact.familyName)) {
|
|
mFamilyName = Normalizer.toAscii(
|
|
mContact.familyName[0].trim().toLowerCase()
|
|
).replace(blankRegExp, '');
|
|
}
|
|
|
|
if (!isEmptyStr(mContact.givenName)) {
|
|
mGivenName = Normalizer.toAscii(
|
|
mContact.givenName[0].trim().toLowerCase()
|
|
).replace(blankRegExp, '');
|
|
}
|
|
// To support seamless matching of SIM contacts
|
|
var mName = (mGivenName || '') + (mFamilyName || '');
|
|
|
|
names.push({
|
|
contact: mContact,
|
|
familyName: mFamilyName,
|
|
givenName: mGivenName,
|
|
name: mName
|
|
});
|
|
|
|
var matchingList = names.filter(function(obj) {
|
|
return ((obj.familyName === targetFN &&
|
|
obj.givenName === targetGN) ||
|
|
(obj.name && obj.name === targetName) && (
|
|
!Array.isArray(obj.contact.category) ||
|
|
obj.contact.category.indexOf(FB_CATEGORY) === -1));
|
|
});
|
|
|
|
matchingList.forEach(function(aMatching) {
|
|
var contact = aMatching.contact;
|
|
matchingsFound[contact.id] = {
|
|
matchingContact: contact
|
|
};
|
|
});
|
|
});
|
|
|
|
reconcileResults(aContact, matchingsFound, results, callbacks);
|
|
},
|
|
|
|
onmismatch: function() {
|
|
notifyMismatch(callbacks);
|
|
}
|
|
};
|
|
|
|
// Matching by email and phone number, then match by names
|
|
doMatchTelAndEmail(aContact, localCbs);
|
|
}
|
|
|
|
function doMatchActive(aContact, callbacks) {
|
|
var options = {
|
|
linkedMatched: {},
|
|
linkedTo: getLinkedTo(aContact)
|
|
};
|
|
|
|
var localCbs = {
|
|
onmatch: function(results) {
|
|
var cbsName = {
|
|
onmatch: function(nameResults) {
|
|
Object.keys(nameResults).forEach(function(aId) {
|
|
if (!results[aId]) {
|
|
results[aId] = nameResults[aId];
|
|
}
|
|
else {
|
|
results[aId].matchings.name =
|
|
nameResults[aId].matchings.name;
|
|
}
|
|
});
|
|
|
|
notifyMatch(callbacks, results);
|
|
},
|
|
onmismatch: function() {
|
|
notifyMatch(callbacks, results);
|
|
}
|
|
};
|
|
|
|
matchByName(aContact, cbsName, options);
|
|
},
|
|
onmismatch: function() {
|
|
matchByName(aContact, callbacks, options);
|
|
}
|
|
};
|
|
|
|
// Matching by email and phone number, then match by names
|
|
doMatchTelAndEmail(aContact, localCbs, options);
|
|
}
|
|
|
|
function notifyFindNameReady() {
|
|
document.dispatchEvent(new CustomEvent('by_name_ready'));
|
|
}
|
|
|
|
// Used for the active mode. Performs matching by familyName and givenName
|
|
function matchByName(aContact, callbacks, options) {
|
|
// First we try to find by familyName
|
|
// Afterwards we search by givenName
|
|
var isSimContact = (Array.isArray(aContact.category) &&
|
|
aContact.category.indexOf('sim') !== -1);
|
|
|
|
if ((isEmptyStr(aContact.familyName) || isEmptyStr(aContact.givenName)) &&
|
|
!isSimContact) {
|
|
notifyMismatch(callbacks);
|
|
return;
|
|
}
|
|
|
|
var finalResult = {};
|
|
|
|
var resultsByName = [];
|
|
if (!isEmptyStr(aContact.name)) {
|
|
var targetName = aContact.name[0].trim();
|
|
// Filter by familyName using startsWith. Gecko 'startsWith' operation
|
|
// acts as 'equal' but does not match case.
|
|
var reqName = dataProvider.find({
|
|
filterValue: targetName,
|
|
filterBy: ['name'],
|
|
filterOp: 'startsWith'
|
|
});
|
|
|
|
reqName.onsuccess = function() {
|
|
resultsByName = reqName.result.filter(function(aResult) {
|
|
return filterFacebook(aResult, options);
|
|
});
|
|
notifyFindNameReady();
|
|
};
|
|
|
|
reqName.onerror = function(e) {
|
|
window.console.warn('Error while trying to find by name: ',
|
|
e.target.error.name);
|
|
notifyFindNameReady();
|
|
};
|
|
}
|
|
else {
|
|
notifyFindNameReady();
|
|
}
|
|
|
|
if (isEmptyStr(aContact.familyName)) {
|
|
endOfMatchByName(finalResult, aContact, resultsByName, callbacks);
|
|
}
|
|
else {
|
|
var targetFamilyName = aContact.familyName[0].trim();
|
|
// Filter by familyName using startsWith. Gecko 'startsWith' operation
|
|
// acts as 'equal' but does not match case.
|
|
var reqFamilyName = dataProvider.find({
|
|
filterValue: targetFamilyName,
|
|
filterBy: ['familyName'],
|
|
filterOp: 'startsWith'
|
|
});
|
|
|
|
reqFamilyName.onsuccess = function() {
|
|
var results = reqFamilyName.result;
|
|
|
|
var givenNames = [];
|
|
var targetGN = null;
|
|
|
|
if (!isEmptyStr(aContact.givenName)) {
|
|
targetGN = Normalizer.toAscii(
|
|
aContact.givenName[0].trim().toLowerCase()).
|
|
replace(blankRegExp, '');
|
|
}
|
|
|
|
results.forEach(function(mContact) {
|
|
if (mContact.id === aContact.id || isEmptyStr(mContact.givenName)) {
|
|
return;
|
|
}
|
|
givenNames.push({
|
|
contact: mContact,
|
|
givenName: Normalizer.toAscii(
|
|
mContact.givenName[0].trim().toLowerCase()).
|
|
replace(blankRegExp, '')
|
|
});
|
|
});
|
|
|
|
var finalMatchings = givenNames.filter(function(obj) {
|
|
var gn = obj.givenName;
|
|
return ((gn === targetGN || targetGN.startsWith(gn) ||
|
|
gn.startsWith(targetGN)) &&
|
|
filterFacebook(obj.contact, options));
|
|
});
|
|
|
|
finalMatchings.forEach(function(aMatching) {
|
|
finalResult[aMatching.contact.id] = {
|
|
matchings: {
|
|
'name': [{
|
|
target: targetFamilyName,
|
|
matchedValue: Array.isArray(aMatching.contact.name) ?
|
|
aMatching.contact.name[0] :
|
|
getCompleteName(aMatching.contact)
|
|
}]
|
|
},
|
|
matchingContact: aMatching.contact
|
|
};
|
|
});
|
|
|
|
if (resultsByName) {
|
|
endOfMatchByName(finalResult, aContact, resultsByName, callbacks);
|
|
}
|
|
else {
|
|
document.addEventListener('by_name_ready', function nameReady() {
|
|
document.removeEventListener('by_name_ready', nameReady);
|
|
endOfMatchByName(finalResult, aContact, resultsByName, callbacks);
|
|
});
|
|
}
|
|
};
|
|
|
|
reqFamilyName.onerror = function(e) {
|
|
window.console.error('Error while trying to find by familyName: ',
|
|
e.target.error.name);
|
|
notifyMismatch(callbacks);
|
|
};
|
|
}
|
|
}
|
|
|
|
function getLinkedTo(contact) {
|
|
var out = null;
|
|
|
|
if (Array.isArray(contact.category)) {
|
|
var idx = contact.category.indexOf(FB_LINKED);
|
|
if (idx !== -1) {
|
|
out = contact.category[idx + 1];
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
function isFbLinked(contact) {
|
|
return (Array.isArray(contact.category) &&
|
|
contact.category.indexOf(FB_LINKED) !== -1);
|
|
}
|
|
|
|
function isFbContact(contact) {
|
|
return (Array.isArray(contact.category) &&
|
|
contact.category.indexOf(FB_CATEGORY) !== -1);
|
|
}
|
|
|
|
function filterFacebook(contact, linkParams) {
|
|
var out = false;
|
|
|
|
if (!isFbContact(contact)) {
|
|
out = true;
|
|
}
|
|
else if (isFbLinked(contact)) {
|
|
var linkedTo = getLinkedTo(contact);
|
|
var targetUid = linkParams.linkedTo || '';
|
|
var linkedMatched = linkParams.linkedMatched || {};
|
|
|
|
if (targetUid === linkedTo) {
|
|
out = true;
|
|
}
|
|
// It is only allowed to match one linked contact or various but all of
|
|
// them linked to the same FB friend
|
|
else if ((Object.keys(linkedMatched).length === 0 ||
|
|
linkedMatched[linkedTo]) && !targetUid) {
|
|
linkedMatched[linkedTo] = linkedTo;
|
|
out = true;
|
|
}
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
function endOfMatchByName(finalResult, aContact, resultsByName, callbacks) {
|
|
resultsByName.forEach(function(aResult) {
|
|
if (aResult.id === aContact.id) {
|
|
return;
|
|
}
|
|
|
|
var matchingObj = {
|
|
matchings: {
|
|
'name': [{
|
|
target: (Array.isArray(aContact.name) &&
|
|
(typeof aContact.name[0] === 'string')) ?
|
|
aContact.name[0].trim() : getCompleteName(aContact),
|
|
matchedValue: Array.isArray(aResult.name) ?
|
|
aResult.name[0] :
|
|
getCompleteName(aResult)
|
|
}]
|
|
},
|
|
matchingContact: aResult
|
|
};
|
|
|
|
if (!finalResult[aResult.id]) {
|
|
finalResult[aResult.id] = matchingObj;
|
|
}
|
|
else {
|
|
finalResult[aResult.id].matchings.name =
|
|
matchingObj.matchings.name;
|
|
}
|
|
});
|
|
|
|
if (Object.keys(finalResult).length > 0) {
|
|
notifyMatch(callbacks, finalResult);
|
|
}
|
|
else {
|
|
notifyMismatch(callbacks);
|
|
}
|
|
}
|
|
|
|
function getCompleteName(contact) {
|
|
var givenName = Array.isArray(contact.givenName) ?
|
|
contact.givenName[0] : '';
|
|
|
|
var familyName = Array.isArray(contact.familyName) ?
|
|
contact.familyName[0] : '';
|
|
|
|
var completeName = givenName && familyName ?
|
|
givenName + ' ' + familyName :
|
|
givenName || familyName;
|
|
|
|
return completeName;
|
|
}
|
|
|
|
function isEmpty(collection) {
|
|
return ((!Array.isArray(collection) ||
|
|
!collection[0]) ||
|
|
(collection[0] &&
|
|
collection[0].value &&
|
|
!collection[0].value.trim()));
|
|
}
|
|
|
|
function isEmptyStr(collection) {
|
|
return (!Array.isArray(collection) ||
|
|
(typeof collection[0] !== 'string') || !(collection[0].trim()));
|
|
}
|
|
|
|
function hasName(aContact) {
|
|
return (!isEmptyStr(aContact.givenName) ||
|
|
!isEmptyStr(aContact.familyName));
|
|
}
|
|
|
|
function reconcileResults(incomingContact, nameMatches, phoneMailMatches,
|
|
callbacks) {
|
|
var finalMatchings = {};
|
|
|
|
// Name matches drive all the process
|
|
Object.keys(nameMatches).forEach(function(aNameMatching) {
|
|
var matchingContact = nameMatches[aNameMatching].matchingContact;
|
|
|
|
var isPhoneMatching = Array.isArray(phoneMailMatches[aNameMatching].
|
|
matchings.tel);
|
|
var isMailMatching = Array.isArray(phoneMailMatches[aNameMatching].
|
|
matchings.email);
|
|
|
|
// Three cases under which a matching is considered
|
|
if (isPhoneMatching && isMailMatching) {
|
|
finalMatchings[aNameMatching] = phoneMailMatches[aNameMatching];
|
|
}
|
|
else if (isPhoneMatching && (isEmpty(incomingContact.email) ||
|
|
isEmpty(matchingContact.email))) {
|
|
finalMatchings[aNameMatching] = phoneMailMatches[aNameMatching];
|
|
}
|
|
else if (isMailMatching && (isEmpty(incomingContact.tel) ||
|
|
isEmpty(matchingContact.tel))) {
|
|
finalMatchings[aNameMatching] = phoneMailMatches[aNameMatching];
|
|
}
|
|
});
|
|
|
|
if (Object.keys(finalMatchings).length > 0) {
|
|
notifyMatch(callbacks, finalMatchings);
|
|
}
|
|
else {
|
|
notifyMismatch(callbacks);
|
|
}
|
|
}
|
|
|
|
// Exported method
|
|
this.match = doMatch;
|
|
|
|
Object.defineProperty(this, 'dataProvider', {
|
|
set: function(theProvider) {
|
|
dataProvider = theProvider;
|
|
}
|
|
});
|
|
}
|
|
|
|
exports.Matcher = new MatcherObj(navigator.mozContacts);
|
|
}(window));
|