Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhanced Offline Support with Multi-Layer Caching in OWASP BLT #3933

Closed
wants to merge 1 commit into from

Conversation

cicada0007
Copy link
Contributor

@cicada0007 cicada0007 commented Mar 13, 2025

User description

πŸš€ Offline-First Solution for OWASP BLT 🌍✨

We successfully implemented a robust offline-first architecture for the OWASP BLT application, ensuring seamless functionality even in network disruptions. This includes:

πŸ”Ή Server-Side API Caching – Middleware (NetworkStatusMiddleware) to track network status, compatible with Django 5.1's async handling, and adds X-Network-Status headers for client awareness.

πŸ”Ή API Response Caching & Fallback – A cache_api_response decorator that stores responses in memory & filesystem, auto-detects network availability, and serves cached data when offline. Includes ApiCacheFallbackMiddleware for resilient API handling.

πŸ”Ή Image Caching System – Prevents broken images offline by storing them locally and replacing API response URLs with cached versions.

πŸ”Ή Client-Side Enhancements – IndexedDB-based caching, a Service Worker for offline asset management, and frontend modifications to handle offline scenarios gracefully.

πŸ”Ή Resilient Architecture – Background network checks, clear offline status indicators, and auto-sync when connectivity is restored.

πŸ’‘ Business Impact
Users can continue using the app seamlessly during network interruptions, ensuring a smooth experience with previously accessed content. This enhances reliability, especially for users with intermittent connectivity.

πŸ”— Check it out:

Screen.Recording.2025-03-13.145535.mp4

🎯 Closes #3357


PR Type

Enhancement, Bug fix, Tests


Description

  • Introduced multi-layer caching for APIs and images to enhance offline support.

  • Added middleware for network status tracking and fallback to cached data.

  • Implemented client-side caching utilities for APIs and images using IndexedDB.

  • Updated templates to dynamically load statistics and handle offline scenarios.


Changes walkthrough πŸ“

Relevant files
Enhancement
10 files
stats_dashboard.html
Added dynamic stats loading with offline fallback.Β  Β  Β  Β  Β  Β  Β 
+106/-101
header.html
Added scripts for API and image caching.Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β 
+31/-0Β  Β 
views.py
Integrated offline-first caching for API views.Β  Β  Β  Β  Β  Β  Β  Β  Β  Β 
+220/-76
cache_utils.py
Added utilities for API and image caching.Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β 
+253/-0Β 
cache.py
Introduced decorators for API caching and offline resilience.
+105/-0Β 
cache_middleware.py
Added middleware for API cache fallback.Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β 
+72/-0Β  Β 
middleware.py
Introduced middleware for network status tracking.Β  Β  Β  Β  Β  Β  Β 
+63/-0Β  Β 
image-cache.js
Implemented client-side image caching utility.Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β 
+268/-0Β 
network-status.js
Added network status detection and UI updates.Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β 
+243/-0Β 
api-cache.js
Developed API caching utility for offline support.Β  Β  Β  Β  Β  Β  Β 
+217/-0Β 
Miscellaneous
2 files
home.html
Removed local mode panel and debug features.Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β 
+3/-119Β 
__init__.py
Added package initialization for API functionality.Β  Β  Β  Β  Β  Β 
+1/-0Β  Β  Β 
Configuration changes
1 files
settings.py
Configured caching settings and middleware integration.Β  Β 
+26/-0Β  Β 
Additional files
1 files
app_logs.txt [link]Β  Β 

Need help?
  • Type /help how to ... in the comments thread for any questions about PR-Agent usage.
  • Check out the documentation for more information.
  • Copy link
    Contributor

    PR Reviewer Guide πŸ”

    Here are some key observations to aid the review process:

    🎫 Ticket compliance analysis πŸ”Ά

    3357 - Partially compliant

    Compliant requirements:

    • Cache API data and images to provide fallback content during network errors.
    • Ensure users see some data instead of a blank screen when offline.

    Non-compliant requirements:

    []

    Requires further human verification:

    []

    ⏱️ Estimated effort to review: 5 πŸ”΅πŸ”΅πŸ”΅πŸ”΅πŸ”΅
    πŸ§ͺΒ No relevant tests
    πŸ”’Β No security concerns identified
    ⚑ Recommended focus areas for review

    Possible Issue

    The fetchWithCache function in api-cache.js does not handle cases where the response is not JSON (e.g., HTML error pages). This could lead to runtime errors when parsing the response.

    const fetchWithCache = async (url, options = {}, expiration = DEFAULT_CACHE_EXPIRATION) => {
      const cacheKey = `${CACHE_PREFIX}${url}`;
      const cachedData = getFromCache(cacheKey);
    
      // Set default headers
      options.headers = options.headers || {};
    
      // If we only want cached data (offline mode)
      if (options.offlineOnly) {
        if (cachedData) {
          return Promise.resolve(cachedData.data);
        }
        return Promise.reject(new Error('No cached data available for offline use'));
      }
    
      // Try to fetch fresh data
      try {
        const response = await fetch(url, options);
    
        if (!response.ok) {
          throw new Error(`API error: ${response.status}`);
        }
    
        const data = await response.json();
    
        // Cache the successful response for future use
        setInCache(cacheKey, data, expiration);
    
        return data;
      } catch (error) {
        console.error('Network error:', error);
    
        // If we have cached data, return it
        if (cachedData) {
          // Add flag to indicate this is cached data
          return {
            ...cachedData.data,
            _fromCache: true,
            _cachedAt: cachedData.timestamp
          };
        }
    
        // No cached data, rethrow the error
        throw error;
    Performance Concern

    The cacheVisibleImages function in image-cache.js processes all visible images on the page, which could lead to performance issues on pages with many images or frequent DOM mutations.

    function cacheVisibleImages() {
        // Find all image elements
        const imgElements = document.querySelectorAll('img');
    
        // Find all elements with background images
        const elementsWithBackgroundImage = Array.from(document.querySelectorAll('*'))
            .filter(el => {
                const style = window.getComputedStyle(el);
                const backgroundImage = style.backgroundImage;
                return backgroundImage && backgroundImage !== 'none' && backgroundImage.includes('url(');
            });
    
        // Extract image URLs from img elements
        const imgUrls = Array.from(imgElements)
            .map(img => img.src)
            .filter(src => src); // Filter out empty URLs
    
        // Extract image URLs from background images
        const backgroundImgUrls = elementsWithBackgroundImage
            .map(el => {
                const style = window.getComputedStyle(el);
                const backgroundImage = style.backgroundImage;
                // Extract URL from the url('...') pattern
                const urlMatch = /url\(['"]?([^'"()]+)['"]?\)/g.exec(backgroundImage);
                return urlMatch ? urlMatch[1] : null;
            })
            .filter(src => src); // Filter out null values
    
        // Combine all URLs and remove duplicates
        const allImageUrls = [...new Set([...imgUrls, ...backgroundImgUrls])];
    
        // Cache each image
        allImageUrls.forEach(url => {
            cacheImage(url);
        });
    Scalability Issue

    The NetworkStatusMiddleware checks network status every 60 seconds for all requests, which might introduce unnecessary overhead in high-traffic environments.

    import time
    from django.utils.deprecation import MiddlewareMixin
    from website.cache_utils import check_network_status, is_network_available
    import asyncio
    
    class NetworkStatusMiddleware:
        """
        Middleware to track network status and inject appropriate headers.
    
        This middleware will:
        1. Periodically check network connectivity
        2. Add response headers to indicate network status
        3. Trigger offline mode behavior when network is unavailable
        """
    
        # Set to 'both' to indicate this middleware supports both sync and async requests
        async_mode = 'both'
    
        def __init__(self, get_response):
            self.get_response = get_response
            self.last_check_time = 0
            self.check_interval = 60  # Check network status every 60 seconds
    
        def __call__(self, request):
            """Handle synchronous requests"""
            # Check network status periodically
            self._check_network_if_needed()
    
            # Add a flag to the request indicating if we're offline
            request.is_offline = not is_network_available()
    
            # Get response from the next middleware/view
            response = self.get_response(request)
    
            # Add a header to indicate network status
            if hasattr(response, 'headers'):
                response.headers['X-Network-Status'] = 'offline' if not is_network_available() else 'online'
    
            return response
    
        async def __acall__(self, request):
            """Handle asynchronous requests"""
            # Check network status periodically
            self._check_network_if_needed()
    
            # Add a flag to the request indicating if we're offline
            request.is_offline = not is_network_available()
    
            # Get response from the next middleware/view
            response = await self.get_response(request)
    
            # Add a header to indicate network status
            if hasattr(response, 'headers'):
                response.headers['X-Network-Status'] = 'offline' if not is_network_available() else 'online'
    
            return response
    
        def _check_network_if_needed(self):
            """Check network status if enough time has passed since last check"""
            current_time = time.time()
            if current_time - self.last_check_time > self.check_interval:
                check_network_status()
                self.last_check_time = current_time 

    Copy link
    Contributor

    PR Code Suggestions ✨

    @DonnieBLT
    Copy link
    Collaborator

    This would be for the flutter app.

    @DonnieBLT DonnieBLT closed this Mar 13, 2025
    @cicada0007
    Copy link
    Contributor Author

    This would be for the flutter app.

    Ok sir

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

    Successfully merging this pull request may close these issues.

    Cache api data and images so that in case of network error we can show user some data instead of blank screen
    2 participants