Transformación en JavaScript
Por defecto, un suscriptor recibe el registro JSON canonical sin tocar. Cuando tu servidor quiere otra cosa — Markdown, una forma de JSON distinta, una simple línea de texto — adjunta un script de transformación a ese suscriptor y construye el cuerpo tú mismo.
El contrato
Tú aportas una función:
function transform(data, context) {
// devuelve el CUERPO de la petición HTTP
}- Devuelve una cadena → se envía tal cual (Markdown, texto plano, codificación de formulario, …).
- Devuelve un objeto o array → se envía
JSON.stringify(...),Content-Typese mantiene enapplication/json. - Devuelve cualquier otra cosa (
undefined, un número, un booleano) → el envío falla, a propósito. Courier nunca envía datos vacíos o a medio construir.
El script controla solo el cuerpo. La URL, el método, las cabeceras y la autenticación quedan como estén configurados en el suscriptor. Con un script adjunto, los parámetros estáticos del cuerpo se omiten — el cuerpo lo construyes tú.
El sandbox
Los scripts se ejecutan en un sandbox de JavaScriptCore sellado, en el propio dispositivo:
- Sin red, sin sistema de archivos, sin DOM, sin
require, sinsetTimeout. - Los errores y los bucles infinitos hacen fallar el envío y el motivo aparece en el historial de envíos.
console.log/warn/errorse capturan y se muestran solo en la vista previa de Probar.
Referencia de context
Todos los valores son cadenas:
| Campo | Ejemplo |
|---|---|
context.endpointName | "Mi relé de Obsidian" |
context.url | la URL de destino |
context.kind | "workout" | "sleep" | "note" | "weather" |
context.now | marca de tiempo ISO-8601 de este envío |
context.timeZone | "Asia/Shanghai" |
context.locale | "zh-Hans" |
context.appVersion / context.appBuild | "1.0.0" / "1" |
context.idempotencyKey | la Idempotency-Key que llevará este envío |
Referencia de data
data es el registro canonical del tipo del suscriptor. Las fechas son cadenas ISO-8601 (new Date(data.start) para interpretarlas); los campos opcionales pueden no estar presentes.
kind === "workout"
| Campo | Tipo | Notas |
|---|---|---|
id | string | UUID de HealthKit — la raíz de la idempotencia |
activityType | string | "running", "cycling", "walking", … |
source | object | { name, bundleIdentifier?, productType?, version? } |
start, end | string | ISO-8601 |
duration | number | segundos |
distanceMeters | number? | |
activeEnergyKcal, totalEnergyKcal | number? | |
elevationGainMeters | number? | |
heartRate | object? | { averageBpm?, minBpm?, maxBpm? } |
avgPaceSecPerKm | number? | |
laps | array? | [{ index, start, duration, distanceMeters? }] |
heartRateSeries | array? | solo si el interruptor de la serie de FC está activado |
route | array? | solo si el interruptor de la ruta está activado; grande |
metadata | object? | metadatos de HealthKit pasados tal cual |
schemaVersion | number |
kind === "sleep"
| Campo | Tipo | Notas |
|---|---|---|
id | string | |
nightOf | string | el día al que se atribuye esta noche |
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"
| Campo | Tipo | Notas |
|---|---|---|
id | string | |
createdAt | string | ISO-8601 |
text | string | el pensamiento capturado (escrito o transcrito) |
source | string | "text" | "audio" |
metadata | object? | |
schemaVersion | number |
kind === "weather"
| Campo | Tipo | Notas |
|---|---|---|
id | string | "{sleepID}:weather" |
capturedAt | string | cuándo se midió (≈ hora de despertar) |
weatherDate | string | el nightOf del sueño asociado |
symbolName | string | estilo SF-Symbol, p. ej. "cloud.sun" |
conditionText | string | p. ej. "Cloudy" |
temperatureC, apparentTemperatureC | number | |
humidityPct | number | 0–100 |
windKmh | number | |
uvIndex | number | |
precipitationChancePct | number | 0–100 |
location | object? | { lat, lon } — solo con el interruptor de coordenadas activado |
schemaVersion | number |
Ejemplos
Una forma de JSON personalizada:
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,
};
}Una sola línea de texto plano:
function transform(data) {
const h = Math.floor(data.totalAsleepSec / 3600);
const m = Math.round((data.totalAsleepSec % 3600) / 60);
return "Dormí " + h + "h" + m + "m el " + data.nightOf.slice(0, 10) +
" (" + data.awakenings + " despertares)";
}Una cita en Markdown para las anotaciones:
function transform(data) {
return "> " + data.text + "\n>\n> — " + data.createdAt;
}Flujo de trabajo
- En el editor de suscriptores, abre la sección script e importa un archivo
.jso pega el código. La app puede darte una plantilla completamente comentada para empezar. - Pulsa Probar: el script se ejecuta contra una muestra reciente y ves el cuerpo de salida exacto, los mensajes de consola y la respuesta HTTP del endpoint — antes de que se envíe nada real.
- Guarda. A partir de ahora, cada envío a este suscriptor pasa por tu script; los errores del script hacen fallar el envío de forma visible en el historial de envíos en lugar de entregar basura.
Los scripts forman parte de las copias de seguridad de la configuración
A diferencia de los secretos (llavero, nunca se exportan), los scripts de transformación sí se incluyen en texto plano en las copias de seguridad de la configuración — no incrustes credenciales en ellos.