| /* |
| * |
| * 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. |
| * |
| */ |
| |
| var ContactField = require('./ContactField'), |
| ContactAddress = require('./ContactAddress'), |
| ContactOrganization = require('./ContactOrganization'), |
| ContactName = require('./ContactName'), |
| ContactError = require('./ContactError'), |
| Contact = require('./Contact'); |
| |
| |
| function convertToContact(windowsContact) { |
| var contact = new Contact(); |
| |
| // displayName & nickname |
| contact.displayName = windowsContact.displayName || windowsContact.name; |
| contact.nickname = windowsContact.name; |
| contact.id = windowsContact.id; |
| |
| // name |
| // Additional fields like lastName, middleName etc. available on windows8.1/wp8.1 only |
| contact.name = new ContactName( |
| windowsContact.displayName || windowsContact.name, |
| windowsContact.lastName, |
| windowsContact.firstName || windowsContact.name, |
| windowsContact.middleName, |
| windowsContact.honorificNamePrefix || windowsContact.honorificPrefix, |
| windowsContact.honorificNameSuffix || windowsContact.honorificSuffix); |
| |
| // phoneNumbers |
| contact.phoneNumbers = []; |
| var phoneSource = windowsContact.phoneNumbers || windowsContact.phones; |
| for (var i = 0; i < phoneSource.size; i++) { |
| var rawPhone = phoneSource[i]; |
| var phone = new ContactField(rawPhone.category || rawPhone.kind, rawPhone.value || rawPhone.number); |
| contact.phoneNumbers.push(phone); |
| } |
| |
| // emails |
| contact.emails = []; |
| var emailSource = windowsContact.emails; |
| for (var i = 0; i < emailSource.size; i++) { |
| var rawEmail = emailSource[i]; |
| var email = new ContactField(rawEmail.category || rawEmail.kind, rawEmail.value || rawEmail.address); |
| contact.emails.push(email); |
| } |
| |
| // addressres |
| contact.addresses = []; |
| var addressSource = windowsContact.locations || windowsContact.addresses; |
| for (var i = 0; i < addressSource.size; i++) { |
| var rawAddress = addressSource[i]; |
| var address = new ContactAddress( |
| null, |
| rawAddress.category || rawAddress.kind, |
| rawAddress.unstructuredAddress, |
| rawAddress.street || rawAddress.streetAddress, |
| rawAddress.city || rawAddress.locality, |
| rawAddress.region, |
| rawAddress.postalCode, |
| rawAddress.country); |
| contact.addresses.push(address); |
| } |
| |
| // ims |
| contact.ims = []; |
| var imSource = windowsContact.instantMessages || windowsContact.connectedServiceAccounts; |
| for (var i = 0; i < imSource.size; i++) { |
| var rawIm = imSource[i]; |
| var im = new ContactField(rawIm.category || rawIm.serviceName, rawIm.userName || rawIm.id); |
| contact.ims.push(im); |
| } |
| |
| // jobInfo field available on Windows 8.1/WP8.1 only |
| var jobInfo = windowsContact.jobInfo; |
| if (jobInfo) { |
| contact.organizations = []; |
| for (var j = 0; j < jobInfo.size; j++) { |
| var rawJob = jobInfo[i]; |
| contact.organizations.push(new ContactOrganization(false, null, |
| rawJob.companyName, rawJob.department, rawJob.title)); |
| } |
| } |
| |
| // note field available on Windows 8.1/WP8.1 only |
| var contactNotes = windowsContact.notes; |
| if (contactNotes) { |
| contact.note = contactNotes; |
| } |
| |
| // returned is a file, a blob url can be made |
| var contactPhoto = windowsContact.thumbnail; |
| if (contactPhoto && contactPhoto.path) { |
| contact.photos = [new ContactField('url', URL.createObjectURL(contactPhoto) , false)]; |
| } |
| |
| return contact; |
| } |
| |
| // Win API Contacts namespace |
| var contactsNS = Windows.ApplicationModel.Contacts; |
| |
| function cdvContactToWindowsContact(contact) { |
| var result = new contactsNS.Contact(); |
| |
| // name first |
| if (contact.name) { |
| result.displayNameOverride = contact.name.formatted; |
| result.firstName = contact.name.givenName; |
| result.middleName = contact.name.middleName; |
| result.lastName = contact.name.familyName; |
| result.honorificNamePrefix = contact.name.honorificPrefix; |
| result.honorificNameSuffix = contact.name.honorificSuffix; |
| } |
| |
| result.nickname = contact.nickname; |
| |
| // phone numbers |
| if (contact.phoneNumbers) { |
| contact.phoneNumbers.forEach(function(contactPhone) { |
| var resultPhone = new contactsNS.ContactPhone(); |
| resultPhone.description = contactPhone.type; |
| resultPhone.number = contactPhone.value; |
| result.phones.push(resultPhone); |
| }); |
| } |
| |
| // emails |
| if (contact.emails) { |
| contact.emails.forEach(function(contactEmail) { |
| var resultEmail = new contactsNS.ContactEmail(); |
| resultEmail.description = contactEmail.type; |
| resultEmail.address = contactEmail.value; |
| result.emails.push(resultEmail); |
| }); |
| } |
| |
| // Addresses |
| if (contact.addresses) { |
| contact.addresses.forEach(function(contactAddress) { |
| var address = new contactsNS.ContactAddress(); |
| address.description = contactAddress.type; |
| address.streetAddress = contactAddress.streetAddress; |
| address.locality = contactAddress.locality; |
| address.region = contactAddress.region; |
| address.postalCode = contactAddress.postalCode; |
| address.country = contactAddress.country; |
| result.addresses.push(address); |
| }); |
| } |
| |
| // IMs |
| if (contact.ims) { |
| contact.ims.forEach(function(contactIM) { |
| var acct = new contactsNS.ContactConnectedServiceAccount(); |
| acct.serviceName = contactIM.type; |
| acct.id = contactIM.value; |
| result.connectedServiceAccounts.push(acct); |
| }); |
| } |
| |
| // JobInfo |
| if (contact.organizations) { |
| contact.organizations.forEach(function(contactOrg) { |
| var job = new contactsNS.ContactJobInfo(); |
| job.companyName = contactOrg.name; |
| job.department = contactOrg.department; |
| job.description = contactOrg.type; |
| job.title = contactOrg.title; |
| result.jobInfo.push(job); |
| }); |
| } |
| |
| result.notes = contact.note; |
| |
| if (contact.photos) { |
| var eligiblePhotos = contact.photos.filter(function(photo) { |
| return typeof photo.value !== 'undefined'; |
| }); |
| if (eligiblePhotos.length > 0) { |
| var supportedPhoto = eligiblePhotos[0]; |
| var path = supportedPhoto.value; |
| |
| try { |
| var streamRef; |
| if (/^([a-z][a-z0-9+\-.]*):\/\//i.test(path)) { |
| streamRef = Windows.Storage.Streams.RandomAccessStreamReference.createFromUri(new Windows.Foundation.Uri(path)); |
| } else { |
| streamRef = Windows.Storage.Streams.RandomAccessStreamReference.createFromFile(path); |
| } |
| result.thumbnail = streamRef; |
| } |
| catch (e) { |
| // incompatible reference to the photo |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| module.exports = { |
| |
| pickContact: function (win, fail, args) { |
| |
| // ContactPicker class works differently on Windows8/8.1 and Windows Phone 8.1 |
| // so we need to detect when we are running on phone |
| var runningOnPhone = navigator.userAgent.indexOf('Windows Phone') !== -1; |
| |
| var picker = new contactsNS.ContactPicker(); |
| if (runningOnPhone) { |
| // TODO: Windows Phone 8.1 requires this specification. This should be noted in quirks |
| // See http://msdn.microsoft.com/en-us/library/windows/apps/windows.applicationmodel.contacts.contactpicker.desiredfieldswithcontactfieldtype.aspx for details |
| // Multiple ContactFieldType items, appended to array causes `Request not suported` error. |
| picker.desiredFieldsWithContactFieldType.append(Windows.ApplicationModel.Contacts.ContactFieldType.phoneNumber); |
| } |
| |
| // pickContactAsync is available on Windows 8.1 or later, instead of |
| // pickSingleContactAsync, which is deprecated in Windows 8.1, |
| // so try to use newer method, if available. |
| // see http://msdn.microsoft.com/en-us/library/windows/apps/windows.applicationmodel.contacts.contactpicker.picksinglecontactasync.aspx |
| |
| var pickRequest = picker.pickContactAsync ? picker.pickContactAsync() : picker.pickSingleContactAsync(); |
| pickRequest.done(function (contact) { |
| // if contact was not picked |
| if (!contact) { |
| fail && fail(new Error("User did not pick a contact.")); |
| return; |
| } |
| // If we are on desktop, just send em back |
| if (!runningOnPhone) { |
| win(convertToContact(contact)); |
| return; |
| } |
| // On WP8.1 fields set in resulted Contact object depends on desiredFieldsWithContactFieldType property of contact picker |
| // so we retrieve full contact by its' Id |
| contactsNS.ContactManager.requestStoreAsync().done(function (contactStore) { |
| contactStore.getContactAsync(contact.id).done(function(con) { |
| win(convertToContact(con)); |
| }, function() { |
| fail(new ContactError(ContactError.UNKNOWN_ERROR)); |
| }); |
| }, function () { |
| fail(new ContactError(ContactError.UNKNOWN_ERROR)); |
| }); |
| }); |
| }, |
| |
| save: function (win, fail, args) { |
| if (typeof contactsNS.ContactList === 'undefined') { |
| // Not supported yet since WinJS API do not provide methods to manage contactStore |
| // On Windows Phone 8.1 this can be implemented using native class library |
| // See Windows.Phone.PersonalInformation namespace |
| // http://msdn.microsoft.com/en-us/library/windows/apps/xaml/windows.phone.personalinformation.aspx |
| |
| //We don't need to create Error object here since it will be created at navigator.contacts.find() method |
| fail && fail(ContactError.NOT_SUPPORTED_ERROR); |
| return; |
| } |
| |
| var winContact = cdvContactToWindowsContact(args[0]); |
| |
| contactsNS.ContactManager.requestStoreAsync(contactsNS.ContactStoreAccessType.appContactsReadWrite).then(function(store) { |
| return store.findContactListsAsync().then(function(lists) { |
| if (lists.length > 0) { |
| return lists[0]; |
| } else { |
| return store.createContactListAsync(''); |
| } |
| }, function(error) { |
| return store.createContactListAsync(''); |
| }); |
| }).then(function(list) { |
| return list.saveContactAsync(winContact); |
| }).done(function(result) { |
| win(convertToContact(winContact)); |
| }, function(error) { |
| fail(error); |
| }); |
| }, |
| |
| remove: function(win, fail, args) { |
| if (typeof contactsNS.ContactList === 'undefined') { |
| // Not supported yet since WinJS API do not provide methods to manage contactStore |
| // On Windows Phone 8.1 this can be implemented using native class library |
| // See Windows.Phone.PersonalInformation namespace |
| // http://msdn.microsoft.com/en-us/library/windows/apps/xaml/windows.phone.personalinformation.aspx |
| |
| //We don't need to create Error object here since it will be created at navigator.contacts.find() method |
| fail && fail(ContactError.NOT_SUPPORTED_ERROR); |
| return; |
| } |
| |
| // This is a complicated scenario because in Win10, there is a notion of 'app contacts' vs 'global contacts'. |
| // search() returns all global contacts, which are "aggregate contacts", so the IDs of contacts that Cordova |
| // creates never match the IDs of the contacts returned from search(). |
| // In order to work around this, we need to: |
| // - Get two Stores: one that is read-write to the app-contacts list, one which is read-only for global contacts |
| // - Read the app-local store to see if a contact with the passed-in ID matches |
| // - Grab the global aggregate contact manager, then ask it for raw contacts (app-local ACM returns access denied) |
| // - Find my app-list of contacts |
| // - Enumerate the raw contacts and see if there is a raw contact whose parent list matches the app-list |
| // - If so, remove the raw contact from the app-list |
| // - If any of this fails, the operation fails |
| WinJS.Promise.join([contactsNS.ContactManager.requestStoreAsync(contactsNS.ContactStoreAccessType.appContactsReadWrite), |
| contactsNS.ContactManager.requestStoreAsync(contactsNS.ContactStoreAccessType.allContactsReadOnly)]).then(function(stores) { |
| var readOnlyStore = stores[1]; |
| var writableStore = stores[0]; |
| |
| var storeReader = writableStore.getContactReader(); |
| return storeReader.readBatchAsync().then(function(batch) { |
| if (batch.status !== contactsNS.ContactBatchStatus.success) { |
| // Couldn't read contacts store |
| throw new ContactError(ContactError.IO_ERROR); |
| } |
| |
| var candidates = batch.contacts.filter(function(testContact) { |
| return testContact.id === args[0]; |
| }); |
| |
| if (candidates.length === 0) { |
| // No matching contact from aggregate store |
| throw new ContactError(ContactError.IO_ERROR); |
| } |
| |
| return candidates[0]; |
| }).then(function(contactToDelete) { |
| return readOnlyStore.aggregateContactManager.findRawContactsAsync(contactToDelete); |
| }).then(function(rawContacts) { |
| return writableStore.findContactListsAsync().then(function(lists) { |
| var deleteList = null; |
| var deleteContact = null; |
| var matched = lists.some(function(list) { |
| for (var i = 0; i < rawContacts.length; i++) { |
| if (rawContacts[i].contactListId === list.id) { |
| deleteList = list; |
| deleteContact = rawContacts[i]; |
| return true; |
| } |
| } |
| return false; |
| }); |
| |
| if (!matched) { |
| throw new ContactError(ContactError.IO_ERROR); |
| } |
| |
| return deleteList.deleteContactAsync(deleteContact); |
| }); |
| }); |
| }).done(function() { |
| win(); |
| }, function(error) { |
| fail(error); |
| }); |
| }, |
| |
| search: function (win, fail, options) { |
| |
| // searchFields is not supported yet due to WP8.1 API limitations. |
| // findContactsAsync(String) method will attempt to match the name, email address, or phone number of a contact. |
| // see http://msdn.microsoft.com/en-us/library/windows/apps/dn624861.aspx for details |
| var searchFields = options[0], |
| searchOptions = options[1], |
| searchFilter = searchOptions.filter, |
| searchMultiple = searchOptions && searchOptions.multiple; |
| |
| // Check if necessary API is available. |
| // If not available, we are running on desktop, which doesn't support searching for contacts |
| if (!(contactsNS.ContactManager && contactsNS.ContactManager.requestStoreAsync)) { |
| fail(new ContactError(ContactError.NOT_SUPPORTED_ERROR)); |
| return; |
| } |
| |
| // Retrieve contact store instance |
| var contactStoreRequest = contactsNS.ContactManager.requestStoreAsync(); |
| |
| // When contact store retrieved |
| contactStoreRequest.done(function (contactStore) { |
| // determine, which function we use depending on whether searchOptions.filter specified or not |
| var contactsRequest = searchFilter ? contactStore.findContactsAsync(searchFilter) : contactStore.findContactsAsync(); |
| // request contacts and resolve either with success or error callback |
| contactsRequest.done(function (contacts) { |
| var result = []; |
| if (contacts.size !== 0) { |
| // Depending on searchOptions we should return all contacts found or only first |
| var outputContactsArray = searchMultiple ? contacts : [contacts[0]]; |
| outputContactsArray.forEach(function (contact) { |
| // Convert windows contacts to plugin's contact objects |
| result.push(convertToContact(contact)); |
| }); |
| } |
| win(result); |
| }, function() { |
| fail(new ContactError(ContactError.UNKNOWN_ERROR)); |
| }); |
| }, function() { |
| fail(new ContactError(ContactError.UNKNOWN_ERROR)); |
| }); |
| } |
| }; |
| |
| require("cordova/exec/proxy").add("Contacts", module.exports); |