mirror of https://github.com/nextcloud/contacts
351 lines
10 KiB
JavaScript
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;
|
|
});
|