blob: 6be8d8ce90532f1c71e67d16206799c317e3378a [file] [log] [blame]
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
// Cordova contact definition:
// http://cordova.apache.org/docs/en/2.5.0/cordova_contacts_contacts.md.html#Contact
// FxOS contact definition:
// https://developer.mozilla.org/en-US/docs/Web/API/mozContact
var Contact = require('./Contact');
var ContactField = require('./ContactField');
var ContactAddress = require('./ContactAddress');
var ContactName = require('./ContactName');
// XXX: a hack to check if id is "empty". Cordova inserts a
// string "this string is supposed to be a unique identifier that will
// never show up on a device" if id is empty
function _hasId(id) {
if (!id || id.indexOf(' ') >= 0) {
return false;
}
return true;
}
// Extend mozContact prototype to provide update from Cordova
function updateFromCordova(contact, fromContact) {
function exportContactFieldArray(contactFieldArray, key) {
if (!key) {
key = 'value';
}
var arr = [];
for (var i=0; i < contactFieldArray.length; i++) {
arr.push(contactFieldArray[i][key]);
};
return arr;
}
function exportAddress(addresses) {
// TODO: check moz address format
var arr = [];
for (var i=0; i < addresses.length; i++) {
var addr = {};
for (var key in addresses[i]) {
if (key == 'formatted' || key == 'id') {
continue;
} else if (key == 'type') {
addr[key] = [addresses[i][key]];
} else if (key == 'country') {
addr['countryName'] = addresses[i][key];
} else {
addr[key] = addresses[i][key];
}
}
arr.push(addr);
}
return arr;
}
function exportContactField(data) {
var contactFields = [];
for (var i=0; i < data.length; i++) {
var item = data[i];
if (item.value) {
var itemData = {value: item.value};
if (item.type) {
itemData.type = [item.type];
}
if (item.pref) {
itemData.pref = item.pref;
}
contactFields.push(itemData);
}
}
return contactFields;
}
// adding simple fields [contactField, eventualMozContactField]
var nameFields = [['givenName'], ['familyName'],
['honorificPrefix'], ['honorificSuffix'],
['middleName', 'additionalName']];
var baseArrayFields = [['displayName', 'name'], ['nickname']];
var baseStringFields = [];
var j = 0; while(field = nameFields[j++]) {
if (fromContact.name[field[0]]) {
contact[field[1] || field[0]] = fromContact.name[field[0]].split(' ');
}
}
j = 0; while(field = baseArrayFields[j++]) {
if (fromContact[field[0]]) {
contact[field[1] || field[0]] = fromContact[field[0]].split(' ');
}
}
j = 0; while(field = baseStringFields[j++]) {
if (fromContact[field[0]]) {
contact[field[1] || field[0]] = fromContact[field[0]];
}
}
if (fromContact.birthday) {
contact.bday = new Date(fromContact.birthday);
}
if (fromContact.emails) {
var emails = exportContactField(fromContact.emails)
contact.email = emails;
}
if (fromContact.categories) {
contact.category = exportContactFieldArray(fromContact.categories);
}
if (fromContact.addresses) {
contact.adr = exportAddress(fromContact.addresses);
}
if (fromContact.phoneNumbers) {
contact.tel = exportContactField(fromContact.phoneNumbers);
}
if (fromContact.organizations) {
// XXX: organizations are saved in 2 arrays - org and jobTitle
// depending on the usecase it might generate issues
// where wrong title will be added to an organization
contact.org = exportContactFieldArray(fromContact.organizations, 'name');
contact.jobTitle = exportContactFieldArray(fromContact.organizations, 'title');
}
if (fromContact.note) {
contact.note = [fromContact.note];
}
}
// Extend Cordova Contact prototype to provide update from FFOS contact
Contact.prototype.updateFromMozilla = function(moz) {
function exportContactField(data) {
var contactFields = [];
for (var i=0; i < data.length; i++) {
var item = data[i];
var itemData = new ContactField(item.type, item.value, item.pref);
contactFields.push(itemData);
}
return contactFields;
}
function makeContactFieldFromArray(data) {
var contactFields = [];
for (var i=0; i < data.length; i++) {
var itemData = new ContactField(null, data[i]);
contactFields.push(itemData);
}
return contactFields;
}
function exportAddresses(addresses) {
// TODO: check moz address format
var arr = [];
for (var i=0; i < addresses.length; i++) {
var addr = {};
for (var key in addresses[i]) {
if (key == 'countryName') {
addr['country'] = addresses[i][key];
} else if (key == 'type') {
addr[key] = addresses[i][key].join(' ');
} else {
addr[key] = addresses[i][key];
}
}
arr.push(addr);
}
return arr;
}
function createOrganizations(orgs, jobs) {
orgs = (orgs) ? orgs : [];
jobs = (jobs) ? jobs : [];
var max_length = Math.max(orgs.length, jobs.length);
var organizations = [];
for (var i=0; i < max_length; i++) {
organizations.push(new ContactOrganization(
null, null, orgs[i] || null, null, jobs[i] || null));
}
return organizations;
}
function createFormatted(name) {
var fields = ['honorificPrefix', 'givenName', 'middleName',
'familyName', 'honorificSuffix'];
var f = '';
for (var i = 0; i < fields.length; i++) {
if (name[fields[i]]) {
if (f) {
f += ' ';
}
f += name[fields[i]];
}
}
return f;
}
if (moz.id) {
this.id = moz.id;
}
var nameFields = [['givenName'], ['familyName'],
['honorificPrefix'], ['honorificSuffix'],
['additionalName', 'middleName']];
var baseArrayFields = [['name', 'displayName'], 'nickname', ['note']];
var baseStringFields = [];
var name = new ContactName();
var j = 0; while(field = nameFields[j++]) {
if (moz[field[0]]) {
name[field[1] || field[0]] = moz[field[0]].join(' ');
}
}
this.name = name;
j = 0; while(field = baseArrayFields[j++]) {
if (moz[field[0]]) {
this[field[1] || field[0]] = moz[field[0]].join(' ');
}
}
j = 0; while(field = baseStringFields[j++]) {
if (moz[field[0]]) {
this[field[1] || field[0]] = moz[field[0]];
}
}
// emails
if (moz.email) {
this.emails = exportContactField(moz.email);
}
// categories
if (moz.category) {
this.categories = makeContactFieldFromArray(moz.category);
}
// addresses
if (moz.adr) {
this.addresses = exportAddresses(moz.adr);
}
// phoneNumbers
if (moz.tel) {
this.phoneNumbers = exportContactField(moz.tel);
}
// birthday
if (moz.bday) {
this.birthday = Date.parse(moz.bday);
}
// organizations
if (moz.org || moz.jobTitle) {
// XXX: organizations array is created from org and jobTitle
this.organizations = createOrganizations(moz.org, moz.jobTitle);
}
// construct a read-only formatted value
this.name.formatted = createFormatted(this.name);
/* Find out how to translate these parameters
// photo: Blob
// url: Array with metadata (?)
// impp: exportIM(contact.ims), TODO: find the moz impp definition
// anniversary
// sex
// genderIdentity
// key
*/
}
function createMozillaFromCordova(successCB, errorCB, contact) {
var moz;
// get contact if exists
if (_hasId(contact.id)) {
var search = navigator.mozContacts.find({
filterBy: ['id'], filterValue: contact.id, filterOp: 'equals'});
search.onsuccess = function() {
moz = search.result[0];
updateFromCordova(moz, contact);
successCB(moz);
};
search.onerror = errorCB;
return;
}
// create empty contact
moz = new mozContact();
// if ('init' in moz) {
// 1.2 and below compatibility
// moz.init();
// }
updateFromCordova(moz, contact);
successCB(moz);
}
function createCordovaFromMozilla(moz) {
var contact = new Contact();
contact.updateFromMozilla(moz);
return contact;
}
// However API requires the ability to save multiple contacts, it is
// used to save only one element array
function saveContacts(successCB, errorCB, contacts) {
// a closure which is holding the right moz contact
function makeSaveSuccessCB(moz) {
return function(result) {
// create contact from FXOS contact (might be different than
// the original one due to differences in API)
var contact = createCordovaFromMozilla(moz);
// call callback
successCB(contact);
}
}
var i=0;
var contact;
while(contact = contacts[i++]){
var moz = createMozillaFromCordova(function(moz) {
var request = navigator.mozContacts.save(moz);
// success and/or fail will be called every time a contact is saved
request.onsuccess = makeSaveSuccessCB(moz);
request.onerror = errorCB;
}, function() {}, contact);
}
}
// API provides a list of ids to be removed
function remove(successCB, errorCB, ids) {
var i=0;
var id;
for (var i=0; i < ids.length; i++){
// throw an error if no id provided
if (!_hasId(ids[i])) {
console.error('FFOS: Attempt to remove unsaved contact');
errorCB(0);
return;
}
// check if provided id actually exists
var search = navigator.mozContacts.find({
filterBy: ['id'], filterValue: ids[i], filterOp: 'equals'});
search.onsuccess = function() {
if (search.result.length === 0) {
console.error('FFOS: Attempt to remove a non existing contact');
errorCB(0);
return;
}
var moz = search.result[0];
var request = navigator.mozContacts.remove(moz);
request.onsuccess = successCB;
request.onerror = errorCB;
};
search.onerror = errorCB;
}
}
var mozContactSearchFields = [['name', 'displayName'], ['givenName'],
['familyName'], ['email'], ['tel'], ['jobTitle'], ['note'],
['tel', 'phoneNumbers'], ['email', 'emails']];
// Searching by nickname and additionalName is forbidden in 1.3 and below
// Searching by name is forbidden in 1.2 and below
// finds if a key is allowed and returns FFOS name if different
function getMozSearchField(key) {
if (mozContactSearchFields.indexOf([key]) >= 0) {
return key;
}
for (var i=0; i < mozContactSearchFields.length; i++) {
if (mozContactSearchFields[i].length > 1) {
if (mozContactSearchFields[i][1] === key) {
return mozContactSearchFields[i][0];
}
}
}
return false;
}
function _getAll(successCB, errorCB, params) {
// [contactField, eventualMozContactField]
var getall = navigator.mozContacts.getAll({});
var contacts = [];
getall.onsuccess = function() {
if (getall.result) {
contacts.push(createCordovaFromMozilla(getall.result));
getall.continue();
} else {
successCB(contacts);
}
};
getall.onerror = errorCB;
}
function search(successCB, errorCB, params) {
var options = params[1] || {};
if (!options.filter) {
return _getAll(successCB, errorCB, params);
}
var filterBy = [];
// filter and translate fields
for (var i=0; i < params[0].length; i++) {
var searchField = params[0][i];
var mozField = getMozSearchField(searchField);
if (searchField === 'name') {
// Cordova uses name for search by all name fields.
filterBy.push('givenName');
filterBy.push('familyName');
continue;
}
if (searchField === 'displayName' && 'init' in new mozContact()) {
// ``init`` in ``mozContact`` indicates FFOS version 1.2 or below
// Searching by name (in moz) is then forbidden
console.log('FFOS ContactProxy: Unable to search by displayName on FFOS 1.2');
continue;
}
if (mozField) {
filterBy.push(mozField);
} else {
console.log('FXOS ContactProxy: inallowed field passed to search filtered out: ' + searchField);
}
}
var mozOptions = {filterBy: filterBy, filterOp: 'startsWith'};
if (!options.multiple) {
mozOptions.filterLimit = 1;
}
mozOptions.filterValue = options.filter;
var request = navigator.mozContacts.find(mozOptions);
request.onsuccess = function() {
var contacts = [];
var mozContacts = request.result;
var moz = mozContacts[0];
for (var i=0; i < mozContacts.length; i++) {
contacts.push(createCordovaFromMozilla(mozContacts[i]));
}
successCB(contacts);
};
request.onerror = errorCB;
}
module.exports = {
save: saveContacts,
remove: remove,
search: search
};
require("cordova/exec/proxy").add("Contacts", module.exports);