Runtime APIs

ScriptWidget exposes JavaScript APIs for system info, device state, networking, storage, read-only HealthKit access, and location.

console

Logging helpers for scripts.

log(...args)
// 
// ScriptWidget 
// https://xnu.app/scriptwidget
// 
// Usage for api console
// 

console.log("Hello ScriptWidget");

console.log(
  "1. A grammatical unit that is syntactically independent and has a subject that is expressed or, as in imperative sentences, understood and a predicate that contains at least one finite verb. 2. The penalty imposed by a law court or other authority upon someone found guilty of a crime or other offense."
);

$scheme

Scheme helpers for deep links and widget navigation.

$scheme.open(url)
// ScriptWidget
// https://xnu.app/scriptwidget
//
// app schemes
//

/*

# open ScriptWidget app
scriptwidget://

# open ScriptWidget app and refresh all widgets
scriptwidget://reload-all

*/

$file

Read packaged files and JSON from the script bundle.

read(path) readJSON(path)
//
// ScriptWidget
// https://xnu.app/scriptwidget
//
// Usage for api file
//

// read as string
console.log($file.read("data.json"));

// read as json
let json = $file.readJSON("data.json");
console.log(json);
console.log(json.name);

$import

Import additional JS/JSX files inside a script package.

$import(path)
// ScriptWidget
// https://xnu.app/scriptwidget
//
// import other js/jsx files
//

$import("util.jsx");
$import("define.js");

$render(
  <vstack>
    <text font="title">test</text>
    {textItems}
    <text font="title">{sum(1, 2)}</text>
  </vstack>
);

Usage Map

Example of advanced ES2020 map usage in ScriptWidget.

// ScriptWidget 
// https://xnu.app/scriptwidget
// 
// Mapping over an array
// Thanks for Reina for telling me this good idea
// 

const values = ["one", "two", "three", "four"];

const test = values.map((value) => { 
  return(<text>{value}</text>)
})

$render(
  <vstack>
    <text font="title">test</text>
    {test}
  </vstack>
);

$gradient

Build gradient string values for color/background props.

$gradient(obj)
// 
// ScriptWidget 
// https://xnu.app/scriptwidget
// 
// Usage for api $gradient
// 

let angularGradient = {
  type: "angular",
  colors: ["green", "blue", "black", "green", "blue", "black", "green"],
  center: "center",
};

$render(
  <vstack background={$gradient(angularGradient)} frame="max">
    <text font="title">AngularGradient</text>
  </vstack>
);

fetch

Convenience HTTP fetch returning response text.

fetch(url) fetch(url, params)
// 
// ScriptWidget 
// https://xnu.app/scriptwidget
// 
// Usage for api $fetch
// 

// query json example
// https://jsonplaceholder.typicode.com/

const url = "https://jsonplaceholder.typicode.com/todos/1";
const result = await fetch(url);
console.log(result);

const model = JSON.parse(result);

$render(
  <vstack>
    <text font="title">receive title: {model.title}</text>
  </vstack>
);

Usage ES2020

ES2020 syntax showcase for ScriptWidget scripts.

//
// ScriptWidget
// https://xnu.app/scriptwidget
//
//



const foo = null ?? 'default string';
console.log(foo);
// expected output: "default string"

const baz = 0 ?? 42;
console.log(baz);
// expected output: 0

const someArray = [0, 1, 2, 3];
console.log(someArray);

const thisWillError = someArray[5];
console.log(thisWillError);

const thisWillBeUndefined = someArray?.[5];
console.log(thisWillBeUndefined);

$render(
  <vstack frame="max">
    <text font="title">Hello ES2020</text>
  </vstack>
);

$getenv

Read environment values such as widget size and parameters.

$getenv(key)
// 
// ScriptWidget 
// https://xnu.app/scriptwidget
// 
// Usage for component $getenv
// 

/*
 widget-size
 - large
 - medium
 - small
*/
const widget_size = $getenv("widget-size");

/*
 parameter config in system widget config panel
*/
const widget_param = $getenv("widget-param");

$render(
  <vstack frame="max">
    <text font="title">Widget Size : {widget_size}</text>
    <text font="caption">Widget Parameter : {widget_param}</text>
  </vstack>
);

$device

Device hardware, screen, battery, disk space, and appearance helpers.

screen() battery() model() name() totalDiskSpace() freeDiskSpace()
// 
// ScriptWidget 
// https://xnu.app/scriptwidget
// 
// Usage for api $device
// 

console.log($device.name());
console.log($device.model());
console.log($device.language());
console.log($device.systemVersion());
console.log(JSON.stringify($device.screen(), null, 2));
console.log(JSON.stringify($device.battery(), null, 2));
console.log($device.isdarkmode());
console.log($device.totalDiskSpace());
console.log($device.freeDiskSpace());

$storage

Key-value storage backed by app group UserDefaults.

getString(key) setString(key, value) getJSON(key) setJSON(key, obj) keys() clear()
//
// ScriptWidget
// https://xnu.app/scriptwidget
//
// Usage for api storage
//

$storage.setString("greeting", "Hello ScriptWidget");
const greeting = $storage.getString("greeting");

$storage.setJSON("profile", {
  name: "Alex",
  city: "Shanghai",
  updatedAt: new Date().toISOString()
});
const profile = $storage.getJSON("profile");

const allKeys = $storage.keys();

$render(
  <vstack frame="max" padding="12" background="#0f172a">
    <text font="caption" color="#94a3b8">Storage</text>
    <text font="title3" color="#e2e8f0">{greeting}</text>
    <text font="caption" color="#94a3b8">Name: {profile.name}</text>
    <text font="caption" color="#94a3b8">Keys: {allKeys.join(", ")}</text>
  </vstack>
);

$health

Read-only HealthKit access for steps, active energy, and heart rate.

isAvailable() requestAuthorization() stepCountToday() activeEnergyToday() heartRateLatest()
//
// ScriptWidget
// https://xnu.app/scriptwidget
//
// Usage for api health
// Note: HealthKit requires main app permission and capability.
// This API reads health data only (no write).
//

if (!$health.isAvailable()) {
  $render(
    <vstack frame="max" padding="12" background="#0f172a">
      <text font="title3" color="#f87171">HealthKit Unavailable</text>
      <text font="caption" color="#94a3b8">This platform does not support HealthKit.</text>
    </vstack>
  );
} else {
  const granted = await $health.requestAuthorization();

  if (!granted) {
    $render(
      <vstack frame="max" padding="12" background="#0f172a">
        <text font="title3" color="#fbbf24">Permission Needed</text>
        <text font="caption" color="#94a3b8">Enable Health access in the main app.</text>
      </vstack>
    );
  } else {
    const steps = await $health.stepCountToday();
    const energy = await $health.activeEnergyToday();
    const heart = await $health.heartRateLatest();

    $render(
      <vstack frame="max" padding="12" background="#0f172a">
        <text font="caption" color="#94a3b8">Health Today</text>
        <text font="title3" color="#e2e8f0">Steps: {steps.value.toFixed(0)}</text>
        <text font="caption" color="#94a3b8">Active Energy: {energy.value.toFixed(0)} kcal</text>
        <text font="caption" color="#94a3b8">Latest HR: {heart.value.toFixed(0)} bpm</text>
      </vstack>
    );
  }
}

$system

System and app metadata, locale, calendar, memory, uptime, power, and CPU info.

appInfo() locale() timeZone() calendarInfo() systemUptime() memory() osVersionString() processorCount()
//
// ScriptWidget
// https://xnu.app/scriptwidget
//
// Usage for api system
//

const app = $system.appInfo();
const tz = $system.timeZone();
const calendar = $system.calendarInfo();
const memory = $system.memory();
const uptimeHours = ($system.systemUptime() / 3600).toFixed(1);
const cpuCount = $system.processorCount();
const activeCpuCount = $system.activeProcessorCount();

$render(
  <vstack frame="max" padding="12" background="#0f172a">
    <text font="caption" color="#94a3b8">System</text>
    <text font="title3" color="#e2e8f0">{app.name}</text>
    <text font="caption" color="#94a3b8">Bundle: {app.bundleId}</text>
    <text font="caption" color="#94a3b8">Version: {app.version} ({app.build})</text>

    <spacer />

    <text font="caption" color="#94a3b8">Platform: {$system.platform()}</text>
    <text font="caption" color="#94a3b8">Locale: {$system.locale()}</text>
    <text font="caption" color="#94a3b8">Timezone: {tz.identifier} ({tz.abbreviation})</text>
    <text font="caption" color="#94a3b8">Calendar: {calendar.identifier}</text>
    <text font="caption" color="#94a3b8">Uptime: {uptimeHours}h</text>
    <text font="caption" color="#94a3b8">OS: {$system.osVersionString()}</text>
    <text font="caption" color="#94a3b8">Host: {$system.hostName()}</text>
    <text font="caption" color="#94a3b8">CPU: {cpuCount} ({activeCpuCount} active)</text>
    <text font="caption" color="#94a3b8">Memory: {(memory.physical / 1024 / 1024 / 1024).toFixed(1)} GB</text>
    <text font="caption" color="#94a3b8">Low Power: {$system.lowPowerMode() ? "On" : "Off"}</text>
  </vstack>
);

$location

Core Location access for current coordinates and authorization status. Options are optional; accuracy defaults to reduced.

isAvailable() authorizationStatus() requestAuthorization(options?) current({ timeout?, timeoutMs?, accuracy?, purposeKey?, maxAge?, maxAgeMs? }?)
//
// ScriptWidget
// https://xnu.app/scriptwidget
//
// Usage for api location
// Note: Location requires main app permission.
// This API reads current location only.
// Options are optional; default accuracy is reduced.
// Use maxAge/maxAgeMs for cached location to return faster.
//

if (!$location.isAvailable()) {
  $render(
    <vstack frame="max" padding="12" background="#0f172a">
      <text font="title3" color="#f87171">Location Unavailable</text>
      <text font="caption" color="#94a3b8">This device does not support location services.</text>
    </vstack>
  );
} else {
  const status = $location.authorizationStatus();
  let granted = status === "authorizedWhenInUse" || status === "authorizedAlways";

  if (!granted) {
    granted = await $location.requestAuthorization({ timeout: 10 });
  }

  if (!granted) {
    $render(
      <vstack frame="max" padding="12" background="#0f172a">
        <text font="title3" color="#fbbf24">Permission Needed</text>
        <text font="caption" color="#94a3b8">Enable Location access in the main app.</text>
      </vstack>
    );
  } else {
    const location = await $location.current({
      timeout: 10,
      maxAge: 30,
      accuracy: "full",
      purposeKey: "ScriptWidgetLocation"
    });

    $render(
      <vstack frame="max" padding="12" background="#0f172a">
        <text font="caption" color="#94a3b8">Current Location</text>
        <text font="title3" color="#e2e8f0">
          {location.latitude.toFixed(5)}, {location.longitude.toFixed(5)}
        </text>
        <text font="caption" color="#94a3b8">
          Accuracy: {Math.round(location.accuracy)}m ({location.accuracyAuthorization})
        </text>
        <text font="caption2" color="#64748b">
          Age: {location.age.toFixed(1)}s {location.isStale ? "(stale)" : ""}
        </text>
        <text font="caption2" color="#64748b">Updated: {location.timestamp}</text>
      </vstack>
    );
  }
}

$http

HTTP helpers for GET/POST/PUT/PATCH/DELETE.

get(url) post(url, params) put(url, params) patch(url, params) delete(url, params)
// 
// ScriptWidget 
// https://xnu.app/scriptwidget
// 
// Usage for api $http
// 

// $http.get
// $http.get is identity to $fetch or fetch api
const get_result = await $http.get("https://jsonplaceholder.typicode.com/todos/1");
console.log(get_result);

// query json example with header
// https://docs.github.com/en/rest/reference/projects
const get_with_header_result = await $http.get("https://api.github.com/users/everettjf/orgs", {
  headers: {
    Accept: "application/vnd.github.inertia-preview+json",
  },
});
console.log(get_with_header_result);


// $http.post
const post_result = await $http.post("https://jsonplaceholder.typicode.com/posts", {
  body: {
    userId: 1,
    id: 1,
    title: "Hello ScriptWidget",
  }
});
console.log(post_result);

const post_with_header_result = await $http.post("https://jsonplaceholder.typicode.com/posts", {  
  headers: {
    Accept: "application/vnd.github.inertia-preview+json",
  },
  body: {
    userId: 1,
    id: 1,
    title: "Hello ScriptWidget",
  }
});
console.log(post_with_header_result);

const post_string_with_header_result = await $http.post("https://jsonplaceholder.typicode.com/posts", {  
  headers: {
    Accept: "application/vnd.github.inertia-preview+json",
  },
  body: "password=123&name=321"
});
console.log(post_string_with_header_result);

// $http.put (same to post)
// $http.patch (same to post)
// $http.delete (same to post)

$render(
  <vstack>
    <text>$http example</text>
  </vstack>
);

$animation

Build animation configuration strings.

$animation(obj)
//
// ScriptWidget
// https://xnu.app/scriptwidget
//
// basic clock animation
//


$render(
  <vstack frame="max,center">
      <zstack>
          <vstack animation="clockSecond">
              <rect frame="5,50" color="yellow"></rect>
              <rect frame="5,50" color="clear"></rect>
          </vstack>
          <vstack animation="clockMinute">
              <rect frame="5,40" color="blue"></rect>
              <rect frame="5,40" color="clear"></rect>
          </vstack>
          <vstack animation="clockHour">
              <rect frame="5,30" color="red"></rect>
              <rect frame="5,30" color="clear"></rect>
          </vstack>
      </zstack>
  </vstack>
);
HealthKit and Location data are only available after user authorization in the main app. Widgets may show cached or fallback data.