Preskočiť na obsah
Integrácie pre vývojárov

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?

Z Puzzelu von
Do Puzzelu

Webhook 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.

Ako to nastaviť
  1. 1 Otvor aktivitu v editore a prejdi do menu Vývojár.
  2. 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. 3 Zahraj si aktivitu sám raz. Prvý POST príde hneď, ako niečo odpovieš.
Prijímač, od začiatku do konca
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);
});
Spúšťa sa priebežne pri hraní, nie raz na konci

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.

Ako rozoznať dokončenie od bežného uloženia

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.

Odosiela ho hráčov prehliadač

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ť.

Ber obsah správy ako neoverený údaj, nie ako dôkaz

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.

index.html
<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>
Skontroluj, odkiaľ správa prišla

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.

Rovnaký obsah, rovnaké načasovanie

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čí.

index.html
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);
});
Čo príde
{
  "completed": true,
  "activityKey": "-Nq8sample_activity_key"
}
Výsledok je v čase príchodu už uložený

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.

Len vnútri iframe

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.

PoleTypČo robí
activityKey
vždy
string
stringAktivita, ku ktorej výsledok patrí. Rovnaký kľúč, aký vidíš v samotnej URL adrese aktivity, za ?p=.
playerUid
vždy
string
stringKto 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
objectRegistrač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
numberAko ď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
numberKedy bol tento výsledok uložený, ako Unix timestamp v milisekundách.
createdAt
niekedy
number
numberKedy 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
objectKtoré z týchto položiek sú správne, indexované rovnako. Chýba, kým ešte nič nebolo zodpovedané.
score
niekedy
number
numberZí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
numberVlastná miera úspešnosti daného typu, tam kde ju sleduje — napríklad slová za minútu pri nácviku písania.
attempts
niekedy
number
numberKtorý 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
booleanBeh sa skončil pri nesprávnej odpovedi a je ukončený bez toho, aby bol dokončený.
hasAlternateCompletion
niekedy
boolean
booleanAktivita 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
arrayZnaky, v ktorých hráč najčastejšie chyboval, zoradené od najčastejších. Len pri nácviku písania.
contentVersion
niekedy
number
numberKtorú 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.
Dokončený kvíz, presne tak, ako príde
{
  "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
}
Chýba, nie je prázdne

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.

reset
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');
Reštart je jediný príkaz

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.

Počúva sa len vkladajúca stránka

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.

Pošli nám hráča

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.

Pošli nám token

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ý.

index.html
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();
});
Požiadaj nás, aby sme to zapli

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 identite

Vytvá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 API

Keď 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.

Nič z toho nesedí?

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