Comment intégrer l’API iCloud Calendar dans votre application

Auteurs
Publié le

Dans notre article précédent, nous avons expliqué comment intégrer l’API Google Calendar à votre application, en détaillant toutes les étapes nécessaires pour que l’intégration fonctionne.

Contrairement à Google Calendar, l’intégration du calendrier iCloud n’est pas aussi simple : la documentation est insuffisante et Apple n’a guère précisé ce que les développeurs doivent faire pour intégrer iCloud Calendar dans une application.

Dans cet article, nous allons examiner en profondeur comment intégrer l’API iCloud Calendar à votre application : authentification, opérations prises en charge, limitations et outils pouvant faciliter l’intégration.

Quels protocoles et standards le calendrier Apple iCloud utilise-t-il ?

Apple iCloud Calendar s’appuie sur le standard CalDAV pour la communication calendrier. CalDAV est une extension de WebDAV qui permet aux clients de gérer des calendriers et des événements sur un serveur.

Les événements sont représentés au format ICS (iCalendar), un format texte pour les données calendaires. Votre application peut donc communiquer avec les calendriers iCloud via HTTP en utilisant des requêtes CalDAV et des données ICS.

L’avantage est qu’il ne s’agit pas d’une implémentation spécifique à un appareil ou à un système Apple ; vous pouvez l’utiliser depuis n’importe quel serveur, quel que soit le système d’exploitation.

La mauvaise nouvelle, c’est qu’iCloud ne fournit pas d’API REST pour les calendriers : CalDAV est donc la seule façon d’intégrer iCloud Calendar à votre application.

Nous recommandons une intégration serveur-à-serveur ; c’est pourquoi nous affirmons que CalDAV est la seule voie possible.

Comment s’authentifier auprès d’iCloud ?

Habituellement, pour intégrer une plateforme dans votre application (par exemple Google Calendar), vous devez :

  1. Créer un compte développeur sur la plateforme.

  2. Créer une application, configurer les scopes, ajouter des utilisateurs de test, renseigner les informations de l’app, puis soumettre pour validation.

  3. Après approbation, rediriger l’utilisateur final vers l’écran OAuth de la plateforme, où il se connecte et accorde les accès demandés.

iCloud ne fonctionne pas ainsi : il n’existe pas de flux OAuth standard comme pour Google Calendar ou Outlook. Pour connecter le calendrier iCloud d’un utilisateur, vous devez l’authentifier avec son Apple ID via Basic Auth sur SSL. Comme la plupart des comptes iCloud utilisent l’authentification à deux facteurs, l’utilisateur doit créer un mot de passe spécifique à l’app dans les réglages de son compte Apple ID, au lieu d’utiliser son mot de passe principal.

Votre application demandera donc l’adresse iCloud/Apple ID de l’utilisateur et ce mot de passe spécifique à l’app (16 caractères). Avec ces identifiants, vous pourrez vous connecter au service CalDAV d’iCloud.

Exemple de demande d’adresse e-mail et de mot de passe spécifique à l’app dans votre application.

apple-enter-app-specific-password.webp

Outre les raisons évoquées ci-dessus, Apple impose ces mots de passe spécifiques pour renforcer la sécurité ; partager son mot de passe iCloud avec une application tierce n’est clairement pas recommandé.

Sous le capot, l’en-tête de votre requête HTTP inclura un mot de passe encodé en Base64 : Authorization: Basic <mot-de-passe-spécifique-à-l’app> 

Quelles méthodes l’API iCloud Calendar prend-elle en charge ?

Le service CalDAV d’iCloud est hébergé sur caldav.icloud.com. Après authentification, les méthodes suivantes sont disponibles :

  • Liste des calendriers de l’utilisateur

  • CRUD sur les événements

  • Récupération d’événements spécifiques

Pour en savoir plus sur CalDAV, consultez la RFC 4791, qui décrit toutes les méthodes et filtres disponibles.

Voici les informations essentielles pour votre intégration iCloud Calendar :

Verbes HTTP pris en charge

Verbe HTTPFonction dans CalDAVSupport iCloudParticularités
OPTIONSDécouvre les capacités du serveurUtile pour le débogage ; non requis en production.
PROPFINDRecherche de principals, calendar-home-set, liste les calendriers, récupère des propriétésAuthentification préalable obligatoire ; utiliser Depth 0 ou 1.
MKCALENDARCrée une nouvelle collection de calendrierDroits d’écriture requis ; voir section 5.3.1.
REPORTInterroge des données (calendar-query, calendar-multiget, free-busy-query)Les trois rapports sont obligatoires et disponibles sur iCloud.
PUTTéléverse / remplace une ressource .ics (événement/tâche)Nécessite un VCALENDAR complet ; pas de PATCH.
DELETESupprime un événement ou un calendrierAssocier à If-Match ETag pour la sécurité.
COPY / MOVECopie ou déplace des événements entre calendriersSoumis aux mêmes pré-conditions que PUT.
GETRécupère une ressource .ics uniqueRetourne text/calendar et ETag.

Propriétés d’une collection de calendriers

PropriétéUtilitéNotes spécifiques à iCloud
CALDAV:calendar-descriptionDescription lisibleEntièrement pris en charge
CALDAV:calendar-timezoneFuseau horaire par défaut pour les requêtesPris en charge
CALDAV:supported-calendar-component-setComposants acceptés (VEVENT, VTODO)Événements et tâches dans des calendriers séparés
CALDAV:supported-calendar-dataMIME/version autorisée (text/calendar 2.0)Valeur par défaut iCloud
CALDAV:max-resource-sizeTaille maximale par événementEnviron 20 Mo sur iCloud
CALDAV:min/max-date-time, max-instances, max-attendees-per-instanceLimites serveur diversesRespecter pour éviter erreurs 403/507

L’API OneCal Unified Calendar est presque prête ; inscrivez-vous à la liste d’attente pour être informé du lancement et profiter des promotions.

Quelles bibliothèques peuvent simplifier l’intégration ?

Manipuler CalDAV, ICS, XML et les spécificités d’iCloud est fastidieux : il faut comprendre chaque méthode, convertir le XML en JSON, etc.

Pour une intégration plus fluide, nous recommandons :

  • tsdav : indispensable en JavaScript/TypeScript. Fournit une API TS de haut niveau qui encapsule les verbes HTTP et le XML (PROPFIND, REPORT, MKCALENDAR, PUT, DELETE, …). Voir la documentation tsdav.

  • ical-generator : iCloud impose de téléverser des fichiers .ics complets lors de la création ou de la mise à jour d’événements. Cette bibliothèque génère correctement les fichiers (UID, DTSTART/DTEND, RRULE, time-zones, etc.).

  • ical.js : moteur JavaScript pur (Mozilla) pour analyser les réponses ICS et les convertir en classes JS.

Tableau récapitulatif du rôle de tsdav :

Rôle dans la pileCe que fait tsdavPourquoi c’est important pour iCloud
Client CalDAV / WebDAVAPI TypeScript haut niveau encapsulant verbes HTTP et XMLFocalisez-vous sur la logique métier au lieu de gérer du XML brut.
Aides à la découvertecreateDAVClient() suit automatiquement la découverte CalDAV (caldav.icloud.com → principal utilisateur → calendar-home-set → URL pXX-caldav.icloud.com)Supprime le boilerplate propre à iCloud.
Enveloppes d’authentificationHelpers intégrés Basic / OAuth 2 ; pour iCloud : { username, password, authMethod: 'Basic' }Pas besoin d’encoder Base64 ni d’ajouter les en-têtes manuellement.
Helpers typés CRUDfetchCalendars(), createCalendarObject(), etc., renvoient des objets JS plutôt que du XMLImplémentation rapide du CRUD sans s’occuper de la syntaxe RFC 4791.
Support des jetons de synchrosyncCollection() encapsule REPORT sync-collection, ne renvoyant que les changementsImplémentez le polling iCloud (pas de push) en une ligne.
Compatibilité navigateur + NodeFonctionne côté serveur ou navigateur (fetch isomorphe)Utile pour extensions ou SPA.
Projet TypeScript moderneTypes complets, modules ES tree-shakables, dépendances minimesIntégration facile dans les pipelines modernes.

tsdav ne génère pas de contenu iCalendar. Utilisez-le avec ical-generator (ou une autre bibliothèque ICS) pour construire les chaînes d’événement.

Exemple d’intégration iCloud Calendar avec tsdav + TypeScript

Dans cet exemple, on suppose que vous avez déjà obtenu l’e-mail de l’utilisateur et son mot de passe spécifique à l’app.

createClient : création du client DAV

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 : fichier de constantes

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

getCalendars : récupérer tous les calendriers

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

 // Vous pouvez abstraire cette initialisation ailleurs. const client = createClient({
 username: <email-here>,
 password: <password-here>,
});

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

getCalendarById : récupérer un calendrier par 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 : lister les événements

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 : récupérer un événement par 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 : créer un événement

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 : mettre à jour un événement

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 : supprimer un événement

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 };
}

Quelles limites présente iCloud Calendar ?

  1. Basic Auth avec mot de passe spécifique à l’app : iCloud n’applique pas OAuth 2.0 ; il faut donc utiliser ce mot de passe spécifique.

  2. Pas de webhooks/notifications push : contrairement à Google Calendar ou Outlook, iCloud n’offre pas la meilleure API calendrier. Impossible de s’abonner aux mises à jour ; il faut recourir au polling périodique et au rapport sync-collection.

  3. Absence de méthode PATCH : impossible de mettre à jour partiellement un événement ; il faut utiliser PUT complet.

  4. Aucun contrôle sur les invitations : iCloud gère automatiquement les invitations. Si vous créez/modifiez un événement avec des participants, iCloud envoie les invitations ; impossible de contrôler manuellement via Outbox/Inbox CalDAV.

Existe-t-il une solution plus simple pour intégrer iCloud Calendar ?

Intégrer iCloud Calendar est complexe : conventions différentes, méthodes CalDAV inopérantes, etc.

Une approche plus simple est d’utiliser une Unified Calendar API.

Unified Calendar API Example

Avantages d’une Unified Calendar API

  • Intégration iCloud via une API documentée et testée conforme aux standards modernes.

  • Moins de temps de développement et de maintenance : l’API gère déjà clients, cas limites, etc.

  • Intégration d’autres fournisseurs (Google, Outlook, …) sans effort supplémentaire.

  • Support des notifications push/webhooks sans devoir coder votre propre polling.

Intégrez iCloud Calendar grâce à une Unified Calendar API

Nous finalisons notre OneCal Unified Calendar API, qui vous apportera tous les avantages cités.

Rejoignez la liste d’attente pour être informé du lancement et profiter des réductions de lancement.