contacts/js/dav/lib/contacts.js

351 lines
10 KiB
JavaScript

import co from 'co';
import url from 'url';
import fuzzyUrlEquals from './fuzzy_url_equals';
import { AddressBook, VCard } from './model';
import * as ns from './namespace';
import * as request from './request';
import * as webdav from './webdav';
let debug = require('./debug')('dav:contacts');
/**
* @param {dav.Account} account to fetch address books for.
*/
export let listAddressBooks = co.wrap(function *(account, options) {
debug(`Fetch address books from home url ${account.homeUrl}`);
var req = request.propfind({
props: [
{ name: 'displayname', namespace: ns.DAV },
{ name: 'owner', namespace: ns.DAV },
{ name: 'getctag', namespace: ns.CALENDAR_SERVER },
{ name: 'resourcetype', namespace: ns.DAV },
{ name: 'sync-token', namespace: ns.DAV },
{ name: 'read-only', namespace: ns.OC },
//{ name: 'groups', namespace: ns.OC },
{ name: 'invite', namespace: ns.OC },
{ name: 'enabled', namespace: ns.OC }
],
depth: 1
});
let responses = yield options.xhr.send(req, account.homeUrl, {
sandbox: options.sandbox
});
let addressBooks = responses
.filter(res => {
return typeof res.props.displayname === 'string';
})
.map(res => {
debug(`Found address book named ${res.props.displayname},
props: ${JSON.stringify(res.props)}`);
return new AddressBook({
data: res,
account: account,
url: url.resolve(account.rootUrl, res.href),
ctag: res.props.getctag,
displayName: res.props.displayname,
resourcetype: res.props.resourcetype,
syncToken: res.props.syncToken
});
});
yield addressBooks.map(co.wrap(function *(addressBook) {
addressBook.reports = yield webdav.supportedReportSet(addressBook, options);
}));
return addressBooks;
});
export function getAddressBook(options) {
let addressBookUrl = url.resolve(options.url, options.displayName);
var req = request.propfind({
props: [
{ name: 'displayname', namespace: ns.DAV },
{ name: 'owner', namespace: ns.DAV },
{ name: 'getctag', namespace: ns.CALENDAR_SERVER },
{ name: 'resourcetype', namespace: ns.DAV },
{ name: 'sync-token', namespace: ns.DAV },
//{ name: 'groups', namespace: ns.OC },
{ name: 'invite', namespace: ns.OC }
],
depth: 1
});
return options.xhr.send(req, addressBookUrl);
}
/**
* @return {Promise} promise will resolve when the addressBook has been created.
*
* Options:
*
* (String) url
* (String) displayName - name for the address book.
* (dav.Sandbox) sandbox - optional request sandbox.
* (dav.Transport) xhr - request sender.
*/
export function createAddressBook(options) {
let collectionUrl = url.resolve(options.url, options.displayName);
options.props = [
{ name: 'resourcetype', namespace: ns.DAV, children: [
{ name: 'collection', namespace: ns.DAV },
{ name: 'addressbook', namespace: ns.CARDDAV }
]
},
{ name: 'displayname', value: options.displayName, namespace: ns.DAV }
]
return webdav.createCollection(collectionUrl, options);
}
/**
* @param {dav.AddressBook} addressBook the address book to be deleted.
* @return {Promise} promise will resolve when the addressBook has been deleted.
*
* Options:
*
* (dav.Sandbox) sandbox - optional request sandbox.
* (dav.Transport) xhr - request sender.
*/
export function deleteAddressBook(addressBook, options) {
return webdav.deleteCollection(addressBook.url, options);
}
/**
* @param {dav.AddressBook} addressBook the address book to be renamed.
* @return {Promise} promise will resolve when the addressBook has been renamed.
*
* Options:
*
* (String) displayName - new name for the address book.
* (dav.Sandbox) sandbox - optional request sandbox.
* (dav.Transport) xhr - request sender.
*/
export function renameAddressBook(addressBook, options) {
options.props = [
{ name: 'displayname', value: options.displayName, namespace: ns.DAV }
]
return webdav.updateProperties(addressBook.url, options);
}
/**
* @param {dav.AddressBook} addressBook the address book to put the object on.
* @return {Promise} promise will resolve when the card has been created.
*
* Options:
*
* (String) data - vcard object.
* (String) filename - name for the address book vcf file.
* (dav.Sandbox) sandbox - optional request sandbox.
* (dav.Transport) xhr - request sender.
*/
export function createCard(addressBook, options) {
let objectUrl = url.resolve(addressBook.url, options.filename);
return webdav.createObject(objectUrl, options.data, options);
}
export let getFullVcards = co.wrap(function *(addressBook, options, hrefs) {
var req = request.addressBookMultiget({
depth: 1,
props: [
{ name: 'getetag', namespace: ns.DAV },
{ name: 'address-data', namespace: ns.CARDDAV }
],
hrefs: hrefs
});
let responses = yield options.xhr.send(req, addressBook.url, {
sandbox: options.sandbox
});
return responses.map(res => {
debug(`Found vcard with url ${res.href}`);
return new VCard({
data: res,
addressBook: addressBook,
url: url.resolve(addressBook.account.rootUrl, res.href),
etag: res.props.getetag,
addressData: res.props.addressData
});
});
});
/**
* Options:
*
* (dav.Sandbox) sandbox - optional request sandbox.
*/
export let listVCards = co.wrap(function *(addressBook, options) {
debug(`Doing REPORT on address book ${addressBook.url} which belongs to
${addressBook.account.credentials.username}`);
var vCardListFields = [ 'EMAIL', 'UID', 'CATEGORIES', 'FN', 'TEL', 'NICKNAME', 'N' ]
.map(function (value) {
return {
name: 'prop',
namespace: ns.CARDDAV,
attrs: [ { name: 'name', value: value } ]
};
});
var req = request.addressBookQuery({
depth: 1,
props: [
{ name: 'getetag', namespace: ns.DAV },
{ name: 'address-data', namespace: ns.CARDDAV, children: vCardListFields }
]
});
let responses = yield options.xhr.send(req, addressBook.url, {
sandbox: options.sandbox
});
return responses.map(res => {
debug(`Found vcard with url ${res.href}`);
return new VCard({
data: res,
addressBook: addressBook,
url: url.resolve(addressBook.account.rootUrl, res.href),
etag: res.props.getetag,
addressData: res.props.addressData
});
});
});
/**
* @param {dav.VCard} card updated vcard object.
* @return {Promise} promise will resolve when the card has been updated.
*
* Options:
*
* (dav.Sandbox) sandbox - optional request sandbox.
* (dav.Transport) xhr - request sender.
*/
export function updateCard(card, options) {
return webdav.updateObject(
card.url,
card.addressData,
card.etag,
options
);
}
/**
* @param {dav.VCard} card target vcard object.
* @return {Promise} promise will resolve when the calendar has been deleted.
*
* Options:
*
* (dav.Sandbox) sandbox - optional request sandbox.
* (dav.Transport) xhr - request sender.
*/
export function deleteCard(card, options) {
return webdav.deleteObject(
card.url,
card.etag,
options
);
}
/**
* @param {dav.Calendar} calendar the calendar to fetch updates to.
* @return {Promise} promise will resolve with updated calendar object.
*
* Options:
*
* (dav.Sandbox) sandbox - optional request sandbox.
* (String) syncMethod - either 'basic' or 'webdav'. If unspecified, will
* try to do webdav sync and failover to basic sync if rfc 6578 is not
* supported by the server.
* (dav.Transport) xhr - request sender.
*/
export function syncAddressBook(addressBook, options) {
options.basicSync = basicSync;
options.webdavSync = webdavSync;
return webdav.syncCollection(addressBook, options);
}
/**
* @param {dav.Account} account the account to fetch updates for.
* @return {Promise} promise will resolve with updated account.
*
* Options:
*
* (dav.Sandbox) sandbox - optional request sandbox.
* (dav.Transport) xhr - request sender.
*/
export let syncCarddavAccount = co.wrap(function *(account, options={}) {
options.loadObjects = false;
if (!account.addressBooks) {
account.addressBooks = [];
}
let addressBooks = yield listAddressBooks(account, options);
addressBooks
.filter(function(addressBook) {
// Filter the address books not previously seen.
return account.addressBooks.every(
prev => !fuzzyUrlEquals(prev.url, addressBook.url)
);
})
.forEach(addressBook => account.addressBooks.push(addressBook));
options.loadObjects = true;
yield account.addressBooks.map(co.wrap(function *(addressBook, index) {
try {
yield syncAddressBook(addressBook, options);
} catch (error) {
debug(`Syncing ${addressBook.displayName} failed with ${error}`);
account.addressBooks.splice(index, 1);
}
}));
return account;
});
export let getContacts = getFullVcards;
let basicSync = co.wrap(function *(addressBook, options) {
let sync = webdav.isCollectionDirty(addressBook, options)
if (!sync) {
debug('Local ctag matched remote! No need to sync :).');
return addressBook;
}
debug('ctag changed so we need to fetch stuffs.');
addressBook.objects = yield listVCards(addressBook, options);
return addressBook;
});
let webdavSync = co.wrap(function *(addressBook, options) {
var req = request.syncCollection({
props: [
{ name: 'getetag', namespace: ns.DAV },
{ name: 'address-data', namespace: ns.CARDDAV }
],
syncLevel: 1,
syncToken: addressBook.syncToken
});
let result = yield options.xhr.send(req, addressBook.url, {
sandbox: options.sandbox
});
// TODO(gareth): Handle creations and deletions.
result.responses.forEach(response => {
// Find the vcard that this response corresponds with.
let vcard = addressBook.objects.filter(object => {
return fuzzyUrlEquals(object.url, response.href);
})[0];
if (!vcard) return;
vcard.etag = response.props.getetag;
vcard.addressData = response.props.addressData;
});
addressBook.syncToken = result.syncToken;
return addressBook;
});