Skip to content

JavaScript 轉換腳本

預設情況下,訂戶收到的是原樣的 canonical JSON 紀錄。當你的伺服器想要別的 ——Markdown、另一種 JSON 結構、一行純文字——就給這個訂戶掛一個轉換 腳本,自行產生請求主體。

契約

你只需提供一個函式:

js
function transform(data, context) {
  // 回傳 HTTP 請求主體
}
  • 回傳字串 → 原樣傳送(Markdown、純文字、表單編碼……)。
  • 回傳物件或陣列 → 傳送 JSON.stringify(...) 的結果,Content-Type 保持 application/json
  • 回傳其他任何東西(undefined、數字、布林值)→ 本次傳送失敗, 這是有意設計——Courier 絕不傳送空的或半成品資料。

腳本只控制請求主體。URL、方法、請求標頭與驗證維持訂戶上的設定不變。掛了 腳本後,靜態 Body 參數會被繞過——請求主體完全由你產生。

沙盒

腳本在本機一個封閉的 JavaScriptCore 沙盒裡執行:

  • 無網路、無檔案系統、無 DOM、無 require、無 setTimeout
  • 報錯與無窮迴圈都會判本次傳送失敗,原因顯示在發行台帳裡。
  • console.log / warn / error 會被捕捉,僅在測試預覽裡顯示。

context 參考

所有值均為字串:

欄位範例
context.endpointName"我的 Obsidian 中繼"
context.url目的地 URL
context.kind"workout" | "sleep" | "note" | "weather"
context.now本次發行的 ISO-8601 時間戳記
context.timeZone"Asia/Taipei"
context.locale"zh-Hant"
context.appVersion / context.appBuild"1.0.0" / "1"
context.idempotencyKey本次傳送將攜帶的 Idempotency-Key

data 參考

data 是該訂戶類型的 canonical 紀錄。日期一律為 ISO-8601 字串(用 new Date(data.start) 解析);可選欄位可能不存在。

kind === "workout"(運動)

欄位類型說明
idstringHealthKit UUID——冪等之根
activityTypestring"running""cycling""walking"……
sourceobject{ name, bundleIdentifier?, productType?, version? }
startendstringISO-8601
durationnumber
distanceMetersnumber?公尺
activeEnergyKcaltotalEnergyKcalnumber?大卡
elevationGainMetersnumber?公尺
heartRateobject?{ averageBpm?, minBpm?, maxBpm? }
avgPaceSecPerKmnumber?秒/公里
lapsarray?[{ index, start, duration, distanceMeters? }]
heartRateSeriesarray?僅當「含心率序列」開啟
routearray?僅當「含路線」開啟;體積大
metadataobject?HealthKit metadata 原樣傳遞
schemaVersionnumber

kind === "sleep"(睡眠)

欄位類型說明
idstring
nightOfstring這一晚歸屬的日曆日
inBedStartinBedEndstring?
sleepStartsleepEndstring
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"(批註)

欄位類型說明
idstring
createdAtstringISO-8601
textstring記下的想法(打字或轉寫)
sourcestring"text" | "audio"
metadataobject?
schemaVersionnumber

kind === "weather"(天氣)

欄位類型說明
idstring"{sleepID}:weather"
capturedAtstring測量時刻(≈ 醒來時)
weatherDatestring伴隨睡眠的 nightOf
symbolNamestringSF Symbol 風格,如 "cloud.sun"
conditionTextstring"Cloudy"
temperatureCapparentTemperatureCnumber攝氏
humidityPctnumber0–100
windKmhnumber
uvIndexnumber
precipitationChancePctnumber0–100
locationobject?{ lat, lon }——僅當「含定位座標」開啟
schemaVersionnumber

範例

自訂 JSON 結構:

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

一行純文字:

js
function transform(data) {
  const h = Math.floor(data.totalAsleepSec / 3600);
  const m = Math.round((data.totalAsleepSec % 3600) / 60);
  return "睡了 " + h + " 小時 " + m + " 分(" + data.nightOf.slice(0, 10) +
         ",醒來 " + data.awakenings + " 次)";
}

批註變 Markdown 引用區塊:

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

工作流程

  1. 在訂戶編輯頁展開腳本區,匯入 .js 檔案或貼上程式碼。App 可直接給你 一份註解完整的範本當起點。
  2. 測試:腳本對一條近期樣本執行,你會看到確切的輸出主體、console 日誌 與端點的 HTTP 回應——一切都在真正傳送之前。
  3. 儲存。此後發往該訂戶的每次發行都會經過你的腳本;腳本出錯會在發行台帳裡 明白地失敗,而不是把垃圾送出門。

腳本會進設定備份

與金鑰不同(金鑰在鑰匙圈,永不匯出),轉換腳本會原文進入設定備份—— 別把憑證寫死在腳本裡。

印於自家 · © 2026 Xheldon