### PWA Installation Detection and Prompting using JavaScript Source: https://context7.com/weroperking/pwa_docs/llms.txt This snippet demonstrates how to detect if a Progressive Web App (PWA) is installable, store the beforeinstallprompt event, show a custom install button, and trigger the installation prompt when clicked. It also covers detecting when the PWA is installed and checking if the PWA is already running in standalone mode. Requires a button with the ID 'install-button'. ```javascript let deferredPrompt; let installButton = document.getElementById('install-button'); window.addEventListener('beforeinstallprompt', (event) => { event.preventDefault(); deferredPrompt = event; installButton.style.display = 'block'; console.log("PWA is installable"); }); installButton.addEventListener('click', async () => { if (!deferredPrompt) { return; } deferredPrompt.prompt(); const { outcome } = await deferredPrompt.userChoice; console.log(`User response: ${outcome}`); if (outcome === 'accepted') { console.log("User accepted the install prompt"); } else { console.log("User dismissed the install prompt"); } deferredPrompt = null; installButton.style.display = 'none'; }); window.addEventListener('appinstalled', (event) => { console.log("PWA was installed"); deferredPrompt = null; installButton.style.display = 'none'; gtag('event', 'pwa_installed'); }); function isPWAInstalled() { const isStandalone = window.matchMedia('(display-mode: standalone)').matches; const isIOSStandalone = window.navigator.standalone === true; return isStandalone || isIOSStandalone; } if (isPWAInstalled()) { console.log("Running as installed PWA"); installButton.style.display = 'none'; } ``` -------------------------------- ### Triggering the Install Prompt using `deferredPrompt.prompt()` in JavaScript Source: https://github.com/weroperking/pwa_docs/blob/main/Installation prompt.md This code shows how to programmatically trigger the browser's PWA installation prompt after the `beforeinstallprompt` event has been captured and saved. It requires the `deferredPrompt` variable to be set by the `beforeinstallprompt` event listener. The `prompt()` method can only be called once per saved event. ```javascript if (deferredPrompt) { deferredPrompt.prompt(); deferredPrompt.userChoice.then((choiceResult) => { if (choiceResult.outcome === 'accepted') { console.log('User accepted the A2HS prompt'); } else { console.log('User dismissed the A2HS prompt'); } deferredPrompt = null; }); } ``` -------------------------------- ### PWA Asset Generator Example Source: https://github.com/weroperking/pwa_docs/blob/main/Enhancements.md An example illustrating how a build system tool like PWA Asset Generator can automate the creation of static splash screen images and corresponding HTML link elements for various PWA configurations. ```bash # Example usage for PWA Asset Generator (command line) # This would typically be part of a build script pwa-asset-generator --manifest "path/to/manifest.json" --output "path/to/icons" ``` -------------------------------- ### Listen for and Handle `beforeinstallprompt` Event in JavaScript Source: https://github.com/weroperking/pwa_docs/blob/main/Installation prompt.md This snippet demonstrates how to listen for the `beforeinstallprompt` event, which fires when a PWA meets installation criteria. It prevents the default prompt and saves the event for later use, allowing for a custom in-app installation experience. Dependencies include a browser supporting the `beforeinstallprompt` event. ```javascript let deferredPrompt; window.addEventListener('beforeinstallprompt', (e) => { // Prevents the default mini-infobar or install dialog from appearing on mobile e.preventDefault(); // Save the event because you'll need to trigger it later. deferredPrompt = e; // Show your customized install prompt for your PWA showInAppInstallPromotion(); }); ``` -------------------------------- ### Capture User Choice for PWA Installation Prompt (JavaScript) Source: https://github.com/weroperking/pwa_docs/blob/main/Installation prompt.md This snippet demonstrates how to capture the user's choice (accepted or dismissed) when they interact with a custom PWA install prompt. It utilizes the `userChoice` promise from the `beforeinstallprompt` event. Ensure the `deferredPrompt` variable is accessible and correctly captures the `beforeinstallprompt` event. ```javascript installButton.addEventListener('click', async () => { // deferredPrompt is a global variable we've been using in the sample to capture the `beforeinstallevent` deferredPrompt.prompt(); // Find out whether the user confirmed the installation or not const { outcome } = await deferredPrompt.userChoice; // The deferredPrompt can only be used once. deferredPrompt = null; // Act on the user's choice if (outcome === 'accepted') { console.log('User accepted the install prompt.'); } else if (outcome === 'dismissed') { console.log('User dismissed the install prompt'); } }); ``` -------------------------------- ### PWA Manifest for File Handling Source: https://github.com/weroperking/pwa_docs/blob/main/OS Integration.md Shows an example of how to configure the web app manifest to register a PWA as a file handler for specific file types. This enables users to open supported files directly within the installed PWA. ```json ... "file_handlers": [ { "action": "/open-file", "accept": { "text/*": [".txt"] } } ] ... ``` -------------------------------- ### Conditional Display of Install Instructions (CSS) Source: https://github.com/weroperking/pwa_docs/blob/main/Installation prompt.md This CSS code snippet shows how to hide an element with the ID `installInstructions` by default and only display it when the browser is in 'browser' display mode. This is useful for showing manual installation instructions only when the PWA is not already installed or running in a standalone mode. ```css #installInstructions { display: none } @media (display-mode: browser) { #installInstructions { display: block } } ``` -------------------------------- ### Offline Fallback Implementation with Workbox Source: https://github.com/weroperking/pwa_docs/blob/main/Workbox.md This example shows how to implement an offline fallback mechanism using Workbox's `setCatchHandler` and the Cache Storage API. During service worker installation, it pre-caches an offline fallback HTML file. If a route fails, it attempts to serve the cached fallback for document requests, otherwise returning an error response. This pattern can be extended for various resource types. ```javascript import { setCatchHandler } from 'workbox-routing'; // Warm the cache when the service worker installs self.addEventListener('install', event => { const files = ['/offline.html']; // you can add more resources here event.waitUntil( self.caches.open('offline-fallbacks') .then(cache => cache.addAll(files)) ); }); // Respond with the fallback if a route throws an error setCatchHandler(async (options) => { const destination = options.request.destination; const cache = await self.caches.open('offline-fallbacks'); if (destination === 'document') { return (await cache.match('/offline.html')) || Response.error(); } return Response.error(); }); ``` -------------------------------- ### JavaScript: Example Usage for Enabling PWA Notifications Source: https://context7.com/weroperking/pwa_docs/llms.txt An example event listener for a button click that initiates the process of subscribing to push notifications. It calls the `subscribeToPush` function and logs the result. Dependencies: `subscribeToPush` function, DOM manipulation. ```javascript document.getElementById('enable-notifications').addEventListener('click', async () => { const subscription = await subscribeToPush(); if (subscription) { console.log("Successfully subscribed to push notifications"); } }); ``` -------------------------------- ### CSS Media Queries for PWA Display Modes Source: https://github.com/weroperking/pwa_docs/blob/main/App design.md Applies different styles based on how the PWA is launched (browser, standalone, minimal-ui, fullscreen). Useful for showing installation prompts or context-specific UI elements. Dependencies: CSS. ```css @media (display-mode: browser) { /* Styles for browser mode */ } @media (display-mode: standalone) { /* Styles for standalone mode */ } @media (display-mode: standalone), (display-mode: fullscreen), (display-mode: minimal-ui) { /* Styles for multiple modes */ } ``` -------------------------------- ### Force Service Worker Activation and Claim Clients Source: https://github.com/weroperking/pwa_docs/blob/main/Update.md Demonstrates how to force a newly installed service worker to activate immediately, bypassing the default waiting period. It also shows how to claim control of all open client pages (tabs/windows) once the service worker becomes active. ```javascript self.addEventListener("install", event => { // forces a service worker to activate immediately self.skipWaiting(); }); self.addEventListener("activate", event => { // when this SW becomes activated, we claim all the opened clients // they can be standalone PWA windows or browser tabs event.waitUntil(clients.claim()); }); ``` -------------------------------- ### Handle PWA App Installation Event with JavaScript Source: https://github.com/weroperking/pwa_docs/blob/main/Detection.md This event listener is triggered on Chromium-based browsers when a user accepts the PWA installation prompt. It's useful for hiding in-app installation promotions and logging the installation success to analytics. Note that this event is part of an incubator for the manifest spec and is only available in Chromium-based browsers. ```javascript window.addEventListener('appinstalled', () => { // If visible, hide the install promotion hideInAppInstallPromotion(); // Log install to analytics console.log('INSTALL: Success'); }); ``` -------------------------------- ### Cache assets on service worker install using async/await (JavaScript) Source: https://github.com/weroperking/pwa_docs/blob/main/ Caching.md Caches application assets using the `async/await` syntax within the service worker's `install` event. This modern approach simplifies promise handling by making asynchronous operations more readable. It ensures assets are cached before the service worker stops, guaranteeing app consistency. ```javascript const urlsToCache = ["/", "app.js", "styles.css", "logo.svg"]; self.addEventListener("install", (event) => { let cacheUrls = async () => { const cache = await caches.open("pwa-assets"); return cache.addAll(urlsToCache); }; event.waitUntil(cacheUrls()); }); ``` -------------------------------- ### Implement Service Worker Lifecycle (JavaScript) Source: https://context7.com/weroperking/pwa_docs/llms.txt Handles the installation and activation of a service worker, caching essential application assets and cleaning up old caches. This implementation uses `self.skipWaiting()` and `self.clients.claim()` for immediate control. ```javascript // serviceworker.js const CACHE_NAME = "pwa-assets-v1"; const urlsToCache = [ "/", "/index.html", "/app.js", "/styles.css", "/logo.svg", "/offline.html" ]; // Install event - cache essential resources self.addEventListener("install", event => { console.log("Service worker installing..."); event.waitUntil( caches.open(CACHE_NAME) .then(cache => { console.log("Opened cache"); return cache.addAll(urlsToCache); }) .then(() => { console.log("All resources cached"); return self.skipWaiting(); // Activate immediately }) .catch(error => { console.error("Cache failed:", error); }) ); }); // Activate event - clean up old caches self.addEventListener("activate", event => { console.log("Service worker activating..."); event.waitUntil( caches.keys().then(cacheNames => { return Promise.all( cacheNames.map(cacheName => { if (cacheName !== CACHE_NAME) { console.log("Deleting old cache:", cacheName); return caches.delete(cacheName); } }) ); }).then(() => { return self.clients.claim(); // Take control immediately }) ); }); ``` -------------------------------- ### Configure PWA Web App Manifest (JSON) Source: https://context7.com/weroperking/pwa_docs/llms.txt Defines the appearance and behavior of a PWA when installed on a user's device. This JSON configuration specifies app name, icons, display mode, and other essential metadata. ```json { "name": "My First Application", "short_name": "MyApp", "start_url": "/", "display": "standalone", "theme_color": "#FF5500", "background_color": "#ffffff", "id": "/", "icons": [ { "src": "icons/192.png", "type": "image/png", "sizes": "192x192" }, { "src": "icons/512.png", "type": "image/png", "sizes": "512x512" }, { "src": "icons/512-maskable.png", "type": "image/png", "sizes": "512x512", "purpose": "maskable" } ], "description": "A complete Progressive Web App example", "screenshots": [ { "src": "screenshots/main.png", "type": "image/png", "sizes": "1280x720" } ], "categories": ["productivity", "utilities"] } ``` -------------------------------- ### Service Worker Lifecycle Event Listeners Source: https://github.com/weroperking/pwa_docs/blob/main/Service workers.md This snippet demonstrates how to listen for the 'install' and 'activate' events within a service worker. These events are crucial for the service worker's setup and activation process. The code executes in the service worker's dedicated thread. ```javascript // This code executes in its own worker or thread self.addEventListener("install", event => { console.log("Service worker installed"); }); self.addEventListener("activate", event => { console.log("Service worker activated"); }); ``` -------------------------------- ### Create and Open IndexedDB Database with `idb` Source: https://github.com/weroperking/pwa_docs/blob/main/Offline data.md Demonstrates how to create or open an IndexedDB database named 'cookbook' with version 1. The `upgrade` callback handles database schema creation, including object stores and indexes. This function is essential for initializing the database structure. ```javascript import { openDB } from 'idb'; async function createDB() { // Using https://github.com/jakearchibald/idb const db = await openDB('cookbook', 1, { upgrade(db, oldVersion, newVersion, transaction) { // Switch over the oldVersion, *without breaks*, to allow the database to be incrementally upgraded. switch(oldVersion) { case 0: // Placeholder to execute when database is created (oldVersion is 0) case 1: // Create a store of objects const store = db.createObjectStore('recipes', { // The `id` property of the object will be the key, and be incremented automatically autoIncrement: true, keyPath: 'id' }); // Create an index called `name` based on the `type` property of objects in the store store.createIndex('type', 'type'); } } }); } ``` -------------------------------- ### Retrieve Data from IndexedDB using idb library Source: https://github.com/weroperking/pwa_docs/blob/main/Offline data.md This JavaScript snippet demonstrates how to retrieve data from an IndexedDB object store using the 'idb' library. It involves starting a read-only transaction, accessing the object store, and then using the 'get' method with a key to fetch a specific record. The 'id' is assumed to be the key for the 'recipes' object store. ```javascript async function getData() { const tx = await db.transaction('recipes', 'readonly') const store = tx.objectStore('recipes'); // Because in our case the `id` is the key, we would // have to know in advance the value of the id to // retrieve the record const value = await store.get([id]); } ``` -------------------------------- ### Configure Related Applications in Web App Manifest Source: https://github.com/weroperking/pwa_docs/blob/main/Detection.md This JSON example shows how to configure the `related_applications` member in a Web App Manifest to link to an Android application on the Google Play Store. It also demonstrates the use of `prefer_related_applications` to potentially redirect users to install the related app instead of the PWA. Note that `prefer_related_applications: true` has no effect if the only related application is of type `webapp`. ```json { "related_applications:" [ { "platform": "play", "url": "https://play.google.com/..." } ], "prefer_related_applications": true } ``` -------------------------------- ### Open Cache Storage using JavaScript Source: https://github.com/weroperking/pwa_docs/blob/main/ Caching.md Demonstrates how to open a specific cache by its name using the `caches.open()` method. This is the initial step before performing any operations like downloading or storing assets within that cache. It returns a promise that resolves with the cache object. ```javascript caches.open("pwa-assets") .then(cache => { // you can download and store, delete or update resources with cache arguments }); ``` -------------------------------- ### Download and Store Assets with JavaScript Source: https://github.com/weroperking/pwa_docs/blob/main/ Caching.md Shows how to use the `add` and `addAll` methods of the Cache Storage API to download and store assets. `add` stores a single HTTP response, while `addAll` stores multiple responses from an array of requests or URLs as a transaction. Both methods return promises indicating success or failure. ```javascript caches.open("pwa-assets") .then(cache => { cache.add("styles.css"); // it stores only one resource cache.addAll(["styles.css", "app.js"]); // it stores two resources }); ``` -------------------------------- ### Geolocation API Integration using JavaScript Source: https://context7.com/weroperking/pwa_docs/llms.txt This snippet provides functions to interact with the Geolocation API. It includes methods to get the current user's location with specified options, watch for continuous location updates, and stop watching. It also includes an example of how to use these functions, handling potential errors and timeouts. Requires browser support for the Geolocation API. ```javascript function getCurrentLocation() { if (!('geolocation' in navigator)) { console.error("Geolocation not supported"); return Promise.reject(new Error("Geolocation not supported")); } return new Promise((resolve, reject) => { const options = { enableHighAccuracy: true, timeout: 5000, maximumAge: 0 }; navigator.geolocation.getCurrentPosition( position => { const location = { latitude: position.coords.latitude, longitude: position.coords.longitude, accuracy: position.coords.accuracy, timestamp: position.timestamp }; console.log("Location obtained:", location); resolve(location); }, error => { console.error("Geolocation error:", error.message); reject(error); }, options ); }); } function watchLocation(callback) { if (!('geolocation' in navigator)) { return null; } const options = { enableHighAccuracy: true, timeout: 10000, maximumAge: 1000 }; const watchId = navigator.geolocation.watchPosition( position => { const location = { latitude: position.coords.latitude, longitude: position.coords.longitude, accuracy: position.coords.accuracy }; callback(location); }, error => { console.error("Watch position error:", error.message); }, options ); return watchId; } function stopWatchingLocation(watchId) { if (watchId !== null) { navigator.geolocation.clearWatch(watchId); console.log("Stopped watching location"); } } async function useGeolocation() { try { const location = await getCurrentLocation(); console.log(`You are at: ${location.latitude}, ${location.longitude}`); const watchId = watchLocation(newLocation => { console.log("Location updated:", newLocation); }); setTimeout(() => { stopWatchingLocation(watchId); }, 30000); } catch (error) { console.error("Failed to get location:", error); } } ``` -------------------------------- ### IndexedDB Operations with idb Library in JavaScript Source: https://context7.com/weroperking/pwa_docs/llms.txt Demonstrates how to use the 'idb' library to interact with IndexedDB for structured data storage. This includes creating and initializing a database, adding, retrieving, querying, updating, and deleting data. ```javascript import { openDB } from 'idb'; // Create and initialize database async function createDatabase() { const db = await openDB('cookbook', 1, { upgrade(db, oldVersion, newVersion, transaction) { switch(oldVersion) { case 0: // Database is being created for the first time const store = db.createObjectStore('recipes', { autoIncrement: true, keyPath: 'id' }); // Create indexes for querying store.createIndex('type', 'type'); store.createIndex('name', 'name'); } } }); return db; } // Add data to IndexedDB async function addRecipe(recipeData) { try { const db = await openDB('cookbook', 1); const recipe = { name: recipeData.name, type: recipeData.type, cook_time_minutes: recipeData.cookTime, ingredients: recipeData.ingredients, instructions: recipeData.instructions }; const tx = db.transaction('recipes', 'readwrite'); const store = tx.objectStore('recipes'); const id = await store.add(recipe); await tx.done; console.log("Recipe added with ID:", id); return id; } catch (error) { console.error("Failed to add recipe:", error); throw error; } } // Retrieve data from IndexedDB async function getRecipe(id) { try { const db = await openDB('cookbook', 1); const recipe = await db.get('recipes', id); if (recipe) { console.log("Retrieved recipe:", recipe); return recipe; } else { console.log("Recipe not found"); return null; } } catch (error) { console.error("Failed to retrieve recipe:", error); throw error; } } // Query data using indexes async function getRecipesByType(type) { try { const db = await openDB('cookbook', 1); const tx = db.transaction('recipes', 'readonly'); const index = tx.store.index('type'); const recipes = await index.getAll(type); console.log(`Found ${recipes.length} ${type} recipes:`, recipes); return recipes; } catch (error) { console.error("Failed to query recipes:", error); throw error; } } // Update existing data async function updateRecipe(id, updates) { try { const db = await openDB('cookbook', 1); const tx = db.transaction('recipes', 'readwrite'); const store = tx.objectStore('recipes'); const recipe = await store.get(id); if (!recipe) { throw new Error("Recipe not found"); } const updatedRecipe = { ...recipe, ...updates }; await store.put(updatedRecipe); await tx.done; console.log("Recipe updated:", updatedRecipe); return updatedRecipe; } catch (error) { console.error("Failed to update recipe:", error); throw error; } } // Delete data async function deleteRecipe(id) { try { const db = await openDB('cookbook', 1); const tx = db.transaction('recipes', 'readwrite'); await tx.store.delete(id); await tx.done; console.log("Recipe deleted"); } catch (error) { console.error("Failed to delete recipe:", error); throw error; } } // Get all recipes async function getAllRecipes() { try { const db = await openDB('cookbook', 1); const allRecipes = await db.getAll('recipes'); console.log("All recipes:", allRecipes); return allRecipes; } catch (error) { console.error("Failed to get all recipes:", error); throw error; } } // Usage example async function demonstrateIndexedDB() { await createDatabase(); const id = await addRecipe({ name: "Chocolate Chip Cookies", type: "dessert", cookTime: 25, ingredients: ["flour", "sugar", "chocolate chips", "butter"], instructions: "Mix, bake at 350°F for 12 minutes" }); const recipe = await getRecipe(id); const desserts = await getRecipesByType("dessert"); await updateRecipe(id, { cook_time_minutes: 30 }); await deleteRecipe(id); } ``` -------------------------------- ### Detect Service Worker Update Event Source: https://github.com/weroperking/pwa_docs/blob/main/Update.md Listens for the 'updatefound' event on the service worker registration to detect when a new service worker is being installed. It then listens for the 'statechange' event on the installing service worker to identify when it reaches the 'installed' state. ```javascript async function detectSWUpdate() { const registration = await navigator.serviceWorker.ready; registration.addEventListener("updatefound", event => { const newSW = registration.installing; newSW.addEventListener("statechange", event => { if (newSW.state == "installed") { // New service worker is installed, but waiting activation } }); }) } ``` -------------------------------- ### Opening a File with File System Access API Source: https://github.com/weroperking/pwa_docs/blob/main/OS Integration.md Demonstrates how to use the `window.showOpenFilePicker()` method to allow users to select a file from their device. It then retrieves a File object from the handle and reads its content as text. This process requires a user gesture, like a button click. ```javascript // Have the user select a file. const [ handle ] = await window.showOpenFilePicker(); // Get the File object from the handle. const file = await handle.getFile(); // Get the file content. // Also available, slice(), stream(), arrayBuffer() const content = await file.text(); ``` -------------------------------- ### Define iOS Startup Image Source: https://github.com/weroperking/pwa_docs/blob/main/Enhancements.md Specifies a static image for iOS startup screens using a link element. The `href` attribute points to the image file, and the `rel` attribute must be set to `apple-touch-startup-image`. ```html ``` -------------------------------- ### Check if a Related App is Installed using JavaScript Source: https://github.com/weroperking/pwa_docs/blob/main/Detection.md This JavaScript code demonstrates how to use the `getInstalledRelatedApps()` method to check if any related applications, including PWAs, are installed on the user's device. It returns an array of installed apps; if the array is empty, no related apps are found. This can be used to conditionally hide prompts or redirect users. ```javascript const relatedApps = await navigator.getInstalledRelatedApps(); const PWAisInstalled = relatedApps.length > 0; ```