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,677 @@
'use strict';
/* global Curtain, FacebookConnector, ImageLoader, Normalizer, utils,
ImportStatusData, oauth2 */
var fb = window.fb || {};
if (!fb.link) {
(function(document) {
// parameter id for the linking contact
var link = fb.link = {};
var UI = link.ui = {};
link.CID_PARAM = 'contactid';
// Contact id that the user wants to be linked to
var contactid;
// The data of such a contact
var cdata;
// Access token
var access_token;
// Used to control the number of queries
var numQueries = 0;
// The uid of the friend to be linked
var friendUidToLink;
// Base query to search for contacts
var SEARCH_QUERY = ['SELECT uid, name, email from user ',
' WHERE uid IN (SELECT uid1 FROM friend WHERE uid2=me()) ',
' AND (', null, ')', ' ORDER BY name'
];
// Conditions
var MAIL_COND, CELL_COND;
/* jshint ignore:start */
MAIL_COND = ['strpos(email, ' , "'", null, "'", ') >= 0'];
CELL_COND = ['strpos(cell, ' , "'", null, "'", ') >= 0'];
/* jshint ignore:end */
var ALL_QUERY = ['SELECT uid, name, last_name, first_name,',
' middle_name, email from user ',
' WHERE uid IN (SELECT uid1 FROM friend WHERE uid2=me()) ',
' ORDER BY name'
];
var COUNT_QUERY = 'SELECT uid FROM user WHERE uid IN ' +
'(SELECT uid1 FROM friend WHERE uid2=me())';
var SEARCH_ACCENTS_FIELDS = {
'last_name': 'familyName',
'first_name': 'givenName',
'middle_name': 'givenName'
};
var friendsList;
var viewButton, mainSection;
var currentRecommendation = null;
var allFriends = null;
// Enables cancelation
var currentNetworkRequest = null;
// State can be proposal or view All
var state;
var imgLoader;
// Only needed for testing purposes
var completedCb;
function notifyParent(message) {
parent.postMessage({
type: message.type || '',
data: message.data || ''
}, fb.CONTACTS_APP_ORIGIN);
}
// Builds the first query for finding a contact to be linked to
function buildQuery(contact) {
var filter = [];
if (contact.tel && contact.tel.length > 0) {
contact.tel.forEach(function(tel) {
CELL_COND[2] = tel.value.trim();
filter.push(CELL_COND.join(''));
filter.push(' OR ');
});
}
if (contact.email && contact.email.length > 0) {
contact.email.forEach(function(email) {
MAIL_COND[2] = email.value.trim();
filter.push(MAIL_COND.join(''));
filter.push(' OR ');
});
}
// Remove the last OR
if (filter.length > 0) {
filter[filter.length - 1] = null;
}
var filterStr = filter.join('');
var out;
if (filterStr) {
SEARCH_QUERY[3] = filterStr;
out = SEARCH_QUERY.join('');
}
else {
out = null;
}
return out;
}
// entry point for obtaining a remote proposal
link.getRemoteProposal = function(acc_tk, contid) {
var cid = contid || contactid;
var req = fb.utils.getContactData(cid);
req.onsuccess = function() {
if (req.result) {
cdata = req.result;
numQueries = 1;
currentRecommendation = null;
var query = buildQuery(cdata);
// Check whether we have enough info for the first query
// otherwise launch the getAll query
if (query === null) {
getRemoteProposalAll(acc_tk);
return;
}
doGetRemoteProposal(acc_tk, cdata, query);
}
else {
throw new Error(
'FB: Contact to be linked not found in mozContacts: ' + cid);
}
};
req.onerror = function(e) {
window.console.error('FB: Error while retrieving contact data: ', cid);
throw e;
};
};
function getRemoteProposalAll(acc_tk) {
numQueries++;
doGetRemoteProposal(acc_tk, null, ALL_QUERY.join(''), true);
}
// This function deals with the response for a proposal
// It takes care whether a response with the number of friends is present
// and updates the cache accordingly
function proposalReadyMultiple(done, error, response) {
// If there is an error we just pass it upstream
if (response.error) {
done(response);
return;
} else if (!Array.isArray(response.data)) {
error({
name: 'QueryResponseError'
});
return;
}
var friendList, totalFriends;
if (response.data.length > 0 &&
Array.isArray(response.data[0].fql_result_set)) {
friendList = response.data[0].fql_result_set;
if (response.data[1] &&
Array.isArray(response.data[1].fql_result_set)) {
totalFriends = response.data[1].fql_result_set.length;
}
}
else {
friendList = response.data;
totalFriends = friendList.length;
}
if (typeof totalFriends !== 'undefined') {
fb.utils.setCachedNumFriends(totalFriends);
}
done({
data: friendList
});
}
// Performs all the work to obtain the remote proposal
// the "isAll" parameter indicates that the query will get all friends
function doGetRemoteProposal(acc_tk, contactData, query, isAll) {
/*
Phone.lookup was analysed but we were not happy about how it worked
var searchService = 'https://api.facebook.com/method/phonebook.lookup' +
'?include_non_fb=false';
var entries = [];
entries.push(getEntry(contactData));
var sentries = JSON.stringify(entries);
*/
var theQuery = query;
var callback = proposalReadyMultiple.bind(null, fb.link.proposalReady,
fb.link.errorHandler);
// If the query is not going to provide a full list of friends
// We count all in order to refresh the total number of friends
if (!isAll) {
theQuery = JSON.stringify({
query1: query,
query2: COUNT_QUERY
});
}
state = 'proposal';
currentNetworkRequest = fb.utils.runQuery(theQuery, {
success: callback,
error: fb.link.errorHandler,
timeout: fb.link.timeoutHandler
}, acc_tk);
}
// Invoked when remoteAll is canceled
function cancelCb(shouldNotifyParent) {
if (currentNetworkRequest) {
currentNetworkRequest.cancel();
currentNetworkRequest = null;
}
Curtain.hide(shouldNotifyParent ? notifyParent.bind(
null, {type: 'abort'}) : null);
}
// Obtains a proposal with all friends
function getRemoteAll() {
Curtain.oncancel = cancelCb;
Curtain.show('wait', 'friends');
state = 'friends';
currentNetworkRequest = fb.utils.runQuery(ALL_QUERY.join(''), {
success: fb.link.friendsReady,
error: fb.link.errorHandler,
timeout: fb.link.timeoutHandler
}, access_token);
}
// Obtains a linking proposal to be shown to the user
link.getProposal = function(cid, acc_tk) {
link.getRemoteProposal(acc_tk, cid);
};
// Executed when the server response is available
link.proposalReady = function(response) {
if (typeof response.error !== 'undefined') {
window.console.error('FB: Error while retrieving link data',
response.error.code, response.error.message);
if (response.error.code != 190) {
setCurtainHandlersErrorProposal();
Curtain.show('error', 'proposal');
}
else {
// access token problem. A new OAuth 2.0 flow has to start
handleTokenError();
}
return;
}
if (response.data.length === 0 && numQueries === 1) {
getRemoteProposalAll(access_token);
}
else {
var data = response.data;
currentRecommendation = data;
var sortedByName = [];
var numFriendsProposed = data.length;
var searchAccentsArrays = {};
var index = 0;
data.forEach(function(item) {
if (!item.email) {
item.email = '';
}
var box = utils.misc.getPreferredPictureBox();
item.picwidth = box.width;
item.picheight = box.height;
// Only do this if we need to prepare the search accents phase
if (numQueries === 2) {
// Saving the original order
sortedByName.push(item);
// Populate the arrays for doing the accents related search
Object.keys(SEARCH_ACCENTS_FIELDS).forEach(function(field) {
searchAccentsArrays[field] = searchAccentsArrays[field] || [];
if (item[field]) {
// The different words for each item
var words = item[field].split(/[ ]+/);
// The whole word itself is added
words.push(item[field]);
words.forEach(function(word) {
var obj = {
originalIndex: index
};
obj[field] = Normalizer.toAscii(word).toLowerCase();
searchAccentsArrays[field].push(obj);
});
}
});
index++;
}
});
if (numQueries === 2) {
var accentsProposal = searchAccents(searchAccentsArrays, cdata);
if (accentsProposal.length === 0) {
data = sortedByName;
mainSection.classList.add('no-proposal');
numFriendsProposed = 0;
}
else {
currentRecommendation = [];
accentsProposal.forEach(function(proposalIndex) {
currentRecommendation.push(sortedByName[proposalIndex]);
});
numFriendsProposed = currentRecommendation.length;
}
} else {
viewButton.setAttribute('data-l10n-id', 'viewAll');
viewButton.onclick = UI.viewAllFriends;
}
utils.templates.append('#friends-list', currentRecommendation);
imgLoader.reload();
if (typeof completedCb === 'function') {
completedCb();
}
Curtain.hide(function onCurtainHide() {
sendReadyEvent();
window.addEventListener('message', function linkOnViewPort(e) {
if (e.origin !== fb.CONTACTS_APP_ORIGIN) {
return;
}
var data = e.data;
if (data && data.type === 'dom_transition_end') {
window.removeEventListener('message', linkOnViewPort);
utils.status.show({
id: 'linkProposal',
args: {
numFriends: numFriendsProposed
}
});
}
});
});
}
};
// This function needs to be called because link proposals through FB Query
// do not work properly with words with special characters (bug 796714)
function searchAccents(searchArrays, contactData) {
var out = [];
var searchFields = Object.keys(SEARCH_ACCENTS_FIELDS);
function compareItems(target, item) {
var out;
if (typeof target === 'string') {
out = target.localeCompare(item);
if (out !== 0) {
if (item.indexOf(target) === 0) {
out = 0;
}
}
}
return out;
} // compareItems Function
searchFields.forEach(function(searchField) {
// The array is ordered according to the search field
// this enables an efficient binary search
var searchArray = searchArrays[searchField].sort(function(a, b) {
return a[searchField].localeCompare(b[searchField]);
});
var fieldToSearch = contactData[SEARCH_ACCENTS_FIELDS[searchField]];
if (fieldToSearch && fieldToSearch[0]) {
// Splitting in the different words
var dataToSearch = fieldToSearch[0].trim().split(/[ ]+/);
dataToSearch.forEach(function(aData) {
var targetString = Normalizer.toAscii(aData).toLowerCase();
var searchResult = utils.binarySearch(targetString, searchArray, {
arrayField: searchField,
compareFunction: compareItems
});
searchResult.forEach(function(aResult) {
var candidate = searchArray[aResult].originalIndex;
// Avoiding to show two times the same result element
if (out.indexOf(candidate) === -1) {
out.push(candidate);
}
});
});
}
});
return out;
}
function sendReadyEvent() {
parent.postMessage({
type: 'ready', data: ''
}, fb.CONTACTS_APP_ORIGIN);
}
function setCurtainHandlersErrorProposal() {
Curtain.oncancel = function() {
cancelCb(true);
};
Curtain.onretry = function getRemoteProposal() {
Curtain.oncancel = function() {
cancelCb(true);
};
Curtain.show('wait', state);
link.getRemoteProposal(access_token, contactid);
};
}
link.baseHandler = function(type) {
if (state === 'friends') {
Curtain.onretry = getRemoteAll;
Curtain.oncancel = Curtain.hide;
} else if (state === 'proposal') {
setCurtainHandlersErrorProposal();
}
else if (state === 'linking') {
Curtain.onretry = function retry_link() {
Curtain.show('message', 'linking');
UI.selected({
target: {
dataset: {
uuid: friendUidToLink
}
}
});
};
Curtain.oncancel = Curtain.hide;
}
Curtain.show(type, state);
};
link.timeoutHandler = function() {
link.baseHandler('timeout');
};
link.errorHandler = function() {
link.baseHandler('error');
};
/**
* Clears the list of contacts
*
*/
function clearList() {
if (!friendsList) {
friendsList = document.querySelector('#friends-list');
}
var template = friendsList.querySelector('[data-template]');
utils.dom.removeChildNodes(friendsList);
friendsList.appendChild(template);
}
link.friendsReady = function(response) {
if (typeof response.error === 'undefined' && response.data) {
viewButton.setAttribute('data-l10n-id', 'viewRecommend');
viewButton.onclick = UI.viewRecommended;
allFriends = response;
fb.utils.setCachedNumFriends(response.data.length);
response.data.forEach(function(item) {
if (!item.email) {
item.email = '';
}
});
clearList();
var fragment = document.createDocumentFragment();
utils.templates.append(friendsList, response.data, fragment);
friendsList.appendChild(fragment);
imgLoader.reload();
Curtain.hide();
}
else {
window.console.error('FB: Error while retrieving friends',
response.error.code, response.error.message);
if (response.error.code !== 190) {
Curtain.onretry = getRemoteAll;
Curtain.oncancel = Curtain.hide;
}
else {
// Error with the access token. It is very unlikely that this happens
Curtain.onretry = handleTokenError;
}
Curtain.show('error', 'friends');
}
};
function setCurtainHandlers() {
Curtain.oncancel = function curtain_cancel() {
cancelCb(true);
};
}
link.start = function(contactId, acc_tk, endCb) {
// Only needed for testing purposes
completedCb = endCb;
access_token = acc_tk;
contactid = contactId;
setCurtainHandlers();
clearList();
imgLoader = new ImageLoader('#mainContent',
'li:not([data-uuid="#uid#"])');
if (!acc_tk) {
oauth2.getAccessToken(function proposal_new_token(new_acc_tk) {
access_token = new_acc_tk;
link.getProposal(contactId, new_acc_tk);
}, 'proposal', 'facebook');
}
else {
link.getProposal(contactId, acc_tk);
}
};
link.init = function() {
mainSection = document.querySelector('#main');
viewButton = document.querySelector('#view-all');
};
function retryOnErrorCb() {
UI.selected({
target: {
dataset: {
uuid: friendUidToLink
}
}
});
}
function handleTokenError() {
Curtain.hide(notifyParent.bind(null, {
type: 'token_error'
}));
var cb = function() {
allFriends = null;
link.start(contactid);
};
ImportStatusData.remove(fb.utils.TOKEN_DATA_KEY).then(cb);
}
UI.selected = function(event) {
Curtain.show('message', 'linking');
var element = event.target.parentNode;
friendUidToLink = element.dataset.uuid;
// First it is needed to check whether is an already imported friend
var req = fb.contacts.get(friendUidToLink);
req.onsuccess = function() {
if (req.result) {
window.setTimeout(function delay() {
Curtain.hide(notifyParent.bind(null, {
type: 'item_selected',
data: {
uid: friendUidToLink
}
}));
}, 1000);
}
else {
state = 'linking';
var callbacks = { };
callbacks.success = function(data) {
Curtain.hide(notifyParent.bind(null, {
type: 'item_selected',
data: data
}));
};
callbacks.error = function(e) {
var error = e.target.error;
window.console.error('FB: Error while importing friend data ',
JSON.stringify(error));
Curtain.oncancel = Curtain.hide;
if (error.code != 190) {
Curtain.onretry = retryOnErrorCb;
}
else {
Curtain.onretry = handleTokenError;
}
Curtain.show('error', 'linking');
};
callbacks.timeout = function() {
link.baseHandler('timeout');
};
imgLoader.unload(); // Removing listeners
FacebookConnector.importContact(friendUidToLink, access_token,
callbacks, 'not_match');
}
};
req.onerror = function() {
window.console.error('FB: Error while importing friend data');
Curtain.oncancel = Curtain.hide;
Curtain.onretry = retryOnErrorCb;
Curtain.show('error', 'linking');
};
};
UI.end = function(event) {
var msg = {
type: 'window_close',
data: ''
};
parent.postMessage(msg, fb.CONTACTS_APP_ORIGIN);
};
UI.viewAllFriends = function(event) {
if (!allFriends) {
getRemoteAll();
}
else {
link.friendsReady(allFriends);
}
return false;
};
UI.viewRecommended = function(event) {
// event.target === viewButton
event.target.onclick = UI.viewAllFriends;
event.target.setAttribute('data-l10n-id', 'viewAll');
clearList();
utils.templates.append(friendsList, currentRecommendation);
imgLoader.reload();
return false;
};
})(document);
}