Files
sentence1/G102-sequence/sdk/package/README.md
lubukhu 65fd0158a3
All checks were successful
Deploy to Production / deploy (push) Successful in 8s
up
2026-01-24 13:35:11 +07:00

507 lines
10 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SDK Message Protocol
Game giao tiếp với SDK thông qua **postMessage** trong hidden iframe. Game chỉ cần biết các message types, payloads, và khi nào gửi/nhận.
---
## 🔌 Architecture
```
┌──────────────────────────────┐
│ Game (React/Vue/etc) │
│ │
│ window.parent.postMessage() │
│ window.addEventListener() │
└──────────────┬───────────────┘
│ postMessage
│ (JSON)
┌──────────────────────┐
│ Hidden Iframe │
│ (sdk-iframe.html) │
│ │
│ - Sanitize data │
│ - Verify answers │
│ - Call API │
│ - Send responses │
└──────────────────────┘
```
---
## 📨 Message Types
### 1⃣ **SDK_INIT** (Game → SDK)
Game khởi tạo SDK với mode và game_code.
**Gửi:**
```javascript
window.parent.postMessage({
type: 'SDK_INIT',
payload: {
mode: 'dev' | 'preview' | 'live',
game_code: 'G001' | 'G002' | ... | 'G123',
// LIVE mode only:
assignment_id?: string,
student_id?: string,
api_base_url?: string,
auth_token?: string
}
}, '*');
```
**Ví dụ DEV mode:**
```javascript
window.parent.postMessage({
type: 'SDK_INIT',
payload: {
mode: 'dev',
game_code: 'G001'
}
}, '*');
```
**Ví dụ LIVE mode:**
```javascript
window.parent.postMessage({
type: 'SDK_INIT',
payload: {
mode: 'live',
game_code: 'G001',
assignment_id: 'ASSIGN_123',
student_id: 'STU_456',
api_base_url: 'https://api.sena.tech',
auth_token: 'token_xyz'
}
}, '*');
```
**Nhận (SDK gửi lại khi ready):**
```javascript
{
type: 'SDK_READY',
payload: {
mode: 'dev' | 'preview' | 'live',
game_code: 'G001'
}
}
```
---
### 2⃣ **SDK_DATA_READY** (SDK → Game)
SDK gửi sanitized data cho game render. Game phải listen sự kiện này.
**Nhận:**
```javascript
window.addEventListener('message', (event) => {
if (event.data.type === 'SDK_DATA_READY') {
const {
items, // Sanitized items (NO answers!)
total_questions,
completed_count,
resume_data // Optional: previous results
} = event.data.payload;
// Render game
renderGame(items);
}
});
```
**Payload items (tùy game_code):**
#### Quiz Games (G001-G004):
```json
{
"id": "q1",
"question": "What is 2+2?",
"options": [
{"text": "5"},
{"text": "4"},
{"text": "3"}
]
}
```
#### Sequence Games (G110-G123):
```json
{
"id": "seq1",
"question": ["H", "", "L", "", "O"],
"options": ["L", "E"],
"audio_url": "https://..." // optional
}
```
---
### 3⃣ **SDK_CHECK_ANSWER** (Game → SDK)
Game gửi user's answer để SDK verify.
**Gửi:**
```javascript
window.parent.postMessage({
type: 'SDK_CHECK_ANSWER',
payload: {
question_id: 'q1',
choice: any, // Index (quiz) hoặc Array (sequence)
time_spent?: number // Milliseconds
}
}, '*');
```
**Quiz example (choice = index):**
```javascript
window.parent.postMessage({
type: 'SDK_CHECK_ANSWER',
payload: {
question_id: 'q1',
choice: 1, // User clicked option index 1
time_spent: 5000
}
}, '*');
```
**Sequence example (choice = reordered array):**
```javascript
window.parent.postMessage({
type: 'SDK_CHECK_ANSWER',
payload: {
question_id: 'seq1',
choice: ["H", "e", "l", "l", "o"], // Reordered
time_spent: 8000
}
}, '*');
```
---
### 4⃣ **SDK_ANSWER_RESULT** (SDK → Game)
SDK gửi kết quả verify.
**Nhận:**
```javascript
window.addEventListener('message', (event) => {
if (event.data.type === 'SDK_ANSWER_RESULT') {
const {
question_id,
correct, // true/false
score, // 0-1 hoặc custom
synced, // true = already synced to server
feedback // Optional: "✅ Correct!" or "❌ Wrong"
} = event.data.payload;
if (correct) {
showCorrectFeedback(question_id);
} else {
showWrongFeedback(question_id);
}
}
});
```
---
### 5⃣ **SDK_PUSH_DATA** (Game → SDK, PREVIEW only)
Nếu PREVIEW mode, game có thể push data thay vì SDK fetch.
**Gửi:**
```javascript
window.parent.postMessage({
type: 'SDK_PUSH_DATA',
payload: {
items: [
{
id: 'q1',
question: 'What is 2+2?',
options: [
{text: '3'},
{text: '4'},
{text: '5'}
],
answer: '4' // Server data (with answer)
},
// ... more items
]
}
}, '*');
```
SDK sẽ sanitize (remove answer, shuffle options) rồi gửi SDK_DATA_READY.
---
### 6⃣ **SDK_ERROR** (SDK → Game)
SDK gửi error notification.
**Nhận:**
```javascript
window.addEventListener('message', (event) => {
if (event.data.type === 'SDK_ERROR') {
const {
code, // Error code
message, // Error message
details // Optional: more info
} = event.data.payload;
console.error(`[SDK Error] ${code}: ${message}`);
}
});
```
---
## 🎮 Data Structures by Game Type
### Quiz Games (G001-G004)
#### Sanitized (Game receives):
```json
{
"id": "q1",
"question": "Audio URL or text",
"image_url": "Image URL (optional)",
"options": [
{"text": "Option A"} or {"audio": "Audio URL"}
]
}
```
#### User Answer Format:
```javascript
choice = 0 // Index of selected option
```
---
### Sequence Word (G110-G113)
#### Sanitized (Game receives):
```json
{
"id": "seq_word_1",
"question": ["H", "", "L", "", "O"],
"options": ["L", "E"],
"audio_url": "URL (optional)"
}
```
#### User Answer Format:
```javascript
choice = ["H", "e", "l", "l", "o"] // Reordered array
```
---
### Sequence Sentence (G120-G123)
#### Sanitized (Game receives):
```json
{
"id": "seq_sent_1",
"question": ["I", "", "reading", ""],
"options": ["love", "books"],
"audio_url": "URL (optional)"
}
```
#### User Answer Format:
```javascript
choice = ["I", "love", "reading", "books"] // Reordered
```
---
## 💻 Complete Game Implementation Example
```javascript
// ============ INITIALIZE ============
// Listen for SDK messages
window.addEventListener('message', (event) => {
handleSdkMessage(event.data);
});
// Initialize SDK
function initGame() {
const mode = 'live';
const gameCode = 'G001';
window.parent.postMessage({
type: 'SDK_INIT',
payload: {
mode,
game_code: gameCode,
assignment_id: 'ASSIGN_123',
student_id: 'STU_456',
api_base_url: 'https://api.sena.tech'
}
}, '*');
}
// Handle all SDK messages
function handleSdkMessage(data) {
switch (data.type) {
case 'SDK_READY':
console.log('SDK initialized:', data.payload);
break;
case 'SDK_DATA_READY':
const items = data.payload.items;
renderGameItems(items);
break;
case 'SDK_ANSWER_RESULT':
const result = data.payload;
if (result.correct) {
showCorrectFeedback(result.question_id);
} else {
showWrongFeedback(result.question_id);
}
break;
case 'SDK_ERROR':
console.error('SDK Error:', data.payload);
break;
}
}
// ============ RENDER GAME ============
function renderGameItems(items) {
items.forEach(item => {
if (item.options) {
// Quiz game
renderQuizQuestion(item);
} else if (Array.isArray(item.question)) {
// Sequence game
renderSequenceGame(item);
}
});
}
// ============ HANDLE USER ANSWER ============
function submitAnswer(questionId, userChoice) {
window.parent.postMessage({
type: 'SDK_CHECK_ANSWER',
payload: {
question_id: questionId,
choice: userChoice, // Index for quiz, array for sequence
time_spent: 5000
}
}, '*');
}
// ============ GAME START ============
// Start when page loads
window.addEventListener('load', () => {
initGame();
});
```
---
## 🔄 Message Flow Examples
### DEV Mode (Local Verify)
```
Game: SDK_INIT
SDK: SDK_READY
SDK: SDK_DATA_READY (mock items)
Game: SDK_CHECK_ANSWER
SDK: SDK_ANSWER_RESULT (instantly)
```
### LIVE Mode (Server Verify)
```
Game: SDK_INIT
SDK: SDK_READY
SDK: fetch from API → SDK_DATA_READY
Game: SDK_CHECK_ANSWER
SDK: POST to server
SDK: SDK_ANSWER_RESULT (wait for server)
```
### PREVIEW Mode (Push Data)
```
Game: SDK_INIT (mode='preview')
SDK: SDK_READY
Game: SDK_PUSH_DATA (items with answers)
SDK: sanitize → SDK_DATA_READY
Game: SDK_CHECK_ANSWER
SDK: SDK_ANSWER_RESULT (local verify)
```
---
## ⚠️ Important Notes
**SDK Always Sanitizes**
- Removes `answer` field
- Removes `word`, `sentence`, `parts`, `missing_letter_count`
- Shuffles options (for quiz)
**Game Never Gets Answer**
- Game receives only question + options
- Answer is stored server-side
**Shuffled Options**
- Game receives shuffled options array
- Game user clicks index
- SDK internally resolves index → text
**Sequence Games**
- Random positions are blanked (based on `missing_letter_count`)
- Game receives question with blanks + missing items
- User reorders missing items
---
## 🚀 Testing
Test locally without SDK:
```javascript
// Simulate SDK messages for testing
function simulateSdkMessage(type, payload) {
const event = new MessageEvent('message', {
data: { type, payload }
});
window.dispatchEvent(event);
}
// Simulate SDK_DATA_READY
simulateSdkMessage('SDK_DATA_READY', {
items: [
{
id: 'q1',
question: 'What is 2+2?',
options: [{text: '4'}, {text: '5'}, {text: '3'}]
}
]
});
```