Transformation JavaScript
Par défaut, un abonné reçoit l'enregistrement JSON canonical tel quel. Quand votre serveur attend autre chose — du Markdown, une autre forme de JSON, une simple ligne de texte — attachez un script de transformation à cet abonné et construisez le corps vous-même.
Le contrat
Vous fournissez une fonction :
function transform(data, context) {
// renvoyer le CORPS de la requête HTTP
}- Renvoyer une chaîne → envoyée telle quelle (Markdown, texte brut, encodage formulaire, …).
- Renvoyer un objet ou un tableau →
JSON.stringify(...)est envoyé,Content-Typeresteapplication/json. - Renvoyer autre chose (
undefined, un nombre, un booléen) → l'envoi échoue, à dessein. Courier n'envoie jamais de données vides ou à moitié construites.
Le script ne contrôle que le corps. URL, méthode, en-têtes et authentification restent tels que configurés sur l'abonné. Avec un script attaché, les paramètres de corps statiques sont ignorés — c'est vous qui construisez le corps.
Le bac à sable
Les scripts s'exécutent sur l'appareil dans un bac à sable JavaScriptCore hermétique :
- Pas de réseau, pas de système de fichiers, pas de DOM, pas de
require, pas desetTimeout. - Les erreurs et les boucles infinies font échouer l'envoi et la raison apparaît dans l'historique d'envoi.
console.log/warn/errorsont capturés et affichés uniquement dans l'aperçu Tester.
Référence de context
Toutes les valeurs sont des chaînes :
| Champ | Exemple |
|---|---|
context.endpointName | "My Obsidian relay" |
context.url | l'URL de destination |
context.kind | "workout" | "sleep" | "note" | "weather" |
context.now | horodatage ISO-8601 de cet envoi |
context.timeZone | "Asia/Shanghai" |
context.locale | "zh-Hans" |
context.appVersion / context.appBuild | "1.0.0" / "1" |
context.idempotencyKey | l'Idempotency-Key que portera cet envoi |
Référence de data
data est l'enregistrement canonical du type de l'abonné. Les dates sont des chaînes ISO-8601 (new Date(data.start) pour les analyser) ; les champs facultatifs peuvent être absents.
kind === "workout"
| Field | Type | Notes |
|---|---|---|
id | string | UUID HealthKit — la racine de l'idempotence |
activityType | string | "running", "cycling", "walking", … |
source | object | { name, bundleIdentifier?, productType?, version? } |
start, end | string | ISO-8601 |
duration | number | secondes |
distanceMeters | number? | |
activeEnergyKcal, totalEnergyKcal | number? | |
elevationGainMeters | number? | |
heartRate | object? | { averageBpm?, minBpm?, maxBpm? } |
avgPaceSecPerKm | number? | |
laps | array? | [{ index, start, duration, distanceMeters? }] |
heartRateSeries | array? | seulement si l'option série FC est activée |
route | array? | seulement si l'option parcours est activée ; volumineux |
metadata | object? | métadonnées HealthKit transmises telles quelles |
schemaVersion | number |
kind === "sleep"
| Field | Type | Notes |
|---|---|---|
id | string | |
nightOf | string | le jour auquel cette nuit est rattachée |
inBedStart, inBedEnd | string? | |
sleepStart, sleepEnd | string | |
timeInBedSec | number? | |
totalAsleepSec | number | |
stages | object | { awakeSec, remSec, coreSec, deepSec, unspecifiedSec } |
awakenings | number | |
efficiencyPct | number? | 0–100 |
vitals | object? | { averageHeartRateBpm?, averageHRVms?, respiratoryRate?, oxygenSaturation?, wristTemperatureDeltaC? } |
segments | array | [{ stage, start, end }], stage : inBed|awake|rem|core|deep|unspecified |
sources | array | [{ name, bundleIdentifier?, … }] |
schemaVersion | number |
kind === "note"
| Field | Type | Notes |
|---|---|---|
id | string | |
createdAt | string | ISO-8601 |
text | string | la pensée capturée (tapée ou transcrite) |
source | string | "text" | "audio" |
metadata | object? | |
schemaVersion | number |
kind === "weather"
| Field | Type | Notes |
|---|---|---|
id | string | "{sleepID}:weather" |
capturedAt | string | moment de la mesure (≈ heure du réveil) |
weatherDate | string | le nightOf du sommeil associé |
symbolName | string | style SF-Symbol, par ex. "cloud.sun" |
conditionText | string | par ex. "Cloudy" |
temperatureC, apparentTemperatureC | number | |
humidityPct | number | 0–100 |
windKmh | number | |
uvIndex | number | |
precipitationChancePct | number | 0–100 |
location | object? | { lat, lon } — seulement avec l'option coordonnées activée |
schemaVersion | number |
Exemples
Une forme JSON personnalisée :
function transform(data, context) {
return {
kind: "workout",
activity: data.activityType,
km: data.distanceMeters ? +(data.distanceMeters / 1000).toFixed(2) : null,
minutes: Math.round(data.duration / 60),
avgHr: (data.heartRate && data.heartRate.averageBpm) || null,
at: data.start,
via: context.endpointName,
};
}Une ligne de texte brut :
function transform(data) {
const h = Math.floor(data.totalAsleepSec / 3600);
const m = Math.round((data.totalAsleepSec % 3600) / 60);
return "Dormi " + h + " h " + m + " min le " + data.nightOf.slice(0, 10) +
" (" + data.awakenings + " réveils)";
}Un bloc de citation Markdown pour les annotations :
function transform(data) {
return "> " + data.text + "\n>\n> — " + data.createdAt;
}Marche à suivre
- Dans l'éditeur d'abonné, ouvrez la section script et importez un fichier
.jsou collez du code. L'app peut vous fournir un modèle entièrement commenté pour démarrer. - Appuyez sur Tester : le script s'exécute sur un échantillon récent et vous voyez le corps exact produit, les journaux de console et la réponse HTTP du point de terminaison — avant tout envoi réel.
- Enregistrez. Désormais, chaque envoi vers cet abonné passe par votre script ; les erreurs de script font échouer l'envoi de façon visible dans l'historique d'envoi plutôt que de livrer n'importe quoi.
Les scripts font partie des sauvegardes de configuration
Contrairement aux secrets (trousseau, jamais exportés), les scripts de transformation sont inclus en clair dans les sauvegardes de configuration — n'y codez pas d'identifiants en dur.