Caldav with meteor

Hi! I’m working on realizing caldav support with meteor (especially from nextcloud).

I have tried a lot of packages, however the best results so far i get with:

Therefore i created a related method:

export const getCalendars = new ValidatedMethod({
    name: 'caldav.getcalendars',
    validate: new SimpleSchema({
      name: { type: String },
    }).validator(),
    run(values: any) {
      if (Meteor.isServer) {
        console.log("getcalendars server");
  
  
        var xhr = new dav.transport.Basic(
          new dav.Credentials({
            username: 'demo',
            password: 'mtiof-ghds-d6PNj-CELed-C3wB4'
          })
        );
  
        dav.createAccount({ server: 'https://192.168.112.12/remote.php/dav/', xhr: xhr })
          .then(function (account) {
            // account instanceof dav.Account
            account.calendars.forEach(function (calendar) {
              console.log('Found calendar named ' + calendar.displayName);
              //   console.log(calendar.data)
              //   const data = ical.parseICS(calendar.data);
              //   console.log(data);
            });
  
            let calendar = account.calendars[0];
  
            const result: any = [];
  
            dav.syncCalendar(calendar, { syncMethod: 'basic', xhr: xhr }).then((resolve) => {
              // console.log(resolve);
              resolve.objects.forEach(element => {
                const directEvents = ical.sync.parseICS(element.calendarData);
                // console.log("Ids",Object.keys(directEvents));
                for (const event of Object.values(directEvents)) {
                  console.log("event", event.summary, moment(new Date(event.start)).format("DD.MM.YYYY HH:mm"));
                  result.push(event);
  
                }
              });
  
              
  
  
              return result;
            });
  
           
  
          });
  
       
      } 
    }
  });

The call looks like this:

        getCalendars.call({name:"Test"},(err,res) => {
            if (err) {
                console.log("Error",err);
            } else {
                console.log("res",res);
            }
        })

The reason why I put the method in “Meteor.isSever” is, that the client is not able to process it, with error

…has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.

With this construct, i can call the method. However, as the dav.syncCalendar works asyc, i immediately get the result “undefined” from the getCalendars method. The server console output shows the related events after some time. But I cannot bring them into the client. Any recommendations?

You might want to read this post by @coagmano for a solution to the async problem in Meteor methods: Throw new Meteor.Error in Callbacks

1 Like

Similar to the linked thread, the core problem is that return will only return from the precise function it’s callled in, not the method’s function.
Luckily, because the service you’re using supports promises, this should be really easily done with async await:

const getCalendars = new ValidatedMethod({
  name: "caldav.getcalendars",
  validate: new SimpleSchema({
    name: { type: String },
  }).validator(),
  async run(values: any) {
    if (Meteor.isServer) {
      console.log("getcalendars server");

      const xhr = new dav.transport.Basic(
        new dav.Credentials({
          username: "demo",
          password: "mtiof-ghds-d6PNj-CELed-C3wB4",
        })
      );

      const account = await dav.createAccount({
        server: "https://192.168.112.12/remote.php/dav/",
        xhr: xhr,
      });
      // account instanceof dav.Account
      account.calendars.forEach(function (calendar) {
        console.log("Found calendar named " + calendar.displayName);
        //   console.log(calendar.data)
        //   const data = ical.parseICS(calendar.data);
        //   console.log(data);
      });

      let calendar = account.calendars[0];

      const result: any = [];

      const resolve = await dav.syncCalendar(calendar, {
        syncMethod: "basic",
        xhr: xhr,
      });
      // console.log(resolve);
      resolve.objects.forEach((element) => {
        const directEvents = ical.sync.parseICS(element.calendarData);
        // console.log("Ids",Object.keys(directEvents));
        for (const event of Object.values(directEvents)) {
          console.log("event", event.summary, moment(new Date(event.start)).format("DD.MM.YYYY HH:mm"));
          result.push(event);
        }
      });
      return result;
    }
  },
});

By making run an async function, you can await the various dav methods, meaning you get your results in the same scope as the run function, and can return it straight to the client.
Also avoids the async pyramid of doom

Though I would probably refactor the resolve.objects.forEach block like this:

const result = resolve.objects.flatMap((element) => {
  const directEvents = ical.sync.parseICS(element.calendarData);
  return Object.values(directEvents);
});
result.forEach(e => console.log("event", e.summary, moment(new Date(e.start)).format("DD.MM.YYYY HH:mm")));
return result

So you don’t mix array functions, loops and mutation of a variable

2 Likes

So awesome! Thanks a lot to both of you! @coagmano @peterfkruger