Vai al contenuto principale

Serving HTTP via Plugins

I plugin possono servire i propri URL. Una volta dichiarato http.serve nel tuo manifest, lo spazio URL in /plugins/\<your-slug>/ appartiene a te: i file statici dalla tua directory public/ vengono serviti letteralmente, e tutto il resto passa al tuo gestore delle richieste.

Il codice è mostrato per entrambi gli SDK. Vedi JavaScript o Python per installare e configurare.

Instradamento​

Una volta dichiarato http.serve, l'host instrada ogni richiesta sotto /plugins/\<your-slug>/ al tuo plugin:

  1. File statici. Qualsiasi cosa nella tua directory public/ è servita letteralmente.
  2. Gestore dinamico. Qualsiasi altra cosa passa al gestore delle richieste del tuo plugin.

Il percorso di una richiesta è relativo allo spazio dei nomi del tuo plugin: una richiesta a /plugins/my-plugin/api/messages raggiunge il tuo gestore come /api/messages (la stringa di query è esclusa). Il gestore legge i parametri di query e il corpo della richiesta dalla richiesta e restituisce una risposta con uno stato, intestazioni opzionali e un corpo opzionale.

Ci sono due stili di routing. In JavaScript scrivi un singolo gestore onHttpRequest(req) e ti ramifichi su req.method / req.path. In Python dichiari i percorsi per metodo con i decoratori. Una richiesta il cui percorso corrisponde a un percorso ma non al suo metodo ottiene un automatico 405, e un percorso non corrispondente passa all'intercettatore generico, altrimenti 404.

const { definePlugin } = require('@owncast/plugin-sdk');

module.exports = definePlugin({
onHttpRequest(req) {
// req: { method, path, headers, query, body, user? }
if (req.method === 'GET' && req.path === '/api/messages') {
return {
status: 200,
headers: { 'Content-Type': 'application/json' },
body: '[]',
};
}
if (req.method === 'POST' && req.path === '/api/messages') {
const data = JSON.parse(req.body || '{}');
return { status: 201 };
}
return { status: 404 };
},
});

The manifest.admin.pages key match at the top is covered in UI: Admin pages. From the perspective of HTTP serving, it is a 401-before-your-handler-runs filter applied to paths matching one of the object's keys.

File statici​

La directory public/ contiene file serviti a /plugins/\<your-slug>/\<path>. Una directory separata assets/ contiene file che l'host legge internamente per i campi del manifest che contengono contenuto in linea (styles, scripts, extraPageContent). Non possono essere raggiunti attraverso lo spazio URL del plugin.

my-plugin/
└── public/
├── index.html → /plugins/my-plugin/index.html (and /plugins/my-plugin/)
├── style.css → /plugins/my-plugin/style.css
└── img/
└── logo.png → /plugins/my-plugin/img/logo.png

Una richiesta a /plugins/my-plugin/ (senza percorso finale) serve automaticamente public/index.html.

Limiti di richiesta e risposta​

  • I corpi delle richieste sono limitati a 1 MB.
  • I corpi delle risposte sono limitati a 10 MB.
  • Il percorso di traversamento (..) negli URL è bloccato a livello dell'host. Non lo vedrai mai nel percorso del tuo gestore.
  • Le intestazioni di risposta sono filtrate attraverso una lista di autorizzazione. Puoi impostare intestazioni Content-Type, Content-Encoding, Content-Language, Cache-Control, Set-Cookie, Location, ETag, Last-Modified, Vary, Link e CORS (Access-Control-*). Le intestazioni di proprietà di Owncast (Server, Content-Security-Policy, Strict-Transport-Security, X-Frame-Options) sono bloccate.
  • I cookie impostati si applicano per impostazione predefinita allo spazio URL del tuo plugin (/plugins/\<your-slug>/). Se vuoi che un cookie venga inviato con richieste al di fuori di quel percorso, imposta esplicitamente Path=.... Altrimenti, il browser lo limita al tuo spazio dei nomi e non lo farà trapelare in altri plugin o nei percorsi di Owncast.
  • Ogni richiesta ha un limite di tempo di 5 secondi prima che l'host restituisca un 504 e scarti la tua risposta.

Pubblico vs. autenticato​

Gli endpoint sono pubblici per impostazione predefinita. To make something admin-only, either check whether the request is authenticated inside your handler and return 401 when it isn't, or add its path glob as a key in manifest.admin.pages and let the host gate it for you (see UI: Admin pages).

Per le richieste effettuate da un utente chat con un token utente valido, la richiesta porta con sé l'identità dell'utente (id, nome visualizzato e scopes). Utile per dashboard personalizzate per utente o strumenti solo per moderatori:

module.exports = definePlugin({
onHttpRequest(req) {
if (!req.user) return { status: 401 }; // not signed in
if (!req.user.scopes?.includes('MODERATOR')) return { status: 403 };
return { status: 200, body: `hello ${req.user.displayName}` };
},
});

For paths matching a key in manifest.admin.pages, the host returns 401 before your handler runs, so you don't have to check at all.

Aggiornamenti in tempo reale (Eventi inviati dal server)​

Per inviare aggiornamenti dal vivo a un browser (un overlay che reagisce alla chat, un dashboard che aggiorna il conteggio degli spettatori, un widget di avviso) dichiara http.sse e utilizza owncast.sse.send.

Non apri o mantieni tu stesso la connessione. Il tuo gestore delle richieste non può inviare stream: ogni chiamata è una singola richiesta/risposta bufferizzata. L'host possiede la connessione a lungo termine ed espone un endpoint predefinito su /plugins/\<your-slug>/_sse/\<channel>. Il tuo plugin invia. L'host distribuisce ogni messaggio a ogni browser connesso.

Lato plugin​

Invia da qualsiasi gestore, ad esempio, dal tuo gestore di chat, chiamando owncast.sse.send(channel, event, data):

const { definePlugin, owncast } = require('@owncast/plugin-sdk');

module.exports = definePlugin({
onChatMessage(msg) {
owncast.sse.send('overlay', 'chat', {
from: msg.user?.displayName,
body: msg.body,
});
},
});
  • channel: quale stream inviare. I browser si iscrivono per canale, quindi puoi eseguire diversi stream indipendenti ("overlay", "admin-stats") da un plugin. Usa "" per un singolo canale predefinito.
  • event: il nome dell'evento al quale il browser si iscrive (addEventListener("chat", ...)). Passa "" per l'evento message predefinito del browser.
  • data: il payload. Le stringhe sono inviate così come sono. Qualsiasi altra cosa è codificata in JSON per te.

Gli invii sono di tipo fire-and-forget. La chiamata restituisce immediatamente e non blocca mai, anche se nessuno è connesso o un client è lento. Client lenti rimuovono i frame piuttosto che bloccare il tuo plugin. Ci sono anche eventi del ciclo di vita della connessione SSE (apertura e chiusura dello stream di un visualizzatore) a cui puoi iscriverti: vedi il riferimento ai gestori.

Lato browser​

API standard EventSource nella pagina del visualizzatore. Nessuna libreria. Questo viene eseguito nel browser, quindi è sempre JavaScript indipendentemente dalla lingua in cui è scritto il tuo plugin:

<!-- public/index.html, served at /plugins/my-plugin/ -->
<script>
const events = new EventSource('/plugins/my-plugin/_sse/overlay');
events.addEventListener('chat', e => {
const { from, body } = JSON.parse(e.data);
document.getElementById('feed').textContent = `${from}: ${body}`;
});
</script>

Note​

  • Fino a 64 connessioni simultanee per plugin. Oltre, l'endpoint restituisce 503. EventSource si riconnette automaticamente.
  • If the channel matches a key in admin.pages, it's auth-gated like any admin route. Utile per uno stream di statistiche accessibile solo agli amministratori.
  • L'endpoint è di proprietà dell'host. Il tuo gestore delle richieste non vede mai le richieste /_sse/..., e non puoi servire il tuo percorso lì.

Mettere tutto insieme: un plugin overlay completo​

Il manifesto dichiara le due autorizzazioni di cui ha bisogno l'overlay:

{
"api": "1",
"name": "Chat Overlay",
"slug": "overlay",
"version": "0.1.0",
"permissions": ["http.serve", "http.sse"]
}

Il plugin si iscrive ai messaggi di chat e invia ognuno al canale SSE overlay:

// src/plugin.js
const { definePlugin, owncast } = require('@owncast/plugin-sdk');

module.exports = definePlugin({
onChatMessage(msg) {
owncast.sse.send('overlay', 'chat', {
from: msg.user?.displayName,
body: msg.body,
});
},
});

La pagina del visualizzatore è lo stesso snippet EventSource mostrato sopra, puntato all'endpoint relativo ./_sse/overlay:

<!-- public/index.html -->
<!doctype html>
<body>
<div id="feed"></div>
<script>
const events = new EventSource('./_sse/overlay');
events.addEventListener('chat', e => {
const { from, body } = JSON.parse(e.data);
document.getElementById('feed').textContent = `${from}: ${body}`;
});
</script>
</body>

Costruisci, impacchetta, installa. Apri /plugins/overlay/ in OBS come sorgente browser.


Improve this page

See something missing or incorrect? Edit this page and improve the documentation for everyone.

Contributors to this documentation
Gabe KangasGabe Kangas
G
Gabe Kangas