Skip to content

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:

js
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-Type se mantiene en application/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, sin setTimeout.
  • Los errores y los bucles infinitos hacen fallar el envío y el motivo aparece en el historial de envíos.
  • console.log / warn / error se capturan y se muestran solo en la vista previa de Probar.

Referencia de context

Todos los valores son cadenas:

CampoEjemplo
context.endpointName"Mi relé de Obsidian"
context.urlla URL de destino
context.kind"workout" | "sleep" | "note" | "weather"
context.nowmarca 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.idempotencyKeyla 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"

CampoTipoNotas
idstringUUID de HealthKit — la raíz de la idempotencia
activityTypestring"running", "cycling", "walking", …
sourceobject{ name, bundleIdentifier?, productType?, version? }
start, endstringISO-8601
durationnumbersegundos
distanceMetersnumber?
activeEnergyKcal, totalEnergyKcalnumber?
elevationGainMetersnumber?
heartRateobject?{ averageBpm?, minBpm?, maxBpm? }
avgPaceSecPerKmnumber?
lapsarray?[{ index, start, duration, distanceMeters? }]
heartRateSeriesarray?solo si el interruptor de la serie de FC está activado
routearray?solo si el interruptor de la ruta está activado; grande
metadataobject?metadatos de HealthKit pasados tal cual
schemaVersionnumber

kind === "sleep"

CampoTipoNotas
idstring
nightOfstringel día al que se atribuye esta noche
inBedStart, inBedEndstring?
sleepStart, sleepEndstring
timeInBedSecnumber?
totalAsleepSecnumber
stagesobject{ awakeSec, remSec, coreSec, deepSec, unspecifiedSec }
awakeningsnumber
efficiencyPctnumber?0–100
vitalsobject?{ averageHeartRateBpm?, averageHRVms?, respiratoryRate?, oxygenSaturation?, wristTemperatureDeltaC? }
segmentsarray[{ stage, start, end }], stage: inBed|awake|rem|core|deep|unspecified
sourcesarray[{ name, bundleIdentifier?, … }]
schemaVersionnumber

kind === "note"

CampoTipoNotas
idstring
createdAtstringISO-8601
textstringel pensamiento capturado (escrito o transcrito)
sourcestring"text" | "audio"
metadataobject?
schemaVersionnumber

kind === "weather"

CampoTipoNotas
idstring"{sleepID}:weather"
capturedAtstringcuándo se midió (≈ hora de despertar)
weatherDatestringel nightOf del sueño asociado
symbolNamestringestilo SF-Symbol, p. ej. "cloud.sun"
conditionTextstringp. ej. "Cloudy"
temperatureC, apparentTemperatureCnumber
humidityPctnumber0–100
windKmhnumber
uvIndexnumber
precipitationChancePctnumber0–100
locationobject?{ lat, lon } — solo con el interruptor de coordenadas activado
schemaVersionnumber

Ejemplos

Una forma de JSON personalizada:

js
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:

js
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:

js
function transform(data) {
  return "> " + data.text + "\n>\n> — " + data.createdAt;
}

Flujo de trabajo

  1. En el editor de suscriptores, abre la sección script e importa un archivo .js o pega el código. La app puede darte una plantilla completamente comentada para empezar.
  2. 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.
  3. 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 se incluyen en texto plano en las copias de seguridad de la configuración — no incrustes credenciales en ellos.

Impreso en casa · © 2026 Xheldon