Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot fetch calendar from caldav.icloud.com #238

Open
cmdr-dmoondra opened this issue Dec 26, 2024 · 7 comments
Open

Cannot fetch calendar from caldav.icloud.com #238

cmdr-dmoondra opened this issue Dec 26, 2024 · 7 comments

Comments

@cmdr-dmoondra
Copy link

I am unable to fetch the data from caldav.icloud.com, always getting the error for "cannot find homeUrl"

I am using this approach,

const client = new DAVClient({
serverUrl: 'https://caldav.icloud.com',
credentials: {
username: '[email protected]',
password: 'xxx',
},
authMethod: 'Basic',
defaultAccountType: 'caldav',
});

and after this when i call the await client.login(); i get the error saying -> "cannot find homeUrl"

@CyrisXD
Copy link

CyrisXD commented Dec 27, 2024

I'm in the same boat, still trying to diagnose the issue.

@CyrisXD
Copy link

CyrisXD commented Dec 27, 2024

@cmdr-dmoondra Reverting back to version 2.1.2 seems to fix the issue.

@cmdr-dmoondra
Copy link
Author

No, the issue still not fixed yet!
Screenshot 2024-12-28 at 11 24 21 AM

@nwLSteam
Copy link

nwLSteam commented Jan 5, 2025

I "fixed" it. iCloud does not like the Accept- headers that fetch uses for some reason.

First, add a fetchOptions to your client init that disables the headers:

const client = new DAVClient({
    // ...
    fetchOptions: {
        headers: {
            "Accept-Encoding": null as unknown as string,
            "Accept-Language": null as unknown as string,
        }
    }
});

(as unknown as string is needed because the typing is only intended to add headers, so we need to trick it to be able to remove headers)

Then, edit the library code. For me, the called file was in dist/tsdav.cjs.js, but unfortunately I do not know enough about JS modules to assure you that it's always that file.

Look for const davResponse in the function davRequest and replace

headers: {
    "Content-Type": "application/xml",
    ...cleanupFalsy(headers),
},

with

headers: Object.assign({
    "Content-Type": "application/xml",
    ...cleanupFalsy(headers),
}, fetchOptions.headers),

Now the requests should work, they succeeded for me.

@natelindev Please make davRequest() respect fetchOptions, I see it is commented out currently, which makes sense as the spread operator would override the headers if set, not add to them. I suggest using deepmerge or your own implementation.

@if-nil
Copy link

if-nil commented Jan 6, 2025

When calling fetchPrincipalUrl, the HTTP header will add "accept-language: *" by default, which will cause Apple to return Internal Server Error. However, if it is not *, no error will occur. To avoid this problem, there are two temporary solutions:

low level api:

import { fetchPrincipalUrl, getBasicAuthHeaders } from "tsdav";

(async () => {
  const principalUrl = await fetchPrincipalUrl({
    account: {
      serverUrl: "https://caldav.icloud.com.cn", 
      rootUrl: "https://caldav.icloud.com.cn",
      accountType: "caldav",
    },
    headers: {
      ...getBasicAuthHeaders({
      username: "{ your account id }",
      password: "{ your password }",
      }),
      "accept-language": "zh-CN,en;q=0.5",   // add "accept-language" field.  Perhaps you should set this value according to your actual situation
    },
  });
})();

or client api:

import { createDAVClient, getBasicAuthHeaders } from "tsdav";

(async () => {
  const client = await createDAVClient({
    serverUrl: "https://caldav.icloud.com.cn",
    credentials: {
      username: "{ your account id }",
      password: "{ your password }",
    },
    authMethod: "Custom",    // Important   use Custom
    defaultAccountType: "caldav",
    authFunction: async (credentials) => {
      return {
        ...getBasicAuthHeaders(credentials),
        "accept-language": "zh-CN,en;q=0.5",   // add "accept-language" field.  Perhaps you should set this value according to your actual situation
      };
    },
  });

  const calendars = await client.fetchCalendars();
  console.log(calendars);
})();

Note: The above code has only been tested on https://caldav.icloud.com.cn/ (This is a host serving Chinese customers), and not sure if other Apple hosts are available

@nwLSteam
Copy link

nwLSteam commented Jan 7, 2025

So authFunction returns headers used for every request? That wasn't clear to me (based on the naming I assumed it authenticated you manually). I guess you can use this as a better workaround then.

That said, only changing Accept-Language did not suffice for me. If Accept-Encoding stayed, I got unreadable binary data (I assume because of some compression algorithm mismatch? I didn't experiment further seeing as it worked when I removed the header).

@if-nil
Copy link

if-nil commented Jan 7, 2025

So authFunction returns headers used for every request?

Yes, I saw from the source code that the added header will be added to every request as authHeaders:

  let authHeaders: Record<string, string> = {};
  switch (authMethod) {
    case 'Basic':
      authHeaders = getBasicAuthHeaders(credentials);
      break;
    case 'Oauth':
      authHeaders = (await getOauthHeaders(credentials)).headers;
      break;
    case 'Digest':
      authHeaders = {
        Authorization: `Digest ${credentials.digestString}`,
      };
      break;
    case 'Custom':
      authHeaders = (await authFunction?.(credentials)) ?? {};
      break;
    default:
      throw new Error('Invalid auth method');
  }

That said, only changing Accept-Language did not suffice for me. If Accept-Encoding stayed, I got unreadable binary data (I assume because of some compression algorithm mismatch? I didn't experiment further seeing as it worked when I removed the header).

Maybe it might be true that the host is different, leading to different results (icloud.com and icloud.com.cn are isolated)

Since my project is still in development, I'm currently only using the function to get the calendar schedule. I'm not quite sure if there's still a problem with the features I'm not using

Here's a complete, example of what worked for me that might help you and others with your testing

import { createDAVClient, getBasicAuthHeaders } from "tsdav";

(async () => {
  const client = await createDAVClient({
    serverUrl: "https://caldav.icloud.com.cn",
    credentials: {
      username: "{account id}",
      password: "{password}",
    },
    authMethod: "Custom",
    defaultAccountType: "caldav",
    authFunction: async (credentials) => {
      return {
        ...getBasicAuthHeaders(credentials),
        "accept-language": "zh-CN,en;q=0.5",
      };
    },
  });

  const calendars = await client.fetchCalendars();
  const obj = await client.fetchCalendarObjects({
    calendar: calendars[0],
  });
  console.log("CalendarObjects:\n", obj);
})();

Then run in terminal:

DEBUG=* node test.js

Then I can get the events that I have created on the calendar:

image image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants