Napoj Puzzel na vlastní platformu
Puzzel umí předat výsledek hráče rovnou jinému systému — jeho skóre, jak daleko se dostal, co odpověděl — aniž by ten systém musel být plnohodnotný LMS. Tahle stránka popisuje úplně všechny kanály: co z ní jde ven, kdy to jde ven a co nejmenšího musíš postavit, abys to zachytil.
- Výsledky odcházejí přes
- Webhook, nebo zprávu stránce kolem aktivity
- Tvoje stránka umí
- Přihlásit hráče a resetovat aktivitu
- Zapíná se
- Pro každou aktivitu zvlášť, v sekci Vývojář v editoru
- Součástí
- Placeného tarifu — na účtu zdarma jsou tato nastavení vypnutá
Který kanál potřebuješ?
Z aktivity můžou odejít tři věci a jedna může přijít dovnitř. Který kanál sedí, záleží na jediné otázce: sedí hráč přímo na tvojí stránce, nebo úplně jinde?
Každý uložený výsledek se metodou POST odešle jako JSON na URL, kterou vlastníš.
- Použij, když
- Hráč může být kdekoli — na sdíleném odkazu, přes QR kód, na cizím webu — a ty chceš mít výsledek ve vlastní databázi.
- Potřebuješ
- Koncový bod HTTPS, který přijme POST z jiné domény.
save_puzzle_results_via_webhookStejný JSON, odeslaný stránce, do které je aktivita vložená, místo na server.
- Použij, když
- Aktivitu vkládáš do vlastní stránky kurzu a ta stránka sama umí s výsledkem něco udělat.
- Potřebuješ
- Iframe na tvojí stránce a naslouchání zprávám. Žádný server, žádné CORS.
save_results_iframe_postmessageJedna zpráva ve chvíli, kdy hráč skončí, a nenese nic jiného než tuhle informaci.
- Použij, když
- Chceš vědět jenom to, jestli je hotovo — abys mohl odškrtnout lekci, odemknout další, nebo zobrazit vlastní obrazovku.
- Potřebuješ
- Iframe na tvojí stránce a naslouchání zprávám.
send_completion_signal_when_embeddedWebhook s výsledky
V editoru zapni "Odesílat výsledky na webhook" a zadej URL. Od té chvíle při každém uložení hráčova postupu odešle jeho prohlížeč metodou POST celý výsledek jako JSON na tuhle adresu.
- 1 Otevři aktivitu v editoru a přejdi do sekce Vývojář.
- 2 Zapni "Odesílat výsledky na webhook" a do pole pod ním vlož adresu svého koncového bodu. Musí to být úplná URL — samotná doména se odmítne — a musí být https, protože prohlížeč blokuje čisté http volání ze stránky, která je sama podávaná přes https.
- 3 Jednou si aktivitu sám zahraj. První POST dorazí, jakmile na něco odpovíš.
import express from 'express';
const app = express();
// The POST is made by the player's browser, so the browser asks permission
// first. Answer the preflight and the real request can land.
app.use((req, res, next) => {
res.set('Access-Control-Allow-Origin', 'https://puzzel.org');
res.set('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') return res.sendStatus(204);
next();
});
app.post('/puzzel/results', express.json(), async (req, res) => {
const result = req.body;
// One run posts several times as it goes. Key on the pair, not on an
// insert: each call carries the whole state, so the newest simply wins.
await db.results.upsert(
{ playerUid: result.playerUid, activityKey: result.activityKey },
result
);
// Only act on the finish once.
if (result.progress >= 100 || result.hasAlternateCompletion) {
await markCourseStepComplete(result);
}
res.sendStatus(200);
});Výsledek odejde při každém uložení záznamu: po pauze v psaní, když se položí karta, když se zastaví čas, a ještě jednou, když je aktivita dokončená. Dlouhá křížovka je pár desítek volání, ne jedno — takže handler napiš jako upsert podle klíče playerUid a activityKey, ne jako insert. Každé volání nese kompletní stav, takže nejnovější vždy nahradí to poslední a chybějící volání dožene to následující.
progress je procento: 100 znamená, že je aktivita dokončená. Pár typů může skončit, aniž by ho dosáhly — kvíz odpovězený až do konce, vyřešené pole s řešením — a ty místo toho nesou hasAlternateCompletion. Obojí považuj za dokončení.
POST přichází z karty, ve které se aktivita hraje, ne ze serveru Puzzel. Většina koncových bodů postavených na příjem webhooků to už zvládá. Pokud ten tvůj požadavek nikdy nevidí, je to tímhle: prohlížeč si nejdřív vyžádá svolení, takže na preflight OPTIONS odpověz hlavičkou Access-Control-Allow-Origin a skutečný POST bude následovat.
Požadavek nemá žádný podpis a přichází z prohlížeče, který nemáš pod kontrolou, takže ho ti může poslat i kdokoli, kdo si stránku prohlédne. Na naplnění ukazatele postupu nebo nástěnky to stačí. U všeho, co bys žákovi nedovolil nastavit si sám — známka, která se počítá, certifikát, platba — si to ověř proti výsledkům ve vlastní nástěnce Puzzel, nebo nech skóre přenést konektory pro známky do LMS.
Výsledky stránce kolem ní
Když aktivitu vkládáš, můžeš nechat stejný JSON odeslat vlastní stránce místo na server. Zapni "Odesílat výsledky nadřazené stránce" a naslouchej zprávě. Z prohlížeče nic neodchází, takže nemusíš stavět žádný koncový bod ani řešit CORS.
<iframe
src="https://puzzel.org/en/quiz/embed?p=-Nq8sample_activity_key"
width="100%"
height="700"
frameborder="0"
allowfullscreen
></iframe>
<script>
window.addEventListener('message', (event) => {
if (event.origin !== 'https://puzzel.org') return;
const result = event.data;
if (!result || !result.activityKey) return;
// Same shape the webhook posts, same advice: it arrives repeatedly.
saveProgress(result);
});
</script>Tvůj listener zaslechne každou zprávu odeslanou na stránku, včetně těch z jiných rámců a rozšíření prohlížeče. Než obsahu uvěříš, porovnej event.origin s https://puzzel.org.
Je to dvojče webhooku: stejná pole, odeslaná ve stejných okamžicích. Všechno z části "Co obsahuje výsledek" platí i tady.
Signál o dokončení
Nejmenší kanál, pro chvíle, kdy ti do samotného výsledku nic není: zapni "Odeslat signál o dokončení" a tvoje stránka dostane jednu zprávu ve chvíli, kdy hráč skončí.
window.addEventListener('message', (event) => {
if (event.origin !== 'https://puzzel.org') return;
if (event.data?.completed !== true) return;
// { completed: true, activityKey: '-Nq8sample_activity_key' }
unlockNextLesson(event.data.activityKey);
});{
"completed": true,
"activityKey": "-Nq8sample_activity_key"
}Signál se záměrně posílá až po tom, co dorazí dokončující uložení, takže stránka, která na něj zareaguje čtením výsledku zpátky, ho tam najde.
Oba zprávové kanály posílají na stránku, která aktivitu rámuje. Otevřená ve vlastní kartě nemá komu to oznámit, takže se nic neodešle.
Co obsahuje výsledek
Jeden tvar, ať ho nese jakýkoli kanál. Hráčovy odpovědi jsou klíčované id položek dané aktivity, takže stejné klíče se objeví i v correctUids.
| Pole | Typ | Co dělá |
|---|---|---|
activityKey vždy string | string | Aktivita, které výsledek patří. Stejný klíč, jaký vidíš v URL aktivity za ?p=. |
playerUid vždy string | string | Kdo hrál, jako anonymní id. Pro tohoto hráče na tomto zařízení je stálé, takže podle něj výsledky klíčuj — není to e-mailová adresa ani účet Puzzel. |
player někdy object | object | Registrační pole, na která se aktivita ptá, přesně jak jsi je nastavil: jméno, e-mail, třída, student_id a podobně. Chybí, dokud se hráč nezaregistruje, a chybí úplně u aktivity, která se na nic neptá. |
progress vždy number | number | Jak daleko se dostal, v procentech. 100 znamená dokončeno. |
timePassed vždy number | number | Čas strávený na aktivitě, v milisekundách. |
lastPlayedAt vždy number | number | Kdy byl tento výsledek uložen, jako unixové časové razítko v milisekundách. |
createdAt někdy number | number | Kdy byl pokus zahájen, jako unixové časové razítko v milisekundách. |
playerInput někdy object | object | Co hráč skutečně zadal, klíčované id položky, ke které to patří. Tvar uvnitř závisí na typu aktivity — slovo, seznam umístěných karet, vybraná možnost. |
correctUids někdy object | object | Které z těch položek jsou správně, klíčované stejným způsobem. Chybí, dokud není zodpovězeno vůbec nic. |
score někdy number | number | Získané body, u typů, které běh bodují. Jinde chybí — i u běhu, který opravdu získal nula bodů, takže si před čtením ověř, že klíč existuje. |
performance někdy number | number | Vlastní měřítko typu, jak dobře to dopadlo, pokud si nějaké vede — třeba slova za minutu u nácviku psaní. |
attempts někdy number | number | Kolikátý je to běh: 1 poprvé, o jednu víc při každém novém startu. Jen u typů, které umí běh ukončit předčasně a počítají opakování. |
knockedOut někdy boolean | boolean | Běh skončil na špatné odpovědi a je u konce, aniž by byl dokončený. |
hasAlternateCompletion někdy boolean | boolean | Aktivita byla dokončena způsobem, který nedosáhne 100 % — kvíz zodpovězený až do konce, vyřešené pole s řešením. Ber to jako dokončení. |
missedKeys někdy array | array | Znaky, které hráč opakovaně plete, nejčastěji chybované první. Jen u nácviku psaní. |
contentVersion někdy number | number | Proti které verzi obsahu aktivity se hrálo. Mění se, když vlastník upraví otázky, takže starý výsledek jde poznat od aktuálního. |
{
"activityKey": "-Nq8sample_activity_key",
"playerUid": "kK3r9TzSampleAnonymousUid",
"player": {
"name": "Ava Ortega",
"email": "ava@school.example",
"class": "5B"
},
"progress": 100,
"timePassed": 243120,
"lastPlayedAt": 1758297843120,
"createdAt": 1758297600000,
"playerInput": {
"-Nq8item_one": "Lisbon",
"-Nq8item_two": "1969"
},
"correctUids": {
"-Nq8item_one": true,
"-Nq8item_two": false
},
"score": 800,
"contentVersion": 1757923200000
}Pole, které se netýká dané aktivity, se z JSON prostě vynechá, místo aby se poslalo jako null nebo nula. Tak se pozná typ, který běh vůbec neskóruje, od běhu, který získal nula bodů — takže čti s výchozí hodnotou a nikdy nepředpokládej, že klíč existuje.
Reset aktivity z tvojí stránky
Jedním směrem jde příkaz opačně. Když je zapnuté "Přijímat příkazy z nadřazené stránky", stránka, která aktivitu vkládá, může smazat hráčovy odpovědi a vrátit aktivitu na začátek — pro vlastní tlačítko "zkusit znovu" mimo rámec.
const frame = document.querySelector('iframe').contentWindow;
// Sent to the frame, from the page that embeds it — no other sender is
// accepted, and the activity has to have the trigger setting switched on.
frame.postMessage({ trigger: { type: 'reset' } }, 'https://puzzel.org');Neexistuje zpráva pro odeslání výsledku, otevření závěrečné zprávy ani přeskok na otázku. Zpráva žádající o cokoli jiného než reset se ignoruje.
Příkaz se přijme jen od stránky, která aktivitu rámuje, a odnikud jinud — ne od sourozeneckého rámce, ne od skriptu na stránce. Nastavení je ve výchozím stavu vypnuté, takže ho zapni u aktivit, které řídíš.
Vlastní identita hráče
Pokud tvoje platforma už ví, kdo hraje, aktivita se ho nemusí ptát znovu. Existují dva způsoby handshaku, oba pro aktivitu uvnitř tvojí stránky, a oba zapínáme my, ne editor — mění totiž, komu výsledek patří, takže se nastavují s námi, ne zaškrtnutím políčka.
Aktivita se ohlásí zprávou 'app-loaded' a čeká. Tvoje stránka pošle zpátky údaje hráče a výsledek se pod nimi založí, aniž by hráč cokoli psal nebo viděl registrační obrazovku.
Stejný handshake, ale tvoje stránka místo samotných polí pošle JWT vydaný tvým poskytovatelem identity. Než hráče pustíme dovnitř, ověříme ho proti vydavatelům nastaveným pro tvůj účet, takže identita je prokázaná, ne jen tvrzená — o tenhle způsob si řekni, když výsledek musí být důvěryhodný.
window.addEventListener('message', (event) => {
if (event.origin !== 'https://puzzel.org') return;
// The activity says it is ready for a player.
if (event.data === 'app-loaded') {
frame.postMessage(
{
player: { name: 'Ava Ortega', email: 'ava@school.example', class: '5B' },
// true starts a fresh attempt instead of resuming this player's last one
should_reset: false
},
'https://puzzel.org'
);
}
// Signed in; the board is coming.
if (event.data?.success === true) hideYourOwnSpinner();
});Napiš nám, který ze dvou způsobů chceš a kam budou aktivity vložené, a účet ti nastavíme a projdeme to spolu s tebou.
Napsat nám o identitěVytváření aktivit z tvého systému
Všechno výše se týkalo výsledku, který jde ven. Opačným směrem — vytvoření samotných aktivit z obsahu, který už máš — slouží Puzzle API: jeden POST na typ aktivity, a zpátky dostaneš klíč a URL pro vložení.
Přečíst referenci APIKdy je lepší odpovědí hotový konektor
Pokud je na druhé straně skutečný LMS, nic z tohohle asi nepotřebuješ. Známky se do jeho třídní knihy dostanou samy, aniž bys musel cokoli hostovat.
Spuštěno zevnitř LMS, se skóre zapsaným zpátky do jeho třídní knihy.
Zveřejni aktivitu jako úkol a známky se vrátí samy.
Platformy pro kurzy, členské weby, intranety a cokoli, co sis postavil sám — přesně na to jsou kanály na téhle stránce. Vložení plus signál o dokončení pokryje většinu případů.
Co tu není
Ať to zbytečně nehledáš:
- Žádný koncový bod pro čtení výsledků zpátky. API aktivity vytváří; výsledky odcházejí přes kanály na téhle stránce nebo přes exporty ve tvojí nástěnce.
- Webhook nemá žádný podpis. Není proti čemu požadavek ověřit, a proto by výsledek neměl být jediné, co stojí za něčím důležitým.
- Žádný webhook platný pro celý účet. URL je nastavení jednotlivé aktivity, takže zkopírovaná aktivita si ho ponese s sebou a nová začíná bez něj.
- Nic v týmové hře ani v živé místnosti. Oba zprávové kanály i webhook jsou pro hru jednoho hráče a nastavení se samy vypnou, jakmile je zapnutá týmová hra.
- Žádná fronta pro doručování. Nic se neukládá a znovu neposílá — dalším pokusem je další uložení a záleží jen na posledním volání běhu.
Něco se nechová, jak má?
Pošli požadavek, který jsi zkusil, a chybu, kterou jsi dostal zpátky, a dostaneš skutečnou odpověď od člověka, který ten endpoint napsal.
Napsat podpoře