Skip to content

ऑफलाइन web games के लिए PWA

Progressive Web Apps की मदद से players आपके game को install करके ऑफलाइन खेल सकते हैं. यह tutorial बताता है कि एक web game में PWA support कैसे जोड़ें.

1) Web App Manifest

manifest.json बनाएं:

json
{
  "name": "My Awesome Game",
  "short_name": "AwesomeGame",
  "description": "An awesome game you can play offline",
  "start_url": "/",
  "display": "fullscreen",
  "orientation": "landscape",
  "background_color": "#1a1a2e",
  "theme_color": "#4ade80",
  "icons": [
    {
      "src": "/icons/icon-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-512.png",
      "sizes": "512x512",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-maskable.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "maskable"
    }
  ]
}

इसे अपने HTML में link करें:

html
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#4ade80">
<link rel="apple-touch-icon" href="/icons/icon-192.png">

2) बेसिक service worker

sw.js बनाएं:

js
const CACHE_NAME = 'game-v1'

const ASSETS = [
  '/',
  '/index.html',
  '/game.js',
  '/style.css',
  '/assets/sprites.png',
  '/assets/sounds/jump.mp3',
  '/assets/sounds/music.mp3',
]

// Install: cache assets
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      return cache.addAll(ASSETS)
    })
  )
})

// Activate: clean old caches
self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then((keys) => {
      return Promise.all(
        keys.filter(key => key !== CACHE_NAME)
            .map(key => caches.delete(key))
      )
    })
  )
})

// Fetch: serve from cache, fall back to network
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((cached) => {
      return cached || fetch(event.request)
    })
  )
})

3) service worker register करें

js
if ('serviceWorker' in navigator) {
  window.addEventListener('load', async () => {
    try {
      const registration = await navigator.serviceWorker.register('/sw.js')
      console.log('SW registered:', registration.scope)
    } catch (err) {
      console.error('SW registration failed:', err)
    }
  })
}

4) network update के साथ cache-first

games के लिए बेहतर है, background updates के साथ तेज़ loading:

js
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.open(CACHE_NAME).then(async (cache) => {
      const cached = await cache.match(event.request)
      
      // Start network fetch in background
      const fetchPromise = fetch(event.request).then((response) => {
        if (response.ok) {
          cache.put(event.request, response.clone())
        }
        return response
      }).catch(() => null)
      
      // Return cached immediately, or wait for network
      return cached || fetchPromise
    })
  )
})

5) updates के लिए versioned cache

js
const CACHE_VERSION = 'v2'
const STATIC_CACHE = `static-${CACHE_VERSION}`
const DYNAMIC_CACHE = `dynamic-${CACHE_VERSION}`

const STATIC_ASSETS = [
  '/',
  '/index.html',
  '/game.js',
  // ... core assets that rarely change
]

self.addEventListener('install', (event) => {
  self.skipWaiting() // Activate immediately
  
  event.waitUntil(
    caches.open(STATIC_CACHE).then(cache => cache.addAll(STATIC_ASSETS))
  )
})

self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then(keys => {
      return Promise.all(
        keys.filter(key => !key.includes(CACHE_VERSION))
            .map(key => caches.delete(key))
      )
    })
  )
  
  clients.claim() // Take control immediately
})

6) game updates संभालना

जब कोई update उपलब्ध हो तो players को सूचित करें:

js
// In main app
let refreshing = false

navigator.serviceWorker.addEventListener('controllerchange', () => {
  if (!refreshing) {
    refreshing = true
    showUpdateNotification()
  }
})

function showUpdateNotification() {
  const banner = document.createElement('div')
  banner.innerHTML = `
    <p>Game updated! Refresh to get the latest version.</p>
    <button onclick="location.reload()">Refresh</button>
  `
  banner.className = 'update-banner'
  document.body.appendChild(banner)
}

7) ऑफलाइन detection

js
function updateOnlineStatus() {
  if (navigator.onLine) {
    hideOfflineBanner()
    syncGameData()
  } else {
    showOfflineBanner()
  }
}

window.addEventListener('online', updateOnlineStatus)
window.addEventListener('offline', updateOnlineStatus)

function showOfflineBanner() {
  document.getElementById('offline-banner').style.display = 'block'
}

function hideOfflineBanner() {
  document.getElementById('offline-banner').style.display = 'none'
}

8) Install prompt

js
let deferredPrompt = null

window.addEventListener('beforeinstallprompt', (e) => {
  e.preventDefault()
  deferredPrompt = e
  showInstallButton()
})

function showInstallButton() {
  const btn = document.getElementById('install-btn')
  btn.style.display = 'block'
  btn.addEventListener('click', installApp)
}

async function installApp() {
  if (!deferredPrompt) return
  
  deferredPrompt.prompt()
  const { outcome } = await deferredPrompt.userChoice
  
  console.log('Install prompt outcome:', outcome)
  deferredPrompt = null
  document.getElementById('install-btn').style.display = 'none'
}

window.addEventListener('appinstalled', () => {
  console.log('App installed!')
  deferredPrompt = null
})

iOS और iPadOS पर beforeinstallprompt event कभी fire नहीं होता, इसलिए ऊपर वाला install button सिर्फ Chromium browsers में दिखता है. iPhone और iPad users Share पर tap करके फिर "Add to Home Screen" चुनकर install करते हैं. 2026 में एक चीज़ बदली है जो जानने लायक है: Safari 26 (iOS 26 / iPadOS 26) से, Home Screen में जोड़ी गई हर site डिफ़ॉल्ट रूप से एक web app की तरह खुलती है, और "Open as Web App" toggle डिफ़ॉल्ट रूप से on रहता है. अब iOS पर installability की कोई शर्त नहीं रही, फिर भी एक manifest और service worker ship करने से आपको कहीं बेहतर ऑफलाइन अनुभव मिलता है.

9) leaderboards के लिए background sync

support के बारे में ध्यान दें: Background Sync API सिर्फ Chromium browsers (Chrome, Edge, Opera, Samsung Internet) में लागू है. Firefox और Safari (iOS समेत) इसे support नहीं करते, इसलिए हमेशा feature-detect करें और एक fallback रखें जो अगली सामान्य app load पर scores submit करे.

js
// In service worker
self.addEventListener('sync', (event) => {
  if (event.tag === 'sync-scores') {
    event.waitUntil(syncScores())
  }
})

async function syncScores() {
  const db = await openDB('game', 1)
  const pendingScores = await db.getAll('pending-scores')
  
  for (const score of pendingScores) {
    try {
      await fetch('/api/scores', {
        method: 'POST',
        body: JSON.stringify(score),
        headers: { 'Content-Type': 'application/json' }
      })
      await db.delete('pending-scores', score.id)
    } catch (err) {
      // Will retry on next sync
      break
    }
  }
}

// In main app
async function submitScore(score) {
  try {
    await fetch('/api/scores', { method: 'POST', body: JSON.stringify(score) })
  } catch {
    // Save for later sync
    const db = await openDB('game', 1)
    await db.add('pending-scores', { ...score, id: Date.now() })
    
    if ('serviceWorker' in navigator && 'sync' in window.registration) {
      await navigator.serviceWorker.ready
      await registration.sync.register('sync-scores')
    }
  }
}

10) PWA testing

Chrome DevTools:

  • Application > Service Workers
  • Application > Manifest
  • Application > Cache Storage
  • Network > Offline checkbox

Lighthouse audit:

  • PWA audit चलाएं
  • installability जांचें
  • ऑफलाइन capability जांचें

असली device पर testing:

  • फ़ोन की home screen पर install करें
  • airplane mode चालू करें
  • सभी features ऑफलाइन test करें

संबंधित

बाहरी संसाधन