/*jshint forin:true, noarg:true, noempty:true, eqeqeq:true, bitwise:true, strict:true, undef:true, curly:true, browser:true, devel:true, indent:2, maxerr:50, moz:true, newcap:false, moz:true */ /* global mozContact: false, ContactTelField: false, parseLDIF: false */ (function () { "use strict"; // Shim for the intersection of two Arrays. if (!Array.prototype.intersection) { Array.prototype.intersection = function (anotherArr) { return this.filter(function(n) { if (anotherArr.indexOf(n) === -1) { return false; } return true; }); }; } const phoneFields = [ "mobile", "facsimiletelephonenumber", "homePhone", "telephoneNumber" ]; const emailFields = [ "mail" ]; function ValueError(message) { this.name = "ValueError"; this.message = message || "Unknown Value"; } ValueError.prototype = new Error(); ValueError.prototype.constructor = ValueError; /** * Translate objects created from LDIF files to Contacts * * @param Array of LDIF objects * @return Contact with all fields filled * @throws ValueError for unknown field in the LDIF object * * ContactsAPI: * @see https://wiki.mozilla.org/WebAPI/ContactsAPI * @spec http://www.w3.org/TR/contacts-manager-api/ * * Thunderbid LDAP Schema: * @see https://developer.mozilla.org/docs/Thunderbird/LDAP_Support * @see https://tools.ietf.org/html/rfc4519 */ function translateObjectToContact(inRec) { var contact = {}, year, month, day, curRec = null; /** * Find the proper record (or create new one) in the multi-value * attribute * * @param idx name of the attribute to which the record belongs * @param subClass Function constructor of the record if there is * none * @param subType String with the type of the record * @return record of the proper class * * Uses global variable contact. */ function findSubElement(idx, subType) { var cAddr = null; function createNewContact(subType) { var cont = {}; if (subType) { cont.type = [subType]; } return cont; } // No contact.adr at all if (!contact.hasOwnProperty(idx)) { cAddr = createNewContact(subType); contact[idx] = cAddr; } // Single-element property else if (!Array.isArray(contact[idx])) { if (contact[idx].type.indexOf(subType) === -1) { contact[idx] = [contact[idx]]; cAddr = createNewContact(subType); contact[idx].push(cAddr); } else { cAddr = contact[idx]; } } // Array else { cAddr = contact[idx].filter(function (addr) { return addr.type.indexOf(subType) !== -1; }); if (cAddr.length === 0) { cAddr = createNewContact(subType); contact[idx].push(cAddr); } else { cAddr = cAddr[0]; } } return cAddr; } /** * Manages squeezing two-line address into one value field * * @param type String ['home', 'work'] * @param target String ContactsAddress attribute to be set (e.g., * streetAddress) * @param first String first line index in inRec * @param second String second line index in inRec * @return None * sets variable inRec local to outer function * deletes both fields from inRec so as to avoid duplication */ function secondLineInAddress(type, target, first, second) { var curRec = findSubElement("adr", type); if (second in inRec) { curRec[target] = inRec[first] + "\n" + inRec[second]; } else if (first in inRec) { curRec[target] = inRec[first]; } if (first in inRec) { delete inRec[first]; } if (second in inRec) { delete inRec[second]; } } for (var key in inRec) { if (["birthyear", "birthmonth", "birthday"].indexOf(key) !== -1) { // We have alternatively either whole date in birthyear field, // e.g. 19940221, or we have all three properties set. year = inRec.birthyear || "1970"; // lowest year in Unix time month = inRec.birthmonth || null; day = inRec.birthday || null; if (year.length === 8) { contact.bday = new Date(parseInt(year.slice(0,4), 10), parseInt(year.slice(4,6), 10) - 1, parseInt(year.slice(6,8), 10)); if (inRec.birthday) { delete inRec.birthday; } if (inRec.birthmonth) { delete inRec.birthmonth; } } else if (month && day) { contact.bday = new Date(parseInt(year, 10), parseInt(month, 10) - 1, parseInt(day, 10)); delete inRec.birthday; delete inRec.birthmonth; delete inRec.birthyear; } else { throw new ValueError("Wrong value of birthday!"); } } else if (key === "c") { curRec = findSubElement("adr", "work"); curRec.countryName = inRec[key]; } else if (key === "cn") { contact.name = [inRec[key]]; } else if (key === "description") { contact.note = [inRec[key]]; } else if (key === "facsimiletelephonenumber") { curRec = findSubElement("tel", "fax"); curRec.value = inRec[key]; } else if (key === "givenName") { contact.givenName = [inRec[key]]; } else if (key === "homePhone") { curRec = findSubElement("tel", "home"); curRec.value = inRec[key]; } else if (key === "l") { curRec = findSubElement("adr", "work"); curRec.locality = inRec[key]; } else if (key === "mail") { curRec = findSubElement("email", "PREF"); curRec.value = inRec[key]; } else if (key === "mozillaSecondEmail") { curRec = findSubElement("email"); curRec.value = inRec[key]; } else if (key === "mobile") { curRec = findSubElement("tel", "mobile"); curRec.value = inRec[key]; } else if (key === "mozillaHomeCountryName") { curRec = findSubElement("adr", "home"); curRec.countryName = inRec[key]; } else if (key === "mozillaHomeLocalityName") { curRec = findSubElement("adr", "home"); curRec.locality = inRec[key]; } else if (key === "mozillaHomePostalCode") { curRec = findSubElement("adr", "home"); curRec.postalCode = inRec[key]; } else if (key === "mozillaHomeState") { curRec = findSubElement("adr", "home"); curRec.region = inRec[key]; } else if (["mozillaHomeStreet", "mozillaHomeStreet2"]. indexOf(key) !== -1) { secondLineInAddress("home", "streetAddress", "mozillaHomeStreet", "mozillaHomeStreet2"); } else if (key === "mozillaWorkUrl") { curRec = findSubElement("url", "work"); curRec.value = inRec[key]; } else if (key === "mozillaHomeUrl") { curRec = findSubElement("url", "home"); curRec.value = inRec[key]; } else if (key === "mozillaNickname") { contact.nickname = [inRec[key]]; } // Per W3C Contacts API // http://www.w3.org/TR/contacts-manager-api/#widl-ContactProperties-org // org of type array of DOMString // A string or set thereof representing the organization(s) // the contact belongs to. It maps to vCard's ORG attribute // // Per RFC 6350: // ============= // 6.6.4. ORG // // Purpose: To specify the organizational name and units associated // with the vCard. // // Value type: A single structured text value consisting of components // separated by the SEMICOLON character (U+003B). // // Special notes: The property is based on the X.520 Organization Name // and Organization Unit attributes [CCITT.X520.1988]. The property // value is a structured type consisting of the organization name, // followed by zero or more levels of organizational unit names. // // Example: A property value consisting of an organizational name, // organizational unit #1 name, and organizational unit #2 name. // // ORG:ABC\, Inc.;North American Division;Marketing else if (["o", "ou"].indexOf(key) !== -1) { if ('ou' in inRec) { if ('o' in inRec) { contact.org = inRec.o + ";" + inRec.ou; } else { throw new ValueError( "Organizational unit without an organization!"); } delete inRec.ou; } else { contact.org = inRec.o; } delete inRec.o; } else if (key === "postalCode") { curRec = findSubElement("adr", "work"); curRec.postalCode = inRec[key]; } else if (key === "sn") { contact.familyName = [inRec[key]]; } else if (key === "st") { curRec = findSubElement("adr", "work"); curRec.region = inRec[key]; } else if (["street", "mozillaWorkStreet2"]. indexOf(key) !== -1) { secondLineInAddress("work", "streetAddress", "street", "mozillaWorkStreet2"); } else if (key === "telephoneNumber") { curRec = findSubElement("tel", "work"); curRec.value = inRec[key]; } else if (key === "title") { contact.jobTitle = [inRec[key]]; } // Unknown attribute else { throw new ValueError("Unknown attribute " + key + " with value:\n" + inRec[key]); } } // Per RFC 4519 section 3.12 cn and sn attributes are always // required if (! contact.hasOwnProperty('name')) { if (contact.hasOwnProperty('familyName') && contact.hasOwnProperty('givenName')) { contact.name = contact.givenName + " " + contact.familyName; } else if (contact.hasOwnProperty("org")) { contact.name = contact.org; } else if (contact.hasOwnProperty("jobTitle")) { contact.name = contact.jobTitle; } } if (Object.keys(contact).length > 0) { var outObj = new mozContact(); outObj.init(contact); return contact; } return null; } /** * Not used presently and not debugged. */ function whenContactAlreadyPresent(rec, cb_not_found, cb_found) { var rec_keys = Object.keys(rec); // Finding whether the contact has email or telephone var email_fields = rec_keys.intersection(emailFields); var phone_fields = rec_keys.intersection(phoneFields); var search_keys = []; if (email_fields.length > 0) { search_keys = email_fields; } else if (phone_fields.length > 0) { search_keys = phone_fields; } if (search_keys.length > 0) { var search_opts = { filterValue : rec(search_keys[0]), filterBy : search_keys, filterOp : "contains", filterLimit : 1 }; var search = navigator.mozContacts.find(search_opts); // Possible duplicate found ... bail out, rather do nothing! search.onsuccess = function() { // search.result is found record (Array of length 1) cb_found(rec, search.result); }; // No duplicates, go ahead and create new record search.onerror = function() { cb_not_found(rec); }; } } function restoreURLForm(url) { document.getElementById("progress-div").style.display = "none"; document.getElementById("URL-form").style.display = "block"; if (url !== undefined) { document.getElementsByName("URL")[0].value = url; } } function insertData(ldifText) { var progressEl = document.querySelector("#progress-div progress"); var records = parseLDIF(ldifText.split("\n")); console.log("records.length = " + records.length); if (records.length > 0) { if (!window.confirm("THIS WILL ERASE ALL CONTACTS ON YOUR PHONE!\n" + "Are you cool with that?")) { return false; } } var cl_req = navigator.mozContacts.clear(); cl_req.onerror = function() { throw new Error("Cannot clear whole Contacts database?"); }; cl_req.onsuccess = function() { progressEl.max = records.length; progressEl.value = 0; records.forEach(function (rec) { var add_contact = translateObjectToContact(rec); var sav_req = navigator.mozContacts.save(add_contact); sav_req.onsuccess = function() { progressEl.value += 1; if (progressEl.value >= records.length) { window.alert("All contacts have been imported!"); restoreURLForm(); } }; sav_req.onerror = function() { console.error("Cannot save record " + add_contact.id + "\n" + rec.toSource()); }; }); }; } function submitHandler (evt) { var URL = document.getElementsByName("URL")[0].value; var progressForm = document.getElementById("progress-div"); document.getElementById("URL-form").style.display = "none"; progressForm.style.display = "block"; var req = new XMLHttpRequest(); req.open("GET", URL, true); var progressEl = progressForm.getElementsByTagName("progress")[0]; req.onprogress = function(evt) { if (evt.lengthComputable) { progressEl.max = evt.total; progressEl.value = evt.loaded; } }; req.onload = function() { var inText = req.responseText; if (inText.length > 0) { insertData(inText); if (localStorage) { localStorage.setItem("lastURL", URL); } } }; req.onerror = function() { window.alert("Cannot load " + URL + "!"); restoreURLForm(URL); }; req.send(); evt.stopPropagation(); evt.preventDefault(); } window.onload = function() { if (localStorage && localStorage.lastURL) { var oldURL = localStorage.getItem('lastURL'); document.getElementsByName("URL")[0].value = oldURL; } document.body.addEventListener("submit", submitHandler, false); }; }());