Prepoj Puzzel s vlastnou platformou
Puzzel dokáže odovzdať výsledok hráča priamo inému systému — jeho skóre, ako ďaleko sa dostal, čo odpovedal — bez toho, aby ten systém musel byť plnohodnotný LMS. Táto stránka opisuje všetky kanály, ktoré existujú: čo z Puzzelu odchádza, kedy to odchádza a čo najmenšie musíš postaviť, aby si to zachytil.
- Výsledky odchádzajú cez
- Webhook alebo správu stránke okolo aktivity
- Tvoja stránka môže
- Prihlásiť hráča a reštartovať aktivitu
- Zapína sa
- Pre jednotlivé aktivity, v editore v sekcii Vývojár
- Súčasť
- Plateného plánu — na bezplatnom účte sú tieto nastavenia vypnuté
Ktorý kanál potrebuješ?
Z aktivity môžu odísť tri veci a jedna môže prísť dovnútra. Ktorá sa hodí, závisí od jedinej otázky: sedí hráč priamo na tvojej stránke, alebo úplne inde?
Každý uložený výsledok sa odošle metódou POST na URL adresu, ktorú vlastníš, vo formáte JSON.
- Použi, keď
- Hráč môže byť kdekoľvek — na zdieľanom odkaze, cez QR kód, na cudzej stránke — a ty chceš mať výsledok vo vlastnej databáze.
- Potrebuješ
- HTTPS endpoint, ktorý prijíma cross-origin POST požiadavky.
save_puzzle_results_via_webhookRovnaký JSON, odoslaný stránke, ktorá aktivitu vkladá, namiesto serveru.
- Použi, keď
- Aktivitu vkladáš do vlastnej stránky kurzu a tá stránka dokáže s výsledkom niečo urobiť.
- Potrebuješ
- Iframe na tvojej stránke a poslucháč správ. Žiadny server, žiadny CORS.
save_results_iframe_postmessageJedna správa, keď hráč skončí, a nenesie nič iné než túto skutočnosť.
- Použi, keď
- Chceš vedieť len to, či hráč skončil — aby si odškrtol lekciu, odomkol ďalšiu alebo zobrazil vlastnú obrazovku.
- Potrebuješ
- Iframe na tvojej stránke a poslucháč správ.
send_completion_signal_when_embeddedWebhook na výsledky
Zapni "Posielať výsledky na webhook" v editore a zadaj URL adresu. Odvtedy pri každom uložení hráčovho postupu jeho prehliadač odošle celý výsledok na túto adresu metódou POST vo formáte JSON.
- 1 Otvor aktivitu v editore a prejdi do menu Vývojár.
- 2 Zapni "Posielať výsledky na webhook" a vlož svoj endpoint do poľa pod ním. Musí to byť úplná URL adresa — samotná doména sa neprijme — a musí byť https, pretože prehliadač blokuje obyčajné http volanie zo stránky podávanej cez https.
- 3 Zahraj si aktivitu sám raz. Prvý POST príde hneď, ako niečo odpovieš.
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ýsledok odchádza pri každom uložení záznamu: po prestávke v písaní, keď sa umiestni karta, keď sa zastaví čas, a ešte raz, keď je aktivita dokončená. Dlhá krížovka to sú desiatky volaní, nie jedno — takže svoj handler napíš ako upsert podľa kľúčov playerUid a activityKey, nie ako vkladanie nového záznamu. Každé volanie nesie kompletný stav, takže najnovšie vždy nahradí predchádzajúce a volanie, ktoré sa stratí, napraví to nasledujúce.
progress je percento: 100 znamená, že aktivita je dokončená. Niekoľko typov sa dá dokončiť aj bez toho, aby ho dosiahli — kvíz zodpovedaný celý až do konca, vyriešené pole riešenia — a tie namiesto toho nesú hasAlternateCompletion. Oboje ber ako dokončenie.
POST prichádza z karty, v ktorej sa aktivita hrá, nie zo servera Puzzel. Väčšina endpointov postavených na prijímanie webhookov to už zvláda. Ak k tebe požiadavka nikdy nedorazí, toto je dôvod: prehliadač si najprv vyžiada povolenie, takže na OPTIONS preflight odpovedz hlavičkou Access-Control-Allow-Origin a skutočný POST bude nasledovať.
Požiadavka nemá žiadny podpis a prichádza z prehliadača, ktorý nemáš pod kontrolou, takže rovnakú vie poslať ktokoľvek, kto si stránku prezrie. To stačí na vyplnenie ukazovateľa postupu alebo prehľadu. Pri všetkom, čo by si žiakovi nedovolil nastaviť si sám — započítavaná známka, certifikát, platba — over si to oproti výsledkom vo vlastnom prehľade Puzzel, alebo nechaj skóre preniesť konektory na prenos známok do LMS.
Výsledky stránke okolo nej
Ak aktivitu vkladáš, môžeš mať ten istý JSON odoslaný vlastnej stránke namiesto serveru. Zapni "Posielať výsledky nadradenej stránke" a počúvaj správu. Z prehliadača nič neodchádza, takže nemusíš stavať žiadny endpoint ani riešiť 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>Tvoj poslucháč zachytí každú správu odoslanú stránke, vrátane tých z iných rámcov a rozšírení prehliadača. Skôr než dôveruješ jej obsahu, porovnaj event.origin s https://puzzel.org.
Toto je dvojča webhooku: rovnaké polia, odoslané v rovnakých momentoch. Všetko, čo je v časti "Čo obsahuje výsledok", platí aj tu.
Signál o dokončení
Najmenší kanál, pre prípad, že samotný výsledok nie je tvoja vec: zapni "Odoslať signál o dokončení" a tvoja stránka dostane jednu správu vo chvíli, keď 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 sa zámerne odosiela až po tom, čo dokončujúce uloženie prebehne, takže stránka, ktorá zareaguje spätným načítaním výsledku, ho tam už nájde.
Oba kanály so správami odosielajú stránke, ktorá aktivitu rámcuje. Keď je aktivita otvorená na vlastnej karte, nie je komu to oznámiť, takže sa nič neodošle.
Čo obsahuje výsledok
Jeden tvar, nech ho prenáša akýkoľvek kanál. Hráčove odpovede sú indexované podľa vlastných id položiek aktivity, takže rovnaké kľúče sa objavia aj v correctUids.
| Pole | Typ | Čo robí |
|---|---|---|
activityKey vždy string | string | Aktivita, ku ktorej výsledok patrí. Rovnaký kľúč, aký vidíš v samotnej URL adrese aktivity, za ?p=. |
playerUid vždy string | string | Kto hral, ako anonymné id. Pre tohto hráča na tomto zariadení je stále rovnaké, takže podľa neho indexuješ výsledky — nie je to e-mailová adresa ani účet Puzzel. |
player niekedy object | object | Registračné polia, ktoré aktivita vyžaduje presne tak, ako si ich nastavil: name, email, class, student_id a podobne. Chýba, kým sa hráč nezaregistruje, a chýba úplne na aktivite, ktorá nič nevyžaduje. |
progress vždy number | number | Ako ďaleko sa dostal, v percentách. 100 znamená dokončené. |
timePassed vždy number | number | Čas strávený na aktivite, v milisekundách. |
lastPlayedAt vždy number | number | Kedy bol tento výsledok uložený, ako Unix timestamp v milisekundách. |
createdAt niekedy number | number | Kedy sa pokus začal, ako Unix timestamp v milisekundách. |
playerInput niekedy object | object | Čo hráč skutočne zadal, indexované podľa id položky, ku ktorej to patrí. Vnútorný tvar závisí od typu aktivity — slovo, zoznam umiestnených kariet, vybraná možnosť. |
correctUids niekedy object | object | Ktoré z týchto položiek sú správne, indexované rovnako. Chýba, kým ešte nič nebolo zodpovedané. |
score niekedy number | number | Získané body, pri typoch, ktoré beh bodujú. Inde chýba — vrátane behu, ktorý naozaj získal nula bodov, takže pred čítaním over, či kľúč existuje. |
performance niekedy number | number | Vlastná miera úspešnosti daného typu, tam kde ju sleduje — napríklad slová za minútu pri nácviku písania. |
attempts niekedy number | number | Ktorý je toto beh v poradí: 1 pri prvom raze, o jeden viac pri každom novom začatí. Len pri typoch, ktoré vedia beh ukončiť predčasne a počítajú opakovania. |
knockedOut niekedy boolean | boolean | Beh sa skončil pri nesprávnej odpovedi a je ukončený bez toho, aby bol dokončený. |
hasAlternateCompletion niekedy boolean | boolean | Aktivita bola dokončená spôsobom, ktorý nedosiahne 100 % — kvíz zodpovedaný celý až do konca, vyriešené pole riešenia. Ber to ako dokončenie. |
missedKeys niekedy array | array | Znaky, v ktorých hráč najčastejšie chyboval, zoradené od najčastejších. Len pri nácviku písania. |
contentVersion niekedy number | number | Ktorú verziu obsahu aktivity hráč hral. Mení sa, keď vlastník upraví otázky, takže starý výsledok je možné odlíšiť od aktuálneho. |
{
"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, ktoré sa na daný prípad nevzťahuje, sa z JSON-u úplne vynechá, namiesto toho, aby sa poslalo ako null alebo nula. Takto sa dá rozoznať typ, ktorý beh vôbec neboduje, od behu, ktorý dostal nula bodov — takže čítaj s predvolenou hodnotou a nikdy nepredpokladaj, že kľúč je prítomný.
Reštartovanie aktivity z tvojej stránky
Jeden pokyn ide opačným smerom. Keď je zapnuté "Prijímať príkazy z nadradenej stránky", stránka, ktorá aktivitu vkladá, môže vymazať hráčove odpovede a vrátiť aktivitu na začiatok — pre vlastné tlačidlo "skúsiť znova" mimo rámca.
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 správa na odoslanie výsledku, otvorenie záverečnej správy ani na preskočenie na otázku. Správa, ktorá žiada čokoľvek iné než reštart, sa ignoruje.
Príkaz sa prijme len od stránky, ktorá aktivitu rámcuje, a odnikiaľ inde — nie od susedného rámca, nie od skriptu na stránke. Nastavenie je predvolene vypnuté, takže si ho zapni pri aktivitách, ktoré riadiš.
Vlastná identita hráča
Ak tvoja platforma už vie, kto hrá, aktivita sa ho nemusí znova pýtať. Existujú dva postupy, oba pre aktivitu na tvojej stránke, a oba zapíname my, nie ty v editore — menia, komu výsledok patrí, takže sa nastavujú spoločne s nami, nie zaškrtnutím políčka.
Aktivita sa ohlási správou 'app-loaded' a čaká. Tvoja stránka pošle späť hráčove údaje a výsledok sa uloží pod nimi bez toho, aby hráč čokoľvek zadával alebo videl registračnú obrazovku.
Rovnaký postup, ale tvoja stránka namiesto samotných údajov pošle JWT, ktorý vydal tvoj poskytovateľ identity. Pred vpustením hráča ho overíme oproti vydavateľom nastaveným pre tvoj účet, takže identita je overená, nie len tvrdená — o tento postup žiadaj vtedy, keď musí byť výsledok dôveryhodný.
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();
});Napíš nám, ktorý z týchto dvoch postupov chceš a kde budú aktivity vložené, a my ti nastavíme účet a prevedieme ťa celým postupom.
Napíš nám o identiteVytváranie aktivít z tvojho systému
Všetko vyššie sa týka výsledku, ktorý odchádza. Opačný smer — vytváranie samotných aktivít z obsahu, ktorý už máš — je Puzzle API: jeden POST na typ aktivity, a späť dostaneš kľúč a URL adresu na vloženie.
Prečítať si referenciu APIKeď je lepšou odpoveďou hotový konektor
Ak je platforma na druhej strane skutočný LMS, väčšinou nič z tohto nepotrebuješ. Známky sa dokážu samy vrátiť do jeho triednej klasifikácie, bez toho, aby si čokoľvek hostoval.
Spustí sa priamo z LMS a skóre sa zapíše späť do jeho triednej klasifikácie.
Zverejni aktivitu ako úlohu a známky sa vrátia automaticky.
Platformy pre kurzy, členské stránky, intranety a čokoľvek, čo si postavil sám, presne na to sú kanály na tejto stránke. Vloženie plus signál o dokončení pokryje väčšinu prípadov.
Čo tu nie je
Aby si to nehľadal zbytočne:
- Žiadny endpoint na spätné čítanie výsledkov. API vytvára aktivity; výsledky odchádzajú cez kanály na tejto stránke alebo cez exporty v tvojom prehľade.
- Žiadny podpis na webhooku. Nie je voči čomu požiadavku overiť, a preto by výsledok nemal byť jedinou oporou niečoho, na čom záleží.
- Žiadny webhook platný pre celý účet. URL adresa je nastavenie jednotlivej aktivity, takže skopírovaná aktivita si ho nesie so sebou a nová ho na začiatku nemá.
- Nič v tímovej hre ani v živej miestnosti. Oba kanály so správami aj webhook sú určené na hru jednotlivca a nastavenia sa samy vypnú, keď je zapnutá tímová hra.
- Žiadny front na doručovanie. Nič sa neukladá a neposiela znova — ďalšie uloženie je ten opakovaný pokus a rozhoduje posledné volanie behu.
Niečo nefunguje, ako má?
Pošli svoju požiadavku aj chybu, ktorá prišla späť, a dostaneš skutočnú odpoveď od človeka, ktorý ten koncový bod napísal.
Napísať podpore