JavaScript-скриптинг
Chute поддерживает JavaScript-скриптинг для расширенной модификации запросов/ответов, пользовательского сопоставления правил, разрешения DNS и запланированных задач. Скрипты используют движок JavaScriptCore от Apple и следуют 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 |
Да | — | Локальный путь к файлу или HTTP(S) URL к JS-скрипту |
pattern |
Нет | (совпадает со всем) | Регулярное выражение URL для фильтрации запуска скрипта |
requires-body |
Нет | автоопределение | Принудительно передавать скрипту полное тело запроса/ответа |
max-size |
Нет | 131072 (128 КБ) | Максимальный размер тела в байтах для скриптов с доступом к телу |
timeout |
Нет | 5.0 секунд | Тайм-аут выполнения одного скрипта |
argument |
Нет | — | Пользовательский строковый аргумент, доступный как $argument в JS |
debug |
Нет | false | Пропустить кэш компиляции для отладки |
cron-expression |
Нет | — | Выражение расписания cron (только для типа cron) |
wake-system |
Нет | false | Пробуждать систему для выполнения cron-скриптов (только iOS) |
enable |
Нет | true | Включить или отключить этот скрипт |
script-update-interval |
Нет | 0 | Секунды между обновлениями удаленных скриптов (-1 = никогда, 0 = только при запуске) |
Типы скриптов
| Строка типа | Enum | Описание |
|---|---|---|
http-request |
HTTP Request | Перехват и изменение HTTP-запросов до отправки |
http-response |
HTTP Response | Перехват и изменение HTTP-ответов до клиента |
http-request-before-send |
HTTP Request Before Send | Изменение запроса после сбора полного тела, перед отправкой |
rule |
Rule | Пользовательская логика сопоставления правил |
dns |
DNS | Пользовательское разрешение DNS |
cron |
Cron | Запланированные/периодические скрипты |
event |
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 и др.) или QUERY для DNS |
.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 | Уникальный идентификатор запроса |
.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) — Обработчик завершения
Должен быть вызван ровно один раз в конце скрипта для сигнализации о завершении. Выполнение скрипта блокируется до вызова $done() или истечения тайм-аута.
$done({}) // Пропуск — без изменений
$done() // Прервать соединение
$done({matched: true}) // Результат сопоставления правила (только для rule-скриптов)
$done({address: "1.2.3.4"}) // Результат DNS (только для dns-скриптов)
Возвращаемые значения скрипта HTTP Request:
$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 Response:
$done({
status: 200, // Изменить код статуса
headers: {"X-Custom": "value"}, // Изменить заголовки ответа
body: "new response body", // Изменить тело ответа
url: "https://other.example.com" // Вызвать 302 редирект
})
Когда указан url, Chute возвращает 302 редирект на указанный URL вместо исходного ответа.
Возвращаемые значения скрипта 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 // Массив IP-адресов DNS-серверов
$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 // Временная метка epoch
Глобальные переменные на выполнение
Следующие переменные внедряются при каждом выполнении скрипта и специфичны для определенных типов скриптов.
$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 — Информация о событии (только 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 Request
Выполняется при получении заголовков запроса. Может изменять URL, заголовки и тело до отправки запроса.
[Script]
ModifyHeaders = type=http-request, script-path=modify.js, pattern=^https://api\.example\.com
Скрипт HTTP Response
Выполняется при получении заголовков ответа. Может изменять статус, заголовки и тело до возврата клиенту.
[Script]
ModifyResponse = type=http-response, script-path=response.js, pattern=^https://api\.example\.com
Скрипт HTTP Request Before Send
Выполняется после сбора полного тела запроса, непосредственно перед отправкой. Полезно для изменения тел POST/PUT запросов.
[Script]
BeforeSend = type=http-request-before-send, script-path=before-send.js, pattern=^https://api\.example\.com, requires-body=true
Скрипт Rule
Пользовательское сопоставление правил. Скрипт должен вызвать $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 минут, значения, разделенные запятыми).
Скрипт Event
Запускается системными событиями. В настоящее время поддерживает событие network-changed (срабатывает при смене Wi-Fi или сотовой сети).
[Script]
NetChange = type=event, script-path=network-changed.js
Объект $event доступен:
$event.name // "network-changed"
Практические примеры
Перенаправление мобильных устройств
Скрипт http-request, который перенаправляет мобильных пользователей на основе User-Agent:
[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-ответах
Скрипт http-response, который удаляет рекламу и спонсируемый контент из JSON-ответа API:
[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)})
Изменение тела запроса перед отправкой
Скрипт http-request-before-send, который очищает POST-данные:
[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 для внутренних доменов
Скрипт dns, который разрешает внутренние имена хостов в локальные IP:
[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")
}
Периодическая проверка здоровья
Скрипт cron, который проверяет здоровье прокси каждые 30 минут:
[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-ответов внешними данными
Скрипт http-response, который обогащает пользовательские данные, вызывая вторичный API:
[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 скрипты ограничены лимитом памяти 10 МБ; на macOS — 512 МБ.
- Удаленные скрипты (пути HTTP/HTTPS) загружаются при запуске и опционально перезагружаются каждые
script-update-intervalсекунд.
Интеграция модульных скриптов
Скрипты также могут быть определены в файлах Module (.sgmodule) в разделе [Script].
```