Skip to content

Instantly share code, notes, and snippets.

@timmapuramreddy
Created September 22, 2025 03:34
Show Gist options
  • Select an option

  • Save timmapuramreddy/e4ef8f4c2e0407f6f35e49b06996f139 to your computer and use it in GitHub Desktop.

Select an option

Save timmapuramreddy/e4ef8f4c2e0407f6f35e49b06996f139 to your computer and use it in GitHub Desktop.
TiQer Async Response API - Complete Frontend Integration Guide

TiQer Async Response API - Frontend Integration Guide

Overview

The TiQer Async Response API provides ultra-fast response submission (target <15ms) by returning immediate acknowledgment and processing responses in the background. This guide provides step-by-step integration instructions for the Frontend team.

πŸš€ Quick Start

API Endpoint

POST /api/v2/tiqer/response-async/

Key Benefits

  • 95%+ performance improvement over synchronous processing
  • Immediate acknowledgment (~15ms response time)
  • Real-time notifications via Firebase RTDB when processing completes
  • Comprehensive error handling with detailed error codes
  • Automatic cleanup to prevent Firebase data accumulation

πŸ“‘ Step 1: Submit Response (Immediate Acknowledgment)

Request Format

const submitResponse = async (responseData) => {
  const response = await fetch('/api/v2/tiqer/response-async/', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Token ${userToken}`
    },
    body: JSON.stringify({
      option_id: 24072,
      card_number: 5,
      question_id: 40795,
      session_token: "677145c7-b80a-4ba8-b75b-24555300c07e",
      response_time_seconds: 10,  // Optional
      confidence_score: 1.0       // Optional
    })
  });

  return await response.json();
};

Success Response (202 Accepted)

{
  "status": "SUCCESS",
  "message": "SUBMITTED",
  "processing_mode": "async_background",
  "task_id": "12dd6374-e260-4a81-a2a4-93702ce00ddd",
  "response_data": {
    "session_token": "677145c7-b80a-4ba8-b75b-24555300c07e",
    "question_id": 40795,
    "card_number": 5,
    "option_id": 24072
  },
  "performance_metrics": {
    "response_time_ms": 12,
    "target_threshold_ms": 15,
    "performance_achieved": true
  },
  "async_processing": {
    "task_scheduled": true,
    "background_processing": true,
    "estimated_completion_seconds": 2,
    "firebase_notification_enabled": true
  },
  "firebase_subscription": {
    "path": "tiqer_sessions/677145c7-b80a-4ba8-b75b-24555300c07e/async_responses",
    "listen_for": "response_processed",
    "notification_method": "Firebase RTDB"
  },
  "timestamp": "2025-09-22T02:57:32.438869+00:00"
}

Error Responses

Missing Fields (400 Bad Request)

{
  "status": "FAILURE",
  "error_code": "VALIDATION_FAILED",
  "error_message": "Missing required fields: question_id, session_token",
  "missing_fields": ["question_id", "session_token"]
}

Invalid Session (404 Not Found)

{
  "status": "FAILURE",
  "error_code": "SESSION_NOT_FOUND",
  "error_message": "Session invalid-session-token not found or not owned by current teacher",
  "session_token": "invalid-session-token",
  "teacher_id": 501
}

Duplicate Response (200 OK)

{
  "status": "SUCCESS",
  "message": "SUBMITTED (duplicate ignored)",
  "duplicate_detected": true,
  "processing_mode": "immediate_response",
  "response_time_ms": 8
}

πŸ”₯ Step 2: Setup Firebase Real-time Listener

Initialize Firebase Listener

// Initialize Firebase listener for async response notifications
const setupAsyncResponseListener = (sessionToken) => {
  const asyncResponsesRef = firebase.database()
    .ref(`tiqer_sessions/${sessionToken}/async_responses`);

  // Listen for new async response notifications
  asyncResponsesRef.on('child_added', (snapshot) => {
    const notification = snapshot.val();
    const notificationId = snapshot.key;
    
    console.log('Async notification received:', notification);
    
    // Handle based on notification type
    if (notification.event === 'response_processed') {
      handleSuccessfulResponse(notification, notificationId, snapshot.ref);
    } else if (notification.event === 'response_error') {
      handleErrorResponse(notification, notificationId, snapshot.ref);
    }
  });

  // Handle listener errors
  asyncResponsesRef.on('error', (error) => {
    console.error('Firebase listener error:', error);
    // Implement fallback polling or retry logic
  });

  return asyncResponsesRef;
};

Success Notification Format

{
  "event": "response_processed",
  "processing_id": "async_resp_1758510124",
  "status": "completed",
  "student_info": {
    "student_id": 123,
    "student_name": "John Doe",
    "card_number": 5,
    "roll_number": "001"
  },
  "response_info": {
    "question_id": 40795,
    "selected_option": 2,
    "is_correct": true,
    "obtained_score": 1.0,
    "response_time_seconds": 10
  },
  "action": "created",  // or "updated"
  "timestamp": "2025-09-22T02:57:35.123456+00:00",
  "task_id": "12dd6374-e260-4a81-a2a4-93702ce00ddd"
}

Error Notification Format

{
  "event": "response_error",
  "processing_id": "async_resp_1758509892",
  "status": "failed",
  "error_info": {
    "error_type": "ValueError",
    "error_message": "Option 99999 does not exist for question 40795. Valid options: [24072, 24074, 24075, 24073]",
    "error_code": "INVALID_OPTION",
    "retries_attempted": 3,
    "full_error": "INVALID_OPTION: Option 99999 does not exist for question 40795. Valid options: [24072, 24074, 24075, 24073]"
  },
  "request_info": {
    "option_id": 99999,
    "card_number": 2,
    "question_id": 40795,
    "session_token": "677145c7-b80a-4ba8-b75b-24555300c07e"
  },
  "task_id": "119fb3e1-534e-4ca1-9474-fc4032a36712",
  "timestamp": "2025-09-22T02:58:13.089306+00:00",
  "execution_time_seconds": 0.100886
}

πŸ“ Step 3: Handle Response Notifications

Handle Successful Response

const handleSuccessfulResponse = (notification, notificationId, firebaseRef) => {
  const { student_info, response_info, action, task_id } = notification;
  
  // Update UI with student response
  updateStudentResponseInUI({
    studentId: student_info.student_id,
    studentName: student_info.student_name,
    cardNumber: student_info.card_number,
    rollNumber: student_info.roll_number,
    isCorrect: response_info.is_correct,
    obtainedScore: response_info.obtained_score,
    responseTime: response_info.response_time_seconds,
    action: action  // 'created' or 'updated'
  });

  // Update live analytics/progress
  updateLiveAnalytics();
  
  // Clean up Firebase notification (recommended)
  cleanupFirebaseNotification(firebaseRef, task_id);
  
  // Show success feedback (optional)
  showSuccessToast(`Response recorded for ${student_info.student_name}`);
};

const updateStudentResponseInUI = (responseData) => {
  // Example UI update logic
  const studentElement = document.querySelector(`[data-student-id="${responseData.studentId}"]`);
  
  if (studentElement) {
    // Update visual indicators
    studentElement.classList.add(responseData.isCorrect ? 'correct' : 'incorrect');
    studentElement.querySelector('.score').textContent = responseData.obtainedScore;
    studentElement.querySelector('.response-time').textContent = `${responseData.responseTime}s`;
    
    // Update response count
    updateResponseCount();
    
    // Update progress bars
    updateProgressBars();
  }
};

Handle Error Response

const handleErrorResponse = (notification, notificationId, firebaseRef) => {
  const { error_info, request_info, task_id } = notification;
  
  console.error('Async response processing failed:', error_info);
  
  // Handle specific error types
  switch (error_info.error_code) {
    case 'INVALID_OPTION':
      showErrorToast(`Invalid option selected. ${error_info.error_message}`);
      // Optionally highlight the problematic option
      highlightInvalidOption(request_info.option_id);
      break;
      
    case 'INVALID_CARD':
      showErrorToast(`Invalid card ${request_info.card_number}. ${error_info.error_message}`);
      // Show available cards to user
      showAvailableCards();
      break;
      
    case 'SESSION_NOT_ACTIVE':
      showErrorToast('Session is no longer active. Please refresh the page.');
      // Optionally redirect or refresh
      break;
      
    case 'QUESTION_MISMATCH':
      showErrorToast('Question synchronization error. Please refresh the page.');
      // Force refresh current question
      refreshCurrentQuestion();
      break;
      
    default:
      showErrorToast(`Response processing failed: ${error_info.error_message}`);
  }
  
  // Log error for debugging
  logErrorToConsole(notification);
  
  // Clean up Firebase notification
  cleanupFirebaseNotification(firebaseRef, task_id);
  
  // Optionally retry submission with corrected data
  if (shouldRetrySubmission(error_info.error_code)) {
    promptForRetry(request_info);
  }
};

const showErrorToast = (message) => {
  // Your toast/notification implementation
  console.error(message);
  // Example: toast.error(message);
};

🧹 Step 4: Firebase Cleanup (Critical for Performance)

Frontend-Driven Cleanup (Recommended)

// Clean up Firebase notification after processing
const cleanupFirebaseNotification = (firebaseRef, taskId) => {
  try {
    // Remove the specific notification
    firebaseRef.remove().then(() => {
      console.log(`Cleaned up Firebase notification for task ${taskId}`);
    }).catch((error) => {
      console.warn('Failed to cleanup Firebase notification:', error);
    });
  } catch (error) {
    console.warn('Firebase cleanup error:', error);
  }
};

// Alternative: Cleanup via API (for bulk cleanup)
const cleanupViaAPI = async (sessionToken, mode = 'partial') => {
  try {
    const response = await fetch('/api/v2/tiqer/firebase-cleanup/', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Token ${userToken}`
      },
      body: JSON.stringify({
        session_token: sessionToken,
        cleanup_mode: mode,  // 'partial' or 'complete'
        immediate: true
      })
    });

    const result = await response.json();
    console.log('API cleanup result:', result);
    return result.cleanup_success;
  } catch (error) {
    console.error('API cleanup failed:', error);
    return false;
  }
};

Session End Cleanup

// Clean up all Firebase data when session ends
const cleanupSessionEnd = async (sessionToken) => {
  // Remove Firebase listener
  if (asyncResponsesListener) {
    asyncResponsesListener.off();
  }
  
  // Complete Firebase cleanup
  await cleanupViaAPI(sessionToken, 'complete');
  
  console.log('Session cleanup completed');
};

πŸ”„ Step 5: Complete Integration Example

React Component Example

import React, { useEffect, useState, useCallback } from 'react';
import { initializeApp } from 'firebase/app';
import { getDatabase, ref, onChildAdded, off } from 'firebase/database';

const TiQerAsyncQuiz = ({ sessionToken, userToken }) => {
  const [responses, setResponses] = useState({});
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [firebaseListener, setFirebaseListener] = useState(null);

  // Initialize Firebase listener
  useEffect(() => {
    const database = getDatabase();
    const asyncResponsesRef = ref(database, `tiqer_sessions/${sessionToken}/async_responses`);
    
    const unsubscribe = onChildAdded(asyncResponsesRef, (snapshot) => {
      const notification = snapshot.val();
      const notificationId = snapshot.key;
      
      if (notification.event === 'response_processed') {
        handleSuccessfulResponse(notification, snapshot.ref);
      } else if (notification.event === 'response_error') {
        handleErrorResponse(notification, snapshot.ref);
      }
    });

    setFirebaseListener({ ref: asyncResponsesRef, unsubscribe });

    return () => {
      if (unsubscribe) unsubscribe();
    };
  }, [sessionToken]);

  // Submit response with async processing
  const submitResponse = useCallback(async (responseData) => {
    setIsSubmitting(true);
    
    try {
      const response = await fetch('/api/v2/tiqer/response-async/', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Token ${userToken}`
        },
        body: JSON.stringify({
          ...responseData,
          session_token: sessionToken
        })
      });

      const result = await response.json();
      
      if (result.status === 'SUCCESS') {
        console.log('Response submitted successfully:', result.task_id);
        // Show immediate feedback
        showSubmissionFeedback(responseData.card_number);
      } else {
        handleSubmissionError(result);
      }
    } catch (error) {
      console.error('Submission failed:', error);
      showErrorToast('Network error. Please try again.');
    } finally {
      setIsSubmitting(false);
    }
  }, [sessionToken, userToken]);

  const handleSuccessfulResponse = (notification, firebaseRef) => {
    const { student_info, response_info } = notification;
    
    // Update local state
    setResponses(prev => ({
      ...prev,
      [student_info.student_id]: {
        studentName: student_info.student_name,
        cardNumber: student_info.card_number,
        isCorrect: response_info.is_correct,
        score: response_info.obtained_score,
        responseTime: response_info.response_time_seconds,
        timestamp: notification.timestamp
      }
    }));

    // Cleanup Firebase
    firebaseRef.remove().catch(console.warn);
  };

  const handleErrorResponse = (notification, firebaseRef) => {
    const { error_info } = notification;
    console.error('Response processing failed:', error_info);
    
    // Show user-friendly error
    showErrorToast(error_info.error_message);
    
    // Cleanup Firebase
    firebaseRef.remove().catch(console.warn);
  };

  // Cleanup on unmount
  useEffect(() => {
    return () => {
      if (firebaseListener) {
        firebaseListener.unsubscribe();
      }
      // Cleanup Firebase data when component unmounts
      cleanupViaAPI(sessionToken, 'complete');
    };
  }, []);

  return (
    <div className="tiqer-async-quiz">
      {/* Your quiz UI components */}
      <button 
        onClick={() => submitResponse({ option_id: 123, card_number: 5, question_id: 456 })}
        disabled={isSubmitting}
      >
        {isSubmitting ? 'Submitting...' : 'Submit Response'}
      </button>
      
      {/* Display responses */}
      <div className="responses">
        {Object.entries(responses).map(([studentId, response]) => (
          <div key={studentId} className={`response ${response.isCorrect ? 'correct' : 'incorrect'}`}>
            <span>{response.studentName}</span>
            <span>Card: {response.cardNumber}</span>
            <span>Score: {response.score}</span>
            <span>Time: {response.responseTime}s</span>
          </div>
        ))}
      </div>
    </div>
  );
};

πŸ›‘οΈ Error Handling Best Practices

1. Network Error Handling

const submitWithRetry = async (responseData, maxRetries = 3) => {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const result = await submitResponse(responseData);
      return result;
    } catch (error) {
      if (attempt === maxRetries) {
        throw new Error(`Failed after ${maxRetries} attempts: ${error.message}`);
      }
      
      // Exponential backoff
      await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
    }
  }
};

2. Firebase Connection Handling

const setupRobustFirebaseListener = (sessionToken) => {
  const database = getDatabase();
  const asyncResponsesRef = ref(database, `tiqer_sessions/${sessionToken}/async_responses`);
  
  let reconnectAttempts = 0;
  const maxReconnectAttempts = 5;
  
  const setupListener = () => {
    const unsubscribe = onChildAdded(asyncResponsesRef, 
      (snapshot) => {
        // Handle notification
        reconnectAttempts = 0; // Reset on successful message
      },
      (error) => {
        console.error('Firebase listener error:', error);
        
        if (reconnectAttempts < maxReconnectAttempts) {
          reconnectAttempts++;
          setTimeout(() => {
            console.log(`Reconnecting Firebase listener (attempt ${reconnectAttempts})`);
            setupListener();
          }, reconnectAttempts * 2000);
        } else {
          console.error('Max reconnection attempts reached. Consider fallback polling.');
          // Implement fallback polling mechanism
          startFallbackPolling(sessionToken);
        }
      }
    );
    
    return unsubscribe;
  };
  
  return setupListener();
};

3. Fallback Polling (If Firebase Fails)

const startFallbackPolling = (sessionToken) => {
  console.warn('Starting fallback polling for async responses');
  
  const pollInterval = setInterval(async () => {
    try {
      // Poll a custom endpoint that checks for completed async tasks
      const response = await fetch(`/api/v2/tiqer/poll-async-status/?session_token=${sessionToken}`, {
        headers: { 'Authorization': `Token ${userToken}` }
      });
      
      const data = await response.json();
      
      if (data.completed_responses?.length > 0) {
        data.completed_responses.forEach(handleCompletedResponse);
      }
    } catch (error) {
      console.error('Polling error:', error);
    }
  }, 2000); // Poll every 2 seconds
  
  // Store interval ID for cleanup
  return pollInterval;
};

πŸ“Š Performance Monitoring

Track API Response Times

const monitorAsyncPerformance = () => {
  const performanceMetrics = {
    responseTimes: [],
    errorRates: { total: 0, errors: 0 },
    firebaseLatency: []
  };

  const trackSubmission = async (responseData) => {
    const startTime = performance.now();
    
    try {
      const result = await submitResponse(responseData);
      const responseTime = performance.now() - startTime;
      
      performanceMetrics.responseTimes.push(responseTime);
      performanceMetrics.errorRates.total++;
      
      if (result.status !== 'SUCCESS') {
        performanceMetrics.errorRates.errors++;
      }
      
      // Log if response time exceeds target
      if (responseTime > 50) { // 50ms threshold including network
        console.warn(`Slow response: ${responseTime}ms`);
      }
      
      return result;
    } catch (error) {
      performanceMetrics.errorRates.total++;
      performanceMetrics.errorRates.errors++;
      throw error;
    }
  };

  const getPerformanceReport = () => {
    const avgResponseTime = performanceMetrics.responseTimes.reduce((a, b) => a + b, 0) / 
                            performanceMetrics.responseTimes.length;
    const errorRate = (performanceMetrics.errorRates.errors / performanceMetrics.errorRates.total) * 100;
    
    return {
      averageResponseTime: avgResponseTime,
      errorRate: errorRate,
      totalRequests: performanceMetrics.errorRates.total,
      medianResponseTime: calculateMedian(performanceMetrics.responseTimes)
    };
  };

  return { trackSubmission, getPerformanceReport };
};

🚨 Common Error Codes Reference

Error Code Description Frontend Action
VALIDATION_FAILED Missing required fields Show field validation errors
INVALID_DATA_TYPE Wrong data types Validate input types
SESSION_NOT_FOUND Invalid/expired session Redirect to session list
INVALID_OPTION Option doesn't exist for question Highlight invalid option, show valid options
INVALID_CARD Card not found in classroom Show available cards list
CARD_INVALID_STATUS Card not available for use Show card status, suggest alternatives
CARD_NO_STUDENT Card not assigned to student Contact teacher for card assignment
SESSION_NOT_ACTIVE Session paused/inactive Show session status, wait for activation
QUESTION_MISMATCH Question sync issue Refresh current question
INVALID_RESPONSE_TIME Response time out of bounds Validate timing constraints
PROCESSING_ERROR Generic processing failure Show retry option, contact support

βœ… Integration Checklist

Pre-Implementation

  • Firebase SDK integrated in frontend
  • Authentication token management in place
  • Error handling framework ready
  • UI components for real-time updates prepared

Implementation

  • Async response API integration complete
  • Firebase listener setup implemented
  • Success response handler implemented
  • Error response handler implemented
  • Firebase cleanup mechanism implemented
  • Performance monitoring added

Testing

  • Test with valid responses (should complete in ~2-3 seconds)
  • Test with invalid option_id (should receive error notification)
  • Test with invalid card_number (should receive error notification)
  • Test network disconnection scenarios
  • Test Firebase connection failures
  • Test cleanup functionality
  • Verify no Firebase data accumulation

Production Readiness

  • Error logging implemented
  • Performance metrics collection added
  • Fallback mechanisms tested
  • User feedback mechanisms in place
  • Documentation for debugging available

πŸ”§ Debugging Tips

1. Enable Debug Logging

// Add to your application initialization
window.TIQER_DEBUG = true;

const debugLog = (message, data) => {
  if (window.TIQER_DEBUG) {
    console.log(`[TiQer Debug] ${message}`, data);
  }
};

2. Monitor Firebase Data

// Add temporary Firebase data monitoring
const monitorFirebaseData = (sessionToken) => {
  const database = getDatabase();
  const sessionRef = ref(database, `tiqer_sessions/${sessionToken}`);
  
  // Log all changes to session data
  onValue(sessionRef, (snapshot) => {
    console.log('Firebase session data:', snapshot.val());
  });
};

3. API Response Debugging

const debugApiResponse = (response, requestData) => {
  console.group('TiQer API Debug');
  console.log('Request:', requestData);
  console.log('Response:', response);
  console.log('Task ID:', response.task_id);
  console.log('Performance:', response.performance_metrics);
  console.groupEnd();
};

πŸ“ž Support & Contact

For technical questions or issues with the async response integration:

  1. API Issues: Check server logs for task processing errors
  2. Firebase Issues: Verify Firebase configuration and connection
  3. Performance Issues: Monitor response times and implement fallbacks
  4. Integration Issues: Use debug logging and check network requests

Backend Team Contact: Available for debugging complex validation errors and performance optimization.


πŸ”„ Version History

  • v1.0 (Current): Initial async response implementation with comprehensive validation and Firebase cleanup
  • Future: Planned enhancements include response batching and advanced analytics integration

This documentation is maintained by the Backend Team. Last updated: September 2025

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment