Клиентские плагины
target: client или both: .wasm подгружается прямо в Lampa и расширяет UI/поведение клиента.Серверные плагины — это балансеры. Клиентские — это полноценные расширения Lampa, написанные на Go/Rust/AssemblyScript: добавляют кнопки, рисуют бейджи на карточках, реагируют на события Lampa, читают/пишут Lampa.Storage.
Зачем не JS
Стандартные Lampa-плагины уже на JS. Зачем нужен WASM-вариант:
- Один и тот же код на сервере и клиенте, если плагин делает обе части (
target: both). - Типобезопасность — Go/Rust ловит ошибки на компиляции.
- Переиспользование — крипто, парсеры, бизнес-логика, написанные для бэкенда, работают в Lampa без портирования.
- Защита кода —
.wasmбинарь читать сложнее, чем минифицированный JS.
Что есть в Lampa
Когда target: client или both, lampac-go:
- Подключает
wwwroot/plugins/wasm_loader.jsк каждой Lampa-сессии (черезlampainit.js). - Loader на старте Lampa запрашивает
/wasm/index.json— каталог всех client-плагинов. - Для каждого делает
WebAssembly.instantiateStreamingс per-plugin host-импортами. - Вызывает
_initialize(если есть) и затемon_load. - Если у плагина есть экспорт
on_event— подписывается наLampa.Listener.follow('full', ...)и форвардит события в плагин.
Manifest
{
"id": "welcome_toast",
"name": "Welcome toast",
"version": "0.1.0",
"target": "client",
"entry_client": "client.wasm",
"language": "tinygo",
"abi_version": 1,
"tags": ["ui", "demo"]
}target: both требует обоих entry-полей: entry_server (балансер) и entry_client (Lampa-extension). Это два разных .wasm файла, но один манифест и одна метадата для маркетплейса.
Host API (импорты lampac_client)
| Функция | Что делает |
|---|---|
lampac.NotyClient(msg) |
Lampa.Noty.show(msg) — toast |
lampac.StorageGet(key) / StorageSet(key, value) |
Lampa.Storage.get/set с namespace wasm_plugin_<id>: |
lampac.EmitEvent(event, jsonData) |
Lampa.Listener.send(event, JSON.parse(data)) |
lampac.ActivityPush(activityJSON) |
Lampa.Activity.push(JSON.parse(...)) — открыть экран |
lampac.ClientInfo("…") / Warn / Error / Debug |
console.{info,warn,error,debug} с префиксом [<id>] |
Все ключи в Lampa.Storage префиксованы wasm_plugin_<id>: — плагины не могут читать/писать друг другу.
Минимальный TinyGo-плагин
wasm_modules/welcome_toast/main.go:
package main
import "lampac.cc/sdk/lampac"
func main() {}
//export on_load
func onLoad() {
if lampac.StorageGet("seen") == "1" {
return
}
lampac.NotyClient("Привет от WASM-плагина 👋")
lampac.StorageSet("seen", "1")
}
//export on_event
func onEvent(ptr, length uint32) {
// Получаем все события Lampa как JSON {"type":"...","data":{...}}
// — здесь решаем, на что реагировать.
}Сборка:
tinygo build -o client.wasm -target wasi -no-debug ./
# 24 КБ — клиентские плагины без net/json получаются маленькимиПосле Install через Browser Studio или просто cp в wasm_modules/welcome_toast/ — Lampa подхватит при следующей перезагрузке страницы.
Реакция на события Lampa
//export on_event
func onEvent(ptr, length uint32) {
raw := lampac.ReadBytes(ptr, length) // []byte JSON
var ev struct {
Type string `json:"type"`
Data json.RawMessage `json:"data"`
}
json.Unmarshal(raw, &ev)
switch ev.Type {
case "full":
// карточка фильма открыта; можно подмешать рейтинги, бейджи и т.д.
case "activity":
// сменилась activity (главная, поиск, плеер…)
}
}Lampa имеет много типов событий — full, line, activity, player, и т.д. Подписка идёт на 'full' (универсальный канал), плагин фильтрует по type сам.
Открытие новых экранов
activity := `{
"url": "/lite/my_balancer",
"title": "My catalog",
"component": "category_full",
"page": 1
}`
lampac.ActivityPush(activity)ActivityPush принимает любой Lampa Activity descriptor — то же, что в JS:
Lampa.Activity.push({ url: "...", component: "category_full", title: "...", page: 1 });Catalog endpoint
GET /wasm/index.json — что отдаёт client-loader:
{
"plugins": [
{
"id": "welcome_toast",
"name": "Welcome toast",
"version": "0.1.0",
"wasm_url": "/wasm/welcome_toast/client.wasm",
"manifest_url": "/wasm/welcome_toast/manifest.json"
}
]
}Только активные client/both плагины, отключённые в админке — пропускаются.
Подводные камни
- Lampa.Listener не везде доступен на старте — loader подписывается на
app:readyесли возможно, иначе зовётon_loadсразу. .wasmкэшируется браузером на 5 минут — чтобы перезагрузить, либо bumpversionв манифесте, либо?v=...вwasm_url.- WASI-shim в loader’е минимальный — если плагин зовёт сложные сисколы (
path_open,fd_read), они вернутENOSYS. - DOM API нет — взаимодействие с UI идёт только через
Lampa.*. Если плагину нужен прямой доступ к DOM, расширьwasm_loader.jsсвоим импортом.
both target
Один манифест, два разных .wasm:
{
"id": "rating_badge",
"target": "both",
"entry_server": "server.wasm", // подсчёт рейтинга на сервере
"entry_client": "client.wasm" // отрисовка бейджа на карточке в Lampa
}Server-часть зовётся через /lite/rating_badge, client-часть подгружается loader’ом. Они общаются через сетевой запрос (HTTPGet со стороны клиента, балансер на сервере).