Přeskočit na obsah
Integrace pro vývojáře

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?

Ven z Puzzel
Dovnitř do Puzzel

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

Jak to nastavit
  1. 1 Otevři aktivitu v editoru a přejdi do sekce Vývojář.
  2. 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. 3 Jednou si aktivitu sám zahraj. První POST dorazí, jakmile na něco odpovíš.
Příjemce od začátku do konce
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);
});
Spouští se za hry, ne až na konci

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

Jak poznat dokončení od pouhého uložení

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

Odesílá ho hráčův prohlížeč

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.

Ber obsah zprávy jako tvrzení, ne jako důkaz

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.

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>
Zkontroluj, odkud zpráva přišla

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.

Stejný obsah, stejné načasování

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

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);
});
Co dorazí
{
  "completed": true,
  "activityKey": "-Nq8sample_activity_key"
}
Výsledek je v okamžiku doručení už uložený

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.

Jen uvnitř iframe

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.

PoleTypCo dělá
activityKey
vždy
string
stringAktivita, které výsledek patří. Stejný klíč, jaký vidíš v URL aktivity za ?p=.
playerUid
vždy
string
stringKdo 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
objectRegistrač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
numberJak 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
numberKdy byl tento výsledek uložen, jako unixové časové razítko v milisekundách.
createdAt
někdy
number
numberKdy byl pokus zahájen, jako unixové časové razítko v milisekundách.
playerInput
někdy
object
objectCo 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
objectKteré 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
numberZí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
numberVlastní 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
numberKoliká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
booleanBěh skončil na špatné odpovědi a je u konce, aniž by byl dokončený.
hasAlternateCompletion
někdy
boolean
booleanAktivita 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
arrayZnaky, které hráč opakovaně plete, nejčastěji chybované první. Jen u nácviku psaní.
contentVersion
někdy
number
numberProti 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.
Dokončený kvíz, tak jak dorazí
{
  "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
}
Chybí, ne prázdné

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.

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');
Reset je jediný příkaz

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.

Slyší se jen vkládající stránka

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.

Pošli nám hráče

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.

Pošli nám token

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

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žádej nás o zapnutí

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 API

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

Nic z toho?

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