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