Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
423
gaia/shared/js/fb/fb_data_reader.js
Executable file
423
gaia/shared/js/fb/fb_data_reader.js
Executable file
|
|
@ -0,0 +1,423 @@
|
|||
'use strict';
|
||||
/* global LazyLoader, TelIndexer */
|
||||
|
||||
// Reader Module for FB Data in a Datastore
|
||||
// To use this library you need to include 'shared/js/fb/fb_request.js'
|
||||
// WARNING: This file lazy loads 'shared/js/fb/fb_tel_index.js'
|
||||
|
||||
var fb = self.fb || {};
|
||||
self.fb = fb;
|
||||
|
||||
(function() {
|
||||
var contacts = fb.contacts || {};
|
||||
fb.contacts = contacts;
|
||||
|
||||
var datastore;
|
||||
// Datastore name declared on the manifest.webapp
|
||||
var DATASTORE_NAME = 'Gaia_Facebook_Friends';
|
||||
// Record Id for the index
|
||||
var INDEX_ID = 1;
|
||||
|
||||
// Indicates the initialization state
|
||||
var readyState = 'notInitialized';
|
||||
// Custom event for notifying initializations
|
||||
var INITIALIZE_EVENT = 'fb_ds_init';
|
||||
|
||||
// Last known revision Id. Used to determine whether the index has to
|
||||
// be reloaded or not
|
||||
var revisionId;
|
||||
|
||||
// Creates the internal Object in the datastore that will act as an index
|
||||
function createIndex() {
|
||||
return {
|
||||
// By tel number and all its possible variants
|
||||
// (We are not supporting dups right now)
|
||||
byTel: Object.create(null),
|
||||
// Prefix tree for enabling searching by partial tel numbers
|
||||
treeTel: []
|
||||
};
|
||||
}
|
||||
|
||||
// The index we need to keep the correspondance between FB Friends and
|
||||
// datastore Ids
|
||||
var index;
|
||||
|
||||
// Ensures a proper error object is returned
|
||||
function safeError(err) {
|
||||
if (err && err.name) {
|
||||
return err;
|
||||
}
|
||||
|
||||
return {
|
||||
name: 'UnknownError'
|
||||
};
|
||||
}
|
||||
|
||||
function notifyOpenSuccess(cb) {
|
||||
readyState = 'initialized';
|
||||
if (typeof cb === 'function') {
|
||||
window.setTimeout(cb, 0);
|
||||
}
|
||||
var ev = new CustomEvent(INITIALIZE_EVENT);
|
||||
document.dispatchEvent(ev);
|
||||
}
|
||||
|
||||
function initError(outRequest, error) {
|
||||
outRequest.failed(error);
|
||||
}
|
||||
|
||||
function handleInitError(err, errorCb) {
|
||||
readyState = 'error';
|
||||
contacts.error = err;
|
||||
|
||||
if (typeof errorCb === 'function') {
|
||||
window.setTimeout(function() {
|
||||
errorCb(err);
|
||||
}, 0);
|
||||
}
|
||||
// We resume other callers state
|
||||
document.dispatchEvent(new CustomEvent(INITIALIZE_EVENT));
|
||||
}
|
||||
|
||||
|
||||
// Creates a default handler for errors
|
||||
function defaultError(request) {
|
||||
return defaultErrorCb.bind(null, request);
|
||||
}
|
||||
|
||||
// Creates a default handler for success
|
||||
function defaultErrorCb(request, error) {
|
||||
request.failed(error);
|
||||
}
|
||||
|
||||
function setIndex(obj) {
|
||||
index = (obj || createIndex());
|
||||
}
|
||||
|
||||
function successGet(outReq, data) {
|
||||
outReq.done(data);
|
||||
}
|
||||
|
||||
function errorGet(outReq, uid, err) {
|
||||
window.console.error('Error while getting object for UID: ', uid);
|
||||
outReq.failed(err.name);
|
||||
}
|
||||
|
||||
Object.defineProperty(contacts, 'datastore', {
|
||||
get: function getDataStore() { return datastore; },
|
||||
enumerable: false,
|
||||
configurable: false
|
||||
});
|
||||
|
||||
Object.defineProperty(contacts, 'dsIndex', {
|
||||
get: function getIndex() { return index; },
|
||||
set: setIndex,
|
||||
enumerable: false,
|
||||
configurable: false
|
||||
});
|
||||
|
||||
/**
|
||||
* Allows to obtain the FB friend information by UID
|
||||
*
|
||||
*
|
||||
*/
|
||||
contacts.get = function(uid) {
|
||||
var outRequest = new fb.utils.Request();
|
||||
|
||||
window.setTimeout(function get() {
|
||||
contacts.init(function() {
|
||||
doGet(uid, outRequest);
|
||||
}, function(err) {
|
||||
initError(outRequest, err);
|
||||
});
|
||||
}, 0);
|
||||
|
||||
return outRequest;
|
||||
};
|
||||
|
||||
function doGet(uid, outRequest) {
|
||||
var successCb = successGet.bind(null, outRequest);
|
||||
var errorCb = errorGet.bind(null, outRequest, uid);
|
||||
|
||||
datastore.get(uid).then(successCb, errorCb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to get FB information by tel number (short or long)
|
||||
* The tel number must be complete
|
||||
*
|
||||
*/
|
||||
contacts.getByPhone = function(tel) {
|
||||
var outRequest = new fb.utils.Request();
|
||||
|
||||
window.setTimeout(function get_by_phone() {
|
||||
contacts.init(function get_by_phone() {
|
||||
doGetByPhone(tel, outRequest);
|
||||
},
|
||||
function(err) {
|
||||
initError(outRequest, err);
|
||||
});
|
||||
}, 0);
|
||||
|
||||
return outRequest;
|
||||
};
|
||||
|
||||
function doGetByPhone(tel, outRequest) {
|
||||
var dsId;
|
||||
var normalizedNumber = navigator.mozPhoneNumberService.normalize(tel);
|
||||
if (datastore.revisionId !== revisionId) {
|
||||
window.console.info('Datastore revision id has changed!');
|
||||
// Refreshing the index just in case
|
||||
datastore.get(INDEX_ID).then(function success(obj) {
|
||||
setIndex(obj);
|
||||
revisionId = datastore.revisionId;
|
||||
dsId = index.byTel[normalizedNumber];
|
||||
|
||||
if (typeof dsId !== 'undefined') {
|
||||
datastore.get(dsId).then(function success(friend) {
|
||||
outRequest.done(friend);
|
||||
}, defaultError(outRequest));
|
||||
}
|
||||
else {
|
||||
outRequest.done(null);
|
||||
}
|
||||
}, function(err) {
|
||||
err = safeError(err);
|
||||
window.console.error('The index cannot be refreshed: ', err.name);
|
||||
outRequest.failed(err);
|
||||
});
|
||||
}
|
||||
else {
|
||||
dsId = index.byTel[normalizedNumber];
|
||||
if (typeof dsId !== 'undefined') {
|
||||
datastore.get(dsId).then(function success(friend) {
|
||||
outRequest.done(friend);
|
||||
}, defaultError(outRequest));
|
||||
}
|
||||
else {
|
||||
outRequest.done(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to search for friends by different criteria.
|
||||
* Numbers can be partial and partial matchings will be found
|
||||
*
|
||||
* by === tel : By Tel number
|
||||
*
|
||||
*/
|
||||
contacts.search = function(by, number) {
|
||||
var outRequest = new fb.utils.Request();
|
||||
|
||||
window.setTimeout(function do_search() {
|
||||
contacts.init(function do_search_init() {
|
||||
if (by === 'phone') {
|
||||
doSearchByPhone(number, outRequest);
|
||||
}
|
||||
},
|
||||
function(err) {
|
||||
initError(outRequest, err);
|
||||
});
|
||||
}, 0);
|
||||
|
||||
return outRequest;
|
||||
};
|
||||
|
||||
|
||||
function doSearchByPhone(number, outRequest) {
|
||||
var normalizedNumber = navigator.mozPhoneNumberService.normalize(number);
|
||||
LazyLoader.load(['/shared/js/fb/fb_tel_index.js',
|
||||
'/shared/js/binary_search.js'], function() {
|
||||
var toSearchNumber = normalizedNumber;
|
||||
// TODO: Temporal way of searching for international numbers
|
||||
// A follow-up is needed by using PhoneNumber.js exposed to Gaia
|
||||
if (number.charAt(0) === '+') {
|
||||
toSearchNumber = number.substring(1);
|
||||
}
|
||||
if (datastore.revisionId !== revisionId) {
|
||||
window.console.info('Datastore revision id has changed!');
|
||||
// Refreshing the index
|
||||
datastore.get(INDEX_ID).then(function success(obj) {
|
||||
|
||||
setIndex(obj);
|
||||
revisionId = datastore.revisionId;
|
||||
var results = TelIndexer.search(index.treeTel, toSearchNumber);
|
||||
var out = null;
|
||||
if (results.length > 0) {
|
||||
out = datastore.get.apply(datastore, results);
|
||||
}
|
||||
return out;
|
||||
},function(err) {
|
||||
err = safeError(err);
|
||||
window.console.error('The index cannot be refreshed: ', err.name);
|
||||
outRequest.failed(err);
|
||||
}).then(function success(objList) {
|
||||
if (objList && !Array.isArray(objList)) {
|
||||
objList = [objList];
|
||||
}
|
||||
else {
|
||||
objList = [];
|
||||
}
|
||||
outRequest.done(objList);
|
||||
}, function error(err) {
|
||||
err = safeError(err);
|
||||
window.console.error('Error while retrieving result data: ',
|
||||
err.name);
|
||||
outRequest.failed(err);
|
||||
});
|
||||
}
|
||||
else {
|
||||
var results = TelIndexer.search(index.treeTel, toSearchNumber);
|
||||
if (results.length > 0) {
|
||||
datastore.get.apply(datastore, results).then(
|
||||
function success(objList) {
|
||||
if (!Array.isArray(objList)) {
|
||||
objList = [objList];
|
||||
}
|
||||
outRequest.done(objList);
|
||||
}, function error(err) {
|
||||
err = safeError(err);
|
||||
window.console.error('Error while retrieving result data: ',
|
||||
err.name);
|
||||
outRequest.failed(err);
|
||||
});
|
||||
}
|
||||
else {
|
||||
outRequest.done(results);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the index data
|
||||
*
|
||||
*/
|
||||
contacts.refresh = function() {
|
||||
var outRequest = new fb.utils.Request();
|
||||
|
||||
window.setTimeout(function clear() {
|
||||
contacts.init(function() {
|
||||
doRefresh(outRequest);
|
||||
},
|
||||
function(err) {
|
||||
initError(outRequest, err);
|
||||
});
|
||||
}, 0);
|
||||
|
||||
return outRequest;
|
||||
};
|
||||
|
||||
function doRefresh(outRequest) {
|
||||
datastore.get(INDEX_ID).then(function success(obj) {
|
||||
setIndex(obj);
|
||||
outRequest.done();
|
||||
}, defaultError(outRequest));
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to obtain how many records contain he FB Database
|
||||
*
|
||||
*/
|
||||
contacts.getLength = function() {
|
||||
var retRequest = new fb.utils.Request();
|
||||
|
||||
window.setTimeout(function() {
|
||||
contacts.init(function get_all() {
|
||||
doGetLength(retRequest);
|
||||
},
|
||||
function(err) {
|
||||
initError(retRequest, err);
|
||||
});
|
||||
}, 0);
|
||||
|
||||
return retRequest;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Allows to return the total number of records in the DataStore (minus 1)
|
||||
* That's because the index object also counts
|
||||
*
|
||||
*/
|
||||
function doGetLength(outRequest) {
|
||||
datastore.getLength().then(function success(length) {
|
||||
outRequest.done(length - 1);
|
||||
},
|
||||
function error(err) {
|
||||
outRequest.failed(safeError(err));
|
||||
});
|
||||
}
|
||||
|
||||
// Needed only for testing purposes
|
||||
contacts.restart = function() {
|
||||
readyState = 'notInitialized';
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialization function
|
||||
*
|
||||
*/
|
||||
contacts.init = function(cb, errorCb) {
|
||||
if (!navigator.getDataStores) {
|
||||
handleInitError({ name: 'DatastoreNotFound' }, errorCb);
|
||||
return;
|
||||
}
|
||||
|
||||
if (readyState === 'initialized') {
|
||||
cb();
|
||||
return;
|
||||
}
|
||||
|
||||
if (readyState === 'initializing') {
|
||||
document.addEventListener(INITIALIZE_EVENT, function oninitalized() {
|
||||
cb();
|
||||
document.removeEventListener(INITIALIZE_EVENT, oninitalized);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
readyState = 'initializing';
|
||||
|
||||
navigator.getDataStores(DATASTORE_NAME).then(function success(ds) {
|
||||
if (ds.length < 1) {
|
||||
handleInitError({ name: 'DatastoreNotFound' }, errorCb);
|
||||
return;
|
||||
}
|
||||
|
||||
datastore = ds[0];
|
||||
// Checking the length as the index should be there
|
||||
datastore.getLength().then(function(length) {
|
||||
if (length === 0 && datastore.readOnly === false) {
|
||||
window.console.info('Adding index as length is 0');
|
||||
setIndex(createIndex());
|
||||
return datastore.add(index);
|
||||
}
|
||||
else if (length > 0) {
|
||||
return datastore.get(INDEX_ID);
|
||||
}
|
||||
else if (datastore.readOnly === true) {
|
||||
window.console.warn('The datastore is empty and readonly');
|
||||
}
|
||||
return null;
|
||||
}).then(function add_index_success(v) {
|
||||
// Index is created in order not to cause errors
|
||||
if (typeof v !== 'number') {
|
||||
setIndex(v);
|
||||
}
|
||||
revisionId = datastore.revisionId;
|
||||
notifyOpenSuccess(cb);
|
||||
}, function add_index_error(err) {
|
||||
err = safeError(err);
|
||||
window.console.error('Error while setting the index: ', err.name);
|
||||
handleInitError(err, errorCb);
|
||||
});
|
||||
}, function error(err) {
|
||||
err = safeError(err);
|
||||
window.console.error('FB: Error while opening the DataStore: ',
|
||||
err.name);
|
||||
handleInitError(err, errorCb);
|
||||
});
|
||||
};
|
||||
})();
|
||||
325
gaia/shared/js/fb/fb_reader_utils.js
Executable file
325
gaia/shared/js/fb/fb_reader_utils.js
Executable file
|
|
@ -0,0 +1,325 @@
|
|||
'use strict';
|
||||
|
||||
// Includes convenience functions for FB Contact Data
|
||||
// To use this library you need to include:
|
||||
// 'shared/js/fb/fb_request.js' and 'shared/js/fb/fb_data_reader.js'
|
||||
|
||||
var fb = self.fb || {};
|
||||
self.fb = fb;
|
||||
|
||||
if (!self.AuxFB) {
|
||||
self.AuxFb = (function() {
|
||||
var CATEGORY = 'facebook';
|
||||
var NOT_LINKED = 'not_linked';
|
||||
var LINKED = 'fb_linked';
|
||||
|
||||
// Shallow copy from source to target object
|
||||
function populate(source, target, propertyNames) {
|
||||
propertyNames.forEach(function(property) {
|
||||
var propertyValue = source[property];
|
||||
if (propertyValue) {
|
||||
if (Array.isArray(propertyValue)) {
|
||||
target[property] = propertyValue.slice(0, propertyValue.length);
|
||||
} else {
|
||||
target[property] = propertyValue;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Auxiliary function for merging
|
||||
function mergeFbData(dcontact, fbdata) {
|
||||
var multipleFields = ['email', 'tel', 'photo', 'org', 'adr'];
|
||||
|
||||
multipleFields.forEach(function(field) {
|
||||
if (!dcontact[field]) {
|
||||
dcontact[field] = [];
|
||||
}
|
||||
var items = fbdata[field];
|
||||
if (items) {
|
||||
items.forEach(function(item) {
|
||||
// If there are no duplicates the merge is done
|
||||
var dupList = checkDuplicates(field, item, dcontact[field],
|
||||
fbdata.shortTelephone);
|
||||
if (dupList.length === 0) {
|
||||
dcontact[field].push(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var singleFields = ['bday', 'anniversary'];
|
||||
singleFields.forEach(function(field) {
|
||||
if (!dcontact[field]) {
|
||||
dcontact[field] = fbdata[field];
|
||||
}
|
||||
});
|
||||
|
||||
// To support the case in which the contact does not have a local name
|
||||
mergeNames(dcontact, fbdata);
|
||||
}
|
||||
|
||||
// Auxiliary function for merging names
|
||||
function mergeNames(devContact, fbContact) {
|
||||
var namesChanged = false;
|
||||
var nameFields = ['givenName', 'familyName'];
|
||||
nameFields.forEach(function(anameField) {
|
||||
// If the device contact does not have name fields setted
|
||||
// we use the Facebook ones
|
||||
var fieldValue = devContact[anameField];
|
||||
var fbValue = fbContact[anameField];
|
||||
if ((!Array.isArray(fieldValue) || fieldValue.length === 0) &&
|
||||
Array.isArray(fbValue) && fbValue.length > 0) {
|
||||
namesChanged = true;
|
||||
devContact[anameField] = (fieldValue && fieldValue[0]) || [];
|
||||
devContact[anameField].push(fbValue[0]);
|
||||
}
|
||||
});
|
||||
|
||||
if (namesChanged) {
|
||||
var givenName = devContact.givenName[0] || '';
|
||||
var familyName = devContact.familyName[0] || '';
|
||||
devContact.name = [givenName + ' ' + familyName];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns truthy if the device contact is a FB Friend
|
||||
*
|
||||
*/
|
||||
function isFbContact(devContact) {
|
||||
return (devContact && devContact.category &&
|
||||
devContact.category.indexOf(CATEGORY) !== -1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns truthy if the device contact is linked to a FB Friend
|
||||
*
|
||||
*/
|
||||
function isFbLinked(devContact) {
|
||||
return (devContact && devContact.category &&
|
||||
devContact.category.indexOf(LINKED) !== -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the data enriching the local contact with FB information
|
||||
*
|
||||
*/
|
||||
function getData(devContact) {
|
||||
var outReq = new fb.utils.Request();
|
||||
|
||||
window.setTimeout(function do_getData() {
|
||||
var uid = getFriendUid(devContact);
|
||||
|
||||
if (uid) {
|
||||
var fbreq = fb.contacts.get(uid);
|
||||
|
||||
fbreq.onsuccess = function() {
|
||||
var fbdata = fbreq.result;
|
||||
var out = mergeContact(devContact, fbdata);
|
||||
outReq.done(out);
|
||||
|
||||
};
|
||||
|
||||
fbreq.onerror = function() {
|
||||
outReq.failed(fbreq.error);
|
||||
};
|
||||
}
|
||||
else {
|
||||
outReq.done(devContact);
|
||||
}
|
||||
}, 0);
|
||||
|
||||
return outReq;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the FB Friend UID associated to a device contact
|
||||
*
|
||||
*/
|
||||
function getFriendUid(devContact) {
|
||||
var out = devContact.uid;
|
||||
|
||||
if (!out) {
|
||||
if (isFbLinked(devContact)) {
|
||||
out = getLinkedTo(devContact);
|
||||
}
|
||||
else if (devContact.category) {
|
||||
var idx = devContact.category.indexOf(CATEGORY);
|
||||
if (idx !== -1) {
|
||||
out = devContact.category[idx + 2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the UID of the FB Friend a device contact is linked to
|
||||
*
|
||||
*/
|
||||
function getLinkedTo(devContact) {
|
||||
var out;
|
||||
|
||||
if (devContact.category) {
|
||||
var idx = devContact.category.indexOf(LINKED);
|
||||
if (idx !== -1) {
|
||||
out = devContact.category[idx + 1];
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge a device contact with a FB Friend
|
||||
*
|
||||
*/
|
||||
function mergeContact(devContact, fbContact) {
|
||||
var out = devContact;
|
||||
|
||||
if (fbContact) {
|
||||
out = Object.create(null);
|
||||
out.updated = devContact.updated;
|
||||
out.published = devContact.published;
|
||||
|
||||
// The id comes from devContact and the rest of properties like
|
||||
// familyName, propertyName, etc... are defined in the proto object
|
||||
populate(devContact, out, Object.getOwnPropertyNames(devContact));
|
||||
populate(devContact, out,
|
||||
Object.getOwnPropertyNames(Object.getPrototypeOf(devContact)));
|
||||
|
||||
mergeFbData(out, fbContact);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a FB Contact (enriched with FB info) which has the tel number
|
||||
*
|
||||
*/
|
||||
function getContactByNumber(number, onsuccess, onerror) {
|
||||
var req = fb.contacts.getByPhone(number);
|
||||
|
||||
req.onsuccess = function get_by_phone_success(e) {
|
||||
var fbData = req.result;
|
||||
if (fbData) {
|
||||
fb.getMozContactByUid(fbData.uid, function merge(result) {
|
||||
if (Array.isArray(result) && result[0]) {
|
||||
var finalContact = fb.mergeContact(result[0], fbData);
|
||||
onsuccess(finalContact);
|
||||
}
|
||||
else {
|
||||
onsuccess(null);
|
||||
}
|
||||
}, function error_get_mozContact(err) {
|
||||
console.error('Error getting mozContact: ', err.name);
|
||||
onerror(err);
|
||||
});
|
||||
}
|
||||
else {
|
||||
onsuccess(null);
|
||||
}
|
||||
};
|
||||
|
||||
req.onerror = function() {
|
||||
onerror(req.error);
|
||||
};
|
||||
}
|
||||
|
||||
// Checks whether there is a duplicate for the field value
|
||||
// both in FB and in the local device Contact data
|
||||
// Returns an array with the values which are duplicated or empty if
|
||||
// no duplicates were found
|
||||
// Parameters are: field on which to search, the corresponding fbItem
|
||||
// the local device items, and extra FB Items which corresp to the short
|
||||
// telephone numbers allowing to filter out dups with intl-ed numbers
|
||||
function checkDuplicates(field, fbItem, devContactItems, extraFbItems) {
|
||||
var potentialDuplicatesFields = ['email', 'tel'];
|
||||
var out = [];
|
||||
|
||||
if (devContactItems &&
|
||||
potentialDuplicatesFields.indexOf(field) !== -1) {
|
||||
var total = devContactItems.length;
|
||||
for (var i = 0; i < total; i++) {
|
||||
var localValue = devContactItems[i].value;
|
||||
var fbValue = fbItem.value;
|
||||
// Checking for telephone international number matching
|
||||
if (localValue) {
|
||||
var trimedLocal = localValue.trim();
|
||||
if (trimedLocal === fbValue ||
|
||||
(field === 'tel' && Array.isArray(extraFbItems) &&
|
||||
extraFbItems.indexOf(trimedLocal) !== -1)) {
|
||||
out.push(trimedLocal);
|
||||
out.push(fbValue);
|
||||
}
|
||||
} // if(localValue)
|
||||
} // for
|
||||
} // if(devContactItems)
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mozContact associated to a certain FB UID
|
||||
*
|
||||
*/
|
||||
function getMozContactByUid(uid, onsuccess, onerror) {
|
||||
var filter = {
|
||||
filterBy: ['category'],
|
||||
filterValue: uid,
|
||||
filterOp: 'contains'
|
||||
};
|
||||
|
||||
var req = navigator.mozContacts.find(filter);
|
||||
req.onsuccess = function() {
|
||||
onsuccess(req.result);
|
||||
};
|
||||
req.onerror = function() {
|
||||
onerror(req.error);
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
'isFbContact': isFbContact,
|
||||
'isFbLinked': isFbLinked,
|
||||
'getData': getData,
|
||||
'getFriendUid': getFriendUid,
|
||||
'getLinkedTo': getLinkedTo,
|
||||
'mergeContact': mergeContact,
|
||||
'getContactByNumber': getContactByNumber,
|
||||
'getMozContactByUid': getMozContactByUid,
|
||||
'checkDuplicates': checkDuplicates,
|
||||
get CATEGORY() {
|
||||
return CATEGORY;
|
||||
},
|
||||
get LINKED() {
|
||||
return LINKED;
|
||||
},
|
||||
get NOT_LINKED() {
|
||||
return NOT_LINKED;
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
// This boilerplate code sets the AuxFb properties to the fb object
|
||||
var props = Object.keys(self.AuxFb);
|
||||
|
||||
for (var j = 0, end = props.length; j < end; j++) {
|
||||
/* jshint loopfunc: true */
|
||||
var prop = props[j];
|
||||
if (typeof self.fb[prop] === 'function') {
|
||||
self.fb[prop] = self.AuxFb[prop];
|
||||
}
|
||||
else {
|
||||
Object.defineProperty(self.fb, prop, {
|
||||
value: (function() {
|
||||
return self.AuxFb[prop];
|
||||
})(), writable: false, enumerable: true, configurable: false
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
39
gaia/shared/js/fb/fb_request.js
Executable file
39
gaia/shared/js/fb/fb_request.js
Executable file
|
|
@ -0,0 +1,39 @@
|
|||
'use strict';
|
||||
|
||||
var fb = self.fb || {};
|
||||
self.fb = fb;
|
||||
|
||||
// Only will be executed in the case of not loading fb.utils previously
|
||||
// i.e. dialer and call log FB integration
|
||||
fb.utils = self.fb.utils || {};
|
||||
self.fb.utils = fb.utils;
|
||||
|
||||
if (typeof fb.utils.Request !== 'function') {
|
||||
/**
|
||||
* Request auxiliary object to support asynchronous calls
|
||||
*
|
||||
*/
|
||||
fb.utils.Request = function() {
|
||||
this.done = function(result) {
|
||||
this.result = result;
|
||||
if (typeof this.onsuccess === 'function') {
|
||||
var ev = {};
|
||||
ev.target = this;
|
||||
window.setTimeout(function() {
|
||||
this.onsuccess(ev);
|
||||
}.bind(this), 0);
|
||||
}
|
||||
};
|
||||
|
||||
this.failed = function(error) {
|
||||
this.error = error;
|
||||
if (typeof this.onerror === 'function') {
|
||||
var ev = {};
|
||||
ev.target = this;
|
||||
window.setTimeout(function() {
|
||||
this.onerror(ev);
|
||||
}.bind(this), 0);
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
279
gaia/shared/js/fb/fb_tel_index.js
Executable file
279
gaia/shared/js/fb/fb_tel_index.js
Executable file
|
|
@ -0,0 +1,279 @@
|
|||
'use strict';
|
||||
|
||||
/* globals utils */
|
||||
/* exported TelIndexer */
|
||||
|
||||
// This object implements a prefix tree (aka trie) for FB tel numbers
|
||||
|
||||
function TrieNode(number) {
|
||||
this.keys = Object.create(null);
|
||||
this.leaves = null;
|
||||
this.value = number;
|
||||
}
|
||||
|
||||
var TelIndexer = {
|
||||
_MIN_TEL_LENGTH: 3,
|
||||
_INSERT_MODE: 'insert',
|
||||
|
||||
_indexOf: function(array, value, byPrefix) {
|
||||
var out = -1;
|
||||
|
||||
if (!Array.isArray(array)) {
|
||||
return out;
|
||||
}
|
||||
|
||||
for (var j = 0, l = array.length; j < l; j++) {
|
||||
if (array[j].value === value || (byPrefix &&
|
||||
array[j].value.startsWith(value))) {
|
||||
out = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
},
|
||||
|
||||
_getStringVariants: function(number) {
|
||||
var out = [];
|
||||
|
||||
var limit = number.length - this._MIN_TEL_LENGTH + 1;
|
||||
for (var j = 0; j < limit; j++) {
|
||||
out.push(number.substr(j));
|
||||
}
|
||||
return out;
|
||||
},
|
||||
|
||||
// Allows to index the number passed as parameter
|
||||
index: function(tree, number, dsId) {
|
||||
var self = this;
|
||||
var variants = this._getStringVariants(number);
|
||||
|
||||
variants.forEach(function(aVariant) {
|
||||
self._insert(tree, aVariant, dsId);
|
||||
});
|
||||
},
|
||||
|
||||
// Inserts a substring in a node. dsId is the object id (uid) associated
|
||||
_insertInNode: function(node, str, dsId) {
|
||||
if (str.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var currentStr = str;
|
||||
var insertPointFound = false;
|
||||
var insertObj;
|
||||
var pointerOriginal = str.length;
|
||||
while (!insertPointFound && currentStr.length > 0) {
|
||||
var inextObj = this._indexOf(node.leaves, currentStr, 'prefix');
|
||||
if (inextObj !== -1) {
|
||||
insertPointFound = true;
|
||||
insertObj = node.leaves[inextObj];
|
||||
}
|
||||
else {
|
||||
currentStr = currentStr.substring(0, currentStr.length - 1);
|
||||
}
|
||||
pointerOriginal--;
|
||||
}
|
||||
|
||||
if (insertPointFound) {
|
||||
if (insertObj.value === str) {
|
||||
// Exact insertion point
|
||||
insertObj.keys[dsId] = true;
|
||||
}
|
||||
else {
|
||||
var keys = Object.keys(insertObj.keys);
|
||||
// Let's check what's the gap
|
||||
var newString = insertObj.value.substring(0, currentStr.length);
|
||||
var restString = insertObj.value.substring(currentStr.length);
|
||||
|
||||
insertObj.value = newString;
|
||||
|
||||
if (restString.length > 0) {
|
||||
var newNode = new TrieNode(restString);
|
||||
for (var j = 0; j < keys.length; j++) {
|
||||
newNode.keys[keys[j]] = true;
|
||||
}
|
||||
if (Array.isArray(insertObj.leaves)) {
|
||||
newNode.leaves = [];
|
||||
insertObj.leaves.forEach(function(aLeave) {
|
||||
newNode.leaves.push(aLeave);
|
||||
});
|
||||
}
|
||||
insertObj.leaves = [];
|
||||
insertObj.leaves.push(newNode);
|
||||
}
|
||||
insertObj.keys[dsId] = true;
|
||||
|
||||
var restOriginal = str.substring(pointerOriginal + 1);
|
||||
// And the rest of the original string is inserted in the insertObj
|
||||
this._insertInNode(insertObj, restOriginal, dsId);
|
||||
}
|
||||
}
|
||||
else {
|
||||
var nextObj = new TrieNode(str);
|
||||
nextObj.keys[dsId] = true;
|
||||
node.leaves = node.leaves || [];
|
||||
node.leaves.push(nextObj);
|
||||
}
|
||||
},
|
||||
|
||||
// Compare function used for binary search
|
||||
_compare: function(a, b) {
|
||||
return a.localeCompare(b);
|
||||
},
|
||||
|
||||
// Find the first level node (corresponding to the 3 first numbers)
|
||||
_getFirstLevelNode: function(tree, str, mode) {
|
||||
var out = null;
|
||||
|
||||
var index;
|
||||
if (mode === this._INSERT_MODE) {
|
||||
index = this._indexOf(tree, str);
|
||||
if (index !== -1) {
|
||||
out = tree[index];
|
||||
}
|
||||
}
|
||||
else {
|
||||
index = utils.binarySearch(str, tree, {
|
||||
compareFunction: this._compare,
|
||||
arrayField: 'value'
|
||||
});
|
||||
|
||||
if (index.length > 0) {
|
||||
out = tree[index[0]];
|
||||
}
|
||||
}
|
||||
return out;
|
||||
},
|
||||
|
||||
// Orders the tree enabling binary search on the first levels
|
||||
orderTree: function(tree) {
|
||||
tree.sort(function(a, b) {
|
||||
return a.value.localeCompare(b.value);
|
||||
});
|
||||
},
|
||||
|
||||
// Inserts a number on the tree
|
||||
_insert: function(tree, str, dsId) {
|
||||
var firstThreeStr = str.substring(0, this._MIN_TEL_LENGTH);
|
||||
var rootObj = this._getFirstLevelNode(tree, firstThreeStr,
|
||||
this._INSERT_MODE);
|
||||
if (!rootObj) {
|
||||
rootObj = new TrieNode(firstThreeStr);
|
||||
tree.push(rootObj);
|
||||
}
|
||||
rootObj.keys[dsId] = true;
|
||||
|
||||
this._insertInNode(rootObj, str.substring(this._MIN_TEL_LENGTH), dsId);
|
||||
},
|
||||
|
||||
// Search a substring in a node
|
||||
_searchInNode: function(node, str) {
|
||||
var currentStr = str;
|
||||
var found = false;
|
||||
var pointerOriginal = str.length;
|
||||
var searchObj;
|
||||
while (!found && currentStr.length > 0) {
|
||||
var idxObj = this._indexOf(node.leaves, currentStr, 'prefix');
|
||||
if (idxObj !== -1) {
|
||||
found = true;
|
||||
searchObj = node.leaves[idxObj];
|
||||
}
|
||||
else {
|
||||
currentStr = currentStr.substring(0, currentStr.length - 1);
|
||||
}
|
||||
pointerOriginal--;
|
||||
}
|
||||
|
||||
if (found) {
|
||||
if (searchObj.value === str) {
|
||||
return searchObj;
|
||||
}
|
||||
// Let's calculate what is the gap
|
||||
var restString = str.substr(pointerOriginal + 1);
|
||||
if (restString.length > 0) {
|
||||
return this._searchInNode(searchObj, restString);
|
||||
}
|
||||
else {
|
||||
return searchObj;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
// Search for a number (which can be partial) on the tree
|
||||
search: function(tree, number) {
|
||||
var out = [];
|
||||
var totalLength = number.length;
|
||||
var MIN_TEL_LENGTH = this._MIN_TEL_LENGTH;
|
||||
|
||||
if (totalLength >= MIN_TEL_LENGTH) {
|
||||
var firstThreeStr = number.substring(0, MIN_TEL_LENGTH);
|
||||
var rootObj = this._getFirstLevelNode(tree, firstThreeStr);
|
||||
|
||||
if (rootObj) {
|
||||
if (totalLength === MIN_TEL_LENGTH) {
|
||||
out = Object.keys(rootObj.keys);
|
||||
}
|
||||
else {
|
||||
var node = this._searchInNode(rootObj,
|
||||
number.substring(MIN_TEL_LENGTH));
|
||||
if (node) {
|
||||
out = Object.keys(node.keys);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
},
|
||||
|
||||
// Removes a number from the tree
|
||||
remove: function(tree, number, dsId) {
|
||||
var self = this;
|
||||
var variants = this._getStringVariants(number);
|
||||
variants.forEach(function(aVariant) {
|
||||
self._unIndex(tree, aVariant, dsId);
|
||||
});
|
||||
},
|
||||
|
||||
// Removes a partial string from a node
|
||||
_removeInNode: function(node, str, dsId) {
|
||||
if (str.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var currentStr = str;
|
||||
var found = false;
|
||||
var pointerOriginal = str.length;
|
||||
var searchObj;
|
||||
while (!found && currentStr.length > 0) {
|
||||
var idxObj = this._indexOf(node.leaves, currentStr, 'prefix');
|
||||
if (idxObj !== -1) {
|
||||
found = true;
|
||||
searchObj = node.leaves[idxObj];
|
||||
}
|
||||
else {
|
||||
currentStr = currentStr.substring(0, currentStr.length - 1);
|
||||
}
|
||||
pointerOriginal--;
|
||||
}
|
||||
|
||||
if (found) {
|
||||
delete searchObj.keys[dsId];
|
||||
this._removeInNode(searchObj, str.substr(pointerOriginal + 1), dsId);
|
||||
}
|
||||
},
|
||||
|
||||
// Removes a number from the tree
|
||||
// TODO: Compact the tree when a number is removed
|
||||
_unIndex: function(tree, number, dsId) {
|
||||
var firstThreeStr = number.substring(0, this._MIN_TEL_LENGTH);
|
||||
|
||||
var rootObj = this._getFirstLevelNode(tree, firstThreeStr);
|
||||
if (rootObj) {
|
||||
delete rootObj.keys[dsId];
|
||||
this._removeInNode(rootObj, number.substr(this._MIN_TEL_LENGTH), dsId);
|
||||
}
|
||||
}
|
||||
};
|
||||
Loading…
Reference in a new issue