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:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
<meta charset="UTF-8">
<meta name="defaultLanguage" content="en-US">
<meta name="availableLanguages" content="en-US">
<link rel="localization" href="/shared/locales/import_contacts/import_contacts.{locale}.properties">
<script type="text/javascript" src="/shared/js/l10n.js"></script>
<script src="/shared/js/async_storage.js"></script>
<script src="/shared/js/contacts/import/import_status_data.js"></script>
<script src="/shared/js/lazy_loader.js"></script>
<script src="/shared/js/notification_helper.js"></script>
<script src="/shared/pages/import/js/parameters.js"></script>
<script src="/shared/js/contacts/import/utilities/config.js"></script>
<script src="/shared/js/contacts/utilities/image_square.js"></script>
<script src="/shared/js/contacts/utilities/image_thumbnail.js"></script>
<script src="/shared/js/contacts/import/utilities/misc.js"></script>
<script src="/shared/js/fb/fb_request.js"></script>
<script src="/shared/js/contacts/import/facebook/fb_data.js"></script>
<script src="/shared/js/contacts/import/facebook/fb_contact.js"></script>
<script src="/shared/js/contacts/import/facebook/fb_utils.js"></script>
<script src="/shared/js/fb/fb_reader_utils.js"></script>
<script src="/shared/js/contacts/import/facebook/fb_contact_utils.js"></script>
<script src="js/fb_sync.js"></script>
<script src="/contacts/js/fb/fb_init.js"></script>
<script src="js/fb_sync_init.js"></script>
</head>
<body>
<p id="status"></p>
</body>
</html>

View file

@ -0,0 +1,29 @@
/* exported console */
'use strict';
(function Console(exports) {
function getString(a) {
var out = '';
for (var c = 0; c < a.length; c++) {
out += a[c];
}
return out;
}
exports.console = {
'error': function() {
self.postMessage({
type: 'error',
data: getString(arguments)
});
},
'log': function() {
self.postMessage({
type: 'trace',
data: getString(arguments)
});
}
};
})(self);

View file

@ -0,0 +1,479 @@
/* global utils */
'use strict';
var fb = window.fb || {};
if (!fb.sync) {
(function() {
var Sync = fb.sync = {};
var theWorker;
// Facebook contacts currently under update process
var fbContactsById;
var totalToChange = 0,
changed = 0;
// Next timestamp to be set
var nextTimestamp;
var completionCallback,
errorCallback;
// Only makes sense when the data from FB is provided to the sync module
// i.e. it is not the worker which obtains that data
var fbFriendsDataByUid;
var logLevel = fb.logLevel || parent.fb.logLevel || 'DEBUG';
var isDebug = (logLevel === 'DEBUG');
var alarmFrame = null,
currentAlarmRequest = null;
// Delay in closing the iframe that schedules the alarm.
// Avoiding console.log issues in the logcat
var DELAY_ALARM_SCHED = 5000;
function debug() {
if (isDebug) {
var theArgs = ['<<FBSync>>'];
for (var c = 0; c < arguments.length; c++) {
theArgs.push(arguments[c]);
}
window.console.log.apply(window.console, theArgs);
}
}
// Starts the worker
function startWorker() {
theWorker = new Worker('/facebook/js/sync_worker.js');
theWorker.onmessage = function(e) {
workerMessage(e.data);
};
theWorker.onerror = function(e) {
window.console.error('Worker Error', e.message, e.lineno, e.column);
if (typeof errorCallback === 'function') {
errorCallback({
type: 'default_error'
});
}
};
}
function workerMessage(m) {
switch (m.type) {
case 'query_error':
var error = m.data || {};
window.console.error('FB: Error reported by the worker',
JSON.stringify(error));
if (typeof errorCallback === 'function') {
errorCallback({
name: 'defaultError'
});
}
break;
case 'token_error':
debug('FB: Token error reported by the worker');
if (typeof errorCallback === 'function') {
errorCallback({
name: 'invalidToken'
});
}
break;
case 'timeout_error':
debug('Timeout error reported by the worker');
if (typeof errorCallback === 'function') {
errorCallback({
name: 'timeout'
});
}
break;
case 'trace':
debug(m.data);
break;
case 'friendRemoved':
removeFbFriend(m.data.contactId);
break;
case 'friendUpdated':
updateFbFriend(m.data.contactId,
fb.friend2mozContact(m.data.updatedFbData));
break;
// Message with the totals
case 'totals':
changed = 0;
totalToChange = m.data.totalToChange;
nextTimestamp = m.data.queryTimestamp;
var newFriendNumber = m.data.newFriendNumber;
if (newFriendNumber) {
debug('New friend number: ', newFriendNumber);
fb.utils.setCachedNumFriends(newFriendNumber);
}
debug('Total to be changed: ', totalToChange);
// If totals === 0 then the completion callback will be invoked
checkTotals();
break;
case 'friendImgReady':
debug('Friend Img Data ready: ', m.data.contactId);
updateFbFriendWhenImageReady(m.data);
break;
}
}
function updateFbFriendWhenImageReady(data) {
var contact = fbContactsById[data.contactId];
var uid = fb.getFriendUid(contact);
var updatedFbData = fbFriendsDataByUid[uid];
if (!data.photo) {
updateFbFriend(data.contactId, updatedFbData);
return;
}
utils.thumbnailImage(data.photo, function gotTumbnail(thumbnail) {
var fbInfo = {};
fbInfo.photo = [data.photo, thumbnail];
fb.setFriendPictureUrl(fbInfo, updatedFbData.pic_big);
updatedFbData.fbInfo = fbInfo;
updateFbFriend(data.contactId, updatedFbData);
});
}
function onsuccessCb() {
changed++;
checkTotals();
}
// Updates the FB data from a friend
function updateFbFriend(contactId, cfdata) {
fb.friend2mozContact(cfdata);
cfdata.fbInfo = cfdata.fbInfo || {};
cfdata.fbInfo.org = [fb.getWorksAt(cfdata)];
var birthDate = null;
if (cfdata.birthday_date && cfdata.birthday_date.length > 0) {
birthDate = fb.getBirthDate(cfdata.birthday_date);
}
cfdata.fbInfo.bday = birthDate;
var address = fb.getAddresses(cfdata);
if (address) {
cfdata.fbInfo.adr = address;
}
if (cfdata.shortTelephone) {
cfdata.fbInfo.shortTelephone = cfdata.shortTelephone;
delete cfdata.shortTelephone;
}
// Then the new data saved to the cache
var fbContact = new fb.Contact(fbContactsById[contactId]);
var fbReq = fbContact.update(cfdata);
// Nothing special
fbReq.onsuccess = function() {
debug('Friend updated correctly', cfdata.uid);
onsuccessCb();
};
// Error. mark the contact as pending to be synchronized
fbReq.onerror = function() {
window.console.error('FB: Error while saving contact data: ',
cfdata.uid);
changed++;
checkTotals();
};
}
function removeFbFriend(contactId) {
debug('Removing Friend: ', contactId);
var removedFriend = fbContactsById[contactId];
var fbContact = new fb.Contact(removedFriend);
if (fb.isFbLinked(removedFriend)) {
debug('Friend is linked: ', contactId);
// No care about what happens
var req = fbContact.unlink('hard');
req.onsuccess = onsuccessCb;
req.onerror = function() {
window.console.error('FB. Error while hard unlinking friend: ',
contactId);
// The counter has to be increased anyway
changed++;
checkTotals();
};
}
else {
debug('Friend is not linked: ', contactId);
var fbContactReq = fbContact.remove();
fbContactReq.onsuccess = onsuccessCb;
fbContactReq.onerror = function() {
window.console.error('FB. Error while removing contact: ',
contactId);
// The counter has to be increased anyway
changed++;
checkTotals();
};
}
}
function checkTotals() {
if (changed === totalToChange) {
debug('Sync process finished!');
if (window.contacts && window.contacts.List) {
window.setTimeout(window.contacts.List.load, 0);
}
// Once sync has finished the last update date is set
// Thus we ensure next will happen in the next <period> hours
fb.utils.setLastUpdate(nextTimestamp, function sync_end() {
completionCallback(totalToChange);
});
if (theWorker) {
theWorker.terminate();
theWorker = null;
}
}
}
// Starts a synchronization
Sync.start = function(params) {
if (params) {
completionCallback = params.success;
errorCallback = params.error;
}
totalToChange = 0;
changed = 0;
// First only take into account those Friends already on the device
// This work has to be done here and not by the worker as it has no
// access to the Web APIs
var req = fb.utils.getAllFbContacts();
req.onsuccess = function() {
var uids = {};
var fbContacts = req.result;
if (fbContacts.length === 0) {
debug('Nothing to be synchronized. No FB Contacts present');
return;
}
startWorker();
// Contacts by id are cached for later update
fbContactsById = {};
// Contacts for which an update will be forced
var forceUpdate = {};
fbContacts.forEach(function(contact) {
fbContactsById[contact.id] = contact;
var pictureUrl = fb.getFriendPictureUrl(contact);
var uid = fb.getFriendUid(contact);
uids[uid] = {
contactId: contact.id,
photoUrl: pictureUrl
};
if (!pictureUrl) {
forceUpdate[uid] = {
contactId: contact.id
// photoUrl is left undefined as it is not known
};
}
});
fb.utils.getLastUpdate(function run_worker(ts) {
fb.utils.getCachedAccessToken(function(access_token) {
// The worker must start
theWorker.postMessage({
type: 'start',
data: {
uids: uids,
imgNeedsUpdate: forceUpdate,
timestamp: ts,
access_token: access_token,
operationsTimeout: fb.operationsTimeout,
targetPictureSize: utils.misc.getPreferredPictureDetail()
}
});
});
});
};
req.onerror = function() {
window.console.error('FB: Error while getting friends on the device',
req.error.name);
if (typeof errorCallback === 'function') {
errorCallback({
name: 'defaultError'
});
}
};
};
// Schedules a next synchronization
Sync.scheduleNextSync = function() {
//
currentAlarmRequest = new fb.utils.Request();
window.setTimeout(function() {
// The alarm has to be scheduled by the same page that will handle it
// https://bugzilla.mozilla.org/show_bug.cgi?id=800431
alarmFrame = document.createElement('iframe');
alarmFrame.src = '/facebook/fb_sync.html';
alarmFrame.width = 1;
alarmFrame.height = 1;
alarmFrame.style.display = 'none';
document.body.appendChild(alarmFrame);
},0);
return currentAlarmRequest;
};
function cleanAlarmSchedFrame() {
alarmFrame.src = null;
document.body.removeChild(alarmFrame);
alarmFrame = null;
}
Sync.onAlarmScheduled = function(date) {
debug('Next synch scheduled at: ', date);
if (alarmFrame) {
window.setTimeout(cleanAlarmSchedFrame, DELAY_ALARM_SCHED);
}
currentAlarmRequest.done(date);
};
Sync.onAlarmError = function(e) {
if (alarmFrame) {
window.setTimeout(cleanAlarmSchedFrame, DELAY_ALARM_SCHED);
}
window.console.error('<<FB Sync>> Error while scheduling a new sync: ',
e);
currentAlarmRequest.failed(e);
};
Sync.debug = function() {
debug.apply(this, arguments);
};
// Starts a synchronization with data coming from import / link
Sync.startWithData = function(contactList, myFriendsByUid, callbacks) {
completionCallback = callbacks.success;
errorCallback = callbacks.error;
nextTimestamp = Date.now();
changed = 0;
// As it is not a priori known how many are going to needed a change
totalToChange = Number.MAX_VALUE;
debug('Starting Synchronization with data');
fbFriendsDataByUid = myFriendsByUid;
// Friends to be updated by the worker (those which profile img changed)
var toBeUpdated = {};
fb.utils.getLastUpdate(function import_updates(lastUpdate) {
var toBeChanged = 0;
var lastUpdateTime = Math.round(lastUpdate / 1000);
debug('Last update time: ', lastUpdateTime);
fbContactsById = {};
contactList.forEach(function(aContact) {
fbContactsById[aContact.id] = aContact;
var uid = fb.getFriendUid(aContact);
var friendData = fbFriendsDataByUid[uid];
if (friendData) {
var friendUpdate = friendData.profile_update_time;
debug('Friend update Time ', friendUpdate, 'for UID: ', uid);
var profileImgUrl = fb.getFriendPictureUrl(aContact);
if (friendUpdate > lastUpdateTime ||
profileImgUrl !== friendData.pic_big) {
debug('Friend changed!! : ', uid);
if (profileImgUrl !== friendData.pic_big) {
debug('Profile img changed: ', profileImgUrl);
toBeUpdated[uid] = {
contactId: aContact.id
};
}
else {
debug('Updating friend: ', friendData.uid);
toBeChanged++;
updateFbFriend(aContact.id, friendData);
}
}
else {
debug('Friend has not changed', uid);
}
}
else {
debug('Removing friend: ', aContact.id);
toBeChanged++;
removeFbFriend(aContact.id);
}
});
debug('First pass of Updates and removed finished');
// Those friends which image has changed will require help from the
// worker
var toBeUpdatedList = Object.keys(toBeUpdated);
if (toBeUpdatedList.length > 0) {
totalToChange = toBeChanged + toBeUpdatedList.length;
debug('Starting worker for updating img data');
startWorker();
fb.utils.getCachedAccessToken(function(access_token) {
theWorker.postMessage({
type: 'startWithData',
data: {
access_token: access_token,
uids: toBeUpdated,
operationsTimeout: fb.operationsTimeout,
targetPictureSize: utils.misc.getPreferredPictureDetail()
}
});
});
}
else {
totalToChange = toBeChanged;
checkTotals();
}
});
};
})();
}

View file

@ -0,0 +1,272 @@
/* globals NotificationHelper */
'use strict';
var fb = window.fb || {};
var Sync = fb.sync || {};
fb.sync = Sync;
(function() {
var Alarm = Sync.alarm = {};
var ALARM_ID_KEY = fb.utils.ALARM_ID_KEY;
var isSyncOngoing = false;
// This is the amount of hours to wait to retry a sync operation
var DEFAULT_RETRY_PERIOD = 1;
// Retry period after timeout (15 minutes)
var RETRY_PERIOD_TIMEOUT = 0.25;
// Delay in closing the window
var CLOSE_DELAY = 5000;
var FB_TRAY_ICON = '/contacts/style/images/f_logo.png';
var FB_SYNC_ERROR_PARAM = 'isSyncError';
Alarm.init = function() {
fb.init(function fb_alarm_init() {
if (navigator.mozHasPendingMessage('alarm')) {
fb.sync.debug('Alarm System Message Received!!!!!');
navigator.mozSetMessageHandler('alarm', handleAlarm);
}
// Workaround for Gecko Bug. mozHasPendingMessage does not work
// https://bugzilla.mozilla.org/show_bug.cgi?id=802876
else if (parent.location === window.location) {
fb.sync.debug('Fb Sync woke up. Alarm ok. mozHasPendingMsg failed');
// Unfortunately this does not also work
// navigator.mozSetMessageHandler('alarm', handleAlarm);
window.setTimeout(function() {
handleAlarm({
data: {
sync: true
}
});
},0);
}
else {
setNextAlarm(true, fb.syncPeriod);
}
}); // fb.init
}; // Alarm.init
function syncSuccess() {
fb.sync.debug('Sync finished ok at ', new Date());
isSyncOngoing = false;
setNextAlarm(false, fb.syncPeriod, function() {
closeApp();
});
} // syncSuccess function
function syncError(error) {
isSyncOngoing = false;
var theError = error;
if (!theError || theError.name === 'undefined') {
theError = {
name: 'defaultError'
};
}
switch (theError.name) {
case 'invalidToken':
fb.sync.debug('Invalid token!!!. Notifying the user');
// A new alarm is not set. It will be set once the user
// logs in Facebook one more time
showNotification({
title: 'facebook',
body: 'notificationLogin',
iconURL: FB_TRAY_ICON + '?' + FB_SYNC_ERROR_PARAM + '=1',
callback: handleInvalidToken
});
break;
case 'timeout':
fb.sync.debug('Timeout in sync. Next sync scheduled in 15 minutes');
scheduleAt(RETRY_PERIOD_TIMEOUT, closeApp);
break;
case 'defaultError':
window.console.error('Error reported in synchronization: ',
JSON.stringify(theError));
showNotification({
title: 'facebook',
body: 'syncError',
iconURL: FB_TRAY_ICON + '?' + FB_SYNC_ERROR_PARAM + '=1',
callback: function() {
scheduleAt(fb.syncPeriod, closeApp);
}
});
break;
}
} // syncError function
function handleAlarm(message) {
// First is checked if this is a sync alarm
if (message.data && message.data.sync === true &&
isSyncOngoing === false && navigator.onLine === true) {
isSyncOngoing = true;
fb.sync.debug('Starting sync at: ', new Date());
ackAlarm(function alarm_process() {
fb.sync.start({
success: syncSuccess,
error: syncError
});
});
}
else if (isSyncOngoing === true) {
fb.sync.debug('There is an ongoing synchronization. Trying it later');
scheduleAt(DEFAULT_RETRY_PERIOD, closeApp);
}
else if (navigator.onLine === false) {
fb.sync.debug('Navigator is not online. Setting an alarm for next hour');
scheduleAt(DEFAULT_RETRY_PERIOD, closeApp);
}
else {
fb.sync.debug('Alarm message but apparently was not a sync message');
closeApp();
}
}
function alarmSetErrorCb(notifyParent, e) {
if (notifyParent) {
window.setTimeout(function() {
parent.fb.sync.onAlarmError(e.target.error);
},0);
}
else {
window.console.error('<<FBSync>> Error while setting next alarm: ',
e.target.error.name);
}
}
function scheduleAt(hours, callback) {
var nextUpdate = Date.now() + hours * 60 * 60 * 1000;
var scheduledDate = new Date(nextUpdate);
fb.sync.debug('Forcing scheduling an alarm at: ', scheduledDate);
var req = addAlarm(scheduledDate);
req.onsuccess = function() {
fb.sync.debug('--> Next Sync forced to happen at: ', scheduledDate);
if (typeof callback === 'function') {
callback();
}
};
req.onerror = function(e) {
alarmSetErrorCb(false, e);
};
}
function ackAlarm(callback) {
// The next alarmid is removed
// Continuation is enabled even if we cannot remove the id
window.asyncStorage.removeItem(ALARM_ID_KEY, callback, callback);
}
// Adds an alarm at the specified datetime ensuring any existing alarm
// it is removed and ensuring the id is stored on the asyncStorage
function addAlarm(at) {
var outReq = new fb.utils.Request();
window.setTimeout(function do_add_alarm() {
window.asyncStorage.getItem(ALARM_ID_KEY, function set_alarm(id) {
if (id) {
navigator.mozAlarms.remove(Number(id));
}
var req = navigator.mozAlarms.add(at, 'honorTimezone', {
sync: true
});
// Setting the new alarm
req.onsuccess = function() {
window.asyncStorage.setItem(ALARM_ID_KEY, String(req.result),
function success_store() {
outReq.done(req.result);
},
function error_store(e) {
var errorParam = {
target: {
error: e || {
name: 'AsyncStorageError'
}
}
};
outReq.failed(errorParam);
});
};
req.onerror = function(e) {
outReq.failed(e);
};
});
}, 0);
return outReq;
}
function setNextAlarm(notifyParent, hours, callback) {
fb.utils.getLastUpdate(function(timestamp) {
var nextUpdate = timestamp + hours * 60 * 60 * 1000;
var scheduledDate = new Date(nextUpdate);
fb.sync.debug('Going to set a new alarm at: ', scheduledDate);
var req = addAlarm(scheduledDate);
req.onsuccess = function() {
if (notifyParent === true) {
window.setTimeout(function() {
parent.fb.sync.onAlarmScheduled(scheduledDate);
},0);
}
fb.sync.debug('--> Next Sync will happen at: ', scheduledDate);
if (typeof callback === 'function') {
callback();
}
};
req.onerror = function(e) {
alarmSetErrorCb(notifyParent, e);
};
}); // Get last update
} // doSetNextAlarm
function showNotification(params) {
navigator.mozApps.getSelf().onsuccess = function(evt) {
var app = evt.target.result;
var iconURL = app.installOrigin + params.iconURL;
var options = {
body: params.body,
icon: iconURL
};
/*jshint unused:false*/
NotificationHelper.send(params.title, options)
.then(notification => {
if (typeof params.callback === 'function') {
params.callback();
}
});
};
}
function closeApp() {
// Wait some seconds to avoid any kind of race condition or console error
window.setTimeout(window.close, CLOSE_DELAY);
}
function handleInvalidToken() {
// TODO: Reset token and other stuff in order to better inform the user
closeApp();
}
// Everything starts
Alarm.init();
})();

View file

@ -0,0 +1,384 @@
/* globals fb */
'use strict';
importScripts('/shared/js/fb/fb_request.js',
'/shared/js/contacts/import/facebook/fb_query.js',
'/shared/js/contacts/import/facebook/fb_contact_utils.js',
'/shared/js/fb/fb_reader_utils.js',
'console.js');
(function(wutils) {
var uids,
timestamp,
access_token,
forceUpdateUids,
targetPictureSize;
wutils.addEventListener('message', processMessage);
debug('Worker up and running ....');
// Query to know what friends need to be updated
var UPDATED_QUERY = [
'SELECT uid, name, first_name, last_name, ' ,
'middle_name, birthday_date, email, current_location, ' ,
'work, phones, hometown_location, pic_big ' ,
' FROM user' ,
' WHERE uid ',
' IN (SELECT uid1 FROM friend WHERE uid2=me()',
' AND uid1 IN (',
null,
') )',
' AND profile_update_time > ',
null,
' OR uid IN (',
null,
')'
];
// Query to know what friends need to be removed
var REMOVED_QUERY = [
'SELECT target_id FROM ',
'connection where source_id = me() ',
' AND target_type = "user" AND is_deleted="true"',
' AND target_id IN (',
null,
')'
];
// Query to know the total amount of friends
var COUNT_QUERY = 'SELECT uid FROM user WHERE uid IN ' +
'(SELECT uid1 FROM friend WHERE uid2=me())';
// Indicates whether the worker started with or without data
var withData = false;
function debug() {
function getString(a) {
var out = '';
for (var c = 0; c < a.length; c++) {
out += a[c];
}
return out;
}
self.console.log(getString(arguments));
}
function errorQueryCb(e) {
self.console.error('<<FB Sync>>: Error while trying to sync', e);
postError({
type: 'query_error',
data: {}
});
}
function timeoutQueryCb(e) {
postError({
type: 'timeout_error'
});
}
function postError(e) {
// Message type to propagate to the worker parent
var type = 'query_error';
if (e && e.code === 190) {
self.console.log('This is a token error. Notifying worker parent');
type = 'token_error';
}
else if (e && e.type === 'timeout_error') {
self.console.log('This is a timeout error. Notifying worker parent');
type = e.type;
}
wutils.postMessage({
type: type,
data: e
});
}
function buildQueries(ts, uids, forcedUids) {
var uidsFilter = uids.join(',');
// The index at which the timestamp is set
var IDX_TS = 10;
UPDATED_QUERY[IDX_TS] = Math.round(ts / 1000);
// The index at which uids filter is set
var IDX_UIDS = 7;
UPDATED_QUERY[IDX_UIDS] = uidsFilter;
// The index at which those uids forced to be updated is set
var IDX_FORCE = 12;
var forceUpdateUidsFilter = '';
if (forcedUids && forcedUids.length > 0) {
forceUpdateUidsFilter = forcedUids.join(',');
}
UPDATED_QUERY[IDX_FORCE] = forceUpdateUidsFilter;
var R_IDX_UIDS = 4;
REMOVED_QUERY[R_IDX_UIDS] = uidsFilter;
// Two queries launched at the same time
var outQueries = {
query1: UPDATED_QUERY.join(''),
query2: REMOVED_QUERY.join('')
};
// In any sync without data (periodic one) we also request the total amount
// of friends in order to update it
// Please take into account that that cannot be done with a friend_count
// query as it also returns those friends who are deactivated
// See https://bugzilla.mozilla.org/show_bug.cgi?id=838605
if (!withData) {
outQueries.query3 = COUNT_QUERY;
}
return JSON.stringify(outQueries);
}
function processMessage(e) {
var message = e.data;
if (message.type === 'start') {
withData = false;
uids = message.data.uids;
access_token = message.data.access_token;
timestamp = message.data.timestamp;
forceUpdateUids = message.data.imgNeedsUpdate;
fb.operationsTimeout = message.data.operationsTimeout;
targetPictureSize = message.data.targetPictureSize;
debug('Worker acks contacts to check: ', Object.keys(uids).length);
if (forceUpdateUids && forceUpdateUids.length > 0) {
debug('These friends are forced to be updated: ' ,
JSON.stringify(forceUpdateUids));
}
getFriendsToBeUpdated(Object.keys(uids), Object.keys(forceUpdateUids));
}
else if (message.type === 'startWithData') {
withData = true;
debug('worker Acks start with data');
fb.operationsTimeout = message.data.operationsTimeout;
uids = message.data.uids;
access_token = message.data.access_token;
targetPictureSize = message.data.targetPictureSize;
getNewImgsForFriends(Object.keys(uids), access_token);
}
}
// Launch a multiple query to obtain friends to be updated and deleted
function getFriendsToBeUpdated(uids, forcedUids) {
var query = buildQueries(timestamp, uids, forcedUids);
var callbacks = {
success: friendsReady,
error: errorQueryCb,
timeout: timeoutQueryCb
};
fb.utils.runQuery(query, callbacks, access_token);
}
// Callback executed when data is ready
function friendsReady(response) {
// Timestamp is captured right now to avoid problems
// with updates in between
var qts = Date.now();
if (typeof response.error === 'undefined') {
var updateList = response.data[0].fql_result_set;
var removeList = response.data[1].fql_result_set;
// removeList = [{target_id: '100001127136581'}];
var theData = {
totalToChange: updateList.length + removeList.length,
queryTimestamp: qts
};
if (!withData) {
if (response.data[2] &&
Array.isArray(response.data[2].fql_result_set)) {
var friendCount = response.data[2].fql_result_set.length;
theData.newFriendNumber = friendCount;
}
}
wutils.postMessage({
type: 'totals',
data: theData
});
syncUpdatedFriends(updateList);
syncRemovedFriends(removeList);
}
else {
postError(response.error);
}
}
function syncRemovedFriends(removedFriends) {
debug('Friends to be removed: ', removedFriends.length);
// Simply an iteration over the collection is done and a message passed
removedFriends.forEach(function(aremoved) {
var removedRef = uids[aremoved.target_id];
if (removedRef) {
wutils.postMessage({
type: 'friendRemoved',
data: {
uid: aremoved.target_id,
contactId: removedRef.contactId
}
});
}
}); // forEach
}
function syncUpdatedFriends(updatedFriends) {
debug('Friends to be updated: ', updatedFriends.length);
// Friends which image has to be updated
var friendsImgToBeUpdated = {};
updatedFriends.forEach(function(afriend) {
var friendInfo = forceUpdateUids[afriend.uid] || uids[afriend.uid];
if (!friendInfo) {
debug('The uid provided is unknown. Doing nothing');
return;
}
if (afriend.pic_big !== friendInfo.photoUrl) {
// Photo changed
debug('Contact Photo Changed!!! for ', afriend.uid);
friendsImgToBeUpdated[afriend.uid] = afriend;
}
else {
debug('Contact Photo unchanged for ', afriend.uid);
wutils.postMessage({
type: 'friendUpdated',
data: {
updatedFbData: afriend,
contactId: friendInfo.contactId
}
});
}
});
debug('Worker terminated processing updates');
var friendImgList = Object.keys(friendsImgToBeUpdated);
if (friendImgList.length > 0) {
debug('Now starting synch for ', friendImgList.length, ' images');
// Now it is time to download the images needed
var imgSync = new ImgSynchronizer(friendImgList);
imgSync.start();
// Once an image is ready friend update is notified
imgSync.onimageready = function(uid, blob) {
var friendData;
if (blob) {
debug('Image retrieved correctly for ', uid);
friendData = friendsImgToBeUpdated[uid];
friendData.fbInfo = {};
friendData.fbInfo.photo = [blob];
fb.setFriendPictureUrl(friendData.fbInfo, friendData.pic_big);
}
else {
self.console.error('Img for UID', uid, ' could not be retrieved ');
// This friend has to be marked in a special state just to be
// synced later on
friendData = friendsImgToBeUpdated[uid];
friendData.fbInfo = {};
friendData.fbInfo.photo = [];
// TODO: This should only remove the URL of the profile picture
friendData.fbInfo.url = [];
}
var contact = uids[uid] || forceUpdateUids[uid];
wutils.postMessage({
type: 'friendUpdated',
data: {
updatedFbData: friendData,
contactId: contact.contactId
}
});
};
}
}
// For dealing with the case that only new imgs have to be retrieved
function getNewImgsForFriends(friendList) {
debug('Getting new imgs for friends', JSON.stringify(friendList));
var imgSync = new ImgSynchronizer(friendList);
imgSync.start();
// Once an image is ready friend update is notified
imgSync.onimageready = function(uid, blob) {
self.console.log('Img Ready from worker');
if (!blob) {
self.console.error('Img for UID: ', uid, ' could not be retrieved ');
}
wutils.postMessage({
type: 'friendImgReady',
data: {
photo: blob,
contactId: uids[uid].contactId
}
});
};
}
var ImgSynchronizer = function(friends) {
var next = 0;
var self = this;
this.friends = friends;
this.start = function() {
retrieveImg(this.friends[next]);
};
function imgRetrieved(blob) {
if (typeof self.onimageready === 'function') {
var uid = self.friends[next];
wutils.setTimeout(function() {
self.onimageready(uid, blob);
},0);
}
// And lets go for the next
next++;
if (next < self.friends.length) {
retrieveImg(self.friends[next]);
}
}
function retrieveImg(uid) {
fb.utils.getFriendPicture(uid, imgRetrieved, access_token,
targetPictureSize);
}
};
})(self);

View file

@ -0,0 +1,17 @@
{
"extends": "../../../../../.jshintrc",
"predef": [
"assert",
"suite",
"test",
"setup",
"teardown",
"suiteSetup",
"suiteTeardown",
"require",
"requireApp",
"sinon",
"mocha",
"loadBodyHTML"
]
}

View file

@ -0,0 +1,102 @@
/* globals MockFbQuery, MockOauthflow*/
'use strict';
require('/shared/js/contacts/import/facebook/fb_contact_utils.js');
require('/shared/js/fb/fb_reader_utils.js');
require('/shared/js/contacts/import/utilities/misc.js');
require('/shared/js/contacts/import/facebook/fb_contact.js');
require('/shared/js/contacts/import/facebook/facebook_connector.js');
requireApp('communications/facebook/test/unit/mock_fb_graph_data.js');
requireApp('communications/facebook/test/unit/mock_fb_query.js');
requireApp('communications/contacts/test/unit/mock_oauthflow.js');
var realFbUtils,
realOauthflow,
subject;
if (!window.FacebookConnector) {
window.FacebookConnector = null;
}
if (!window.fb) {
window.fb = null;
}
if (!window.oauthflow) {
window.oauthflow = null;
}
suite('Facebook Connector Tests', function() {
suiteSetup(function() {
realFbUtils = window.fb.utils;
window.fb.utils = MockFbQuery;
subject = window.FacebookConnector;
realOauthflow = window.oauthflow;
window.oauthflow = MockOauthflow;
});
test('List all friends. Adapt data for showing', function(done) {
subject.listAllContacts('fake_token', {
success: function(data) {
assert.isNotNull(data.data);
assert.isTrue(Array.isArray(data.data));
var theData = data.data[0];
var adapted = subject.adaptDataForShowing(theData);
assert.equal(adapted.givenName[0], 'Carlos Angel');
assert.equal(adapted.familyName[0], 'Bureta');
assert.equal(adapted.email1, 'ppburetaxyz@gmail.com');
assert.equal(adapted.uid, 45677);
done();
}
});
});
test('List all friends. Adapt Data for Saving', function(done) {
subject.listAllContacts('fake_token', {
success: function(data) {
assert.isNotNull(data.data);
assert.isTrue(Array.isArray(data.data));
var theData = data.data[0];
var adapted = subject.adaptDataForSaving(theData);
assert.equal(adapted.givenName[0], 'Carlos Angel');
assert.equal(adapted.familyName[0], 'Bureta');
assert.equal(adapted.email[0].type[0], 'other');
assert.equal(adapted.email[0].value, 'ppburetaxyz@gmail.com');
assert.equal(adapted.tel[0].type[0], 'other');
assert.equal(adapted.tel[0].value, '+34609274801');
assert.equal(adapted.fbInfo.org[0], 'Telefonica');
assert.equal(adapted.fbInfo.adr[0].type[0], 'home');
assert.equal(adapted.fbInfo.adr[0].locality, 'Valladolid');
assert.equal(adapted.fbInfo.adr[0].region, 'Castilla y Leon');
assert.equal(adapted.fbInfo.adr[0].countryName, 'Spain');
assert.equal(adapted.fbInfo.adr[1].type[0], 'current');
assert.equal(adapted.fbInfo.adr[1].locality, 'Greater London');
assert.equal(adapted.fbInfo.adr[1].region, 'London');
assert.equal(adapted.fbInfo.adr[1].countryName, 'United Kingdom');
assert.equal(adapted.fbInfo.bday.getUTCMonth(), 11);
assert.equal(adapted.fbInfo.bday.getUTCDate(), 3);
done();
}
});
});
suiteTeardown(function() {
window.fb.utils = realFbUtils;
window.oauthflow = realOauthflow;
});
});

View file

@ -0,0 +1,23 @@
'use strict';
/* exported MockCurtain */
var MockCurtain = {
show: function() {
return {
update: function() {
},
setTotal: function() {
},
getValue: function() {
}
};
},
hide: function() {
},
hideMenu: function(){}
};

View file

@ -0,0 +1,59 @@
/* exported MockFbGraphData */
'use strict';
var MockFbGraphData = {
data: [
{
'uid': 45677,
'name': 'Carlos Angel Bureta',
'first_name': 'Carlos',
'last_name': 'Bureta',
'pic_big':
'http://profile.ak.fbcdn.net/hprofile-ak-ash3/49136_779462559_2920_n.jpg',
'hometown_location': {
'city': 'Valladolid',
'state': 'Castilla y Leon',
'country': 'Spain',
'zip': '',
'id': 108236755863231,
'name': 'Valladolid, Spain'
},
'current_location': {
'city': 'Greater London',
'state': 'London',
'country': 'United Kingdom',
'zip': '',
'id': 902104567293783,
'name': 'London, United Kingdom'
},
'middle_name': 'Angel',
'birthday_date': '12/03/1984',
'email': 'ppburetaxyz@gmail.com',
'profile_update_time': 1360140477,
'work': [
{
'employer': {
'id': 115396748472536,
'name': 'Telefonica'
}
},
{
'employer': {
'id': 129653317102827,
'name': 'Telefonica I+D'
}
}
],
'phones': [
{
'country_code': '34',
'number': '609274801',
'type': 'cell',
'phone_type': 'Mobile',
'is_verified': true,
'is_sms_enabled': false
}
]
}
]
};

View file

@ -0,0 +1,12 @@
/* global MockFbGraphData */
/* exported MockFbQuery */
'use strict';
var MockFbQuery = {
runQuery: function(query, callbacks) {
callbacks.success(MockFbGraphData);
},
getFriendPicture: function(uid, callbacks) {
callbacks.success(null);
}
};