VulpesOS/gaia/apps/communications/facebook/js/fb_sync.js
OverSu b38dea0597 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.
2026-09-30 23:13:03 +02:00

479 lines
14 KiB
JavaScript
Executable file

/* 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();
}
});
};
})();
}