summaryrefslogtreecommitdiffstats
path: root/js/dav/lib/contacts.js
blob: b333c4c5185d73c1ef855dc9c36ffc1d4819de1b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
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: 'getctag', namespace: ns.CALENDAR_SERVER },
      { name: 'resourcetype', namespace: ns.DAV },
      { name: 'sync-token', namespace: ns.DAV }
    ],
    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;
});

/**
 * @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);
}

/**
 * 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 req = request.addressBookQuery({
    depth: 1,
    props: [
      { name: 'getetag', namespace: ns.DAV },
      { name: 'address-data', namespace: ns.CARDDAV }
    ]
  });

  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;
});

let basicSync = co.wrap(function *