Come integrare l'API di iCloud Calendar nella tua app

Autori
Pubblicato il

Nel nostro articolo precedente, abbiamo spiegato come integrare l'API di Google Calendar nella tua app, evidenziando tutti i passaggi necessari affinché l'integrazione funzioni.

A differenza di Google Calendar, integrare iCloud Calendar non è altrettanto semplice, poiché la documentazione è scarsa e Apple non ha dedicato lo stesso impegno a dettagliare ciò che uno sviluppatore deve fare per integrare iCloud Calendar in un’applicazione.

In questo articolo approfondiremo come integrare l’API di iCloud Calendar nella tua applicazione, inclusi autenticazione, operazioni supportate, limitazioni e strumenti che puoi utilizzare per semplificare l’integrazione.

Quali protocolli e standard utilizza Apple iCloud Calendar?

Apple iCloud Calendar utilizza lo standard CalDAV per la comunicazione dei calendari. CalDAV è un’estensione di WebDAV che consente ai client di gestire calendari ed eventi su un server.

Gli eventi sono rappresentati nel formato ICS (iCalendar), un formato basato su testo per i dati di calendario. Ciò significa che la tua applicazione può comunicare con i calendari iCloud tramite HTTP utilizzando richieste CalDAV e dati ICS.

Il vantaggio è che non si tratta di un’implementazione specifica per i dispositivi o i sistemi operativi Apple; puoi utilizzarla da qualsiasi server che gira su qualunque piattaforma.

La cattiva notizia è che iCloud non fornisce un’API REST per i calendari, quindi CalDAV è l’unico modo per integrare iCloud Calendar nella tua app.

Ti consigliamo di realizzare l’integrazione server-to-server; ecco perché diciamo che CalDAV è l’unica strada percorribile.

Come ci si autentica con iCloud?

Di solito, quando vuoi integrare una piattaforma nella tua applicazione (ad esempio Google Calendar), devi creare un account sviluppatore su quella piattaforma, creare un’applicazione, configurare gli scope, aggiungere utenti di test, compilare le informazioni dell’app e inviare per l’approvazione.

Dopo aver seguito tutti questi passaggi ed essere stato approvato, puoi quindi reindirizzare l’utente finale alla schermata OAuth della piattaforma, dove l’utente deve essere loggato e concedere l’accesso esplicito allo scope richiesto dalla tua applicazione. L’utente vede anche il nome della tua applicazione e tutte le informazioni che hai fornito quando hai configurato l’applicazione su quella piattaforma.

Apple iCloud non funziona in questo modo, poiché iCloud non dispone di un flusso OAuth standard come Google Calendar o Outlook. Per collegare il calendario iCloud di un utente, devi autenticarti con il suo Apple ID utilizzando l’Autenticazione di base (Basic Auth) su SSL. Poiché la maggior parte degli account iCloud ha l’autenticazione a due fattori, l’utente deve creare una password specifica per l’app tramite le impostazioni del proprio account Apple ID invece di usare la password principale dell’account Apple.

La tua app chiederà all’utente il suo indirizzo email iCloud/Apple ID e questa password specifica per l’app di 16 caratteri. Usando queste credenziali, puoi connetterti al servizio CalDAV di iCloud.

Esempio di come la tua applicazione chiederebbe all’utente di inserire l’indirizzo email e la password specifica per l’app.

apple-enter-app-specific-password.webp

Oltre alle ragioni elencate sopra, Apple richiede password specifiche per le app per l’accesso dei calendari di terze parti al fine di migliorare la sicurezza, poiché condividere la tua password iCloud con un’applicazione di terze parti non è l’ideale.

A livello tecnico, l’intestazione (header) della tua richiesta HTTP includerà la password specifica per l’app codificata in Base64, in questo formato: Authorization: Basic <app-specific-password-here>

Quali metodi supporta l’API di iCloud Calendar?

Il servizio CalDAV di iCloud è ospitato su caldav.icloud.com. Dopo l’autenticazione, è disponibile il seguente elenco di metodi:

  • Elencare i calendari di un utente

  • CRUD sugli eventi

  • Recuperare eventi specifici

Per saperne di più sullo standard CalDAV, leggi RFC 4791, che spiega tutti i metodi disponibili, i filtri e altro ancora.

Di seguito riepilogo le informazioni più importanti che devi conoscere per la tua integrazione con iCloud Calendar.

Verbi HTTP supportati:

Verbo HTTPCosa fa in CalDAVSupporto iCloudNote
OPTIONSScopre le capacità del serverUtile per il debug; non richiesto in produzione.
PROPFINDCerca principal, calendar-home-set, elenca calendari, ottiene proprietàÈ necessario autenticarsi prima; usare Depth 0 o 1.
MKCALENDARCrea una nuova collezione calendarioRichiede diritti di scrittura; vedi Sezione 5.3.1.
REPORTInterroga dati (calendar-query, calendar-multiget, free-busy-query)Tutti e tre i report sono obbligatori per le specifiche e presenti su iCloud.
PUTCarica / sostituisce una risorsa .ics (evento/attività)È necessario inviare l’intero VCALENDAR; niente PATCH.
DELETERimuove un evento o un calendarioDa usare insieme a If-Match ETag per sicurezza.
COPY / MOVECopia o sposta eventi tra calendariSoggetto alle stesse pre-condizioni di PUT.
GETRecupera una singola risorsa .icsRestituisce text/calendar ed ETag.

Proprietà delle collezioni calendario

ProprietàScopoNote specifiche di iCloud
CALDAV:calendar-descriptionDescrizione leggibilePienamente supportata
CALDAV:calendar-timezoneFuso orario predefinito per le querySupportato
CALDAV:supported-calendar-component-setQuali componenti (VEVENT, VTODO) il calendario accettaEventi e attività in calendari separati.
CALDAV:supported-calendar-dataMIME/versione consentita (di solito text/calendar 2.0)iCloud = default
CALDAV:max-resource-sizeDimensioni massime per eventoLimite iCloud ≈ 20 MB
CALDAV:min/max-date-time, max-instances, max-attendees-per-instanceVari limiti del serverRispettare per evitare errori 403/507.

L’OneCal Unified Calendar API è quasi pronta, iscriviti alla nostra lista d’attesa per essere avvisato al lancio e approfittare delle promozioni di lancio.

Quali librerie puoi usare per semplificare l’integrazione?

Con CalDAV, ICS, XML e gli altri dettagli specifici di iCloud Calendar, l’integrazione non è ideale: spenderesti molto tempo a comprendere ogni metodo, convertire XML in JSON e usarlo nella tua applicazione.

Per un’integrazione più agevole, consigliamo le seguenti librerie:

  • tsdav: Imperdibile se utilizzi JavaScript/TypeScript. Con tsdav puoi comunicare facilmente con il server iCloud senza dover usare la sintassi o la terminologia specifica di CalDAV. tsdav fornisce un’API TypeScript di alto livello che incapsula tutti i verbi HTTP e l’XML che altrimenti scriveresti manualmente (PROPFIND, REPORT, MKCALENDAR, PUT, DELETE, ecc.). Consulta la documentazione di tsdav per saperne di più su come funziona.

  • ical-generator: Quando integri iCloud Calendar tramite CalDAV, devi caricare e sostituire interi file .ics ogni volta che crei o aggiorni un evento. Scrivere questi file a mano è soggetto a errori e ogni VEVENT richiede gli header corretti, UID, formattazione DTSTART/DTEND, stringhe RRULE, fusi orari e altro. ical-generator ti aiuta a gestire tutti questi aspetti.

  • ical.js è un parser/engine JavaScript puro creato dal team Mozilla Calendar, usato per analizzare le risposte ics in classi JS.

La tabella seguente spiega il ruolo di tsdav nell’integrazione con iCloud Calendar:

Ruolo nello stackCosa fa tsdavPerché è importante per iCloud
CalDAV / WebDAV clientFornisce un’API TypeScript di alto livello che incapsula tutti i verbi HTTP e l’XML che altrimenti dovresti scrivere e convertire manualmente.Ti permette di concentrarti sulla logica di business invece di generare stringhe XML raw e analizzare le risposte multistatus.
Helper di discoverycreateDAVClient() segue automaticamente il flusso di discovery CalDAV: contatta caldav.icloud.com, trova il principal dell’utente, risolve il calendar-home-set e memorizza l’URL base corretto pXX-caldav.icloud.com per le chiamate successive.Elimina il boilerplate per la danza di discovery in due fasi unica di iCloud.
Wrapper di autenticazioneHelper integrati per Basic e OAuth 2. Per iCloud passi { username: 'user@icloud.com', password: '<app-specific-pw>', authMethod: 'Basic' }.Non serve codificare le credenziali in Base64 o inserire gli header manualmente.
Helper tipizzati per attività comunifetchCalendars(), fetchCalendarObjects(), createCalendarObject(), updateCalendarObject(), deleteCalendarObject() restituiscono/accettano oggetti JS plain invece di XML.Implementi rapidamente il CRUD senza preoccuparti della sintassi XML di RFC-4791.
Supporto token di syncsyncCollection() incapsula il REPORT CalDAV sync-collection, traccia i token e restituisce solo gli elementi modificati/eliminati.Ti consente di implementare il polling per iCloud (che non ha push) con una sola riga.
Compatibilità Browser + NodeFunziona nel codice server (Node) o nel browser grazie all’uso di fetch isomorfico.Utile se parte della tua app gira in un’estensione browser o SPA.
Progetto TS moderno e tipizzatoViene fornito con type def completi, moduli ES tree-shakable e dipendenze minime.Facile da integrare nelle pipeline di build moderne.

tsdav non genera iCalendar. Usalo insieme a ical-generator (o un altro builder ICS di tua scelta) per produrre le stringhe del payload degli eventi.

Esempio di integrazione di iCloud Calendar usando tsdav + ts

In questo esempio si presume che tu abbia già ottenuto lo username dell’utente e la password specifica per l’app di Apple.

createClient Metodo che crea il DAVClient:

import { DAVCalendar, DAVClient, DAVNamespaceShort, DAVObject } from "tsdav";

const APPLE_DAV_URL = "https://caldav.icloud.com";

function createClient({
username,
password,
}: {
username: string;
password: string;
}) {
 const client = new DAVClient({
 serverUrl: APPLE_DAV_URL,
 credentials: {
username,
password,
},
 authMethod: "Basic",
 defaultAccountType: "caldav",
});

 return client;
}

constants File che contiene costanti

export const PROD_ID = {
company: "your-company-name-here",
product: "your-product-name-here",
};

getCalendars Metodo che recupera tutti i calendari

async getCalendars(
...params: Parameters<typeof DAVClient.prototype.fetchCalendars>
): Promise<DAVCalendar[]> {

 // You can abstract this initialization into another method. // For the sake of simplicity, we'll initialize the client on each method. const client = createClient({
 username: <email-here>,
 password: <password-here>,
});

 return client.fetchCalendars(...params);
}

getCalendarById Metodo che recupera un calendario per ID

async getCalendarById(calendarUrl: string): Promise<DAVCalendar> {
 const calendars = await getCalendars();
 const calendar = calendars.find((el) => el.url === calendarUrl);
 if (!calendar) {
 throw new Error(`Apple Calendar with id ${calendarUrl} not found`);
}

 return calendar;
}

getCalendarEvents Metodo per elencare tutti gli eventi del calendario

async getCalendarEvents(
calendarUrl: string,
query: GetCalendarEventsQuery = {}
) {
 const client = createClient({
 username: <email-here>,
 password: <password-here>,
});

 const calendar = await getCalendarById(calendarUrl);

 const events = await client.fetchCalendarObjects({
calendar,
 timeRange: query.dateRange ?? undefined,
})


 return { events, nextSyncToken: calendar.syncToken };
}

getEventById Metodo che restituisce un evento del calendario per ID

async getEventById(calendarUrl: string, eventId: string) {
 const client = createClient({
 username: <email-here>,
 password: <password-here>,
});

 const eventUrl = new URL(`${eventId}.ics`, calendarUrl).pathname;

 const responses = await client.calendarMultiGet({
 url: calendarUrl,
 props: {
[`${DAVNamespaceShort.DAV}:getetag`]: {},
[`${DAVNamespaceShort.CALDAV}:calendar-data`]: {},
},
 objectUrls: [eventUrl],
 depth: "1",
})


 if (responses.length === 0) {
 throw new Error(
 `Received no response while fetching ${eventUrl}`,
 null
);
} else if (responses[0].status >= 400) {
 throw new Error(
 `Failed to get Apple event by id. Status: ${responses[0].statusText}`,
responses[0]
);
}

 const response = responses[0];
 const calendarObject: DAVObject = {
 url: new URL(response.href ?? "", calendarUrl).href,
 etag: `${response.props?.getetag}`,
 data:
response.props?.calendarData?._cdata ?? response.props?.calendarData,
};

 try {
 return calendarObject;
} catch (err: any) {
 this.logger.error("Failed to process Apple Response", {
 message: err.message,
 event: calendarObject,
});
 return [];
}
}

createEvent Metodo che crea un evento

async createEvent(calendarUrl: string, data: AppleEvent) {
 const client = createClient({
 username: <email-here>,
 password: <password-here>,
});

 const eventId = data.id ?? <generate-id-here>

 const calendar = ical({
 prodId: PROD_ID,
 method: ICalCalendarMethod.REQUEST,
});

 const event = calendar.createEvent({ ...data, id: eventId });

 const response = await client.createCalendarObject({
 calendar: {
 url: calendarUrl,
},
 filename: `${event.id()}.ics`,
 iCalString: calendar.toString(),
})

 if (!response.ok) {
 throw new Error(
 `Failed to create Apple event: ${response.statusText}`,
response
);
}

 return { id: event.id(), eventWithExceptions };
}

updateEvent Metodo che aggiorna un evento

async updateEvent(calendarUrl: string, eventId: string, data: AppleEvent) {
 const client = createClient({
 username: <email-here>,
 password: <password-here>,
});


 const originalEventData = await getEventById(calendarUrl, eventId);

 const calendar = ical({
 prodId: PROD_ID,
 method: ICalCalendarMethod.REQUEST,
});

 for (let event of originalEventData) {
 if (event.status === ICalEventStatus.CANCELLED) continue;

 if (event.id === eventId) {
calendar.createEvent({ ...event, ...data, id: eventId, url: null });
} else {
calendar.createEvent({ ...event, id: eventId, url: null });
}
}

 const calendarObjectUrl = new URL(`${eventId}.ics`, calendarUrl);

 const response = await
client.updateCalendarObject({
 calendarObject: {
 url: calendarObjectUrl.href,
 data: calendar.toString(),
},
})

 if (!response.ok) {
 throw new Error(
 `Failed to update Apple event: ${response.statusText}`,
response
);
}

 return getEventById(calendarUrl, eventId);
}

deleteEvent Metodo che elimina un evento per ID

async deleteEvent(calendarUrl: string, eventId: string) {
 const client = createClient({
 username: <email-here>,
 password: <password-here>,
});


 const calendarObjectUrl = new URL(`${eventId}.ics`, calendarUrl);

 const response = await client.deleteCalendarObject({
 calendarObject: {
 url: calendarObjectUrl.href,
},
})

 if (!response.ok) {
 throw new Error(
 `Failed to delete Apple event: ${response.statusText}`,
response
);
}

 return { id: eventId };
}

Quali limitazioni ha Apple iCloud Calendar?

  1. Basic Auth con password specifica per l’app: Come menzionato, iCloud non segue le convenzioni standard OAuth 2.0; devi usare una password specifica per l’app per autenticarti e comunicare con iCloud Calendar.

  2. Nessun supporto per webhook/notifiche push: A differenza di Google Calendar o Outlook, iCloud non dispone della migliore API di calendario, poiché non puoi registrare webhook per essere notificato delle modifiche nei calendari. Le app di terze parti non possono iscriversi a aggiornamenti in tempo reale. Un workaround è effettuare polling periodico e utilizzare il report sync-collection per ottenere in modo efficiente le modifiche.

  3. Nessun supporto per i metodi PATCH: Non puoi usare i metodi PATCH per aggiornare parzialmente gli eventi; devi invece eseguire un PUT completo per aggiornare gli eventi.

  4. Nessun controllo sugli inviti: iCloud Calendar gestisce automaticamente gli inviti alle riunioni. Se crei o modifichi un evento con partecipanti, iCloud Calendar invierà gli inviti e aggiornerà gli stati dei partecipanti. Non puoi usare la scheduling Outbox/Inbox CalDAV per controllare manualmente gli inviti.

Esiste un modo più semplice per integrare iCloud Calendar nella mia applicazione?

Integrare iCloud Calendar nella tua applicazione non è un’impresa da poco, poiché non segue le convenzioni standard dei calendari e molti metodi e filtri CalDAV supportati non funzionano.

Un modo più semplice per integrare iCloud Calendar nella tua applicazione è usare un’API di calendario unificata (Unified Calendar API).

Unified Calendar API Example

Usare un Unified Calendar API offre i seguenti vantaggi:

  • Integra iCloud Calendar nella tua applicazione utilizzando un’API ben documentata e testata che segue standard moderni.

  • Trascorri meno tempo nello sviluppo e nella manutenzione dell’integrazione. In definitiva, usare una Unified Calendar API ti fa risparmiare tempo, poiché il prodotto ha già risolto la maggior parte delle problematiche, a partire dall’API, i client, i casi limite, ecc. Inoltre, non dovrai mantenere l’integrazione o correggere i casi limite che potrebbero presentarsi.

  • Integra altri provider di calendario oltre a iCloud Calendar nella tua applicazione senza lavoro aggiuntivo. La maggior parte dei prodotti Unified Calendar API si integra con i principali provider di calendario moderni, rendendo più facile integrare tutti i provider, come Google Calendar e Outlook, nella tua applicazione.

  • Supporto per notifiche push/webhook senza dover scrivere soluzioni di polling personalizzate. Scrivere una soluzione di polling per iCloud è difficile, poiché dovresti avviare una coda e nuovi server per elaborare i messaggi.

Usa una Unified Calendar API per integrare iCloud Calendar nella tua applicazione

Siamo quasi pronti a lanciare la nostra OneCal Unified Calendar API, che ti offrirà tutti i vantaggi di una Unified Calendar API che abbiamo evidenziato sopra.

Iscriviti alla nostra lista d’attesa per essere avvisato non appena lanceremo e approfittare degli sconti e delle promozioni di lancio.