Podłącz Puzzel do własnej platformy
Puzzel może przekazać wynik gracza bezpośrednio do innego systemu — jego punktację, to, jak daleko doszedł, co odpowiedział — bez potrzeby, żeby ten system był pełnym systemem LMS. Ta strona zawiera wszystkie dostępne kanały: co z niej wychodzi, kiedy wychodzi i najmniejszą rzecz, którą musisz zbudować, żeby to odebrać.
- Wyniki wychodzą przez
- Webhook lub wiadomość do strony osadzającej aktywność
- Twoja strona może
- Zalogować gracza i zresetować aktywność
- Włączane
- Dla każdej aktywności osobno, w sekcji Deweloper w edytorze
- Dostępne w
- Płatnym planie — na koncie bezpłatnym te ustawienia są wyłączone
Którego kanału potrzebujesz?
Z aktywności mogą wyjść trzy rzeczy, a jedna może do niej wejść. To, która pasuje, zależy od jednego pytania: czy gracz znajduje się wewnątrz twojej strony, czy zupełnie gdzie indziej?
Każdy zapisany wynik jest wysyłany metodą POST jako JSON na adres URL, który podasz.
- Użyj, gdy
- Gracz może być gdziekolwiek — na udostępnionym linku, kodzie QR, cudzej stronie — a ty chcesz mieć wynik we własnej bazie danych.
- Potrzebujesz
- Endpointu HTTPS, który przyjmuje zapytania POST z innej domeny.
save_puzzle_results_via_webhookTen sam JSON, wysyłany do strony, w której osadzono aktywność, zamiast na serwer.
- Użyj, gdy
- Osadzasz aktywność na własnej stronie kursu, a ta strona sama może coś zrobić z wynikiem.
- Potrzebujesz
- Iframe na twojej stronie i nasłuchiwacz wiadomości. Bez serwera, bez CORS.
save_results_iframe_postmessageJedna wiadomość, gdy gracz skończy — niosąca tylko tę jedną informację.
- Użyj, gdy
- Chcesz wiedzieć tylko, czy gracz skończył — żeby odhaczyć lekcję, odblokować kolejną albo pokazać własny ekran.
- Potrzebujesz
- Iframe na twojej stronie i nasłuchiwacz wiadomości.
send_completion_signal_when_embeddedWebhook wyników
Włącz ustawienie "Wysyłaj wyniki do webhooka" w edytorze i podaj adres URL. Od tej pory za każdym razem, gdy postęp gracza zostanie zapisany, jego przeglądarka wyśle metodą POST cały wynik na ten adres jako JSON.
- 1 Otwórz aktywność w edytorze i przejdź do menu Deweloper.
- 2 Włącz "Wysyłaj wyniki do webhooka" i wklej adres swojego endpointu w polu poniżej. Musi to być pełny URL — sama domena zostanie odrzucona — i musi zaczynać się od https, bo przeglądarka blokuje zwykłe zapytanie http wysyłane ze strony serwowanej przez https.
- 3 Zagraj raz sam w aktywność. Pierwsze zapytanie POST dotrze, gdy tylko coś odpowiesz.
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);
});Wynik wychodzi za każdym razem, gdy wpis zostanie zapisany: po przerwie w pisaniu, gdy karta zostanie odłożona, gdy zatrzyma się stoper, i jeszcze raz, gdy aktywność zostanie ukończona. Długa krzyżówka to kilkadziesiąt wywołań, nie jedno — dlatego napisz swój handler jako upsert po kluczu playerUid i activityKey, a nie jako insert. Każde wywołanie niesie kompletny stan, więc najnowsze zawsze zastępuje poprzednie, a wywołanie, które nie dotrze, zostanie nadrobione przez następne.
progress to wartość procentowa: 100 oznacza, że aktywność jest ukończona. Kilka typów może się zakończyć bez osiągnięcia tej wartości — quiz, na który odpowiedziano do końca, rozwiązane pole solution — i wtedy zamiast tego niosą hasAlternateCompletion. Oba przypadki traktuj jako ukończenie.
Zapytanie POST pochodzi z karty przeglądarki, w której toczy się gra, a nie z serwera Puzzel. Większość endpointów zbudowanych do odbierania webhooków już to obsługuje. Jeśli twój nigdy nie widzi żadnego zapytania, to właśnie dlatego: przeglądarka najpierw pyta o zgodę, więc odpowiedz na zapytanie wstępne OPTIONS nagłówkiem Access-Control-Allow-Origin, a właściwe zapytanie POST przyjdzie zaraz po nim.
Zapytanie nie ma podpisu i pochodzi z przeglądarki, nad którą nie masz kontroli, więc każdy, kto zajrzy na tę stronę, może wysłać ci taki sam. To wystarcza do wypełnienia paska postępu czy panelu. Do wszystkiego, czego nie pozwoliłbyś ustawić uczniowi samodzielnie — liczącej się oceny, certyfikatu, płatności — sprawdź to z wynikami we własnym panelu Puzzel albo pozwól, aby punktację przekazały zamiast tego integracje ocen z LMS.
Wyniki do strony osadzającej
Jeśli osadzasz aktywność, możesz sprawić, że ten sam JSON trafi na twoją własną stronę zamiast na serwer. Włącz "Wysyłaj wyniki do strony nadrzędnej" i nasłuchuj wiadomości. Nic nie opuszcza przeglądarki, więc nie musisz budować żadnego endpointu ani martwić się o 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>Twój nasłuchiwacz odbiera każdą wiadomość wysłaną do strony, w tym z innych ramek i rozszerzeń przeglądarki. Zanim zaufasz jej zawartości, porównaj event.origin z https://puzzel.org.
To bliźniak webhooka: te same pola, wysyłane w tych samych momentach. Wszystko, co jest napisane w sekcji "Co zawiera wynik", dotyczy też tego kanału.
Sygnał ukończenia
Najmniejszy kanał, na wypadek gdy sam wynik cię nie interesuje: włącz "Wysyłaj sygnał ukończenia", a twoja strona dostanie jedną wiadomość w chwili, gdy gracz skończy.
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"
}Sygnał jest celowo wysyłany dopiero po tym, jak zapis kończący dotrze do celu, więc strona, która w reakcji na niego odczyta wynik z powrotem, znajdzie go już zapisany.
Oba kanały wiadomości wysyłają dane do strony, która osadza aktywność w ramce. Otwarta we własnej karcie aktywność nie ma komu nic przekazać, więc nic nie zostaje wysłane.
Co zawiera wynik
Jeden kształt danych, niezależnie od tego, który kanał je niesie. Odpowiedzi gracza są indeksowane identyfikatorami elementów samej aktywności, więc te same klucze pojawiają się w correctUids.
| Pole | Typ | Do czego służy |
|---|---|---|
activityKey zawsze string | string | Aktywność, do której należy wynik. Ten sam klucz, który widzisz w adresie URL aktywności, po ?p=. |
playerUid zawsze string | string | Kto grał, jako anonimowy identyfikator. Stały dla tego gracza na tym urządzeniu, więc to na nim powinieneś indeksować wyniki — to nie jest adres e-mail ani konto Puzzel. |
player czasami object | object | Pola rejestracyjne, o które prosi aktywność, tak jak je skonfigurowano: imię, e-mail, klasa, student_id i tak dalej. Nieobecne, dopóki gracz się nie zarejestruje, a całkowicie nieobecne w aktywności, która o nic nie pyta. |
progress zawsze number | number | Jak daleko gracz doszedł, w procentach. 100 oznacza ukończenie. |
timePassed zawsze number | number | Czas spędzony na aktywności, w milisekundach. |
lastPlayedAt zawsze number | number | Kiedy ten wynik został zapisany, jako znacznik czasu Unix w milisekundach. |
createdAt czasami number | number | Kiedy rozpoczęto podejście, jako znacznik czasu Unix w milisekundach. |
playerInput czasami object | object | Co gracz faktycznie wpisał, indeksowane identyfikatorem elementu, do którego to należy. Kształt danych w środku zależy od typu aktywności — słowo, lista rozłożonych kart, wybrana opcja. |
correctUids czasami object | object | Które z tych elementów są poprawne, indeksowane w ten sam sposób. Nieobecne, dopóki nic nie zostało jeszcze odpowiedziane. |
score czasami number | number | Zdobyte punkty, w typach, które liczą punktację przebiegu. Nieobecne wszędzie indziej — także przy przebiegu, który naprawdę zdobył zero punktów, dlatego przed odczytem sprawdź, czy klucz w ogóle istnieje. |
performance czasami number | number | Własna miara danego typu określająca, jak dobrze poszło — tam, gdzie taka miara w ogóle istnieje — na przykład liczba słów na minutę w nauce pisania na klawiaturze. |
attempts czasami number | number | Który to przebieg: 1 za pierwszym razem, o jeden więcej przy każdym starcie od nowa. Występuje tylko w typach, które mogą zakończyć przebieg przedwcześnie i liczą podejścia. |
knockedOut czasami boolean | boolean | Przebieg zakończył się na błędnej odpowiedzi i jest zakończony bez ukończenia. |
hasAlternateCompletion czasami boolean | boolean | Aktywność została ukończona w sposób, który nie osiąga 100% — quiz, na który odpowiedziano do końca, rozwiązane pole solution. Traktuj to jako ukończenie. |
missedKeys czasami array | array | Znaki, w których gracz najczęściej się mylił, od najczęściej mylonych. Tylko w nauce pisania na klawiaturze. |
contentVersion czasami number | number | Wersja treści aktywności, na której rozegrano ten przebieg. Zmienia się, gdy właściciel edytuje pytania, dzięki czemu stary wynik da się odróżnić od aktualnego. |
{
"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, które nie ma zastosowania, jest pomijane w JSON, zamiast być wysyłane jako null albo zero. Dzięki temu typ, który w ogóle nie liczy punktacji, da się odróżnić od przebiegu, który zdobył zero punktów — dlatego odczytuj dane z wartością domyślną i nigdy nie zakładaj, że dany klucz na pewno tam jest.
Resetowanie aktywności z twojej strony
Jedna instrukcja podróżuje w drugą stronę. Gdy włączysz "Przyjmuj polecenia ze strony nadrzędnej", strona osadzająca może wyczyścić odpowiedzi gracza i cofnąć aktywność do początku — na potrzeby własnego przycisku "spróbuj ponownie" poza ramką.
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');Nie ma wiadomości do przesłania wyniku, otwarcia wiadomości końcowej ani przeskoczenia do pytania. Wiadomość z prośbą o cokolwiek innego niż reset jest ignorowana.
Polecenie jest przyjmowane wyłącznie ze strony, która osadza aktywność w ramce, i znikąd indziej — nie z sąsiedniej ramki, nie ze skryptu na stronie. Ustawienie jest domyślnie wyłączone, więc włącz je dla aktywności, którymi sterujesz.
Wykorzystanie własnej tożsamości gracza
Jeśli twoja platforma już wie, kto gra, aktywność nie musi pytać o to ponownie. Istnieją dwa uzgodnienia, oba dla aktywności osadzonej na twojej stronie, i oba włączane przez nas, a nie w edytorze — zmieniają one, do kogo należy wynik, więc ustawiamy je razem z tobą, a nie za pomocą zwykłego przełącznika.
Aktywność zgłasza się komunikatem 'app-loaded' i czeka. Twoja strona odsyła z powrotem dane gracza, a wynik zostaje zapisany pod nimi bez konieczności, żeby gracz cokolwiek wpisywał albo widział ekran rejestracji.
To samo uzgodnienie, ale zamiast samych pól twoja strona wysyła token JWT wystawiony przez twojego dostawcę tożsamości. Zanim wpuścimy gracza, weryfikujemy go względem wystawców skonfigurowanych dla twojego konta, więc tożsamość jest udowodniona, a nie tylko zadeklarowana — o to warto poprosić, gdy wynik musi być wiarygodny.
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();
});Powiedz nam, którego z tych dwóch rozwiązań potrzebujesz i gdzie będą osadzone aktywności, a my skonfigurujemy twoje konto i przeprowadzimy cię przez cały proces.
Napisz do nas o tożsamościTworzenie aktywności z twojego systemu
Wszystko powyżej dotyczy wyniku, który wychodzi na zewnątrz. W drugą stronę — tworzenie samych aktywności z treści, które już masz — służy do tego Puzzle API: jedno zapytanie POST na typ aktywności, w zamian dostajesz klucz i adres URL do osadzenia.
Przeczytaj dokumentację APIKiedy gotowa integracja jest lepszym rozwiązaniem
Jeśli platforma po drugiej stronie to prawdziwy LMS, prawdopodobnie nic z tego nie jest ci potrzebne. Oceny mogą trafiać do jego dziennika ocen same, bez niczego, co musiałbyś hostować.
Uruchamiane z poziomu LMS, z punktacją zapisywaną z powrotem do jego dziennika ocen.
Opublikuj aktywność jako zadanie, a oceny wrócą automatycznie.
Platformy kursowe, strony z dostępem członkowskim, intranety i wszystko, co zbudowałeś sam, to dokładnie to, do czego służą kanały na tej stronie. Osadzenie plus sygnał ukończenia pokrywa większość przypadków.
Czego tu nie ma
Żebyś nie szukał tego na próżno:
- Brak endpointu do odczytu wyników z powrotem. API tworzy aktywności; wyniki wychodzą przez kanały opisane na tej stronie albo przez eksporty w twoim panelu.
- Brak podpisu na webhooku. Nie ma niczego, względem czego można zweryfikować zapytanie, dlatego wynik nie powinien być jedyną rzeczą stojącą za czymś, na czym naprawdę zależy.
- Brak webhooka na poziomie całego konta. Adres URL jest ustawieniem pojedynczej aktywności, więc skopiowana aktywność przenosi go ze sobą, a nowa zaczyna bez niego.
- Nic w grze drużynowej ani w pokoju na żywo. Oba kanały wiadomości i webhook są przeznaczone do gry solo, a ustawienia same się wyłączają, gdy włączony jest tryb drużynowy.
- Brak kolejki dostarczania. Nic nie jest przechowywane i wysyłane ponownie — kolejny zapis pełni rolę ponowienia, a liczy się tylko ostatnie wywołanie danego przebiegu.
Coś nie działa jak trzeba?
Wyślij swoje żądanie i błąd, który wrócił, a dostaniesz konkretną odpowiedź — od osoby, która napisała ten endpoint.
Napisz do pomocy