Open Stream Deck

Плагин на JavaScript и TypeScript

Глобальный объект opensd: действия, кнопки, сеть, приложения, таймеры и журнал.

Плагин на JavaScript это один файл с исходным кодом. Он попадает в пакет как есть и исполняется в изолированном воркере движка WebView приложения. Самому плагину на JavaScript Node.js и сборка не нужны: Node.js нужен только для установки CLI через npm. TypeScript собирается в один файл через esbuild.

Проект

npm create @opensd my-plugin
opensd new my-plugin --lang js

Первая команда создаёт проект на TypeScript, вторая на JavaScript. Установка описана в быстром старте.

JavaScript:

opensd.toml      манифест с [runtime] type = "js", main = "plugin.js"
plugin.js        код плагина
opensd.d.ts      типы API для подсказок редактора
jsconfig.json    проверка типов в редакторе
assets/          картинки

TypeScript: код лежит в src/plugin.ts. npm install ставит @opensd/cli, esbuild и TypeScript. npm run dev и npm run pack вызывают opensd dev и opensd pack, а те сами запускают npm run build, который пишет plugin.js.

Действия

opensd.action("com.example.counter.counter", {
  appear({ key, settings }) {
    key.setTitle(String(settings.count ?? 0));
  },
  tap({ key, settings }) {
    const count = (settings.count ?? 0) + 1;
    key.saveSettings({ ...settings, count }).setTitle(String(count));
  },
  longPress({ key, settings }) {
    key.saveSettings({ ...settings, count: 0 }).setTitle("0").flash("ok");
  },
});

Обработчик получает событие со свойствами context, action, controller, position, settings, state и готовым key. Обработчики могут быть асинхронными. Исключение или отклонённый Promise пишется в журнал плагина, плагин продолжает работать.

Имена обработчиков совпадают с типами событий: appear, disappear, keyDown, keyUp, tap, longPress, holdStart, holdRepeat, holdEnd, settings, inspectorOpen, inspectorClose, inspectorMessage.

События без кнопки слушаются через opensd.on:

opensd.on("init", ({ app, device, globals }) => opensd.log("started in", app.language));
opensd.on("globals", ({ settings }) => applyGlobals(settings));
opensd.on("settingsPageMessage", ({ payload }) => handle(payload));

Кнопка

МетодЧто делает
setTitle(text, style?)Подпись; null возвращает подпись из манифеста. style: align, size, color, bold
setImage(image)Путь в пакете или data:image/…;base64,…; null возвращает картинку из манифеста
setState(n)Состояние из манифеста
flash("ok" | "alert")Отметка поверх кнопки
saveSettings(settings)Настройки этой кнопки
toInspector(payload)Сообщение странице настроек кнопки

Методы возвращают ту же кнопку, поэтому вызовы соединяются в цепочку. opensd.keys(action) перечисляет видимые кнопки, opensd.key(context) даёт кнопку по контексту.

Состояние плагина

Свойство или методЧто даёт
opensd.pluginid и version
opensd.languageЯзык интерфейса приложения
opensd.deviceУстройство: сетка кнопок, боковая полоса, подключено ли
opensd.globals, opensd.setGlobals(settings)Общие настройки плагина
opensd.toSettingsPage(payload)Сообщение окну настроек плагина
opensd.now()Миллисекунды с запуска плагина

Хранить состояние можно в обычных переменных модуля: экземпляр живёт, пока плагин запущен.

Сеть и приложения

const list = await opensd.http("http://127.0.0.1:9222/json/list", { timeout: 2000 });
const weather = await opensd.http("https://api.example.com/v1/now");

const socket = opensd.ws("ws://127.0.0.1:9222/devtools/page/X");
socket.onmessage = (data) => opensd.log(data);

const status = await opensd.app.status("editor");
await opensd.app.launch("editor", [9222]);
await opensd.clipboard("https://example.com/track/1");
  • Адреса 127.0.0.1 и localhost идут через брокер приложения: нужно разрешение loopback, CORS и заголовка Origin нет.
  • Остальные адреса требуют разрешения network. Для них работают и обычные fetch и WebSocket, и opensd.http, но действует политика CORS браузерного движка.
  • app и clipboard требуют разрешений launch и clipboard.

Без согласия пользователя промисы отклоняются с сообщением о недостающем разрешении.

Таймеры

Обычные setTimeout и setInterval работают. Держите интервалы только пока они нужны: плагин, у которого нет видимых кнопок, не должен тратить процессор.

let refresh = null;
opensd.on("appear", () => {
  refresh ??= setInterval(update, 1000);
});
opensd.on("disappear", () => {
  if (opensd.keys().length === 0) {
    clearInterval(refresh);
    refresh = null;
  }
});

Журнал

console.log, console.warn, console.error, а также opensd.log, opensd.warn, opensd.error пишут в журнал плагина. opensd dev выводит его в терминал, в приложении журнал открывается кнопкой «Журнал» в «Настройки», «Плагины».

Что недоступно

eval, new Function, importScripts и import() по сети, IndexedDB, Cache Storage, cookies, localStorage, DOM. Код плагина должен быть одним файлом: зависимости из npm собирайте в него через esbuild, как в шаблоне TypeScript.