1

I want to fetch the CalDAV calendar and address book from the server, store the values locally on disk to cache them, and save the syncToken. Then, later, either in an interval or at the latest during the next start of the app, run the sync again. During that second sync, I want to get only those (and exactly those) calendar entries and contacts that were changed (which includes added, removed, or modified).

I cannot figure out how to do that, from the calendar and address book sync API of lambdabaa/dav = npm dav. The documentation does not describe that. There is a syncCalendar() function, which from the name should do what I need, but the params and the overall process are not described. In the code, there is a syncToken (and then there's also a ctag and etag). I am trying to use the syncToken, which should do what I need, but it doesn't.

After the sync, I get calendar.syncToken and store it, and before the next sync (which might be after a restart), I restore the syncToken. But I get a full sync every time. It seems that I am using the API wrong, but I don't know what is right.

Dedicated test case:

  1. git clone https://github.com/benbucksch/test-dav-sync/
  2. yarn install
  3. yarn start

The code, for self-contained-ness:

import dav from 'dav';
import config from './config.js'; /* Pseudo JSON File with:
export default
{
  "serverURL": "https://...hostname.../remote.php/dav",
  "username": "...",
  "password": "..."
}
*/

var gJSONSavedFile = {}; // would normally be a file or database table

async function start() {
  let syncToken = gJSONSavedFile.syncToken; // get it from file
  console.info("sync token from file", syncToken);

  console.time("calendar-connect");
  let xhr = new dav.transport.Basic(
    new dav.Credentials({
      username: config.username,
      password: config.password,
    })
  );
  let account = await dav.createAccount({
    server: config.serverURL,
    //loadCollections: false,
    //loadObjects: false,
    xhr: xhr,
  });
  console.timeEnd("calendar-connect");
  let calendar = account.calendars[0]; // just the first, just for testing
  console.time("calendar sync");
  console.log("sync token after init", calendar.syncToken);
  if (syncToken) {
    console.log("but setting sync token to", syncToken);
    calendar.syncToken = syncToken; // TODO Already set. Doesn't work.
  }
  calendar = await dav.syncCalendar(calendar, {
    syncToken: syncToken, // TODO Doesn't work
    xhr: xhr,
  });
  console.timeEnd("calendar sync");
  gJSONSavedFile.syncToken = calendar.syncToken; // save it
  console.log("sync token after sync", gJSONSavedFile.syncToken);
  console.log("Got", calendar.objects.length, "calendar entries");
}


(async () => {
  try {
    console.log("Test first start");
    await start();
    console.log("\nTest second start");
    await start();
  } catch (ex) {
    console.error(ex);
  }
})();

The output, when running against NextCloud:

Test first start
sync token from file undefined
calendar-connect: 4.765s
sync token after init http://sabre.io/ns/sync/1157
calendar sync: 2.562s
sync token after sync http://sabre.io/ns/sync/1157
Got 1051 calendar entries

Test second start
sync token from file http://sabre.io/ns/sync/1157
calendar-connect: 3.351s
sync token after init http://sabre.io/ns/sync/1157
but setting sync token to http://sabre.io/ns/sync/1157
calendar sync: 2.510s
sync token after sync http://sabre.io/ns/sync/1157
Got 1051 calendar entries
Done in 13.29s.

Expected output would be:

  1. Second sync returns 0 entries.
  2. calendar.syncToken is not set immediately after init and login.

The fact that the syncToken is already set after init suggests that I am misunderstanding the API here.

Ben Bucksch
  • 395
  • 3
  • 13
  • I'm looking for the same... unfortunately the documentation for caldav is very very poor. I think the `dav.syncCalendar` doesn't have any syncToken protperty in the options-object, at least i cannot find any in the source-code. – MS1 Feb 05 '21 at 12:14

1 Answers1

0

After intensive analysis of the dav library I figured it out how to sync with the token. (Actually the props part of the request took the most time, as the doc is very poor)

as the syncCollection represents just the request, you need to send it manually, i.e. you need to do the following:

  1. create your request:
var req = dav.request.syncCollection({
  syncLevel: 1,
  syncToken: "http://sabre.io/ns/sync/138", //here you need to put your token
  props: [{
    namespace: "DAV:",
    name: "getetag"
  }],
});
  1. send the request:
var result = await xhr.send(req, 'https://demo.calendar.com/dav/calendars/user_123/democalendar/')

This results in the following response:

result {
    responses: 
    [
       {
          href: '/dav/calendars/user_123/democalendar/4564659816516.ical',
          props: [Object]
       }
    ],
    syncToken: 'http://sabre.io/ns/sync/139'
}
MS1
  • 508
  • 9
  • 23