JavaScriptスクリプト
Chuteは、高度なリクエスト/レスポンス変更、カスタムルールマッチング、DNS解決、スケジュールタスクのためのJavaScriptスクリプトをサポートしています。スクリプトはAppleのJavaScriptCoreエンジンを使用し、Surge互換のスクリプトAPIに従います。
スクリプトは設定ファイルの[Script]セクションで定義されます。
設定
[Script]
MyScript = type=http-request, script-path=/path/to/script.js, pattern=^https?://example\.com, requires-body=true, max-size=262144, timeout=10, argument=myArg, debug=true
CronJob = type=cron, script-path=/path/to/cron.js, cron-expression=* * * * *, wake-system=true
スクリプトパラメータ
| パラメータ | 必須 | デフォルト | 説明 |
|---|---|---|---|
type |
はい | http-request |
スクリプトトリガータイプ(下記参照) |
script-path |
はい | — | JSスクリプトへのローカルファイルパスまたはHTTP(S) URL |
pattern |
いいえ | (全てにマッチ) | スクリプトがトリガーされるタイミングをフィルタするURL正規表現パターン |
requires-body |
いいえ | 自動検出 | スクリプトに完全なリクエスト/レスポンスボディの受信を強制 |
max-size |
いいえ | 131072 (128KB) | ボディにアクセスするスクリプトの最大ボディサイズ(バイト) |
timeout |
いいえ | 5.0秒 | スクリプトごとの実行タイムアウト |
argument |
いいえ | — | JS内で$argumentとして利用可能なカスタム文字列引数 |
debug |
いいえ | false | デバッグ用にコンパイルキャッシュをスキップ |
cron-expression |
いいえ | — | Cronスケジュール式(cronタイプのみ) |
wake-system |
いいえ | false | cronスクリプト実行のためにシステムをスリープ解除(iOSのみ) |
enable |
いいえ | true | このスクリプトを有効または無効にする |
script-update-interval |
いいえ | 0 | リモートスクリプト更新間隔の秒数(-1 = 更新しない、0 = 起動時のみ) |
スクリプトタイプ
| タイプ文字列 | 列挙型 | 説明 |
|---|---|---|
http-request |
HTTPリクエスト | アップストリーム前にHTTPリクエストをインターセプトして変更 |
http-response |
HTTPレスポンス | クライアント前にHTTPレスポンスをインターセプトして変更 |
http-request-before-send |
HTTPリクエスト送信前 | 完全なボディ収集後、送信前にリクエストを変更 |
rule |
ルール | カスタムルールマッチングロジック |
dns |
DNS | カスタムDNS解決 |
cron |
Cron | スケジュール/タイマースクリプト |
event |
イベント | システムイベントハンドラ(例: network-changed) |
ボディ自動検出: スクリプトソースに
$request.bodyまたは$response.bodyが含まれている場合、ボディは自動的にmax-sizeまで提供されます。この動作を強制するにはrequires-body=trueを使用してください。
JavaScript APIリファレンス
スクリプトは、以下のグローバルオブジェクトが利用可能なサンドボックス化されたJavaScriptCore環境で実行されます。
$request(読み取り専用)
利用可能なタイプ: http-request、http-response、http-request-before-send、rule
| プロパティ | 型 | 説明 |
|---|---|---|
.url |
String | 完全なリクエストURL |
.method |
String | HTTPメソッド(GET、POSTなど)またはDNSの場合はQUERY |
.headers |
Object | キーと値のペアとしてのリクエストヘッダー |
.body |
String または null | リクエストボディ(UTF-8デコード) |
.hostname |
String | ターゲットホスト名 |
.destPort |
Number | 宛先ポート |
.processPath |
String | リクエスト元プロセスパス(macOSのみ) |
.userAgent |
String | User-Agentヘッダー値 |
.sourceIP |
String | 送信元IPアドレス |
.listenPort |
Number | プロキシリッスンポート |
.requestId |
String | 一意のリクエストID |
.dnsResult |
String | 解決されたIPアドレス |
.srcPort |
Number | 送信元ポート |
.protocol |
String | 検出されたプロトコル: http、https、tcp、dns |
$response(読み取り専用)
利用可能なタイプ: http-response
| プロパティ | 型 | 説明 |
|---|---|---|
.status |
Number | HTTPステータスコード |
.headers |
Object | キーと値のペアとしてのレスポンスヘッダー |
.body |
String または null | レスポンスボディ(UTF-8デコード) |
$done(value) — 完了ハンドラ
スクリプトの終了時に必ず1回だけ呼び出して完了を通知する必要があります。スクリプトの実行は$done()が呼び出されるかタイムアウトが切れるまでブロックされます。
$done({}) // パススルー — 変更なし
$done() // 接続を中止
$done({matched: true}) // ルールマッチ結果(ルールスクリプトのみ)
$done({address: "1.2.3.4"}) // DNS結果(DNSスクリプトのみ)
HTTPリクエストスクリプトの戻り値:
$done({
url: "https://new.example.com/path", // URLをリライト
headers: {"X-Custom": "value"}, // ヘッダーを変更
body: "new request body", // ボディを変更
response: { // 合成レスポンスを返す(アップストリームをスキップ)
status: 200,
headers: {"Content-Type": "text/html"},
body: "<html>Blocked</html>"
}
})
responseが提供された場合、リクエストは短絡されます: Chuteはアップストリームサーバーに接続せずに、合成レスポンスをクライアントに直接返します。これはブロック、APIのモック、キャッシュされたコンテンツの返却に便利です。
HTTPレスポンススクリプトの戻り値:
$done({
status: 200, // ステータスコードを変更
headers: {"X-Custom": "value"}, // レスポンスヘッダーを変更
body: "new response body", // レスポンスボディを変更
url: "https://other.example.com" // 302リダイレクトをトリガー
})
urlが提供された場合、Chuteは元のレスポンスの代わりに指定されたURLへの302リダイレクトを返します。
DNSスクリプトの戻り値:
$done({address: "1.2.3.4"}) // 単一IP
$done({addresses: ["1.2.3.4", "5.6.7.8"]}) // 複数IP
$done({address: "10.0.0.1", ttl: 300}) // カスタムTTL付き(秒、デフォルト60)
$done({server: "8.8.8.8"}) // 特定のDNSサーバーに転送
$httpClient — 非同期HTTPクライアント
スクリプト内からHTTPリクエストを作成します。全てのリクエストは$done()またはタイムアウト時にキャンセルされます。
$httpClient.get(url, function(error, response, data) {
if (error) {
console.log("Request failed: " + error)
} else {
console.log("Status: " + response.status)
console.log("Response: " + data)
}
})
$httpClient.post(url, {headers: {...}, body: "...", timeout: 5}, callback)
$httpClient.put(url, options, callback)
$httpClient.del(url, options, callback)
$httpClient.head(url, options, callback)
$httpClient.options(url, options, callback)
$httpClient.patch(url, options, callback)
コールバックシグネチャ: callback(error, response, data)
error: エラー文字列またはnullresponse:{status: Number, headers: Object}またはnulldata: UTF-8文字列レスポンスボディまたはnull
$persistentStore — キーバリューストレージ
スクリプトやプロセスの再起動後も存続する永続的なキーバリューストレージ。NSUserDefaultsによってバックアップされます。
$persistentStore.write(data, key) // 値を保存
$persistentStore.read(key) // 値を取得
$persistentStore.remove(key) // 値を削除
$notification — ローカル通知
ローカルシステム通知を投稿します。
$notification.post("Title", "Subtitle", "Notification body text")
$network — ネットワーク情報
読み取り専用のネットワーク状態情報。
$network.dns // DNSサーバーIPの配列
$network.wifi // {ssid: "WiFiName", bssid: "aa:bb:cc:dd:ee:ff"}
$environment — ランタイム情報
$environment.system // "iOS" または "macOS"
$environment.appVersion // アプリバージョン文字列
$utils — ユーティリティ
$utils.geoip("1.2.3.4") // 国コード(例: "US")
$utils.ipasn("1.2.3.4") // ASN番号(例: "13335")
$utils.ungzip(data) // gzipデータを展開
$klne — プロキシ制御API
スクリプトからプロキシランタイムを制御します。
$klne.policyGroups // 全てのポリシーグループを取得
$klne.selectPolicy("Group", "Proxy") // グループのポリシーを切り替え
$klne.getActiveConnections() // アクティブな接続を一覧表示
$klne.closeConnection("id") // 接続を閉じる
$klne.flushDNS() // DNSキャッシュを削除
$klne.startURLTest("Group") // グループのURLテストをトリガー
$klne.reloadConfiguration() // 全ての設定を再読み込み
$klne.setOutboundMode("rule") // モードを設定: "global"、"proxy"、"direct"、"rule"
$klne.setHTTPCaptureEnabled(true) // MITMを有効/無効
$script — スクリプトメタデータ
$script.name // 設定からのスクリプト名
$script.type // スクリプトタイプ文字列
$script.startTime // エポックタイムスタンプ
実行ごとのグローバル変数
以下の変数はスクリプト実行ごとに注入され、特定のスクリプトタイプに固有です。
$argument — スクリプト引数
スクリプト設定のargument=パラメータからの文字列値。利用可能なタイプ: http-request、http-response、http-request-before-send、rule、dns、cron。
console.log("Argument: " + $argument)
$domain — DNSドメイン(DNSスクリプトのみ)
クエリ対象のドメイン名。dnsスクリプトでのみ利用可能です。
var domain = $domain // 例: "example.com"
$cronexp — Cron式(Cronスクリプトのみ)
スクリプト設定からのcronスケジュール式。cronスクリプトでのみ利用可能です。
console.log("Schedule: " + $cronexp) // 例: "*/30 * * * *"
$event — イベント情報(イベントスクリプトのみ)
トリガーイベントに関する情報。現在はnetwork-changedのみがサポートされています。
console.log("Event: " + $event.name) // "network-changed"
console — ログ出力
console.log("Debug message") // 詳細ログ
console.warn("Warning message") // 警告ログ
console.error("Error message") // [JS-ERROR]プレフィックス付きの警告ログ
setTimeout(fn, seconds) — タイマー
遅延後に関数を実行するようスケジュールします。
setTimeout(function() {
console.log("Delayed execution")
}, 2.5) // 2.5秒
$script(subScriptPath) — サブスクリプトローダー
別のJavaScriptファイルを読み込んで評価します。
$script("/path/to/helper.js")
$script("https://example.com/remote-script.js")
// $persistentStoreを使用してスクリプト間でデータを渡す
スクリプトタイプの詳細
HTTPリクエストスクリプト
リクエストヘッダーを受信したときに実行されます。リクエストが転送される前にURL、ヘッダー、ボディを変更できます。
[Script]
ModifyHeaders = type=http-request, script-path=modify.js, pattern=^https://api\.example\.com
HTTPレスポンススクリプト
レスポンスヘッダーを受信したときに実行されます。クライアントに返す前にステータス、ヘッダー、ボディを変更できます。
[Script]
ModifyResponse = type=http-response, script-path=response.js, pattern=^https://api\.example\.com
HTTPリクエスト送信前スクリプト
完全なリクエストボディが収集された後、アップストリームに送信する直前に実行されます。POST/PUTリクエストボディの変更に便利です。
[Script]
BeforeSend = type=http-request-before-send, script-path=before-send.js, pattern=^https://api\.example\.com, requires-body=true
ルールスクリプト
カスタムルールマッチング。スクリプトは$done({matched: true})または$done({matched: false})を呼び出す必要があります。
[Rule]
SCRIPT,MyRuleScript,DIRECT
[Script]
MyRuleScript = type=rule, script-path=rule.js
DNSスクリプト
カスタムDNS解決。$domainを受け取り、解決されたアドレスを返します。
// dns.js
var domain = $domain
if (domain === "internal.example.com") {
$done({address: "10.0.0.1", ttl: 300})
} else {
$done({}) // 通常のDNS解決にパススルー
}
Cronスクリプト
cron式を使用したスケジュール実行。最小間隔は60秒です。
[Script]
HourlyTask = type=cron, script-path=hourly.js, cron-expression=0 * * * *
簡略化されたcron構文がサポートされています(例: 30分ごとの*/30 * * * *、カンマ区切りの値)。
イベントスクリプト
システムイベントによってトリガーされます。現在はnetwork-changedイベント(Wi-Fiまたは携帯電話ネットワークが変更されたときに発生)をサポートしています。
[Script]
NetChange = type=event, script-path=network-changed.js
$eventオブジェクトが利用可能です:
$event.name // "network-changed"
実践例
モバイルデバイスのリダイレクト
User-Agentに基づいてモバイルユーザーをリダイレクトするhttp-requestスクリプト:
[Script]
MobileRedirect = type=http-request, script-path=mobile-redirect.js, pattern=^https://example\\.com
// mobile-redirect.js
var ua = $request.headers["User-Agent"] || ""
if (/Mobile|Android|iPhone/.test(ua)) {
$done({
response: {
status: 302,
headers: {"Location": "https://m.example.com" + $request.url.replace(/.*example\\.com/, "")},
body: ""
}
})
} else {
$done({})
}
APIレスポンスのコンテンツをブロック
JSON APIレスポンスから広告とスポンサーコンテンツを削除するhttp-responseスクリプト:
[Script]
RemoveAds = type=http-response, script-path=remove-ads.js, pattern=^https://api\\.example\\.com/feed, requires-body=true
// remove-ads.js
var body = JSON.parse($response.body)
if (body.ads) {
delete body.ads
}
if (body.recommendations) {
body.recommendations = body.recommendations.filter(function(r) {
return !r.sponsored
})
}
$done({body: JSON.stringify(body)})
送信前のリクエストボディを変更
POSTペイロードをサニタイズするhttp-request-before-sendスクリプト:
[Script]
SanitizePayload = type=http-request-before-send, script-path=sanitize.js, pattern=^https://api\\.example\\.com/submit, requires-body=true
// sanitize.js
var body = JSON.parse($request.body)
body.clientSecret = "[REDACTED]"
body.timestamp = Math.floor(Date.now() / 1000)
$done({body: JSON.stringify(body)})
カスタムルール: 時間ベースのルーティング
時間帯に応じて異なるプロキシを選択するruleスクリプト:
[Rule]
SCRIPT,TimeBasedRule,ProxyA
[Script]
TimeBasedRule = type=rule, script-path=time-rule.js
// time-rule.js
var hour = new Date().getHours()
if (hour >= 9 && hour < 18) {
$done({matched: false}) // 勤務時間中は次のルールにフォールスルー
} else {
$done({matched: true}) // 勤務時間外はProxyAを使用
}
内部ドメインのカスタムDNS
内部ホスト名をローカルIPに解決するdnsスクリプト:
[Script]
InternalDNS = type=dns, script-path=internal-dns.js
// internal-dns.js
var internalHosts = {
"gitlab.local": "10.0.0.10",
"registry.local": "10.0.0.11",
"monitor.local": "10.0.0.12"
}
if (internalHosts[$domain]) {
$done({address: internalHosts[$domain], ttl: 3600})
} else {
$done({}) // 通常のDNSにパススルー
}
ネットワーク変更時の自動ポリシー切り替え
携帯電話ネットワーク時に保守的なポリシーグループに切り替えるeventスクリプト:
[Script]
NetSwitch = type=event, script-path=network-switch.js
// network-switch.js
if (!$network.wifi.ssid) {
// 携帯電話ネットワーク — 低データグループを使用
$klne.selectPolicy("MainGroup", "LowDataProxy")
console.log("Switched to cellular profile")
} else if ($network.wifi.ssid === "Office") {
$klne.selectPolicy("MainGroup", "DIRECT")
console.log("Switched to office profile")
}
定期的なヘルスチェック
30分ごとにプロキシの健全性をチェックするcronスクリプト:
[Script]
HealthCheck = type=cron, script-path=health-check.js, cron-expression=*/30 * * * *
// health-check.js
$httpClient.head("https://www.google.com/generate_204", {timeout: 10},
function(error, response, data) {
if (error || response.status !== 204) {
console.error("Health check failed: " + (error || "status " + response.status))
$notification.post("Chute Alert", "Health Check", "Cannot reach Google")
} else {
console.log("Health check OK")
}
}
)
$done()
外部データでAPIレスポンスを強化
二次APIを呼び出してユーザーデータを強化するhttp-responseスクリプト:
[Script]
EnrichUsers = type=http-response, script-path=enrich.js, pattern=^https://api\\.example\\.com/users, requires-body=true
// enrich.js
var users = JSON.parse($response.body)
var pending = users.length
if (pending === 0) { $done({}) }
users.forEach(function(user, index) {
$httpClient.get("https://internal-api.example.com/avatar/" + user.id,
function(error, resp, data) {
if (!error && resp.status === 200) {
users[index].avatar = JSON.parse(data).url
}
pending--
if (pending === 0) {
$done({body: JSON.stringify(users)})
}
}
)
})
実行モデル
- 全てのスクリプトはスレッドセーフのために専用のシリアルキューで実行されます。
- 各スクリプト実行にはスクリプトごとのタイムアウトがあります。タイムアウト内に
$done()が呼び出されない場合、スクリプトはパススルーとして扱われます。 - パフォーマンスのためにJSContextプール(サイズ3)が使用され、コンテキストは実行間で再利用されます。
- スクリプトのコンパイルはデフォルトでキャッシュされます。キャッシュをバイパスするには
debug=trueを使用してください。 - iOS/tvOSでは、スクリプトは10MBのメモリ制限の対象となります。macOSでは512MBです。
- リモートスクリプト(HTTP/HTTPSパス)は起動時に取得され、オプションで
script-update-interval秒ごとに再取得されます。
モジュールスクリプト統合
スクリプトはモジュールファイル(.sgmodule)の[Script]セクションでも定義できます。