Skip to content

Build extensions / Reference

API reference

Import everything from @orbtorrent/api:

import { fetch, files, getPreferenceValues, progress, sleep, storage, type Download, type Result } from "@orbtorrent/api";

Extensions run in OrbTorrent’s sandboxed extension host, with JavaScriptCore, not Node.js or a browser. They have no file system or network of their own: everything outside the JavaScript language goes through these functions, which OrbTorrent checks against the permissions in package.json.

The functions only work inside OrbTorrent, or in orb test. Called anywhere else, they throw “@orbtorrent/api only works inside OrbTorrent, or in orb test.”

type Hook = (download: Download) => Result | Promise<Result>;

A hook is the default export of src/<hook name>.ts. Hooks explains when each kind runs.

type HookEvent = "beforeStart" | "afterFinish" | "afterChecks";

When a hook runs, as its event in package.json says. afterChecks since 1.2.

interface Download {
/** Its info hash. */
id: string;
name: string;
/** Bytes of the wanted files. */
size: number;
/** Whether it's from a private tracker. */
isPrivate: boolean;
files: DownloadFile[];
/** For an `afterChecks` hook: what the other extensions' checks found, worst first. */
checks?: CheckResult[];
/** For an `afterChecks` hook: whether a check is holding it before it starts. Otherwise it's finished. */
isHeld?: boolean;
}

The download a hook runs on. checks and isHeld since 1.2.

interface DownloadFile {
index: number;
/** Relative to the download's folder, including its top folder. Never an absolute path. */
path: string;
/** In bytes. */
size: number;
/** Whether the user chose to download it. */
wanted: boolean;
/**
* Once the download has finished: whether this file finished since the hook
* last ran on it, such as one the user chose to download later. Every file
* is new the first time, and when the user runs the hook again.
*/
isNew?: boolean;
}

isNew since 1.2.

interface Result {
status: Status;
/** One line, shown in the list. Say which file and why. */
summary: string;
/** Markdown, shown under Details. */
details?: string;
link?: { title: string; url: string };
/**
* Also shows the summary as a notification, such as "Added English
* subtitles". A warning or failure after a download finishes, and a
* download held before it starts, are notifications already.
*/
notify?: boolean;
/**
* What it found about particular files. OrbTorrent marks each problem
* beside its file, and the result counts as no better than its worst file.
* Each result replaces the hook's last one, so name every file you know
* about, not only the new ones.
*/
files?: FileResult[];
}

What a hook returns. Results explains how each part shows. files since 1.2.

interface FileResult {
/** A file from `download.files`, or its index. */
file: DownloadFile | number;
status: "passed" | "warning" | "failed";
/** Why, in a few words, such as "5 of 70 engines call it malicious". */
summary?: string;
link?: { title: string; url: string };
}

What a hook found about one of the download’s files. Since 1.2.

interface CheckResult {
extension: { name: string; title: string };
/** The hook's name. */
hook: string;
event: "beforeStart" | "afterFinish";
/** `didNotFinish` when it threw, crashed or went over a limit. */
status: Status | "didNotFinish";
summary: string;
details?: string;
link?: { title: string; url: string };
/** What it found about particular files, problems first. */
files?: { index: number; path: string; status: "passed" | "warning" | "failed"; summary?: string; link?: { title: string; url: string } }[];
}

Another extension’s result on the download, as an afterChecks hook sees it in download.checks. Results still running aren’t included. Since 1.2.

type Status = "passed" | "warning" | "failed" | "skipped";
  • passed: nothing wrong.
  • warning: worth a look; the download carries on.
  • failed: something’s wrong. Before a download starts, this holds it until the user decides.
  • skipped: the check didn’t apply, or couldn’t run.
function getPreferenceValues<T = OrbPreferences>(): T;

The extension’s settings, as the user set them, with defaults filled in. Every setting has a value: what the user chose, or its default, or else "", false, or a dropdown’s first option.

declare global {
interface OrbPreferences {}
}

The type of the extension’s settings. orb build fills it in from package.json, in orb-env.d.ts, so getPreferenceValues() knows each setting’s name and type. Settings shows an example.

type Verify = (preferences: OrbPreferences) => VerifyResult | Promise<VerifyResult>;

The default export of the file verify names in package.json. It checks the settings when the user presses Verify.

type VerifyResult = { ok: true; message?: string } | { ok: false; message: string };

What verify returns. The message shows under the settings.

function fetch(url: string | URL, init?: RequestInit): Promise<Response>;

An HTTPS request to a host listed in permissions.network. It throws if the request is refused, or fails.

  • HTTPS on port 443 only, and never an address on the Mac or its local network. OrbTorrent connects to the very address it checked.
  • Redirects are followed only to listed hosts. Otherwise the redirect itself is returned.
  • Responses are limited to 10 MB, and each request to 30 seconds.
  • There’s no global fetch: import this one.

Network requests has examples.

interface RequestInit {
method?: "GET" | "HEAD" | "POST" | "PUT" | "PATCH" | "DELETE";
headers?: Record<string, string>;
/** Text (use `JSON.stringify` for JSON), or a form from `files.formData`. */
body?: string | FormBody;
}
interface Response {
readonly status: number;
/** Whether the status is 200–299. */
readonly ok: boolean;
/** The address it came from, after any redirects. */
readonly url: string;
readonly headers: Headers;
text(): Promise<string>;
json<T = any>(): Promise<T>;
/** The body's bytes, such as an image or a zip file. */
bytes(): Promise<Uint8Array>;
arrayBuffer(): Promise<ArrayBuffer>;
}

text() and json() read the body as UTF-8, and bytes() and arrayBuffer() as it came. Since 1.1.

interface Headers {
get(name: string): string | null;
has(name: string): boolean;
entries(): [string, string][];
}

A response’s headers. Names are case-insensitive, and entries() gives them in lowercase.

const files: {
sha256(file: DownloadFile | number): Promise<string>;
formData(fields: Record<string, string | DownloadFile>): FormBody;
save(file: DownloadFile | number, suffix: string, contents: string | Uint8Array | ArrayBuffer): Promise<SavedFile>;
};

The download’s files, only once it’s finished: in afterFinish hooks, and in afterChecks hooks when it isn’t held. Reading them needs "files": "contents" in permissions, and adding files next to them needs "files": "add", or ["contents", "add"] for both. Pass a file from download.files, or its index.

  • files.sha256(file): the file’s SHA-256 fingerprint in lowercase hex, computed by OrbTorrent. Large files are quick, and the time doesn’t count towards CPU time. OrbTorrent remembers it while the file stays as it was, so asking again, such as once more of the download has finished, doesn’t read it again.
  • files.formData(fields): a multipart form to upload with fetch. OrbTorrent streams each file from disk.
  • files.save(file, suffix, contents): adds a file next to file, named after it: files.save(video, ".en.srt", text) saves Film.2024.en.srt beside Film.2024.mkv. The suffix starts with ., -, _ or a space, and ends with an extension. Only subtitles, text, images and checksums, up to 10 MB each and 50 MB a run. It never replaces a file, except one the same hook added to the same download before, and throws if one’s there. Text is saved as UTF-8. Since 1.1.

Read and add files has examples.

interface SavedFile {
/** Relative to the download's folder, like a `DownloadFile`'s path. */
path: string;
/** In bytes. */
size: number;
}

A file files.save added.

class FormBody {
readonly parts: FormPart[];
append(name: string, value: string | DownloadFile, options?: { filename?: string; contentType?: string }): this;
}

A multipart form, which can include the download’s files. Make one with files.formData, and add to it with append.

type FormPart =
| { name: string; value: string }
| { name: string; file: number; filename?: string; contentType?: string };

One field of a FormBody: text, or a file by its index.

function progress(update: { done?: number; total?: number; message?: string }): Promise<void>;

Shows how far along the check is, in the Checks tab and in orb dev, such as “Checking 3 of 10 files…”. The message is cut at 200 characters.

const storage: {
get<T = unknown>(key: string): Promise<T | undefined>;
set(key: string, value: unknown): Promise<void>;
remove(key: string): Promise<void>;
keys(): Promise<string[]>;
};

JSON values the extension keeps between runs, up to 1 MB in all, with keys up to 200 characters. Kept across runs and updates, and deleted with the extension. Storage and progress has examples.

function sleep(milliseconds: number): Promise<void>;

Waits, without using CPU time.

const environment: {
/** The extension's `name` from package.json. */
readonly extensionName: string;
/** Whether it's loaded from a folder with `orb dev`, rather than installed. */
readonly isDevelopment: boolean;
/** The hook or `verify` function running now. */
readonly entryPoint: { type: "hook" | "verify"; name: string; event?: HookEvent };
/** The API version this OrbTorrent provides, such as "1.2.0". */
readonly apiVersion: string;
};

About the extension, and how it’s running.

Besides the JavaScript language itself, ES2023 and newer, extensions have:

console log, info, debug, trace, warn and error, to the extension’s log.
Timers setTimeout, setInterval, clearTimeout, clearInterval and queueMicrotask.
URL and URLSearchParams Including URL.canParse.
TextEncoder and TextDecoder UTF-8 only.
atob and btoa Base64.
crypto randomUUID() and getRandomValues().
structuredClone

Not available: fs, path, process, Buffer, require, the global fetch, window, the DOM, and npm packages that need them. orb build refuses Node.js modules and says what to use instead, and orb lint flags the globals. npm packages that are plain JavaScript work: orb build bundles them in.

@orbtorrent/api’s major and minor version are the extension API’s. OrbTorrent runs extensions built with its own major version, up to the minor version it supports: one built with 1.2 needs an OrbTorrent that supports 1.2, and OrbTorrent says so if it’s too old, rather than run it.

  • Patch versions, such as 1.0.3, improve orb and fix bugs without changing the API.
  • Minor versions, such as 1.1, add to the API.
  • Major versions, such as 2.0, are for anything that would break an extension.

npx orb doctor says which API the OrbTorrent on your Mac supports. The changelog lists every version.