Skip to main content

Script API Reference

Hodor runs your scripts in a built-in JavaScript engine. Depending on the script type, Hodor calls one of the entry functions below at the right time — you only implement the logic you need. If you do not modify anything, delete the unused entry function to avoid unnecessary calls.

Available global objects and classes:

CategoryNames
Entry functionsonRequest, onResponse, onSessionCompleted, onSchedule, onMock, abort()
Native $ modules$codec, $hash, $hmac, $crypto, $jwt, $format, $fs, $env, $uuid, $log
Web standard APIsfetch, HTTPClient, setTimeout / setInterval, btoa / atob / sleep / require / TextEncoder / crypto, console
HTTP classesRequest, Response, Body, Headers, URLSearchParams
StorageSharedPreferences, sdk variables
ContextsRewriteContext, CompletedContext, ScheduleContext
tip

Use console.log(...) anywhere in a script to inspect values — output is shown in the script's log view.

Entry Functions​

Scripts are detected by scanning for async function declarations — the parameter count of each entry function is fixed (onRequest/onResponse/onMock take 3, onSessionCompleted takes 4, onSchedule takes 1). Arrow-function assignments (let onRequest = async (...) => {}) are not detected. Parameter names are free.

onRequest(context, url, request)​

Designed for Rewrite Script and Quick Request pre-request scripts. This function is called before the HTTP request is sent to the server. Return the (possibly modified) request.

ParameterTypeDescription
contextRewriteContextThe context object.
urlstringThe URL of the HTTP request.
requestRequestThe HTTP request object.
// modify request body
async function onRequest(context, url, request) {
// parse to Text
var text = request.body.parseText()
console.log(text)
request.body = "this is a replaced request text"

// parse to JSON
var json = request.body.parseJSON()
json["script_json"] = "JSONValue"
request.body = json

// parse to URLSearchParams
var urlencoded = request.body.parseURLSearchParams()
urlencoded.append("script", "parseURLSearchParams")
request.body = urlencoded
// Done
return request;
}
// redirect, modify path / queries / headers
async function onRequest(context, url, request) {
console.log(url);
// set a parameter, you can get it in onResponse
context.parameters["custom"] = "custom parameter on request"

// redirect
request.scheme = "http"
request.path = "/upload/json"
request.host = "192.168.3.30"
request.port = 6000
// change method
request.method = "POST"

// queries operation
request.queries.append("name", "value1") // add query
request.queries.set("name", "value2") // replace or add query
request.queries.get("name") // get query by name
request.queries.delete("name") // delete query by name

// headers operation
request.headers.delete("accept") // delete header by name
request.headers.append("hodor", "custom value") // add name-value pair
request.headers.forEach((value, name) => {
console.log(name + ":" + value)
});
// Done
return request;
}

onResponse(context, url, response)​

Designed for Rewrite Script and Quick Request post-response scripts. This function is called before the HTTP response is sent to the client. Return the (possibly modified) response.

ParameterTypeDescription
contextRewriteContextThe context object.
urlstringThe URL of the HTTP response.
responseResponseThe HTTP response object.
// response body
async function onResponse(context, url, response) {
// parse to Text
var text = response.body.parseText()
console.log(text)
response.body = "this is a replaced response text"

// parse to JSON
var json = response.body.parseJSON()
json["script_json"] = "JSONValue"
response.body = json

// parse to URLSearchParams
var urlencoded = response.body.parseURLSearchParams()
urlencoded.append("script", "parseURLSearchParams")
response.body = urlencoded
// Done
return response;
}
// change status code and headers
async function onResponse(context, url, response) {
// get parameters set in onRequest
console.log(context.parameters["custom"]) // Output "custom parameter on request"

response.statusCode = 200 // change status code
response.statusPhrase = "OK" // change status phrase

response.headers.delete("accept")
response.headers.append("hodor", "custom value")
response.headers.forEach((value, name) => {
console.log(name + ":" + value)
});
// Done
return response
}

onSessionCompleted(context, url, request, response)​

Designed for HTTP Completed Script. Called asynchronously after the HTTP response is returned, without affecting data transmission between the app and the server. Useful for data collection and forwarding.

ParameterTypeDescription
contextCompletedContextThe context object.
urlstringThe URL of the HTTP request.
requestRequestThe HTTP request object.
responseResponseThe HTTP response object.
// sample: pass session data to your own server
async function onSessionCompleted(context, url, request, response) {
try {
var reqText = request.body.parseText();
var rspText = response.body.parseText()
var params = {
"body": { // HTTPClient post body data
"url": url, // request url
"reqHeader": request.headers.encode(), // request headers
"reqText": reqText, // request body
"rspHeader": response.headers.encode(), // response headers
"rspText": rspText, // response body
},
"headers": { // HTTPClient headers
"from": "onSessionCompleted"
},
}
const rsp = await HTTPClient.post("http://192.168.3.30:8000/upload", params)
let text = rsp.body.parseText()
console.log(text)
} catch(error) {
console.log(error)
}
}

onSchedule(context)​

Designed for Schedule Script. Called at the scheduled trigger time (5 or 6-part cron expressions are supported). Use abort() to stop the schedule loop.

ParameterTypeDescription
contextScheduleContextThe context object.
async function onSchedule(context) {
// set custom parameters for next runloop
context.parameters["custom"] = "custom parameters for next runloop";
// repeated count
console.log(`repeatedCount ${context.repeatedCount}`);
// if repeatedCount is 2, abort schedule
if (context.repeatedCount == 2) {
abort()
}
// you can also use HTTPClient to send requests
const rsp = await HTTPClient.get("https://www.apple.com")
let text = rsp.body.parseText()
console.log(text)
}

onMock(context, url, request)​

Designed for Mock Script. Called instead of sending the request to the server — whatever you return is delivered to the client as the response.

ParameterTypeDescription
contextRewriteContextThe context object.
urlstringThe URL of the HTTP request.
requestRequestThe HTTP request object.
// return a mocked JSON response
async function onMock(context, url, request) {
const response = new Response({
statusCode: 200,
statusPhrase: 'OK',
headers: {'Content-Type': 'application/json'},
textBody: JSON.stringify({code: 0, message: 'mocked by script', data: {time: Date.now()}}),
});
return response;
}

abort()​

Inside channel scripts (onRequest / onResponse / onSchedule), call abort() to stop the script and abort the current execution immediately — it throws an AbortError that Hodor reports back to you.

// ✅ correct — abort() is injected into the script scope
async function onRequest(context, url, request) {
if (url.includes("/tracking")) abort();
return request;
}
warning

Do not declare onRequest(context, url, request, aborter) with a 4th parameter. The parameter count of entry functions is fixed — a 4th parameter makes the entry undetectable.

Native Modules​

All $-prefixed modules are injected when the runtime activates. They are implemented natively, return synchronously (unless noted) and treat string inputs as UTF-8.

$codec — Encode & Decode​

Base64 / Hex / URL percent encoding for protocol conversion and data transfer.

MethodReturns
$codec.base64Encode(str)Base64 string.
$codec.base64Decode(str)Decoded string; invalid input returns "" (does not throw).
$codec.hexEncode(str)Lowercase hex string.
$codec.hexDecode(str)Decoded string; invalid characters are skipped.
$codec.urlEncode(str)Percent-encoded string.
$codec.urlDecode(str)Decoded string; invalid sequences are returned as-is.
$codec.base64Encode("Hello, World!"); // "SGVsbG8sIFdvcmxkIQ=="
$codec.base64Decode("SGVsbG8sIFdvcmxkIQ=="); // "Hello, World!"
$codec.hexEncode("ABC"); // "414243"
$codec.hexDecode("414243"); // "ABC"
$codec.urlEncode("hello world&foo=bar"); // "hello%20world%26foo%3Dbar"

$hash — Hashing​

Digest computation for integrity checks, content addressing and anonymization. Returns lowercase hex.

MethodOutput length
$hash.md5(str)32 hex chars
$hash.sha1(str)40 hex chars
$hash.sha256(str)64 hex chars
$hash.sha384(str)96 hex chars
$hash.sha512(str)128 hex chars
$hash.md5("hello"); // "5d41402abc4b2a76b9719d911017c592"
$hash.sha256("hello"); // "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"

// integrity check
const checksum = $hash.sha256(JSON.stringify({user: "admin"}));

$hmac — Message Authentication Code​

Keyed hashing for API signing and tamper detection. Both (data, key) are UTF-8 encoded; returns lowercase hex.

MethodAlgorithm
$hmac.md5(data, key)HMAC-MD5
$hmac.sha1(data, key)HMAC-SHA1
$hmac.sha256(data, key)HMAC-SHA256
$hmac.sha384(data, key)HMAC-SHA384
$hmac.sha512(data, key)HMAC-SHA512
const signature = $hmac.sha256("important data", "my-secret-key");
const isValid = (signature === $hmac.sha256("important data", "my-secret-key")); // true

$crypto — Symmetric Encryption​

AES-GCM authenticated encryption (recommended) and DES (legacy compatibility).

MethodReturns
$crypto.encryptAES(plaintext, password, options?)Base64 ciphertext; failures return an "Error: <reason>" string.
$crypto.decryptAES(ciphertext, password, options?)Plaintext; invalid ciphertext returns "Error: Invalid ciphertext".
$crypto.encryptDES(plaintext, key)DES-ECB + PKCS7, key truncated to 8 bytes → Base64.
$crypto.decryptDES(ciphertext, key)Plaintext; non-Base64 input returns "".

options: { keySize: 128 \| 192 \| 256 } (default 256). The AES ciphertext format is base64(nonce[12] + ciphertext + tag[16]) — pair it with decryptAES; when interoperating with other languages you must unpack this format yourself.

const encrypted = $crypto.encryptAES("sensitive data", "my-password");
$crypto.decryptAES(encrypted, "my-password"); // "sensitive data"

// 128-bit key
const e128 = $crypto.encryptAES("data", "pass", { keySize: 128 });

// errors are returned as strings, not thrown — check the prefix
const out = $crypto.decryptAES("not-base64!!", "p");
if (out.startsWith("Error:")) { /* handle error */ }

$jwt — JSON Web Token​

JWT creation, parsing and signature verification (HMAC family) for authentication flow scripts.

MethodReturns
$jwt.sign(payload, secret, algorithm?)JWT string; default HS256.
$jwt.decode(token){header, payload} object (signature is not verified); returns null on malformed tokens.
$jwt.verify(token, secret, algorithm?)boolean — signature verification result.

algorithm: "HS256" | "HS384" | "HS512".

// sign
const payload = JSON.stringify({ sub: "user123", exp: Math.floor(Date.now() / 1000) + 3600 });
const token = $jwt.sign(payload, "my-secret", "HS256");

// decode — returns an object directly, no JSON.parse needed
const decoded = $jwt.decode(token);
decoded.header.alg; // "HS256"
decoded.payload.sub; // "user123"

// verify
$jwt.verify(token, "my-secret", "HS256"); // true
$jwt.verify(token, "wrong-secret", "HS256"); // false

// expiry check
const exp = $jwt.decode(token).payload.exp;
const expired = exp * 1000 < Date.now();

$format — Data Formatting​

JSON/XML pretty-printing and minification for logging and debugging. JSON keys are sorted alphabetically in the output.

MethodReturns
$format.json(str, indent?)Pretty-printed JSON; indent clamped to 1–8 (default 2); invalid input returns "Error: Invalid JSON".
$format.minify(str)Minified JSON (sorted keys, no whitespace); invalid input returns "Error: Invalid JSON".
$format.xml(str)Simple indented XML.
$format.json('{"b":1,"a":2}');
// {
// "a": 2,
// "b": 1
// }
$format.json('{"a":2}', 4); // 4-space indent
$format.minify('{ "a": 2 }'); // '{"a":2}'
$format.xml('<root><child>t</child></root>');
// <root>
// <child>t</child>
// </root>

$fs — File System​

Lightweight file read/write inside scripts (config, cache, export). Paths support ~ expansion; write creates missing parent directories automatically.

MethodReturns
$fs.read(path)UTF-8 content; failure returns "Error: Cannot read file at <path>".
$fs.readBinary(path)Bytes as Base64; failure returns "".
$fs.write(path, content)boolean — success.
$fs.exists(path)boolean.
$fs.delete(path)boolean; missing files return false.
$fs.list(path)string[] of directory entries; failure returns [].
$fs.info(path)JSON string {size, modified, isDirectory, path}; missing paths return undefined.
const file = $env.tmpDir() + "/hodor-test.txt";
$fs.write(file, "Hello from Hodor!");
$fs.read(file); // "Hello from Hodor!"
$fs.exists(file); // true
$fs.list($env.tmpDir()); // ["hodor-test.txt", ...]

const info = JSON.parse($fs.info(file));
info.size; // byte count
info.isDirectory; // false

$fs.delete(file); // true
note

Failure shapes differ per method: read returns an "Error: ..." string, readBinary returns "", list returns [] and info returns undefined — handle each accordingly.

$env — Environment Variables​

Process environment variables and system paths. Note: $env is the process environment (shared across executions, lives with the process). To persist user-facing variables use sdk.setEnvVar or SharedPreferences instead.

MethodReturns
$env.get(key)Value; missing keys return "".
$env.set(key, value)boolean — setenv result.
$env.all()JSON string of all environment variables.
$env.cwd()Current working directory.
$env.tmpDir()Temporary directory.
$env.homeDir()User home directory.
$env.set("APP_VERSION", "1.0.0");
$env.get("APP_VERSION"); // "1.0.0"
const all = JSON.parse($env.all());

$uuid — UUID​

Generate and validate UUIDs (request IDs, idempotency keys).

MethodReturns
$uuid.v4()Standard v4 UUID (uppercase, hyphenated), e.g. "550e8400-E29B-41D4-A716-446655440000".
$uuid.compact()Hyphen-free, lowercase form.
$uuid.validate(str)Whether the string is a valid UUID.
const id = $uuid.v4();
$uuid.compact(); // "550e8400e29b41d4a716446655440000"
$uuid.validate($uuid.v4()); // true
$uuid.validate("not-uuid"); // false
note

validate only accepts the hyphenated format — validate($uuid.compact()) returns false. Re-insert hyphens before validating a compact string.

$log — Structured Logging​

Timestamped, leveled log lines routed through the engine's console system (collected into the script's log view).

MethodReturns
$log.log(level, message, tag?)Formatted log line (also printed).
$log.debug / info / warn / error(message)Convenience level methods, output only.

Log line format: [yyyy-MM-dd'T'HH:mm:ss.SSSZ] [LEVEL] [tag] message (the [tag] segment is omitted when no tag is given).

$log.info("Server started on port 8080");
// [2025-07-24T01:11:45.548+0800] [INFO] Server started on port 8080
$log.log("info", "Request processed", "API");
// [2025-07-24T01:11:45.548+0800] [INFO] [API] Request processed

Web APIs​

fetch​

Send real HTTP requests from scripts — the main channel for Postman-style workflows.

fetch(url: string, options?: object) → Promise<response>

options:

FieldTypeDefaultDescription
methodstring"GET"Case-insensitive; unknown values fall back to GET.
timeoutIntervalnumber30Seconds.
headers[[k, v]] or [{k: v}][]Both shapes are accepted.
textBodystring—Request body text.
encodingstring—"hex" means textBody is a hex string.

The resolved value is a plain object: statusCode (number), headers (array of single-key objects, duplicate keys preserved), and textBody() — a function that returns {text, encoding} with automatic character-set detection (undecodable bodies fall back to {text: <hex>, encoding: "hex"}). The rejected value is an error message string.

// GET
const rsp = await fetch("https://httpbin.org/get");
if (rsp.statusCode === 200) {
const body = rsp.textBody(); // {text: "...", encoding: "utf-8"}
const json = JSON.parse(body.text);
}

// POST JSON
const rsp2 = await fetch("https://httpbin.org/post", {
method: "POST",
headers: [["Content-Type", "application/json"]],
textBody: JSON.stringify({a: 1}),
timeoutInterval: 10,
});

HTTPClient​

The HTTPClient class sends HTTP requests from any script (implemented on top of fetch). All methods are async and return a response object with statusCode, headers and textBody().

MethodDescription
HTTPClient.get(url, params?)Send a GET request.
HTTPClient.post(url, params?)Send a POST request.
HTTPClient.put(url, params?)Send a PUT request.
HTTPClient.update(url, params?)Send an UPDATE request.
HTTPClient.delete(url, params?)Send a DELETE request.
HTTPClient.request(url, method, params?)Send a request with a custom method.

params is an optional object with:

  • headers: request headers object, e.g. {"key1": "value1"}
  • body: request body — a string, JSON object (serialized automatically) or URLSearchParams
  • timeoutInterval: timeout in seconds (default 60)
try {
var params = {
"headers": {"key1": "value1"},
"body": {"a": "a1", "b": "b1"},
};
const response = await HTTPClient.post("https://dummyjson.com/products/1", params);
console.log(response.textBody().text);
} catch(error) {
console.log(error);
}

Timers​

setTimeout / setInterval / clearTimeout / clearInterval work as in browsers. Callbacks are dispatched on a serial engine queue, so timers fire reliably regardless of the host thread.

FunctionReturns
setTimeout(callback, ms)Timer id (string), fires once.
setInterval(callback, ms)Timer id, fires repeatedly.
clearTimeout(id) / clearInterval(id)Cancel (idempotent, unknown ids are ignored).
await new Promise(resolve => setTimeout(resolve, 500)); // wait 0.5s
const timer = setInterval(() => console.log("tick"), 1000);
setTimeout(() => clearInterval(timer), 3500);

Global Functions​

NameSignatureDescription
btoa(str)(string) → stringBase64 encode — UTF-8 symmetric semantics (non-Latin1 strings are allowed, unlike browsers).
atob(str)(string) → stringBase64 decode; invalid input returns "".
performance.now()() → numberMilliseconds since the epoch (not page load).
sleep(ms)(number) → PromiseResolve after ms; use await sleep(1000).
require(id)(string) → anyLoad a script library by CommonJS semantics (module.exports); the key is @libs/<key>, resolved as script-library identifier → script-library name (unique) → built-in libs; throws when not found.
TextEncodernew TextEncoder().encode(str)UTF-8 encode into a Uint8Array (encoding is always "utf-8").
crypto.getRandomValues(n)(Array) → number[]Fill with n random UInt32 values.
crypto.randomUUID()() → stringRandom UUID (uppercase, hyphenated).
btoa("Hello"); // "SGVsbG8="
atob("SGVsbG8="); // "Hello"
btoa("中文"); // works — UTF-8 bytes are encoded
performance.now(); // 1760000000000.123
await sleep(100);
const { encode } = require("@libs/base64"); // built-in lib
const { sign } = require("@libs/sign-lib"); // your script library
new TextEncoder().encode("hi"); // Uint8Array [104, 105]

Libraries are managed in the script module manager. The require key is always @libs/<key>: your script libraries resolve by identifier first (stable across renames), then by name (unique match), then the built-in libs @libs/base64 / @libs/hex. Libraries export via module.exports, may require other libraries, and a missing key throws (pre-send failures abort the request, post-response failures keep the original response). See Quick Request scripting, Require Script Libraries for the full guide.

console​

console.log / info / warn / error prints values to the script's log view. Multiple arguments are serialized, so you can log objects and arrays directly:

console.log("headers", {Accept: "application/json"}, [1, 2, 3]);

$log offers structured, leveled output (see $log) — both channels are visible in Hodor's script log view.

HTTP Classes​

Request​

The Request class is the packaged HTTP request passed to onRequest / onSessionCompleted.

MemberTypeDescription
schemestring (get / set)The scheme of the request, e.g. https.
hoststring (get / set)The host of the request.
portnumber (get / set)The port of the request.
pathstring (get / set)The path of the request.
methodstring (get / set)The HTTP method, e.g. GET.
queriesURLSearchParams (get)The query parameters of the request.
headersHeaders (get)The headers of the request.
bodyBody (get) / string | Map | Array | URLSearchParams (set)The body of the request.
note

Headers must be modified with .set() / .append() — index assignment (e.g. headers["X-Key"] = v) is ignored.

You can also construct a request yourself:

const request = new Request({
scheme: 'https',
host: 'example.com',
port: 8080,
path: '/api',
method: 'GET',
queries: 'param1=value1&param2=value2',
headers: [
['Content-Type', 'application/json'],
['Authorization', 'Bearer token']
],
textBody: '{"key": "value"}'
});
console.log(request.encode());

Response​

The Response class represents the HTTP response passed to onResponse / onSessionCompleted.

MemberTypeDescription
statusCodenumber (get / set)The status code of the response.
statusPhrasestring (get / set)The status phrase of the response, e.g. OK.
headersHeaders (get)The headers of the response.
bodyBody (get) / string | Map | Array | URLSearchParams (set)The body of the response.
const response = new Response({
statusCode: 404,
statusPhrase: 'Not Found',
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache'
},
textBody: '{"error": "Resource not found"}'
});
console.log(response.encode());

Body​

The Body class represents the body of an HTTP request or response. Assigning a new value to request.body / response.body replaces the whole body.

MethodReturnsDescription
parseText()string | nullParse the body as text.
parseJSON()object | nullParse the body as JSON.
parseURLSearchParams()URLSearchParams | nullParse the body as URL-encoded parameters.
const bodyInstance = new Body('{"user": "alice"}');
const json = bodyInstance.parseJSON();
if (json) {
console.log('User:' + json.user);
} else {
console.error('Failed to parse JSON');
}

Headers​

The Headers class represents HTTP headers.

MethodReturnsDescription
append(name, value)voidAppend a value to the specified header.
set(name, value)voidSet (replace) the value of the specified header.
get(name)string | nullGet the value of the specified header.
has(name)booleanCheck if the specified header exists.
delete(name)voidDelete the specified header.
forEach(callback, thisArg?)voidCall a function for each header.
entries() / keys() / values()IteratorIterate over entries / keys / values.
encode()ArrayEncode the headers into a serializable format.
const headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.set('Content-Length', '1024');
console.log(headers.get('Content-Type')); // 'application/json'
console.log(headers.has('Authorization')); // false
headers.forEach((value, name) => {
console.log(name + ' ' + value);
});

URLSearchParams​

The URLSearchParams class represents URL query (or urlencoded body) parameters.

MethodReturnsDescription
append(name, value)voidAppend a value to the specified parameter.
set(name, value)voidSet (replace) the value of the specified parameter.
get(name)string | nullGet the value of the specified parameter.
has(name)booleanCheck if the specified parameter exists.
delete(name)voidDelete the specified parameter.
forEach(callback, thisArg?)voidCall a function for each parameter.
entries() / keys() / values()IteratorIterate over entries / keys / values.
sort()voidSort the parameters.
toString()stringSerialize to a urlencoded string.
const params = new URLSearchParams({ key1: 'value1', key2: 'value2' });
console.log(params.get('key1')); // 'value1'
console.log(params.get('key3')); // null
params.forEach((value, key) => console.log(key + ' ' + value));

SharedPreferences​

The SharedPreferences class provides local persistent key-value storage that survives script runs and app restarts (backed by system user defaults).

MethodReturnsDescription
SharedPreferences.write(key, value)voidWrite a value (string / number / boolean / object / array). Writing null deletes the key.
SharedPreferences.read(key)anyRead the value associated with the key, or null if it does not exist.
SharedPreferences.write("test_key", "test_value");
const value = SharedPreferences.read("test_key");
console.log(value);

SharedPreferences.write("cfg", {a: 1}); // objects are stored directly
SharedPreferences.write("cfg", null); // deletes "cfg"

sdk — Quick Request Scripts​

sdk (alias pm) is injected only in Quick Request pre/post scripts. It bridges the app's variable storage (environments, global and runtime variables) and collects assertions, with a Postman-like API shape.

MemberDescription
sdk.env.get(key)Read an environment variable defined in the app.
sdk.env.set(key, value)Set an environment variable (same as sdk.setEnvVar).
sdk.setEnvVar(key, value)Write an env variable — collected and persisted back to the app.
sdk.setVar(key, value)Write a runtime (local) variable.
sdk.setGlobalVar(key, value)Write a global variable.
sdk.getVar(key)Read a runtime variable.
sdk.test(name, fn)Run a named assertion block; failures are collected and reported in the app.
sdk.expect(value)Assertion primitives: toBe, toEqual, toBeTruthy.
sdk.jsonPath(object, path)Dot-path lookup, e.g. "$.data.list[0].id".
sdk.utils.uuid()Same as crypto.randomUUID().
sdk.utils.base64Encode(s) / base64Decode(s)Same as btoa / atob.
sdk.utils.md5(s)Same as $hash.md5.
// Quick Request pre-request script: sign the request
async function onRequest(context, url, request) {
// read variables defined in the app's environment
const secret = sdk.env.get("secret") || "test-key";
sdk.expect(secret).toBeTruthy();

const ts = String(Date.now());
const bodyText = request.body && request.body.text ? request.body.text : '';
const sig = $hmac.sha256(ts + bodyText, secret);

// headers must be set via .set / .append
request.headers.set("X-Signature", sig);
request.headers.set("X-Timestamp", ts);

// persist variables back to the app
sdk.setEnvVar("lastTs", ts);
sdk.test("signature is 64 hex", () => {
sdk.expect(sig.length).toBe(64);
});

console.log("signed request for", url);
return request;
}
note

Assertion results are reported for pre-request scripts. In post-response scripts, assertions run but results are not collected.

Contexts​

RewriteContext​

The context object passed to rewrite scripts (onRequest / onResponse).

MemberTypeDescription
isEnableOnRequestboolean (get)Whether the onRequest entry is enabled.
isEnableOnResponseboolean (get)Whether the onResponse entry is enabled.
parametersobject (get / set)A parameters dictionary shared between onRequest and onResponse.
scriptNamestring (get)The name of the script.

CompletedContext​

The context object passed to HTTP completed scripts.

MemberTypeDescription
sessionobject (get)The session object associated with the completed script.
scriptNamestring (get)The name of the completed script.

ScheduleContext​

The context object passed to schedule scripts.

MemberTypeDescription
parametersobject (get / set)Parameters passed to the next runloop when set.
cronstring (get)The cron expression of the schedule.
repeatedCountnumber (get)How many times the script has been repeated.
scriptNamestring (get)The name of the scheduled script.

Behavior Notes​

  • Uncaught exceptions fail the script. Errors (including AbortError from abort()) propagate to Hodor and the execution is reported as failed — partial rewrites made before the error are discarded, so guard risky blocks with try / catch.
  • Timeout watchdog. Each script run has a time budget; a script that never finishes (e.g. an endless loop or a hung await) is force-interrupted and reported as a timeout.
  • Errors as return values. Some APIs never throw: $codec.base64Decode returns "", $crypto returns "Error: ..." strings, $fs.read returns "Error: ..." strings and $fs.info returns undefined. Check these return values instead of relying on try / catch.
  • console.log vs $log. Both are visible in the script log view; $log adds timestamps and levels.
  • Script runtime persistence. Variables written with sdk.setVar and files written with $fs remain available to subsequent executions of the same script; SharedPreferences persists across app restarts.