Skip to content

JavaScript-Transformation

Standardmäßig erhält ein Abonnent den canonical JSON-Datensatz unverändert. Wenn dein Server etwas anderes will — Markdown, eine andere JSON-Struktur, eine schlichte Textzeile — hänge ein Transformations-Skript an diesen Abonnenten und baue den Body selbst.

Der Vertrag

Du lieferst eine Funktion:

js
function transform(data, context) {
  // gib den HTTP-Request-BODY zurück
}
  • Gib einen String zurück → wird unverändert gesendet (Markdown, reiner Text, form-encoded, …).
  • Gib ein Objekt oder Array zurück → JSON.stringify(...) wird gesendet, Content-Type bleibt application/json.
  • Gib irgendetwas anderes zurück (undefined, eine Zahl, ein Boolean) → die Sendung schlägt fehl, mit Absicht. Courier sendet nie leere oder halbfertige Daten.

Das Skript kontrolliert nur den Body. URL, Methode, Header und Auth bleiben, wie am Abonnenten konfiguriert. Mit angehängtem Skript werden statische Body-Parameter übergangen — du baust den Body selbst.

Die Sandbox

Skripte laufen in einer abgeschotteten JavaScriptCore-Sandbox auf dem Gerät:

  • Kein Netzwerk, kein Dateisystem, kein DOM, kein require, kein setTimeout.
  • Fehler und Endlosschleifen lassen die Sendung fehlschlagen, und der Grund erscheint im Versandverlauf.
  • console.log / warn / error werden mitgeschnitten und nur in der Test-Vorschau angezeigt.

context-Referenz

Alle Werte sind Strings:

FeldBeispiel
context.endpointName"My Obsidian relay"
context.urldie Ziel-URL
context.kind"workout" | "sleep" | "note" | "weather"
context.nowISO-8601-Zeitstempel dieses Versands
context.timeZone"Asia/Shanghai"
context.locale"zh-Hans"
context.appVersion / context.appBuild"1.0.0" / "1"
context.idempotencyKeyder Idempotency-Key, den diese Sendung tragen wird

data-Referenz

data ist der canonical Datensatz für die Art des Abonnenten. Datumsangaben sind ISO-8601-Strings (mit new Date(data.start) parsen); optionale Felder können fehlen.

kind === "workout"

FeldTypHinweise
idstringHealthKit-UUID — die Wurzel der Idempotenz
activityTypestring"running", "cycling", "walking", …
sourceobject{ name, bundleIdentifier?, productType?, version? }
start, endstringISO-8601
durationnumberSekunden
distanceMetersnumber?
activeEnergyKcal, totalEnergyKcalnumber?
elevationGainMetersnumber?
heartRateobject?{ averageBpm?, minBpm?, maxBpm? }
avgPaceSecPerKmnumber?
lapsarray?[{ index, start, duration, distanceMeters? }]
heartRateSeriesarray?nur wenn der HF-Serien-Schalter an ist
routearray?nur wenn der Routen-Schalter an ist; groß
metadataobject?HealthKit-Metadaten, unverändert durchgereicht
schemaVersionnumber

kind === "sleep"

FeldTypHinweise
idstring
nightOfstringder Tag, dem diese Nacht zugerechnet wird
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"

FeldTypHinweise
idstring
createdAtstringISO-8601
textstringder festgehaltene Gedanke (getippt oder transkribiert)
sourcestring"text" | "audio"
metadataobject?
schemaVersionnumber

kind === "weather"

FeldTypHinweise
idstring"{sleepID}:weather"
capturedAtstringZeitpunkt der Messung (≈ Aufwachzeit)
weatherDatestringdas nightOf des zugehörigen Schlafs
symbolNamestringim SF-Symbol-Stil, z. B. "cloud.sun"
conditionTextstringz. B. "Cloudy"
temperatureC, apparentTemperatureCnumber
humidityPctnumber0–100
windKmhnumber
uvIndexnumber
precipitationChancePctnumber0–100
locationobject?{ lat, lon } — nur bei aktiviertem Koordinaten-Schalter
schemaVersionnumber

Beispiele

Eine eigene JSON-Struktur:

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

Eine schlichte Textzeile:

js
function transform(data) {
  const h = Math.floor(data.totalAsleepSec / 3600);
  const m = Math.round((data.totalAsleepSec % 3600) / 60);
  return "Geschlafen: " + h + "h" + m + "m am " + data.nightOf.slice(0, 10) +
         " (" + data.awakenings + " Aufwachphasen)";
}

Ein Markdown-Blockzitat für Anmerkungen:

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

Arbeitsablauf

  1. Öffne im Abonnenten-Editor den Skript-Bereich und importiere eine .js-Datei oder füge Code ein. Die App kann dir eine vollständig kommentierte Vorlage als Ausgangspunkt geben.
  2. Tippe auf Test: Das Skript läuft gegen einen aktuellen Beispieldatensatz und du siehst den exakten Ausgabe-Body, die Konsolen-Logs und die HTTP-Antwort des Endpunkts — bevor irgendetwas Echtes gesendet wird.
  3. Speichern. Ab jetzt läuft jeder Versand an diesen Abonnenten durch dein Skript; Skriptfehler lassen die Sendung sichtbar im Versandverlauf fehlschlagen, statt Müll auszuliefern.

Skripte sind Teil der Konfigurations-Backups

Anders als Geheimnisse (Schlüsselbund, werden nie exportiert) sind Transformations-Skripte im Klartext in Konfigurations-Backups enthalten — hinterlege darin keine Zugangsdaten.

Zu Hause gedruckt · © 2026 Xheldon