Μετάβαση στο περιεχόμενο
Ενσωματώσεις για προγραμματιστές

Σύνδεσε το Puzzel με τη δική σου πλατφόρμα

Το Puzzel μπορεί να δώσει το αποτέλεσμα ενός παίκτη κατευθείαν σε ένα άλλο σύστημα — τη βαθμολογία του, πόσο προχώρησε, τι απάντησε — χωρίς αυτό το σύστημα να είναι ολόκληρο LMS. Αυτή η σελίδα καλύπτει κάθε κανάλι που υπάρχει: τι βγαίνει, πότε βγαίνει, και το μικρότερο πράγμα που πρέπει να φτιάξεις για να το πιάσεις.

Τα αποτελέσματα βγαίνουν μέσω
Ενός webhook, ή ενός μηνύματος στη σελίδα γύρω από τη δραστηριότητα
Η σελίδα σου μπορεί να
Συνδέσει τον παίκτη και να επαναφέρει τη δραστηριότητα
Ενεργοποιείται
Ανά δραστηριότητα, στο μενού Προγραμματιστές του επεξεργαστή
Περιλαμβάνεται σε
Ένα πληρωμένο πρόγραμμα — αυτές οι ρυθμίσεις είναι απενεργοποιημένες σε δωρεάν λογαριασμό

Ποιο κανάλι χρειάζεσαι;

Τρία πράγματα μπορούν να βγουν από μια δραστηριότητα και ένα μπορεί να μπει. Ποιο ταιριάζει εξαρτάται από μία μόνο ερώτηση: ο παίκτης βρίσκεται μέσα στη σελίδα σου, ή κάπου εντελώς αλλού;

Έξω από το Puzzel
Webhook αποτελεσμάτων

Κάθε αποθηκευμένο αποτέλεσμα στέλνεται με POST σε ένα URL που κατέχεις εσύ, ως JSON.

Χρησιμοποίησέ το όταν
Ο παίκτης μπορεί να είναι οπουδήποτε — ένας κοινοποιημένος σύνδεσμος, ένας κωδικός QR, ο ιστότοπος κάποιου άλλου — και θέλεις το αποτέλεσμα στη δική σου βάση δεδομένων.
Χρειάζεσαι
Ένα endpoint HTTPS που δέχεται POST cross-origin.
Ρύθμιση save_puzzle_results_via_webhook
Αποτελέσματα στη σελίδα γύρω της

Το ίδιο JSON, σταλμένο στη σελίδα που ενσωματώνει τη δραστηριότητα αντί σε έναν διακομιστή.

Χρησιμοποίησέ το όταν
Ενσωματώνεις τη δραστηριότητα στη δική σου σελίδα μαθήματος και η ίδια η σελίδα μπορεί να κάνει κάτι με το αποτέλεσμα.
Χρειάζεσαι
Ένα iframe στη σελίδα σου και έναν listener μηνυμάτων. Χωρίς διακομιστή, χωρίς CORS.
Ρύθμιση save_results_iframe_postmessage
Σήμα ολοκλήρωσης

Ένα μήνυμα όταν ο παίκτης τελειώνει, που δεν μεταφέρει τίποτα άλλο εκτός από αυτό το γεγονός.

Χρησιμοποίησέ το όταν
Το μόνο που θέλεις να ξέρεις είναι αν τελείωσε — για να τσεκάρεις το μάθημα, να ξεκλειδώσεις το επόμενο, ή να δείξεις τη δική σου οθόνη.
Χρειάζεσαι
Ένα iframe στη σελίδα σου και έναν listener μηνυμάτων.
Ρύθμιση send_completion_signal_when_embedded
Μέσα στο Puzzel

Webhook αποτελεσμάτων

Ενεργοποίησε την επιλογή «Αποστολή αποτελεσμάτων σε webhook» στον επεξεργαστή και δώσε της ένα URL. Από τότε και μετά, κάθε φορά που αποθηκεύεται η πρόοδος του παίκτη, το πρόγραμμα περιήγησής του στέλνει με POST ολόκληρο το αποτέλεσμα σε αυτό το URL, ως JSON.

Πώς να τη ρυθμίσεις
  1. 1 Άνοιξε τη δραστηριότητα στον επεξεργαστή και πήγαινε στο μενού Προγραμματιστές.
  2. 2 Ενεργοποίησε την επιλογή «Αποστολή αποτελεσμάτων σε webhook» και επικόλλησε το endpoint σου στο πεδίο από κάτω. Πρέπει να είναι ένα πλήρες URL — ένας απλός τομέας απορρίπτεται — και πρέπει να είναι https, γιατί το πρόγραμμα περιήγησης μπλοκάρει μια απλή κλήση http από μια σελίδα που σερβίρεται μέσω https.
  3. 3 Παίξε τη δραστηριότητα μία φορά ο ίδιος. Το πρώτο POST φτάνει μόλις απαντήσεις κάτι.
Ένας δέκτης, από την αρχή ως το τέλος
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);
});
Ενεργοποιείται καθώς παίζεις, όχι μόνο στο τέλος

Ένα αποτέλεσμα βγαίνει κάθε φορά που η εγγραφή αποθηκεύεται: μετά από μια παύση στην πληκτρολόγηση, όταν τοποθετείται μια κάρτα, όταν σταματά το χρονόμετρο, και ακόμα μία φορά όταν η δραστηριότητα ολοκληρώνεται. Ένα μεγάλο σταυρόλεξο είναι μερικές δεκάδες κλήσεις, όχι μία — γι' αυτό γράψε τον χειριστή σου σαν upsert με κλειδί τα playerUid και activityKey, όχι σαν εισαγωγή (insert). Κάθε κλήση μεταφέρει την πλήρη κατάσταση, οπότε η πιο πρόσφατη αντικαθιστά πάντα την προηγούμενη, και μια κλήση που χάνεται καλύπτεται από την επόμενη.

Πώς ξεχωρίζεις ένα τέλος από μια αποθήκευση

Το progress είναι ποσοστό: το 100 σημαίνει ότι η δραστηριότητα έχει ολοκληρωθεί. Μερικοί τύποι μπορούν να τελειώσουν χωρίς να το φτάσουν — ένα Quiz που απαντήθηκε μέχρι το τέλος, ένα πεδίο λύσης που λύθηκε — και αυτοί μεταφέρουν αντ' αυτού hasAlternateCompletion. Αντιμετώπισε και τα δύο ως ολοκλήρωση.

Το στέλνει το πρόγραμμα περιήγησης του παίκτη

Το POST έρχεται από την καρτέλα μέσα στην οποία παίζεται η δραστηριότητα, όχι από έναν διακομιστή του Puzzel. Τα περισσότερα endpoints φτιαγμένα να δέχονται webhooks το δέχονται ήδη. Αν το δικό σου δεν βλέπει ποτέ κανένα αίτημα, να γιατί: το πρόγραμμα περιήγησης ζητά άδεια πρώτα, οπότε απάντησε στο προκαταρκτικό αίτημα OPTIONS με μια κεφαλίδα Access-Control-Allow-Origin, και το πραγματικό POST ακολουθεί.

Αντιμετώπισε το payload ως ισχυρισμό, όχι ως απόδειξη

Δεν υπάρχει υπογραφή στο αίτημα, και έρχεται από ένα πρόγραμμα περιήγησης που δεν ελέγχεις εσύ, οπότε όποιος κοιτάξει τη σελίδα μπορεί να σου στείλει κι αυτός ένα. Αυτό είναι εντάξει για να γεμίζεις μια μπάρα προόδου ή έναν πίνακα ελέγχου. Για οτιδήποτε δεν θα άφηνες έναν μαθητή να ορίσει μόνος του — έναν βαθμό που μετράει, ένα πιστοποιητικό, μια πληρωμή — έλεγξέ το σε σχέση με τα αποτελέσματα στον δικό σου πίνακα ελέγχου του Puzzel, ή άσε τις συνδέσεις βαθμολόγησης του LMS να μεταφέρουν τη βαθμολογία αντ' αυτού.

Αποτελέσματα στη σελίδα γύρω της

Αν ενσωματώνεις τη δραστηριότητα, μπορείς να στέλνεις το ίδιο JSON στη δική σου σελίδα αντί σε έναν διακομιστή. Ενεργοποίησε την επιλογή «Αποστολή αποτελεσμάτων στη γονική σελίδα» και άκουσε για το μήνυμα. Τίποτα δεν φεύγει από το πρόγραμμα περιήγησης, οπότε δεν υπάρχει endpoint να φτιάξεις ούτε 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>
Έλεγξε από πού ήρθε το μήνυμα

Ο listener σου ακούει κάθε μήνυμα που στέλνεται στη σελίδα, ακόμα και από άλλα frames και επεκτάσεις προγράμματος περιήγησης. Σύγκρινε το event.origin με το https://puzzel.org πριν εμπιστευτείς όσα περιέχει.

Το ίδιο payload, η ίδια χρονική στιγμή

Αυτό είναι το δίδυμο του webhook: τα ίδια πεδία, σταλμένα τις ίδιες στιγμές. Όλα όσα αναφέρονται στο «Τι περιέχει ένα αποτέλεσμα» ισχύουν και εδώ.

Σήμα ολοκλήρωσης

Το πιο μικρό κανάλι, για όταν το ίδιο το αποτέλεσμα δεν σε αφορά: ενεργοποίησε την επιλογή «Αποστολή σήματος ολοκλήρωσης» και η σελίδα σου παίρνει ένα μήνυμα τη στιγμή που ο παίκτης τελειώνει.

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);
});
Τι φτάνει
{
  "completed": true,
  "activityKey": "-Nq8sample_activity_key"
}
Το αποτέλεσμα είναι ήδη αποθηκευμένο όταν φτάνει

Το σήμα στέλνεται σκόπιμα αφού έχει ολοκληρωθεί η αποθήκευση που ολοκληρώνει τη δραστηριότητα, οπότε μια σελίδα που αντιδρά διαβάζοντας ξανά το αποτέλεσμα θα το βρει εκεί.

Μόνο μέσα σε ένα iframe

Και τα δύο κανάλια μηνυμάτων στέλνουν στη σελίδα που πλαισιώνει τη δραστηριότητα. Ανοιγμένη στη δική της καρτέλα δεν υπάρχει κανείς να ενημερωθεί, οπότε δεν στέλνεται τίποτα.

Τι περιέχει ένα αποτέλεσμα

Μία μορφή, όποιο κανάλι κι αν τη μεταφέρει. Οι απαντήσεις του παίκτη έχουν ως κλειδί τα δικά τους αναγνωριστικά αντικειμένων της δραστηριότητας, οπότε τα ίδια κλειδιά εμφανίζονται και στο correctUids.

ΠεδίοΤύποςΤι κάνει
activityKey
πάντα
string
stringΗ δραστηριότητα στην οποία ανήκει το αποτέλεσμα. Το ίδιο κλειδί που βλέπεις στο ίδιο το URL της δραστηριότητας, μετά το ?p=.
playerUid
πάντα
string
stringΠοιος έπαιξε, ως ανώνυμο id. Σταθερό για αυτόν τον παίκτη σε αυτή τη συσκευή, οπότε είναι αυτό στο οποίο κλειδώνεις τα αποτελέσματα — δεν είναι διεύθυνση email ούτε λογαριασμός Puzzel.
player
μερικές φορές
object
objectΤα πεδία εγγραφής που ζητά η δραστηριότητα, όπως τα ρύθμισες: όνομα, email, τάξη, student_id και ούτω καθεξής. Απόν μέχρι να εγγραφεί ο παίκτης, και εντελώς απόν σε μια δραστηριότητα που δεν ζητά τίποτα.
progress
πάντα
number
numberΠόσο έχει προχωρήσει, ως ποσοστό. Το 100 σημαίνει ότι τελείωσε.
timePassed
πάντα
number
numberΟ χρόνος στη δραστηριότητα, σε χιλιοστά του δευτερολέπτου.
lastPlayedAt
πάντα
number
numberΠότε αποθηκεύτηκε αυτό το αποτέλεσμα, ως χρονοσφραγίδα Unix σε χιλιοστά του δευτερολέπτου.
createdAt
μερικές φορές
number
numberΠότε ξεκίνησε η προσπάθεια, ως χρονοσφραγίδα Unix σε χιλιοστά του δευτερολέπτου.
playerInput
μερικές φορές
object
objectΤι έγραψε πραγματικά ο παίκτης, με κλειδί το id του αντικειμένου στο οποίο ανήκει. Η μορφή από μέσα εξαρτάται από τον τύπο δραστηριότητας — μια λέξη, μια λίστα τοποθετημένων καρτών, μια επιλεγμένη επιλογή.
correctUids
μερικές φορές
object
objectΠοια από αυτά τα αντικείμενα είναι σωστά, με το ίδιο κλειδί. Απόν όσο δεν έχει απαντηθεί ακόμα τίποτα.
score
μερικές φορές
number
numberΟι πόντοι που σημειώθηκαν, στους τύπους που βαθμολογούν μια προσπάθεια. Απόν σε όλους τους άλλους — ακόμα και σε μια προσπάθεια που σημείωσε πραγματικά μηδέν, γι' αυτό έλεγξε ότι υπάρχει το κλειδί πριν το διαβάσεις.
performance
μερικές φορές
number
numberΤο δικό του μέτρο κάθε τύπου για το πόσο καλά πήγε, εκεί όπου κρατά ένα — λέξεις ανά λεπτό στην εξάσκηση στην πληκτρολόγηση, για παράδειγμα.
attempts
μερικές φορές
number
numberΠοια προσπάθεια είναι αυτή: 1 την πρώτη φορά, μία παραπάνω σε κάθε νέα εκκίνηση. Μόνο σε τύπους που τερματίζουν μια προσπάθεια νωρίς και μετράνε τις επαναλήψεις.
knockedOut
μερικές φορές
boolean
booleanΗ προσπάθεια τελείωσε σε λάθος απάντηση και έκλεισε χωρίς να ολοκληρωθεί.
hasAlternateCompletion
μερικές φορές
boolean
booleanΗ δραστηριότητα ολοκληρώθηκε με έναν τρόπο που δεν φτάνει το 100% — ένα Quiz που απαντήθηκε μέχρι το τέλος, ένα πεδίο λύσης που λύθηκε. Αντιμετώπισέ το ως ολοκλήρωση.
missedKeys
μερικές φορές
array
arrayΧαρακτήρες στους οποίους ο παίκτης έκανε συνεχώς λάθος, με τον πιο συχνό πρώτο. Μόνο στην εξάσκηση στην πληκτρολόγηση.
contentVersion
μερικές φορές
number
numberΠοια έκδοση του περιεχομένου της δραστηριότητας παίχτηκε. Αλλάζει όταν ο κάτοχος επεξεργάζεται τις ερωτήσεις, οπότε ένα παλιό αποτέλεσμα ξεχωρίζει από ένα τρέχον.
Ένα ολοκληρωμένο Quiz, όπως φτάνει
{
  "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
}
Απόν, όχι κενό

Ένα πεδίο που δεν ισχύει παραλείπεται από το JSON αντί να σταλεί ως null ή μηδέν. Έτσι ξεχωρίζει ένας τύπος που δεν βαθμολογεί μια προσπάθεια από μια προσπάθεια που βαθμολογήθηκε με μηδέν — οπότε διάβασε με μια προεπιλεγμένη τιμή και μην υποθέτεις ποτέ ότι ένα κλειδί υπάρχει.

Επαναφορά της δραστηριότητας από τη σελίδα σου

Μία εντολή ταξιδεύει προς την αντίθετη κατεύθυνση. Με ενεργοποιημένη την επιλογή «Αποδοχή εντολών από τη γονική σελίδα», η σελίδα που κάνει την ενσωμάτωση μπορεί να καθαρίσει τις απαντήσεις του παίκτη και να επαναφέρει τη δραστηριότητα στην αρχή — για ένα δικό σου κουμπί «δοκίμασε ξανά», έξω από το πλαίσιο.

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');
Η επαναφορά είναι η μόνη εντολή

Δεν υπάρχει μήνυμα για υποβολή αποτελέσματος, άνοιγμα του μηνύματος ολοκλήρωσης ή μετάβαση σε μια ερώτηση. Ένα μήνυμα που ζητά οτιδήποτε άλλο εκτός από επαναφορά αγνοείται.

Ακούγεται μόνο η σελίδα που κάνει την ενσωμάτωση

Η εντολή γίνεται δεκτή από τη σελίδα που πλαισιώνει τη δραστηριότητα και από πουθενά αλλού — όχι από ένα αδερφό frame, όχι από ένα script στη σελίδα. Η ρύθμιση είναι απενεργοποιημένη από προεπιλογή, οπότε ενεργοποίησέ την για τις δραστηριότητες που ελέγχεις εσύ.

Φέρνοντας τη δική σου ταυτότητα παίκτη

Αν η πλατφόρμα σου ξέρει ήδη ποιος παίζει, η δραστηριότητα δεν χρειάζεται να τον ρωτήσει ξανά. Υπάρχουν δύο χειραψίες, και οι δύο για μια δραστηριότητα μέσα στη σελίδα σου, και οι δύο ενεργοποιούνται από εμάς αντί από τον επεξεργαστή — αλλάζουν σε ποιον ανήκει ένα αποτέλεσμα, γι' αυτό ρυθμίζονται μαζί σου αντί από ένα πλαίσιο επιλογής.

Στείλε μας τον παίκτη

Η δραστηριότητα ανακοινώνει τον εαυτό της με 'app-loaded' και περιμένει. Η σελίδα σου στέλνει πίσω τα στοιχεία του παίκτη, και το αποτέλεσμα καταχωρείται στο όνομά του χωρίς ο παίκτης να πληκτρολογήσει τίποτα ή να δει οθόνη εγγραφής.

Στείλε μας ένα token

Η ίδια χειραψία, αλλά η σελίδα σου στέλνει το JWT που εξέδωσε ο πάροχος ταυτότητάς σου αντί για τα ίδια τα πεδία. Το επαληθεύουμε σε σχέση με τους εκδότες που έχουν ρυθμιστεί για τον λογαριασμό σου πριν αφήσουμε τον παίκτη να μπει, οπότε η ταυτότητα αποδεικνύεται αντί να δηλώνεται απλώς — αυτή είναι η επιλογή που ζητάς όταν το αποτέλεσμα πρέπει να είναι αξιόπιστο.

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();
});
Ζήτησέ μας να το ενεργοποιήσουμε

Πες μας ποιο από τα δύο θέλεις και πού θα ενσωματωθούν οι δραστηριότητες, και θα ρυθμίσουμε τον λογαριασμό σου και θα τα περάσουμε μαζί σου.

Στείλε μας email για την ταυτότητα

Δημιουργία δραστηριοτήτων από το σύστημά σου

Όλα τα παραπάνω αφορούν ένα αποτέλεσμα που βγαίνει προς τα έξω. Το αντίστροφο — να φτιάχνεις τις ίδιες τις δραστηριότητες από περιεχόμενο που έχεις ήδη — είναι το Puzzle API: ένα POST ανά τύπο δραστηριότητας, και παίρνεις πίσω ένα κλειδί και ένα URL για ενσωμάτωση.

Διάβασε την τεκμηρίωση του API

Όταν μια έτοιμη σύνδεση είναι η καλύτερη απάντηση

Αν η πλατφόρμα από την άλλη πλευρά είναι ένα πραγματικό LMS, μάλλον δεν χρειάζεσαι τίποτα από όλα αυτά. Οι βαθμοί μπορούν να επιστρέψουν μόνοι τους στο βαθμολόγιό του, χωρίς να χρειάζεται να φιλοξενήσεις εσύ τίποτα.

Τίποτα από αυτά;

Πλατφόρμες μαθημάτων, ιστότοποι συνδρομητών, intranet και οτιδήποτε έχεις φτιάξει εσύ ο ίδιος είναι ακριβώς αυτά για τα οποία υπάρχουν τα κανάλια αυτής της σελίδας. Μια ενσωμάτωση συν το σήμα ολοκλήρωσης καλύπτουν τα περισσότερα.

Τι δεν υπάρχει

Για να μην το ψάχνεις:

  • Κανένα endpoint για να διαβάσεις αποτελέσματα πίσω. Το API δημιουργεί δραστηριότητες· τα αποτελέσματα βγαίνουν μέσα από τα κανάλια αυτής της σελίδας, ή μέσα από τις εξαγωγές στον πίνακα ελέγχου σου.
  • Καμία υπογραφή στο webhook. Δεν υπάρχει τίποτα να ελέγξεις το αίτημα απέναντί του, γι' αυτό ένα αποτέλεσμα δεν πρέπει να είναι το μόνο στήριγμα πίσω από κάτι σημαντικό.
  • Κανένα webhook σε επίπεδο λογαριασμού. Το URL είναι ρύθμιση μιας δραστηριότητας, οπότε μια δραστηριότητα που αντιγράφεις το κουβαλά μαζί της και μια καινούρια ξεκινά χωρίς αυτό.
  • Τίποτα σε ένα παιχνίδι ομάδων ή σε μια ζωντανή αίθουσα. Και τα δύο κανάλια μηνυμάτων και το webhook είναι για ατομικό παιχνίδι, και οι ρυθμίσεις απενεργοποιούνται μόνες τους όταν είναι ενεργό το παιχνίδι για πολλούς παίκτες.
  • Καμία ουρά παράδοσης. Τίποτα δεν αποθηκεύεται και ξαναστέλνεται — η επόμενη αποθήκευση είναι η επανάληψη, και η τελευταία κλήση της προσπάθειας είναι αυτή που μετράει.

Κάτι δεν δουλεύει σωστά;

Στείλε το αίτημα που δοκίμασες και το σφάλμα που πήρες πίσω και θα λάβεις μια πραγματική απάντηση, από το άτομο που έγραψε το endpoint.

Στείλε email στην υποστήριξη