Przejdź do treści
Integracje dla deweloperów

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?

Wyjście z Puzzel
Wejście do Puzzel

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

Konfiguracja
  1. 1 Otwórz aktywność w edytorze i przejdź do menu Deweloper.
  2. 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. 3 Zagraj raz sam w aktywność. Pierwsze zapytanie POST dotrze, gdy tylko coś odpowiesz.
Odbiornik od początku do końca
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);
});
Uruchamia się w trakcie gry, nie dopiero na końcu

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.

Jak odróżnić zakończenie od zwykłego zapisu

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.

Wysyła to przeglądarka gracza

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.

Traktuj dane jako deklarację, nie dowód

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.

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>
Sprawdź, skąd przyszła wiadomość

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.

Te same dane, ten sam moment wysyłki

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.

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 przychodzi
{
  "completed": true,
  "activityKey": "-Nq8sample_activity_key"
}
Wynik jest już zapisany, gdy sygnał dociera

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.

Tylko wewnątrz iframe

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.

PoleTypDo czego służy
activityKey
zawsze
string
stringAktywność, do której należy wynik. Ten sam klucz, który widzisz w adresie URL aktywności, po ?p=.
playerUid
zawsze
string
stringKto 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
objectPola 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
numberJak daleko gracz doszedł, w procentach. 100 oznacza ukończenie.
timePassed
zawsze
number
numberCzas spędzony na aktywności, w milisekundach.
lastPlayedAt
zawsze
number
numberKiedy ten wynik został zapisany, jako znacznik czasu Unix w milisekundach.
createdAt
czasami
number
numberKiedy rozpoczęto podejście, jako znacznik czasu Unix w milisekundach.
playerInput
czasami
object
objectCo 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
objectKtó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
numberZdobyte 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
numberWł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
numberKtó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
booleanPrzebieg zakończył się na błędnej odpowiedzi i jest zakończony bez ukończenia.
hasAlternateCompletion
czasami
boolean
booleanAktywność 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
arrayZnaki, w których gracz najczęściej się mylił, od najczęściej mylonych. Tylko w nauce pisania na klawiaturze.
contentVersion
czasami
number
numberWersja 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.
Ukończony quiz, tak jak przychodzi
{
  "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
}
Brak pola, nie puste pole

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

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 to jedyne polecenie

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.

Słuchana jest tylko strona osadzająca

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.

Wyślij nam gracza

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.

Wyślij nam token

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.

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();
});
Poproś nas o włączenie

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ści

Tworzenie 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ę API

Kiedy 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ć.

Żadne z powyższych?

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