Giter Site home page Giter Site logo

natelindev / tsdav Goto Github PK

View Code? Open in Web Editor NEW
187.0 3.0 30.0 2 MB

WebDAV, CALDAV, and CARDDAV client for Nodejs and the Browser

Home Page: https://tsdav.vercel.app

License: MIT License

TypeScript 99.24% JavaScript 0.76%
dav caldav carddav webdav ical vcard addressbook calendar contact calendars

tsdav's Introduction

webdav request made easy

Bundlephobia Types NPM Version MIT License

Features

  • Easy to use, well documented JSON based WEBDAV API
  • Works in both Browsers and Node.js
  • Supports Both commonjs and esm
  • OAuth2 & basic auth helpers built-in
  • Native typescript, fully linted and well tested
  • Supports WEBDAV, CALDAV, CARDDAV
  • Tested with multiple cloud providers

Install

npm install tsdav

or

yarn add tsdav

Quickstart

Google CALDAV
import { createDAVClient } from 'tsdav';

(async () => {
  const client = await createDAVClient({
    serverUrl: 'https://apidata.googleusercontent.com/caldav/v2/',
    credentials: {
      tokenUrl: 'https://accounts.google.com/o/oauth2/token',
      username: 'YOUR_EMAIL_ADDRESS',
      refreshToken: 'YOUR_REFRESH_TOKEN_WITH_CALDAV_PERMISSION',
      clientId: 'YOUR_CLIENT_ID',
      clientSecret: 'YOUR_CLIENT_SECRET',
    },
    authMethod: 'Oauth',
    defaultAccountType: 'caldav',
  });

  const calendars = await client.fetchCalendars();

  const calendarObjects = await client.fetchCalendarObjects({
    calendar: calendars[0],
  });
})();
Apple CARDDAV
import { createDAVClient } from 'tsdav';

(async () => {
  const client = await createDAVClient({
    serverUrl: 'https://contacts.icloud.com',
    credentials: {
      username: 'YOUR_APPLE_ID',
      password: 'YOUR_APP_SPECIFIC_PASSWORD',
    },
    authMethod: 'Basic',
    defaultAccountType: 'carddav',
  });

  const addressBooks = await client.fetchAddressBooks();

  const vcards = await client.fetchVCards({
    addressBook: addressBooks[0],
  });
})();

After v1.1.0, you have a new way of creating clients.

Google CALDAV
import { DAVClient } from 'tsdav';

const client = new DAVClient({
  serverUrl: 'https://apidata.googleusercontent.com/caldav/v2/',
  credentials: {
    tokenUrl: 'https://accounts.google.com/o/oauth2/token',
    username: 'YOUR_EMAIL_ADDRESS',
    refreshToken: 'YOUR_REFRESH_TOKEN_WITH_CALDAV_PERMISSION',
    clientId: 'YOUR_CLIENT_ID',
    clientSecret: 'YOUR_CLIENT_SECRET',
  },
  authMethod: 'Oauth',
  defaultAccountType: 'caldav',
});

(async () => {
  await client.login();

  const calendars = await client.fetchCalendars();

  const calendarObjects = await client.fetchCalendarObjects({
    calendar: calendars[0],
  });
})();
Apple CARDDAV
import { DAVClient } from 'tsdav';

const client = new DAVClient({
  serverUrl: 'https://contacts.icloud.com',
  credentials: {
    username: 'YOUR_APPLE_ID',
    password: 'YOUR_APP_SPECIFIC_PASSWORD',
  },
  authMethod: 'Basic',
  defaultAccountType: 'carddav',
});

(async () => {
  await client.login();

  const addressBooks = await client.fetchAddressBooks();

  const vcards = await client.fetchVCards({
    addressBook: addressBooks[0],
  });
})();

Documentation

Check out the Documentation

License

MIT

Changelog

refers to Changelog

Debugging

this package uses debug package, add tsdav:* to DEBUG env variable to enable debug logs

tsdav's People

Contributors

frankleng avatar jelmer avatar kuba-orlik avatar m0dch3n avatar mathisfrenais avatar molaux avatar n8io avatar natelindev avatar nibdo avatar renovate-bot avatar renovate[bot] avatar zomars avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

tsdav's Issues

Update plan for tsdav

Hello all,
Thanks for your support for this lib.
I'm currently very busy, this does not mean I won't update this lib in the future.
The next update will be no later than 3/31/2023 but no earlier than 2/1/2023

Support VTODO items

Just a feature request, it would be nice to be able to deal with tasks, and not only calendars. It means supporting VTODO entries.

next cloud vcardstring format

whats the fornat for vcard string of next cloud? no proper documentation is mentioned anywhere or if its stable or nor

Rollup is requested as a peer dependency from one of tsdav direct dependencies

When using tsdav I'm getting a warning (from yarn in this case) of an unmet peer dependency for rollup.

"workspace-aggregator-165f5ad3-7952-4846-9cdd-20e379f7ba54 > @calcom/web > tsdav > rollup-plugin-polyfill-node > @rollup/plugin-inject > @rollup/[email protected]" has unmet peer dependency "rollup@^1.20.0||^2.0.0".

As rollup is being added as a devDependency here, it will force any project using tsdav to add it as dependency in order to remove this warning unless it is imported as a dependency (not devDep) here in tsdav.

Rollup is a peer dep of the rollup-plugin-polyfill-node package which is a direct dependency of tsdav.
Being tsdav a lib and not a standalone project I feel it should be here where rollup should be added as a dependency and not in the project using tsdav.

google API account carddev template

is there any documentation or sample on how to use carddav on google api? ive been facing invalid credentials problem for a while now and following steps all according to my information (which only related to caldav)

const client = new DAVClient({
serverUrl: 'https://www.googleapis.com/carddav/v1/principals/YOUREMAIL',
credentials: {
tokenUrl: 'https://oauth2.googleapis.com/token',
clientId: 'OAuth 2.0 Client IDs',
clientSecret: 'OAuth 2.0 Client SECRET',
redirectUrl: "http://localhost:3001",
authorizationCode:"OAuth 2.0 AUTHORIZATION"
},
authMethod: 'Oauth',
defaultAccountType: 'cardddav',
});

Support for digest authentication

Hi,

I was wondering if there is any plan to implement support for digest authentication type?
Some calendar clients (such as Mac OSX Calendar) seem to only support digest and not basic, so when using a calendar server like baikal, we have to use digest if we want to be able to administrate the calendar with ex. OSX Calendar app.

So it would be a really nice feature to support digest in tsdav as well.

fetchVCards returns empty array

Hi,

our company have carddav/caldav service. It works fine with Thunderbird, Outlook, even Android open Sync. But I can not fetch any contacts or even calendar events from it with tsdav. I can not obviously provide credentials, but I can give you an url to the service in the example below.

I am able to fetch address books with fetchAddressBooks method, but fetchVCards returns empty array.

My code:

import { DAVClient } from 'tsdav';

const client = new DAVClient({
  serverUrl: 'https://carddav.seznam.cz',
  credentials: {
    username: '...',
    password: '...',
  },
  authMethod: 'Basic',
  defaultAccountType: 'carddav',
});

(async () => {
  await client.login();

  // I get 2 books, that is OK.
  const addressBooks = await client.fetchAddressBooks();

  // Empty []
  const vcards = await client.fetchVCards({
    addressBook: addressBooks[0],
  });

  console.log(vcards);
})();

Cannot use Google CardDAV API

I'm having a lot of difficulty downloading vCards from the Google contacts API.

async function main() {
  const client = new DAVClient({
    serverUrl: "https://www.googleapis.com/",
    credentials: {
      clientId: "...",
      clientSecret: "...",
      tokenUrl: "https://oauth2.googleapis.com/token",
      refreshToken: "...",
    },
    authMethod: "Oauth",
    defaultAccountType: "carddav",
  });

  await client.login();

  const addressBooks = await client.fetchAddressBooks();
  return addressBooks;
}

Please note that several of the examples on the README/documentation site contain incorrect information, so I have had to do a fair amount of experimentation to get this far and it's possible I've got one of these URLs configured wrong. In order to get this working at all, I also had to patch the code in serviceDiscovery to issue a PROPFIND instead of a GET, since Google does not respond otherwise. However, the fetchAddressBooks method fails regardless, with error Cannot read property 'supportedReport' of undefined, originating on this line.

For what it's worth, the following is also in the debug logs (originating from this line:

  tsdav:addressBook Found address book named Address Book,
  tsdav:addressBook              props: {"displayname":"Address Book","resourcetype":{"collection":{},"addressbook":{}},"getctag":{},"syncToken":{}} +0ms

Is this a known issue? Is there a working example of downloading vCards from Google? Thanks!

No Documentation for Nextcloud

When reading the docs there are instructions on connecting to Google as well as Apple, but no nextcloud documentation, not even on the providers page.

Could someone with success connecting to nextcloud please make a PR to the intro tutorial for Nextcloud?

I'm having trouble getting it to connect, when calling client.connect using basic auth it's giving cors errors even though I've added localhost as trusted. I'm guessing I'll have to use oAuth?

google oauth "Invalid grant_type: "

hello,

I'm trying to try out your oauth helper but when using getOauthHeaders I get the error message:

error"unsupported_grant_type"
error_description"Invalid grant_type: "

result{"tokens":{},"headers":{"authorization":"Bearer undefined"}}

let test = function () {
  (async () => {
    try {
      const tokens = await getOauthHeaders({
        authorizationCode: localStorage.getItem("authorizationCode"),
        clientId: google_cred.clientId,
        clientSecret: google_cred.clientSecret,
        tokenUrl: "https://accounts.google.com/o/oauth2/token",
        redirectUrl: "https://strukturart.github.io/greg/",
      });
      console.log("result" + JSON.stringify(tokens));
    } catch (e) {
      console.log("error" + e);
    }
  })();
}

what am I doing wrong ?

Calendar color property in fetchCalendars

Hello, love tsdav, very easy to use.

I have just one question: would it be possible to add prop to get calendar color in fetchCalendars method?

Request payload would have this prop then:
<n0:calendar-color xmlns:n0="http://apple.com/ns/ical/"/>

Or maybe if not hard-coded to the method, it could be add via additional optional props, like you can customize other methods.

Thank you!

Deleting a calendar

Hello! I'm still a novice with CalDAV, so apologies if this question has an obvious answer. It seems straightforward to create and query calendars with this library, but is there any capability to delete calendars?

Unexpected results for `fetchCalendarObjects` when using rrule expansion

Hi there! I'm getting unexpected results when using fetchCalendarObject, and I'm not certain if it's something wrong with the library or due to my own lack of knowledge on iCal. If it matters, I am this to interface with a local DAViCal instance that is otherwise empty.

I have created an empty calendar and added one event that recurs weekly. When I query for a date-range that does not overlap with a recurrence of the item, I am still getting that event as a result - where I would expect to get no results. When I use the expand flag, the results are only an empty VCALENDAR. The following sample code demonstrates the behavior:

// make a new (empty!) calendar
const calendar = await client.makeCalendar({
  url: 'url-for-calendar', props: { displayname: 'Test Calendar' },
});

// create an event that recurs weekly
const eventId = 'f2af973a-c9cc-4410-ac79-c7c32fad70e7';
const iCalString = `
BEGIN:VCALENDAR
BEGIN:VEVENT
SUMMARY:Test Event
UID:${eventId}
DTSTART:20220606T000000
RRULE:FREQ=WEEKLY
END:VEVENT
END:VCALENDAR
`.trim();
await client.createCalendarObject({
  calendar, iCalString, filename: `${eventId}.ics`,
});

// query a range OUTSIDE a recurrence of the event
const events = await client.fetchCalendarObjects({
  calendar,
  expand: true,
  timeRange: {
    start: '2022-06-08T10:00:00.000Z',
    end: '2022-06-09T10:00:00.000Z',
  },
});
// RESULTS:
// events = [{
//   url: 'url-for-calendar/f2af973a-c9cc-4410-ac79-c7c32fad70e7.ics',
//   etag: '"f1569d1a6f2562ce9cd486e4b3ef1d09"',
//   data: 'BEGIN:VCALENDAR\r\nEND:VCALENDAR'
// }]

google oAuth

I'm trying to use oauth helper and I don't understand how to get the authorization code to log in successfully.

let test = function () {
  const g_client = new DAVClient({
    serverUrl: "https://apidata.googleusercontent.com/caldav/v2/",
    credentials: {
      tokenUrl: "https://accounts.google.com/o/oauth2/token",
      username: google_cred.email,
      refreshToken: "",
      clientId: google_cred.clientId,
      clientSecret: google_cred.clientSecret,
      redirectUrl: "http://localhost:8080",
      authorizationCode: "",
    },
    authMethod: "Oauth",
    defaultAccountType: "caldav",
  });

  (async () => {
    await g_client.login();

    const calendars = await client.fetchCalendars();

    const calendarObjects = await client.fetchCalendarObjects({
      calendar: calendars[0],
    });
  })();
};
test();

Unable to use TSdav with Mdaemon

I'm trying to use tsdav library against Mdaemon caldav server.

I haev a CORS problem when using createCalendarObject to push an event in the calendar

In fact, in the request, I can see this header :
image

Tsdav correctly provide header he will need to read.
But Mdaemon answer this :
image
and
image

Mdaemon seems not read the request header Access-Control-Request-Headers, and as, in createCalendarObject, tsdav add the header If-None-Match: *, the PUT request fails.

I didnt manage to find a way to configure the Mdaemon server to correctly accept this header.

So there is a way to have an option in tsdav, to allow to deactivate the header If-None-Match ?

Thanks

Returns addressbook as vcard when addressbook is empty

Steps to reproduce:

  • Create an empty address book on the CardDAV server
  • Use fetchVCards() against it
  • Note that you receive 1 record which has the address of the address book and not a VCard

Having looked into it, a little (by uncommenting the code that logs xml requests and responses), it looks like the result of the addressbook-query is an empty set, but somehow in this code that is converted into a URL for the address book itself:

tsdav/src/addressBook.ts

Lines 136 to 150 in 0546cc0

const vcardUrls = (
objectUrls ??
// fetch all objects of the calendar
(
await addressBookQuery({
url: addressBook.url,
props: { [`${DAVNamespaceShort.DAV}:getetag`]: {} },
depth: '1',
headers,
})
).map((res) => res.href ?? '')
)
.map((url) => (url.includes('http') ? url : new URL(url, addressBook.url).href))
.map((url) => new URL(url).pathname)
.filter(urlFilter ?? ((url: string): url is string => url !== addressBook.url));

Then in the next request, the addressbook-multiget, the request looks like:

<card:addressbook-multiget xmlns:d="DAV:" xmlns:card="urn:ietf:params:xml:ns:carddav">
  <d:prop>
    <d:getetag />
    <card:address-data />
  </d:prop>
  <d:href>/user/contacts/abook/</d:href>
</card:addressbook-multiget>

Where the d:href is the address book itself.

The addressbook-query always looks like:

<?xml version="1.0" encoding="utf-8"?>
<card:addressbook-query xmlns:card="urn:ietf:params:xml:ns:carddav" xmlns:d="DAV:">
  <d:prop>
    <d:getetag />
  </d:prop>
  <card:filter>
    <card:prop-filter name="FN" />
  </card:filter>
</card:addressbook-query>

Then the response, when the collection is empty, from xandikos, is:

<ns0:multistatus xmlns:ns0="DAV:" />

I suspect that what's happening is online 146 above, the empty response is being turned into an empty string. Then lines 148 and 149 turn that empty string into the URL of the address book. But the filter on line 150 is comparing a full URL against a path. At least that's what my debug() output shows when I log those values.

I'd guess that the filter will always fail, because line 149 always converts URLs into paths.

Debugging further, it looks like inside davRequest if the response is empty, as per the XML above, then a single element gets returned like so:

[ { status: 207, statusText: 'Multi-Status', ok: true } ]

While I guess that an empty array would be more appropriate, as the response is empty. Okay, I think I figured it out, I'll submit a PR right after this.

caldav icloud and CORS?

Hello,

I tried to get my calendar events from browser js. For that purpose, I created an app-specific password.
I import tsdav in the browser like this:

<script src="./tsdav.js" type="module" ></script>
<script type="module">
        import { Tests } from './tests.js';
        Tests.testTSDAV();
</script>

Relevant part of test.js:

static async testTSDAV() {

        const client = new DAVClient({
            serverUrl: 'https://caldav.icloud.com',
            credentials: {
                username: '$ICLOUD ACCOUNT LOGIN',
                password: '$APP_SPECIFIC PASSWORD',
            },
            authMethod: 'Basic',
            defaultAccountType: 'caldav',
        });
        await client.login(); // CORS error here
}
}

On Chrome I get the error message:

Access to XMLHttpRequest at 'https://caldav.icloud.com/.well-known/caldav' from origin 'http://127.0.0.1:5500' 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.
tsdav.js:581          PROPFIND https://caldav.icloud.com/.well-known/caldav net::ERR_FAILED

I think I understand what is happening: The icloud server has no "Access-Control-Allow-Origin:*" in its response.
I'm just wondering that I haven't seen anyone else asking about this - am I the first who tried to access iCloud?
Or hopefully, there is something I missed - because the tsdav readme says it works in a browser...?

Note that I was able to successfully connect to iCloud with curl, so it's not the authentication that fails...

Thanks in advance!

Issue Fetching vcards from Yahoo carddav server

Hello,

I have a requirement to fetch a list of contacts/emails from a yahoo user/email account. After much research I found that Yahoo deprecated their old Contacts API and is now using CardDAV, but theres virtually no documentation online about Yahoo's implementation (I assume because CardDAV is a standard protocol).

I'm having issues using tsDAV to retrieve a list of contacts from Yahoo and could use some help.

So far I've been able to authenticate successfully with Yahoo OAuth and can retrieve an AddressBook with fetchAddressBooks(), but I can't seem to be able to fetch VCards or issue an addressBookQuery successfully.

Code:

...initialize client
const addressBook = await client.fetchAddressBooks();

const vCards = await client.fetchVCards({//This fails with a 400 error
  addressBook: addressBook[0],
});

const addressbooks = await client.addressBookQuery({//This fails with a 400 error
        url: addressBook[0].url,
        props: [
          { name: 'getetag', namespace: DAVNamespace.DAV }, 
        ],
        depth: '1'
      });

Here's what I'm seeing in the logs for the call to client.fetchVCards():

I added log statements in request.js to see the url, headers, and body (before marshalled to xml). One interesting thing is that the log statements gets called twice in a row with a different body for the same call to fetchVCards(). Not sure if this is correct behavior.

Request:

  tsdav:addressBook Fetching vcards from https://carddav.address.yahoo.com/dav/username/Contacts/ +200ms
url:  https://carddav.address.yahoo.com/dav/username/Contacts/
headers:  {
  authorization: 'Bearer ...',
  depth: '1'
}
body:  {
  "addressbook-query": {
    "_attributes": {
      "xmlns:card": "urn:ietf:params:xml:ns:carddav",
      "xmlns:d": "DAV:"
    },
    "d:prop": {
      "d:getetag": {}
    },
    "filter": {
      "prop-filter": {
        "_attributes": {
          "name": "FN"
        }
      }
    }
  }
}
url:  https://carddav.address.yahoo.com/dav/username/Contacts/
headers:  {
  authorization: 'Bearer ...',
  depth: '1'
}
body:  {
  "addressbook-multiget": {
    "_attributes": {
      "xmlns:d": "DAV:",
      "xmlns:card": "urn:ietf:params:xml:ns:carddav"
    },
    "d:prop": {
      "d:getetag": {},
      "card:address-data": {}
    },
    "d:href": [] <---- This is empty, and the error below says that hrefs need to be specified, but not sure what these hrefs are supposed to be???
  }
}

Response:

vCards[
  {
    "res": {
      "href": "https://carddav.address.yahoo.com/dav/username/Contacts/",
      "ok": false,
      "status": 400,
      "statusText": "Bad Request",
      "raw": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><error xmlns:ns1=\"http://www.yahooapis.com/v1/base.rng\" xml:lang=\"en-US\"><ns1:description>Bad Request</ns1:description><ns1:detail>One or more hrefs need to be specified in the request</ns1:detail></error>"
    },
    "url": "https://carddav.address.yahoo.com/dav/username/Contacts/"
  }
]

And here is the log for the call to addressBookQuery():

url:  https://carddav.address.yahoo.com/dav/username/Contacts/
headers:  {
  authorization: 'Bearer  ...',
  depth: '1'
}
body:  {<-- The IETF standard example (https://datatracker.ietf.org/doc/html/rfc6352#section-8.6) looks similar to this, so I assume it should work???
  "addressbook-query": {
    "_attributes": {
      "xmlns:card": "urn:ietf:params:xml:ns:carddav",
      "xmlns:d": "DAV:"
    },
    "d:prop": {
      "d:getetag": {},
      "card:href": [
        "https://carddav.address.yahoo.com/dav/username/Contacts/"
      ]
    },
    "filter": {
      "prop-filter": {
        "_attributes": {
          "name": "FN"
        }
      }
    }
  }
}

Response:

addressbooks: [
 {
   "href": "https://carddav.address.yahoo.com/dav/username/Contacts/",
   "ok": false,
   "status": 400,
   "statusText": "Bad Request",
   "raw": ""
 }
]

Is it possible that the Yahoo CardDAV implementation is broken (non-standards compliant)??

Any help is greatly appreciated.

Using tsdav in the Browser

I'm trying to build a Todo-App that runs in a browser and fetches items from a CalDAV-Server, but I'm struggling to include tsdav with esm. Can you point me to a working project using tsdav in the browser?

Here is what I did:
To be able to use "import from 'tsdav';", I set up a importmap, then it loads dist-esm/index.js, but fails to load "https://tsdav.ddev.site/tsdavPOC/node_modules/tsdav/dist-esm/account" without the .js extension.

    <title>tsdav POC</title>
    <script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>


<script type="importmap">
{
  "imports": {
    "tsdav": "./node_modules/tsdav/dist-esm/index.js"
  }
}
</script>
<script type="module">
    import { DAVClient } from 'tsdav';
    const client = new DAVClient({
        serverUrl: 'https://apidata.googleusercontent.com/caldav/v2/',
        credentials: {
            refreshToken: 'YOUR_REFRESH_TOKEN_WITH_CALDAV_PERMISSION',
        },
        authMethod: 'Oauth',
        defaultAccountType: 'caldav',
    });
    (async () => {
        await client.login();
        const calendars = await client.fetchCalendars();
        const calendarObjects = await client.fetchCalendarObjects({
            calendar: calendars[0],
        });
    })();
</script>


tsdav not compatible with Vite

Hi! I have successfully run my code as a VanillaJS script, but was unable to run the exact same code, which was bundled via Vite in a backround script of a webextension.
Steps I did:

  1. Created a DAVClient
  2. Logged in
  3. Ran fetchCalendars()

Bundling the exact same code into a Vue app with vite outputs the following error:
TypeError: Failed to execute 'fetch' on 'WorkerGlobalScope': Illegal invocation

  var _a;
  const { url, init, convertIncoming = true, parseOutgoing = true } = params;
  const { headers = {}, body, namespace, method, attributes } = init;
  const xmlBody = convertIncoming ? convert.js2xml(Object.assign(Object.assign({ _declaration: { _attributes: { version: "1.0", encoding: "utf-8" } } }, body), { _attributes: attributes }), {
    compact: true,
    spaces: 2,
    elementNameFn: (name) => {
      if (namespace && !/^.+:.+/.test(name)) {
        return `${namespace}:${name}`;
      }
      return name;
    }
  }) : body;
//////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////// FAILS HERE ///////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
  const davResponse = yield browserPonyfillExports.fetch(url, {
    headers: Object.assign({ "Content-Type": "text/xml;charset=UTF-8" }, cleanupFalsy(headers)),
    body: xmlBody,
    method
  });
//////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
  const resText = yield davResponse.text();
  if (!davResponse.ok || !((_a = davResponse.headers.get("content-type")) === null || _a === void 0 ? void 0 : _a.includes("xml")) || !parseOutgoing) {
    return [
      {
        href: davResponse.url,
        ok: davResponse.ok,
        status: davResponse.status,
        statusText: davResponse.statusText,
        raw: resText
      }
    ];
  }
  const result = convert.xml2js(resText, {
    compact: true,
    trim: true,
    textFn: (value, parentElement) => {
      try {
        const parentOfParent = parentElement._parent;
        const pOpKeys = Object.keys(parentOfParent);
        const keyNo = pOpKeys.length;
        const keyName = pOpKeys[keyNo - 1];
        const arrOfKey = parentOfParent[keyName];
        const arrOfKeyLen = arrOfKey.length;
        if (arrOfKeyLen > 0) {
          const arr = arrOfKey;
          const arrIndex = arrOfKey.length - 1;
          arr[arrIndex] = nativeType(value);
        } else {
          parentOfParent[keyName] = nativeType(value);
        }
      } catch (e) {
        debug$5(e.stack);
      }
    },
    // remove namespace & camelCase
    elementNameFn: (attributeName) => camelCase$1(attributeName.replace(/^.+:/, "")),
    attributesFn: (value) => {
      const newVal = Object.assign({}, value);
      delete newVal.xmlns;
      return newVal;
    },
    ignoreDeclaration: true
  });
  const responseBodies = Array.isArray(result.multistatus.response) ? result.multistatus.response : [result.multistatus.response];
  return responseBodies.map((responseBody) => {
    var _a2, _b;
    const statusRegex = /^\S+\s(?<status>\d+)\s(?<statusText>.+)$/;
    if (!responseBody) {
      return {
        status: davResponse.status,
        statusText: davResponse.statusText,
        ok: davResponse.ok
      };
    }
    const matchArr = statusRegex.exec(responseBody.status);
    return {
      raw: result,
      href: responseBody.href,
      status: (matchArr === null || matchArr === void 0 ? void 0 : matchArr.groups) ? Number.parseInt(matchArr === null || matchArr === void 0 ? void 0 : matchArr.groups.status, 10) : davResponse.status,
      statusText: (_b = (_a2 = matchArr === null || matchArr === void 0 ? void 0 : matchArr.groups) === null || _a2 === void 0 ? void 0 : _a2.statusText) !== null && _b !== void 0 ? _b : davResponse.statusText,
      ok: !responseBody.error,
      error: responseBody.error,
      responsedescription: responseBody.responsedescription,
      props: (Array.isArray(responseBody.propstat) ? responseBody.propstat : [responseBody.propstat]).reduce((prev, curr) => {
        return Object.assign(Object.assign({}, prev), curr === null || curr === void 0 ? void 0 : curr.prop);
      }, {})
    };
  });
});

I have highlighted the line where it fails with a comment.
I'd highly appreciate your help!

Kind regards

Can I omit the etag in updateCalendarObject?

I'm implementing a one-way sync, so I don't care what the current state of the remote object is, I just want to overwrite it. Omitting the etag field seems to work, but it fails typechecking. Could that be changed in the function signature or is there some other issue I'm overlooking?

[RFC] upcoming breaking change

I'm planning on removing function formatProps and formatFilters in requestHelpers.
This is hard to maintain and brings uncessary complexity while not being able to cover 100% of the scenarios.
Instead I want to just replace them with plain xml-js ElementCompact, which looks like this:

interface ElementCompact {
  [key: string]: any
  _declaration?: {
    _attributes?: DeclarationAttributes
  }
  _instruction?: {
    [key: string]: string
  }
  _attributes?: Attributes
  _cdata?: string
  _doctype?: string
  _comment?: string
  _text?: string | number
}

prop

{
  "calendarData": {
    "expand": {
      "_attributes": {
        "start": "20060103T000000Z",
        "end": "20060105T000000Z"
      }
    }
  }
}

filter

{
  "compFilter": {
    "_attributes": {
      "name": "VCALENDAR"
    },
    "compFilter": {
      "_attributes": {
        "name": "VEVENT"
      },
      "timeRange": {
        "_attributes": {
          "start": "20060103T000000Z",
          "end": "20060105T000000Z"
        }
      }
    }
  }
}

The prop and filter will be feed directly to xml-js as-is.
This elimates any potential bugs and made it 100% compatible with xml-js, which happens to be easy to understand.

Feel free to comment.

Add an example of loading a calendar with only access_token without a davClient.

Hello, thank you so much for creating a library before the issue.

I am currently working on sync Google calendar with Electron, and I made a separate server for token issuance because I don't want to store client_secret inside Electron code.

In this way, why don't you include a manual use case of issuing refresh_token without using client_secret? It's something I can understand by reading the document, but it takes time to figure out what I need, so I suggest it.

import { createAccount, fetchCalendars, fetchCalendarObjects } from 'tsdav';

const headers = {
  authorization: `Bearer <google_access_token>`,
};

const account = await createAccount({
  account: { 
    serverUrl: 'https://apidata.googleusercontent.com/caldav/v2/',
    accountType: 'caldav',
  },
  headers,
});

const calendars = await fetchCalendars({
  account,
  headers,
});

const calendarObjects = await fetchCalendarObjects({
  calendar: calendars[0],
  headers,
});

Handle time-range start & end string that contain timezone postfixes like +00:00

Currently when configuring a time-range it handles converting a ISO8601 time string to a ISO8601 time string without seperators, this works pretty well:

timeRange?.start.replace(/[-:.]/g, '')

// "2018-10-17T20:49:00Z".replace(/[-:.]/g, "")
// "20181017T204900Z" = neat

Based on which Date library or formatting you use however, I believe there are a couple of valid isodate strings that this library should support:

  • 2018-10-17T20:49:00+00:00 (Long form UTC, I believe this should auto-convert to Z) - currently this becomes the invalid string: 20181017T204900+0000.
  • 2018-10-17T20:49:00+08:00 (Local time, must not be used rfc5545) - currently becomes 20181017T204900+0800. My thoughts are this should strip the +0800 part OR maybe even throw an error that the user passed a non-UTC timestamp.

๐Ÿ‘ Great library - willing to help contribute the above, let me know what you think.

Missing `DTEND` from Synology Calendar CALDAV

When I parse the calendars from Synology NAS calendar CalDAV, the result of parsing would miss DTEND, only DURATION existed. That makes some error on consuming the parsing result.

The code that I used to get is;

const { DAVClient, DAVNamespace } = require('tsdav')

 const client = new DAVClient({ ... })

const fetchedCalendars = await client.fetchCalendars()
  for (let calendar of fetchedCalendars) {
    const objects = await client.fetchCalendarObjects({
      calendar,
    })
    ...

It will return something like this.

BEGIN:VEVENT
CREATED:20240215T101655
LAST-MODIFIED:20240215T101655
DTSTAMP:20240215T101655
UID:[email protected]
SEQUENCE:2
SUMMARY:period test2
TRANSP:OPAQUE
DESCRIPTION:
LOCATION:Kriftel
X-APPLE-STRUCTURED-LOCATION;VALUE=URI;X-TITLE=Kriftel:
 geo:50.083558\,8.4693855
DTSTART:20240215T090000Z
DURATION:PT15M
END:VEVENT

Originally that event was this;
image

Usually, feeds from other CalDAV provider (e.g. Google Calendar) are OK. Only feed from Synology Calendar has this symptom.
(Synology Calendar version: 2.4.6-20944 / Synology DSM 7.2.1-69057 Update 3 / Hardware DS920+)

As far as I know, RFC 5545 will regard, "date-time" type DTSTART without DTEND as a momently finished event. So this needs some help.

Could you take a look at this?

Unable to fetch additional props on fetchCalendars

I added the following property when I fetch my calendars from Apple.

    const props = {
...
      [`${DAVNamespaceShort.CALDAV_APPLE}:calendar-order`]: {},
...
    }
        const davCalendars = await davClient.fetchCalendars({ props })

However my davCalendars do then not include the calendarOrder.

How about adding the following PR: #136

So I could do:

const davCalendars = await davClient.fetchCalendars({ props, projectedProps: { calendarOrder: true } })

Sync Calendar Issue

Hi @natelindev, we are trying to sync our application events to Apple Device Calendar.

  1. We have a scheduled auto sync, and on every sync we delete all the calendar objects using deleteCalendarObject API.
  2. We re-insert using the createCalendarObject API.
  3. But on every auto sync, duplicate events are showing on Apple Devices, and it is not consistent on all device. Some device shows duplicate events on the apple calendar app and some don't.
  4. Just to note, when we visit iCloud.com/calendar, we see there are single events, but when the auto sync is happening we see the events getting duplicated, but as soon as the auto sync completes and we refresh the browser tab, we see again only the single events which is the expected result.

We have used the same API for Outlook and Google Calendar also, but we don't see any duplicate events there. Is our approach (deleting and re adding Calendar Objects) wrong for Auto Sync or is there a better way to achieve this? Is there any caching issue which we might be dealing with the Apple devices?

We also tried using the syncCalendars API but we are not sure what happens, does the calendar automatically syncs with the updated data or do we have to do something with the returned response from the API?

Google account can't update,delete event

Hello,

I have a problem when updating, deleting or creating caldav events with a google account. to compare i tried a nextcloud account, this can do all actions.

I get the following error message:

<?xml version="1.0" encoding="UTF-8"?>
<D:error xmlns:D="DAV:"/>

Object { type: "default", status: 400, ok: false, statusText: "Bad Request", headers: Object, url: "https://apidata.googleusercontent.cโ€ฆ", _bodyInit: Blob, _bodyBlob: Blob }

I'm trying to update an event like this:

let update_caldav = function (etag, url, data, account_id) {
  popup("Please wait...", "show");


  accounts.forEach(function (p) {
    if (p.id == account_id) {
      console.log("match");

      const client = "";
      if (p.type == "oauth") {
        client = new DAVClient({
          serverUrl: p.server_url,
          credentials: {
            tokenUrl: "https://oauth2.googleapis.com/token",
            refreshToken: p.tokens.refresh_token,
            clientId: google_cred.clientId,
            clientSecret: google_cred.clientSecret,
            authorizationCode: p.authorizationCode,
            redirectUrl: "https://strukturart.github.io/greg/",
          },
          authMethod: "Oauth",
          defaultAccountType: "caldav",
        });
      } else {
        client = new DAVClient({
          serverUrl: p.server_url,
          credentials: {
            username: p.user,
            password: p.password,
          },
          authMethod: "Basic",
          defaultAccountType: "caldav",
        });
      }
      (async () => {
        try {
          await client.login();
        } catch (e) {
          if (e.message == "Invalid credentials")
            toaster(
              "there was a problem logging into your account " +
                item.name +
                " please check your account details",
              5000
            );
        }
        try {
          const result = await client.updateCalendarObject({
            calendarObject: {
              url: url,
              data: data,
              etag: etag,
            },

            headers: client.authHeaders,
          });

          console.log(result);

          if (result.ok) {
            popup("", "close");
            m.route.set("/page_calendar");
            cache_caldav();
            //get new ETAG
            try {
              const [res] = await client.propfind({
                url: result.url,
                props: {
                  [`${DAVNamespaceShort.DAV}:getetag`]: {},
                },
                depth: "0",
                headers: client.authHeaders,
              });

              events.map((item) => {
                if (item.etag === etag) {
                  item.etag = res.props.getetag;
                  return item.etag;
                } else {
                  return item;
                }
              });
            } catch (e) {
              console.log(e);
            }
          } else {
            popup(
              "There was a problem saving, please try again later.",
              "show"
            );
            setTimeout(function () {
              popup("", "close");
            }, 5000);
          }
        } catch (e) {
          popup("There was a problem saving, please try again later.", "show");
          setTimeout(function () {
            popup("", "close");
          }, 5000);
        }
      })();
    }
  });
};

fetchAddressBooks results in all entities being listed

I'm using tsdav to connect to xandikos. During the `fetchAddressBooks() call, I see the following request being sent to xandikos:

 <?xml version="1.0" encoding="utf-8"?>
 <d:propfind xmlns:c="urn:ietf:params:xml:ns:caldav" xmlns:ca="http://apple.com/ns/ical/" xmlns:cs="http://calendarserver.org/ns/" xmlns:card="urn:ietf:params:xml:ns:carddav" xmlns:d="DAV:">
   <d:prop>
     <d:supported-report-set/>
   </d:prop>
 </d:propfind>

And I see the following response back:

<ns0:multistatus xmlns:ns0="DAV:" xmlns:ns1="urn:ietf:params:xml:ns:carddav">
  <ns0:response>
    <ns0:href>/user/contacts/addressbook/</ns0:href>
    <ns0:propstat>
      <ns0:status>HTTP/1.1 200 OK</ns0:status>
      <ns0:prop>
        <ns0:supported-report-set>
          <ns0:supported-report>
            <ns1:addressbook-multiget />
          </ns0:supported-report>
          <ns0:supported-report>
            <ns1:addressbook-query />
          </ns0:supported-report>
          <ns0:supported-report>
            <ns0:expand-property />
          </ns0:supported-report>
          <ns0:supported-report>
            <ns0:sync-collection />
          </ns0:supported-report>
        </ns0:supported-report-set>
      </ns0:prop>
    </ns0:propstat>
  </ns0:response>
  <ns0:response>
    <ns0:href>/user/contacts/addressbook/913e5a13-454c-e34a-945e-1354c9c5e92b.vcf</ns0:href>
    <ns0:propstat>
      <ns0:status>HTTP/1.1 404 Not Found</ns0:status>
      <ns0:prop>
        <ns0:supported-report-set />
      </ns0:prop>
    </ns0:propstat>
  </ns0:response>
  <ns0:response>
    <ns0:href>/user/contacts/addressbook/923e5a13-454c-e34a-945e-1354c9c5e92b.vcf</ns0:href>
    <ns0:propstat>
      <ns0:status>HTTP/1.1 404 Not Found</ns0:status>
      <ns0:prop>
        <ns0:supported-report-set />
      </ns0:prop>
    </ns0:propstat>
  </ns0:response>
  <ns0:response>
    <ns0:href>/user/contacts/addressbook/9479c4c0-ec6a-7b4d-87b4-3e0e07a55f1b.vcf</ns0:href>
    <ns0:propstat>
      <ns0:status>HTTP/1.1 404 Not Found</ns0:status>
      <ns0:prop>
        <ns0:supported-report-set />
      </ns0:prop>
    </ns0:propstat>
  </ns0:response>
  <ns0:response>
    <ns0:href>/user/contacts/addressbook/abc.vcf</ns0:href>
    <ns0:propstat>
      <ns0:status>HTTP/1.1 404 Not Found</ns0:status>
      <ns0:prop>
        <ns0:supported-report-set />
      </ns0:prop>
    </ns0:propstat>
  </ns0:response>
  <ns0:response>
    <ns0:href>/user/contacts/addressbook/abcd.vcf</ns0:href>
    <ns0:propstat>
      <ns0:status>HTTP/1.1 404 Not Found</ns0:status>
      <ns0:prop>
        <ns0:supported-report-set />
      </ns0:prop>
    </ns0:propstat>
  </ns0:response>
</ns0:multistatus>

I've tried the same against FastMail, and it also lists every record in the account. I'm quite new to the whole WebDAV space, and CardDAV, so I might be wrong here, but isn't there an option to find "address books" without pulling every record. I notice that in the response tsdav says res[0], so it looks like every item is being listed but only the first item is being used.

Of course, maybe that's just the way WebDAV works and there's no way round it, in which case feel free to close this issue without comment.

broken since 1.1.3 for next

1.1.2 is the last version that works with next

since then it can't find the module

ModuleNotFoundError: Module not found: Error: Can't resolve 'tsdav'

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Rate-Limited

These updates are currently rate-limited. Click on a checkbox below to force their creation now.

  • Replace dependency rollup-plugin-terser with @rollup/plugin-terser 0.1.0
  • Update dependency @tsconfig/docusaurus to v2.0.3
  • Update dependency clsx to v2.1.1
  • Update dependency sort-package-json to v2.10.0
  • Update dependency typescript to v5.4.5
  • Update dependency eslint to v9
  • ๐Ÿ” Create all rate-limited PRs at once ๐Ÿ”

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Detected dependencies

github-actions
.github/workflows/ci.yml
  • actions/checkout v4
  • actions/setup-node v4
.github/workflows/release.yml
  • actions/checkout v4
  • actions/setup-node v4
npm
dist/package.json
  • base-64 1.0.0
  • cross-fetch 4.0.0
  • debug 4.3.4
  • xml-js 1.6.11
  • @rollup/plugin-commonjs 25.0.7
  • @rollup/plugin-node-resolve 15.2.3
  • @rollup/plugin-typescript 11.1.6
  • @types/base-64 1.0.2
  • @types/debug 4.1.12
  • @types/jest 29.5.12
  • @types/node 20.11.19
  • @typescript-eslint/eslint-plugin 7.0.2
  • @typescript-eslint/parser 7.0.2
  • copyfiles 2.4.1
  • cross-env 7.0.3
  • dotenv 16.4.5
  • eslint 8.56.0
  • eslint-config-airbnb 19.0.4
  • eslint-config-airbnb-typescript 17.1.0
  • eslint-config-prettier 9.1.0
  • eslint-module-utils 2.8.0
  • eslint-plugin-import 2.29.1
  • eslint-plugin-prettier 5.1.3
  • jest 29.7.0
  • prettier 3.2.5
  • rimraf 5.0.5
  • rollup 4.12.0
  • rollup-plugin-dts 6.1.0
  • rollup-plugin-node-builtins 2.1.2
  • rollup-plugin-polyfill-node 0.13.0
  • rollup-plugin-terser 7.0.2
  • sort-package-json 2.8.0
  • ts-jest 29.1.2
  • tslib 2.6.2
  • typescript 5.3.3
  • node >=10
docs/package.json
  • @docusaurus/core 3.0.1
  • @docusaurus/preset-classic 3.0.1
  • @easyops-cn/docusaurus-search-local 0.38.1
  • @mdx-js/react 3.0.0
  • @svgr/webpack 8.1.0
  • buffer 6.0.3
  • clsx 2.1.0
  • file-loader 6.2.0
  • prism-react-renderer 2.3.1
  • react 18.2.0
  • react-dom 18.2.0
  • stream-browserify 3.0.0
  • url-loader 4.1.1
  • xml-js 1.6.11
  • @docusaurus/module-type-aliases 3.0.1
  • @tsconfig/docusaurus 2.0.2
  • @types/react 18.2.57
  • @types/react-helmet 6.1.11
  • @types/react-router-dom 5.3.3
  • typescript 5.3.3
package.json
  • base-64 1.0.0
  • cross-fetch 4.0.0
  • debug 4.3.4
  • xml-js 1.6.11
  • @rollup/plugin-commonjs 25.0.7
  • @rollup/plugin-node-resolve 15.2.3
  • @rollup/plugin-typescript 11.1.6
  • @types/base-64 1.0.2
  • @types/debug 4.1.12
  • @types/jest 29.5.12
  • @types/node 20.11.19
  • @typescript-eslint/eslint-plugin 7.0.2
  • @typescript-eslint/parser 7.0.2
  • copyfiles 2.4.1
  • cross-env 7.0.3
  • dotenv 16.4.5
  • eslint 8.56.0
  • eslint-config-airbnb 19.0.4
  • eslint-config-airbnb-typescript 17.1.0
  • eslint-config-prettier 9.1.0
  • eslint-module-utils 2.8.0
  • eslint-plugin-import 2.29.1
  • eslint-plugin-prettier 5.1.3
  • jest 29.7.0
  • prettier 3.2.5
  • rimraf 5.0.5
  • rollup 4.12.0
  • rollup-plugin-dts 6.1.0
  • rollup-plugin-node-builtins 2.1.2
  • rollup-plugin-polyfill-node 0.13.0
  • rollup-plugin-terser 7.0.2
  • sort-package-json 2.8.0
  • ts-jest 29.1.2
  • tslib 2.6.2
  • typescript 5.3.3
  • node >=10
nvm
.nvmrc
  • node v20

  • Check this box to trigger a request for Renovate to run again on this repository

Fetch public calendars

Is there a way to fetch public (nextcloud) calendars with tsdav? I.e. without authenticating against the server.

fetchCalendarObjects skipping events created on the Calendar directly

Hey,
I am using fetchCalendarObjects to fetch the calendar objects from the connected Kerio CalDAV calendar, and here's the issue I'm facing.

I'm using cal.com connector and when I create an event using Cal.com, that event is pulled just fine with the fetchCalendarObjects call. However, any events created directly on the Calendar are not pulled, but skipped entirely for some reason.

BEGIN:VCALENDAR
PRODID:-//Kerio Technologies//Kerio Connect//EN
METHOD: PUBLISH
VERSION:2.0
X-WR-CALNAME:Test's calendar - Calendar
BEGIN:VTIMEZONE
TZID:Europe/Berlin
BEGIN:STANDARD
DTSTART:19961027T030000
TZOFFSETTO:+0100
TZOFFSETFROM:+0200
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:19810329T020000
TZOFFSETTO:+0200
TZOFFSETFROM:+0100
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
DTSTAMP:20220808T203150Z
UID:4f220266-5620-4195-acea-084a90b0838f
DTSTART;TZID=Europe/Berlin:20220809T123000
SUMMARY:Block
DTEND;TZID=Europe/Berlin:20220809T210000
X-LABEL:0
PRIORITY:5
END:VEVENT
BEGIN:VEVENT
DTSTAMP:20220808T203159Z
UID:0db4c643-2924-4848-9450-dbb4cdc48367
DTSTART;TZID=Europe/Berlin:20220810T120000
SUMMARY:FREI 
DTEND;TZID=Europe/Berlin:20220810T170000
X-LABEL:0
PRIORITY:5
END:VEVENT
BEGIN:VEVENT
DTSTAMP:20220817T080733Z
UID:fbfb97cd-96f1-4456-a06b-202806897b82
DTSTART;TZID=Europe/Berlin:20220818T100000
SUMMARY:New Test Event
DTEND;TZID=Europe/Berlin:20220818T130000
X-LABEL:0
PRIORITY:5
END:VEVENT
BEGIN:VEVENT
UID:06436a27-4991-4c1e-ae44-d86af6a8818f
SUMMARY:30min between Pro Example and Test
DTSTAMP:20220817T060100Z
DTSTART:20220818T093000Z
DESCRIPTION:What:\n30min\n  \n\nInvitee Time Zone:\nAsia/Calcutta\n  \n\nWho
 :\n\nPro Example - Organizer\[email protected]\n  \nTest\[email protected]\n      \n 
  \nWhere:\n\n\n\n\n\nNeed to reschedule or cancel?\nhttp://localhost:3000/cancel/hzw
 hM1MgW89q83kahDpev6
ORGANIZER;CN="Pro Example":mailto:[email protected]
ATTENDEE;RSVP=FALSE;PARTSTAT=NEEDS-ACTION;CN=Test:mailto:[email protected]
DURATION:PT30M
END:VEVENT
BEGIN:VEVENT
UID:b4ad33ad-aaee-4320-aea3-52c1516b1afc
SUMMARY:30min between Pro Example and Another test
DTSTAMP:20220817T060100Z
DTSTART:20220825T043000Z
DESCRIPTION:What:\n30min\n  \n\nInvitee Time Zone:\nAsia/Calcutta\n  \n\nWho
 :\n\nPro Example - Organizer\[email protected]\n  \nAnother test\[email protected]\n
       \n  \nWhere:\n\n\n\n\n\nNeed to reschedule or cancel?\nhttp://localhost:3000/ca
 ncel/2u4dzH6aGRe2LTGiGErjmi
ORGANIZER;CN="Pro Example":mailto:[email protected]
ATTENDEE;RSVP=FALSE;PARTSTAT=NEEDS-ACTION;CN="Another test":mailto:[email protected]
DURATION:PT30M
END:VEVENT
BEGIN:VEVENT
UID:9bafe5ff-a10a-46d0-a447-61e8ab6948c6
SUMMARY:60min between Pro Example and FreeBusy Test
DTSTAMP:20220819T111800Z
DTSTART:20220824T073000Z
DESCRIPTION:What:\n60min\n  \n\nInvitee Time Zone:\nAsia/Calcutta\n  \n\nWho
 :\n\nPro Example - Organizer\[email protected]\n  \nFreeBusy Test\nfreebusy@example
 .com\n      \n  \nWhere:\n\n\n\n\n\nNeed to reschedule or cancel?\nhttp://localhost:30
 00/cancel/hXE2hFLZgvCeweLqDsxMpX
ORGANIZER;CN="Pro Example":mailto:[email protected]
ATTENDEE;RSVP=FALSE;PARTSTAT=NEEDS-ACTION;CN="FreeBusy Test":mailto:[email protected]
DURATION:PT60M
END:VEVENT
BEGIN:VEVENT
UID:f18cdfc1-c12e-4ea8-82dc-28168442c16a
SUMMARY:60min between Pro Example and FreeBusy Test
DTSTAMP:20220819T111800Z
DTSTART:20220822T073000Z
DESCRIPTION:What:\n60min\n  \n\nInvitee Time Zone:\nAsia/Calcutta\n  \n\nWho
 :\n\nPro Example - Organizer\[email protected]\n  \nFreeBusy Test\nfreebusy@example
 .com\n      \n  \nWhere:\n\n\n\n\n\nNeed to reschedule or cancel?\nhttp://localhost:30
 00/cancel/aNXdcyQStFQvMo3A3btP2q
ORGANIZER;CN="Pro Example":mailto:[email protected]
ATTENDEE;RSVP=FALSE;PARTSTAT=NEEDS-ACTION;CN="FreeBusy Test":mailto:[email protected]
DURATION:PT60M
END:VEVENT
END:VCALENDAR

the following are pulled just fine:

BEGIN:VEVENT
UID:06436a27-4991-4c1e-ae44-d86af6a8818f
SUMMARY:30min between Pro Example and Test
DTSTAMP:20220817T060100Z
DTSTART:20220818T093000Z
DESCRIPTION:What:\n30min\n  \n\nInvitee Time Zone:\nAsia/Calcutta\n  \n\nWho
 :\n\nPro Example - Organizer\[email protected]\n  \nTest\[email protected]\n      \n 
  \nWhere:\n\n\n\n\n\nNeed to reschedule or cancel?\nhttp://localhost:3000/cancel/hzw
 hM1MgW89q83kahDpev6
ORGANIZER;CN="Pro Example":mailto:[email protected]
ATTENDEE;RSVP=FALSE;PARTSTAT=NEEDS-ACTION;CN=Test:mailto:[email protected]
DURATION:PT30M
END:VEVENT

BEGIN:VEVENT
UID:b4ad33ad-aaee-4320-aea3-52c1516b1afc
SUMMARY:30min between Pro Example and Another test
DTSTAMP:20220817T060100Z
DTSTART:20220825T043000Z
DESCRIPTION:What:\n30min\n  \n\nInvitee Time Zone:\nAsia/Calcutta\n  \n\nWho
 :\n\nPro Example - Organizer\[email protected]\n  \nAnother test\[email protected]\n
       \n  \nWhere:\n\n\n\n\n\nNeed to reschedule or cancel?\nhttp://localhost:3000/ca
 ncel/2u4dzH6aGRe2LTGiGErjmi
ORGANIZER;CN="Pro Example":mailto:[email protected]
ATTENDEE;RSVP=FALSE;PARTSTAT=NEEDS-ACTION;CN="Another test":mailto:[email protected]
DURATION:PT30M
END:VEVENT

BEGIN:VEVENT
UID:9bafe5ff-a10a-46d0-a447-61e8ab6948c6
SUMMARY:60min between Pro Example and FreeBusy Test
DTSTAMP:20220819T111800Z
DTSTART:20220824T073000Z
DESCRIPTION:What:\n60min\n  \n\nInvitee Time Zone:\nAsia/Calcutta\n  \n\nWho
 :\n\nPro Example - Organizer\[email protected]\n  \nFreeBusy Test\nfreebusy@example
 .com\n      \n  \nWhere:\n\n\n\n\n\nNeed to reschedule or cancel?\nhttp://localhost:30
 00/cancel/hXE2hFLZgvCeweLqDsxMpX
ORGANIZER;CN="Pro Example":mailto:[email protected]
ATTENDEE;RSVP=FALSE;PARTSTAT=NEEDS-ACTION;CN="FreeBusy Test":mailto:[email protected]
DURATION:PT60M
END:VEVENT

BEGIN:VEVENT
UID:f18cdfc1-c12e-4ea8-82dc-28168442c16a
SUMMARY:60min between Pro Example and FreeBusy Test
DTSTAMP:20220819T111800Z
DTSTART:20220822T073000Z
DESCRIPTION:What:\n60min\n  \n\nInvitee Time Zone:\nAsia/Calcutta\n  \n\nWho
 :\n\nPro Example - Organizer\[email protected]\n  \nFreeBusy Test\nfreebusy@example
 .com\n      \n  \nWhere:\n\n\n\n\n\nNeed to reschedule or cancel?\nhttp://localhost:30
 00/cancel/aNXdcyQStFQvMo3A3btP2q
ORGANIZER;CN="Pro Example":mailto:[email protected]
ATTENDEE;RSVP=FALSE;PARTSTAT=NEEDS-ACTION;CN="FreeBusy Test":mailto:[email protected]
DURATION:PT60M
END:VEVENT

The rest are not. Is there a reason why this is?

Support browser build for CDN

It would be great, if the package can be adapted so that https://unpkg.com/ can handle it correctly.

I tried to read how this can be achieved but I have to admit that I'm a little bit lost were to start.

client.smartCollectionSync detailedResult can't be passed as true

Hey there,

It seems there's a bug within the type declaration. The function smartCollectionSync doesn't allow detailedResult to be passed as true (can only be passed as undefined or false).

image
image

When I set it to true as false, the expected response is received.

image
image

The documentation shows that detailedResult can be passed as true so I assume this should be valid. I'm not familiar with caldav so I might be making the wrong assumption though.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.