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

10 KiB
Raw Blame History

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:

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:

window.parent.postMessage({
    type: 'SDK_INIT',
    payload: {
        mode: 'dev',
        game_code: 'G001'
    }
}, '*');

Ví dụ LIVE mode:

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):

{
    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:

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):

{
    "id": "q1",
    "question": "What is 2+2?",
    "options": [
        {"text": "5"},
        {"text": "4"},
        {"text": "3"}
    ]
}

Sequence Games (G110-G123):

{
    "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:

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):

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):

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:

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:

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:

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):

{
    "id": "q1",
    "question": "Audio URL or text",
    "image_url": "Image URL (optional)",
    "options": [
        {"text": "Option A"} or {"audio": "Audio URL"}
    ]
}

User Answer Format:

choice = 0  // Index of selected option

Sequence Word (G110-G113)

Sanitized (Game receives):

{
    "id": "seq_word_1",
    "question": ["H", "", "L", "", "O"],
    "options": ["L", "E"],
    "audio_url": "URL (optional)"
}

User Answer Format:

choice = ["H", "e", "l", "l", "o"]  // Reordered array

Sequence Sentence (G120-G123)

Sanitized (Game receives):

{
    "id": "seq_sent_1",
    "question": ["I", "", "reading", ""],
    "options": ["love", "books"],
    "audio_url": "URL (optional)"
}

User Answer Format:

choice = ["I", "love", "reading", "books"]  // Reordered

💻 Complete Game Implementation Example

// ============ 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:

// 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'}]
        }
    ]
});