İçeriğe geç
Geliştirici entegrasyonları

Puzzel'i kendi platformuna bağla

Puzzel, bir oyuncunun sonucunu — puanını, ne kadar ilerlediğini, ne cevapladığını — başka bir sisteme doğrudan iletebilir; bunun için o sistemin tam bir LMS olması gerekmez. Bu sayfa var olan tüm kanalları anlatır: nelerin gittiğini, ne zaman gittiğini ve bunu yakalamak için kurman gereken en küçük şeyi.

Sonuçlar şu yolla gider
Bir webhook veya etkinliği çevreleyen sayfaya gönderilen bir mesaj
Sayfan şunu yapabilir
Oyuncunun girişini yapabilir ve etkinliği sıfırlayabilir
Nerede açılır
Etkinlik başına, düzenleyicide Geliştirici altında
Dahil olduğu paket
Ücretli bir paket — bu ayarlar ücretsiz hesapta kapalıdır

Hangi kanala ihtiyacın var?

Bir etkinlikten çıkabilecek üç şey, içine girebilecek bir şey var. Hangisinin uyduğu tek bir soruya bağlı: oyuncu senin sayfanın içinde mi, yoksa tamamen başka bir yerde mi?

Puzzel'den çıkan
Puzzel'e giren

Sonuç webhook'u

Düzenleyicide "Sonuçları bir webhook'a gönder" ayarını aç ve bir URL ver. Bundan sonra oyuncunun ilerlemesi her kaydedildiğinde, tarayıcısı sonucun tamamını JSON olarak bu URL'ye POST eder.

Kurulum
  1. 1 Etkinliği düzenleyicide aç ve Geliştirici menüsüne git.
  2. 2 "Sonuçları bir webhook'a gönder" ayarını aç ve uç noktanı altındaki alana yapıştır. Tam bir URL olmalı — yalın bir alan adı reddedilir — ve https olmalı, çünkü tarayıcı, https üzerinden sunulan bir sayfadan yapılan düz http çağrısını engeller.
  3. 3 Etkinliği bir kez kendin oyna. Bir şey cevapladığın anda ilk POST ulaşır.
Uçtan uca bir alıcı
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);
});
Sonda bir kez değil, oynarken tetiklenir

Giriş her kaydedildiğinde bir sonuç gider: yazarken bir duraklamanın ardından, bir kart yerleştirildiğinde, süre durduğunda ve etkinlik bittiğinde bir kez daha. Uzun bir kare bulmaca tek bir çağrı değil, düzinelerce çağrı demektir — bu yüzden işleyicini bir insert olarak değil, playerUid ve activityKey üzerinden anahtarlanan bir upsert olarak yaz. Her çağrı tam durumu taşır, yani en yenisi her zaman öncekinin yerini alır ve kaybolan bir çağrının eksiği bir sonrakiyle kapanır.

Bir kaydı bitişten ayırt etmek

progress bir yüzdedir: 100, etkinliğin tamamlandığı anlamına gelir. Birkaç tür buna ulaşmadan bitebilir — sonuna kadar cevaplanmış bir quiz, çözülmüş bir çözüm alanı — bunlar bunun yerine hasAlternateCompletion taşır. İkisini de bitmiş say.

Oyuncunun tarayıcısı tarafından gönderilir

POST, bir Puzzel sunucusundan değil, etkinliğin oynandığı sekmeden gelir. Webhook almak için kurulmuş uç noktaların çoğu bunu zaten kabul eder. Seninki hiç istek görmüyorsa nedeni bu: tarayıcı önce izin ister, bu yüzden OPTIONS preflight isteğini bir Access-Control-Allow-Origin başlığıyla yanıtla; gerçek POST ardından gelir.

Veriyi bir kanıt değil, bir iddia olarak değerlendir

İstek üzerinde bir imza yok ve senin kontrolünde olmayan bir tarayıcıdan geliyor, yani sayfaya bakan herkes sana da bir tane gönderebilir. Bu, bir ilerleme çubuğunu ya da bir paneli doldurmak için sorun değil. Bir öğrencinin kendisinin belirlemesine izin vermeyeceğin herhangi bir şey için — geçerli bir not, bir sertifika, bir ödeme — bunu kendi Puzzel panelindeki sonuçlarla karşılaştır ya da puanı taşımayı LMS not bağlayıcılarına bırak.

Sonuçlar, onu çevreleyen sayfaya

Etkinliği yerleştirirsen, aynı JSON'ın bir sunucu yerine kendi sayfana gönderilmesini sağlayabilirsin. "Sonuçları üst sayfaya gönder" ayarını aç ve mesajı dinle. Tarayıcıdan hiçbir şey çıkmaz, yani kurulacak bir uç nokta ve düşünülecek bir CORS yoktur.

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>
Mesajın nereden geldiğini kontrol et

Dinleyicin, diğer frame'ler ve tarayıcı uzantıları dahil, sayfaya gönderilen her mesajı duyar. İçindekine güvenmeden önce event.origin'i https://puzzel.org ile karşılaştır.

Aynı veri, aynı zamanlama

Bu, webhook'un ikizidir: aynı alanlar, aynı anlarda gönderilir. "Bir sonuç neler içerir" başlığı altındaki her şey burası için de geçerlidir.

Bitiş sinyali

Sonucun kendisinin seni ilgilendirmediği durumlar için en küçük kanal: "Bitiş sinyali gönder" ayarını aç ve oyuncu bitirdiği anda sayfan tek bir mesaj alır.

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);
});
Ne gelir
{
  "completed": true,
  "activityKey": "-Nq8sample_activity_key"
}
Sinyal geldiğinde sonuç zaten kaydedilmiştir

Sinyal, bilerek bitiren kayıt ulaştıktan sonra gönderilir; böylece sonucu geri okuyarak tepki veren bir sayfa onu orada bulur.

Yalnızca bir iframe içinde

İki mesaj kanalı da etkinliği çerçeveleyen sayfaya gönderir. Kendi sekmesinde açıldığında haber verilecek kimse olmadığından hiçbir şey gönderilmez.

Bir sonuç neler içerir

Hangi kanal taşırsa taşısın, tek bir şekil. Oyuncunun cevapları, etkinliğin kendi öğe kimlikleriyle anahtarlanır, bu yüzden aynı anahtarlar correctUids içinde de karşına çıkar.

AlanTürNe işe yarar
activityKey
her zaman
string
stringSonucun ait olduğu etkinlik. Etkinliğin kendi URL'sinde ?p='den sonra gördüğün aynı anahtar.
playerUid
her zaman
string
stringKimin oynadığı, anonim bir kimlik olarak. Bu oyuncu için bu cihazda sabittir, bu yüzden sonuçları bunun üzerinden anahtarlarsın — bir e-posta adresi ya da bir Puzzel hesabı değildir.
player
bazen
object
objectEtkinliğin sorduğu, senin yapılandırdığın haliyle kayıt alanları: ad, e-posta, sınıf, student_id ve benzerleri. Oyuncu kayıt olana kadar yoktur, hiçbir şey sormayan bir etkinlikte ise tamamen yoktur.
progress
her zaman
number
numberYüzde olarak ne kadar ilerlendiği. 100, bittiği anlamına gelir.
timePassed
her zaman
number
numberEtkinlikte geçen süre, milisaniye cinsinden.
lastPlayedAt
her zaman
number
numberBu sonucun ne zaman kaydedildiği, milisaniye cinsinden bir Unix zaman damgası olarak.
createdAt
bazen
number
numberDenemenin ne zaman başladığı, milisaniye cinsinden bir Unix zaman damgası olarak.
playerInput
bazen
object
objectOyuncunun gerçekte ne girdiği, ait olduğu öğenin kimliğiyle anahtarlanmış olarak. İçindeki şekil etkinlik türüne bağlıdır — bir kelime, yerleştirilmiş kartların bir listesi, seçilmiş bir seçenek.
correctUids
bazen
object
objectBu öğelerden hangilerinin doğru olduğu, aynı şekilde anahtarlanmış olarak. Henüz hiçbir şey cevaplanmamışken yoktur.
score
bazen
number
numberBir çalıştırmayı puanlayan türlerde kazanılan puan. Diğer her yerde yoktur — gerçekten sıfır puan alan bir çalıştırmada bile — bu yüzden okumadan önce anahtarın var olup olmadığını kontrol et.
performance
bazen
number
numberBir türün kendine ait, ne kadar iyi gittiğine dair ölçüsü, eğer bir tanesini tutuyorsa — örneğin klavye alıştırmasında dakikadaki kelime sayısı.
attempts
bazen
number
numberBu çalıştırmanın kaçıncı olduğu: ilk seferinde 1, her yeniden başlamada bir fazlası. Yalnızca bir çalıştırmayı erken bitiren ve tekrar denemeleri sayan türlerde bulunur.
knockedOut
bazen
boolean
booleanÇalıştırma yanlış bir cevapla bitti ve tamamlanmadan sona erdi.
hasAlternateCompletion
bazen
boolean
booleanEtkinlik, %100'e ulaşmayan bir şekilde bitirildi — sonuna kadar cevaplanmış bir quiz, çözülmüş bir çözüm alanı. Bunu bir bitiş olarak say.
missedKeys
bazen
array
arrayOyuncunun sürekli yanlış yaptığı karakterler, en çok yanlış yapılan önce. Yalnızca klavye alıştırmasında.
contentVersion
bazen
number
numberBunun etkinliğin içeriğinin hangi sürümüne karşı oynandığı. Sahip soruları düzenlediğinde değişir, bu yüzden eski bir sonuç güncel bir sonuçtan ayırt edilebilir.
Bitmiş bir quiz, geldiği haliyle
{
  "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
}
Boş değil, yok

Geçerli olmayan bir alan, null ya da sıfır olarak gönderilmek yerine JSON'dan tamamen çıkarılır. Bir çalıştırmayı puanlamayan bir tür böylece hiç puan almamış bir çalıştırmadan ayırt edilir — bu yüzden bir varsayılan değerle oku ve bir anahtarın orada olduğunu asla varsayma.

Etkinliği sayfandan sıfırlamak

Bir talimat da diğer yöne gider. "Üst sayfadan gelen tetikleyicileri kabul et" ayarı açıkken, yerleştirmeyi yapan sayfa oyuncunun cevaplarını temizleyip etkinliği baştan başlatabilir — frame'in dışında kendi "tekrar dene" düğmen için.

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');
Tek tetikleyici sıfırlamadır

Bir sonucu göndermek, bitiş mesajını açmak ya da bir soruya atlamak için bir mesaj yoktur. Sıfırlama dışında bir şey isteyen bir mesaj yok sayılır.

Yalnızca yerleştiren sayfa duyulur

Tetikleyici yalnızca etkinliği çerçeveleyen sayfadan kabul edilir, başka hiçbir yerden değil — ne bir kardeş frame'den ne de sayfadaki bir betikten. Ayar varsayılan olarak kapalıdır, bu yüzden yönettiğin etkinlikler için aç.

Kendi oyuncu kimliğini kullanmak

Platformun kimin oynadığını zaten biliyorsa, etkinliğin bunu tekrar sorması gerekmez. İkisi de sayfanın içindeki bir etkinlik için olan ve düzenleyicide değil bizim tarafımızdan açılan iki el sıkışma vardır — bunlar bir sonucun kime ait olduğunu değiştirir, bu yüzden bir onay kutusundan değil seninle birlikte kurulur.

Bize oyuncuyu gönder

Etkinlik kendini 'app-loaded' ile duyurur ve bekler. Sayfan oyuncunun bilgilerini geri gönderir ve sonuç, oyuncu hiçbir şey yazmadan ya da bir kayıt ekranı görmeden onun adına kaydedilir.

Bize bir token gönder

Aynı el sıkışma, ama sayfan alanların kendisi yerine kimlik sağlayıcının verdiği JWT'yi gönderir. Oyuncu içeri alınmadan önce bunu hesabın için kurulmuş verenlerle doğrularız, yani kimlik iddia edilmek yerine kanıtlanmış olur — sonucun güvenilir olması gerektiğinde istemen gereken budur.

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();
});
Bunu açmamızı iste

İkisinden hangisini istediğini ve etkinliklerin nereye yerleştirileceğini bize söyle; hesabını kuralım ve seninle birlikte adım adım anlatalım.

Kimlik için bize e-posta gönder

Etkinlikleri kendi sisteminden oluşturmak

Yukarıdakilerin hepsi bir sonucun çıkmasıyla ilgili. Diğer yönde gitmek — etkinliklerin kendisini zaten sahip olduğun içerikten oluşturmak — Puzzle API'sidir: etkinlik türü başına bir POST, karşılığında yerleştirmek için bir anahtar ve bir URL alırsın.

API referansını oku

Hazır bir bağlayıcının daha iyi olduğu durumlar

Karşı taraftaki platform gerçek bir LMS ise büyük olasılıkla bunların hiçbirine ihtiyacın yoktur. Notlar, senin barındırman gereken hiçbir şey olmadan kendiliğinden not defterine geri döner.

Bunlardan biri değil mi?

Kurs platformları, üyelik siteleri, intranetler ve kendi oluşturduğun her şey, tam olarak bu sayfadaki kanalların ne için olduğudur. Bir yerleştirme artı bitiş sinyali çoğu durumu karşılar.

Ne yok

Boşuna aramayasın diye:

  • Sonuçları geri okumak için bir uç nokta yok. API etkinlikler oluşturur; sonuçlar bu sayfadaki kanallardan ya da panelindeki dışa aktarmalardan çıkar.
  • Webhook'ta imza yok. İsteği doğrulayacak bir şey olmadığından, önemli bir şeyin arkasında tek dayanak bir sonuç olmamalı.
  • Hesap genelinde bir webhook yok. URL, bir etkinliğin ayarıdır, bu yüzden kopyaladığın bir etkinlik onu da beraberinde taşır, yeni bir etkinlik ise onsuz başlar.
  • Bir takım oyununda ya da canlı bir odada hiçbir şey yok. Her iki mesaj kanalı da webhook da tek başına oynamak içindir, takım oyunu açıkken ayarlar kendiliğinden kapanır.
  • Bir teslimat kuyruğu yok. Hiçbir şey saklanıp yeniden gönderilmez — bir sonraki kayıt yeniden deneme demektir, önemli olan çalıştırmanın son çağrısıdır.

Bir şey beklendiği gibi çalışmıyor mu?

Denediğin isteği ve aldığın hatayı gönder; uç noktayı yazan kişiden gerçek bir cevap alırsın.

Destek ekibine e-posta gönder