Разделы · 7/11
Кнопки и события
Поток /events, at-most-once доставка, sparku listen в NDJSON и с --exec, другие действия кнопок.
Кто такой слушатель#
Кнопка, капсула, тумблер или меню в карточке может отправлять «событие слушателю»
(Типы полей, элементы управления). Слушатель — это клиент, который
держит открытым GET /v1/integrations/{slug}/events. Пока поток открыт хотя бы у
одного клиента, элементы с действием «событие» активны; без слушателя — неактивны,
нажимать некуда.
Доставка at-most-once, без очереди: событие, случившееся, пока никто не слушал, пропадает безвозвратно. Переподключение слушателя не возвращает пропущенные нажатия.
Поток GET …/events#
curl --unix-socket ~/.sparku/run/api.sock \
-H "Authorization: Bearer $SPARKU_TOKEN_DEPLOY" \
http://sparku/v1/integrations/deploy/events
Ответ — text/event-stream, соединение не закрывается, пока его не закроет одна
из сторон.
Первым событием приходит hello со списком элементов управления карточки:
event: hello
data: {"integration":"deploy","controls":[{"id":"pause","kind":"toggle"},{"id":"restart","kind":"button"}]}
Дальше — control на каждое нажатие:
event: control
data: {"integration":"deploy","control":"pause","value":true,"at":"2026-09-28T10:15:03Z"}
value есть только у тумблера (bool) и меню (state) — то, во что переключили
элемент. У кнопки и капсулы value в событии нет.
Раз в 15 секунд сервер пишет комментарий SSE:
: ping
Клиенты его игнорируют; сервер по ошибке записи такого пульса узнаёт, что клиент пропал (сон, обрыв сети), и закрывает соединение — держать мёртвого слушателя незачем.
Пока поток открыт, отправлять в него ничего не нужно и нельзя: всё, что клиент пришлёт после запроса, сервер игнорирует.
sparku listen#
sparku listen <slug> [--exec "команда"] [--reconnect]
Без --exec каждое событие печатается в stdout строкой JSON (NDJSON) — удобно для
jq и while read:
sparku listen deploy | while read -r line; do
control=$(echo "$line" | jq -r .control)
value=$(echo "$line" | jq -r .value)
echo "нажали $control = $value"
done
С --exec на каждое событие control запускается команда; hello в неё не
попадает. Данные события приходят обработчику только через переменные
окружения, не в аргументах и не в тексте команды:
| Переменная | Значение |
|---|---|
SPARKU_SLUG |
адрес интеграции |
SPARKU_CONTROL |
id элемента управления |
SPARKU_VALUE |
значение тумблера/меню (true/false или id состояния); у кнопки пусто |
SPARKU_EVENT |
всё событие целиком, как пришло по SSE (JSON-строка) |
sparku listen deploy --exec './on-click.sh'
#!/bin/bash
# on-click.sh
case "$SPARKU_CONTROL" in
pause) echo "пауза: $SPARKU_VALUE" ;;
restart) ./restart-deploy.sh ;;
esac
--reconnect переподключается, пока SparkU недоступен или API выключили, вместо
того чтобы завершиться. Без него listen завершается кодом 69, когда соединение
обрывается.
sparku listen deploy --reconnect --exec './on-click.sh' &
NOTE
sparku listen требует токен так же, как push: --token, --token-file или
SPARKU_TOKEN_<АДРЕС> / SPARKU_TOKEN.
Пример на Python (без зависимостей)#
Читает SSE прямо из UNIX-сокета через http.client:
import http.client, json, os, socket
class SparkU(http.client.HTTPConnection):
def __init__(self):
super().__init__("sparku")
def connect(self):
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.sock.connect(os.path.expanduser("~/.sparku/run/api.sock"))
token = os.environ["SPARKU_TOKEN_DEPLOY"]
api = SparkU()
api.request("GET", "/v1/integrations/deploy/events", headers={
"Authorization": f"Bearer {token}",
})
response = api.getresponse()
buffer = ""
for chunk in iter(lambda: response.read(1), b""):
buffer += chunk.decode("utf-8")
if buffer.endswith("\n\n"):
for line in buffer.splitlines():
if line.startswith("data: "):
event = json.loads(line[len("data: "):])
if "control" in event:
print("нажали", event["control"], event.get("value"))
buffer = ""
Пример на Node#
const http = require("node:http");
const os = require("node:os");
const request = http.request({
socketPath: `${os.homedir()}/.sparku/run/api.sock`,
path: "/v1/integrations/deploy/events",
headers: { Authorization: `Bearer ${process.env.SPARKU_TOKEN_DEPLOY}` },
});
request.on("response", (response) => {
let buffer = "";
response.on("data", (chunk) => {
buffer += chunk.toString("utf8");
const frames = buffer.split("\n\n");
buffer = frames.pop();
for (const frame of frames) {
const line = frame.split("\n").find((l) => l.startsWith("data: "));
if (!line) continue;
const event = JSON.parse(line.slice("data: ".length));
if (event.control) console.log("нажали", event.control, event.value);
}
});
});
request.end();
Другие действия кнопок#
Событие слушателю — не единственное, что может делать элемент управления. Что именно, задаёт человек в редакторе интеграции (не скрипт):
- Открыть ссылку, файл или приложение —
{ключ}в адресе подставляется значением поля с процентным кодированием; шаблон из одного поля-ссылки ({url}) берётся как есть. - Выполнить shell-команду — текст команды пишет человек в редакторе. Значения
полей никогда не подставляются в текст команды, только в окружение —
SPARKU_FIELD_<КЛЮЧ>(ключ — в верхнем регистре), плюсSPARKU_SLUG,SPARKU_CONTROLиSPARKU_VALUEдля тумблера/меню. - Запустить команду «Команды» (Shortcuts) — по имени, без shell:
shortcuts run <имя>.
Слушатели получают событие о нажатии независимо от того, какое из этих действий настроено, — оно выполняется наравне с отправкой в поток.