├── config.json ├── assets └── icons │ └── icon.png ├── js ├── settings.js ├── main.js └── install-handler.js ├── README.md ├── css └── main.css ├── manifest.json ├── service-worker.js └── index.html /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "version":1.889 3 | } -------------------------------------------------------------------------------- /assets/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PranoySarkar/OpenPWA/HEAD/assets/icons/icon.png -------------------------------------------------------------------------------- /js/settings.js: -------------------------------------------------------------------------------- 1 | var Settings = { 2 | 3 | initIfNot: () => { 4 | if (localStorage.getItem('settings') == undefined) { 5 | localStorage.setItem('settings', '{}') 6 | } 7 | 8 | }, 9 | 10 | getVersion: () => { 11 | let settings = JSON.parse(localStorage.getItem('settings')); 12 | return settings.version || 0; 13 | }, 14 | 15 | setVersion: (version) => { 16 | let settings = JSON.parse(localStorage.getItem('settings')); 17 | settings.version = version; 18 | localStorage.setItem('settings', JSON.stringify(settings)) 19 | } 20 | 21 | 22 | } 23 | 24 | Settings.initIfNot(); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🔸OpenPWA🔸 2 | ##### OpenPWA is a boilerplate for deploying pwa using Github Pages. 3 | 4 | 5 | # [Open PWA](http://pranoysarkar.github.io/OpenPWA/) 6 | 7 | #### How to build your own? 8 | - Fork this project on github 9 | - Enable Github pages option in settings 10 | - Done!! Get the link in the GitHub pages section 11 | 12 | #### ❗️❕ This includes cache busting mechnism, as pwa serves from cache so changes will not be refected untill you change the version in config.json 13 | 14 | #### ❗️❕ On changing repository name you need to update the reposity name in 👉 "service-worker.js", line no 4 15 | 16 | This is a PWA, everything is made using webtechnologies. 🍄 17 | -------------------------------------------------------------------------------- /css/main.css: -------------------------------------------------------------------------------- 1 | html { 2 | scroll-behavior: smooth; 3 | -webkit-tap-highlight-color: #FFF0; 4 | } 5 | 6 | body { 7 | margin: 0; 8 | color: #636363; 9 | font-family: 'Open Sans', sans-serif; 10 | } 11 | 12 | .body { 13 | padding: 1rem; 14 | } 15 | 16 | header { 17 | text-align: center; 18 | } 19 | .downloadPrompt { 20 | display: none; 21 | padding: 10px; 22 | box-shadow: 0px 0 7px 4px #00000030; 23 | justify-content: center; 24 | max-width: 300px; 25 | margin: auto; 26 | margin-top: 60px; 27 | } 28 | 29 | .downloadButton { 30 | background: #109d58; 31 | color: aliceblue; 32 | padding: 5px 10px; 33 | min-width: 100px; 34 | border-radius: 2px; 35 | border: 1px solid #00723b; 36 | text-transform: uppercase; 37 | /* box-shadow: -10px 6px 5px 1px #109d581a; */ 38 | } 39 | 40 | #appContainer { 41 | overflow: hidden; 42 | display: grid; 43 | height: 100vh; 44 | grid-template-rows: auto 1fr auto; 45 | } -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Open PWA", 3 | "short_name": "Open PWA", 4 | "start_url": "./", 5 | "scope": ".", 6 | "display": "standalone", 7 | "orientation": "portrait", 8 | "background_color": "#fff", 9 | "theme_color": "#640700", 10 | "newBranch":"true", 11 | "icons": [ 12 | { 13 | "src": "assets/icons/icon.png", 14 | "type": "image/png", 15 | "sizes": "48x48" 16 | }, 17 | { 18 | "src": "assets/icons/icon.png", 19 | "type": "image/png", 20 | "sizes": "96x96" 21 | }, 22 | { 23 | "src": "assets/icons/icon.png", 24 | "sizes": "128x128", 25 | "type": "image/png" 26 | }, 27 | { 28 | "src": "assets/icons/icon.png", 29 | "sizes": "144x144", 30 | "type": "image/png" 31 | }, 32 | { 33 | "src": "assets/icons/icon.png", 34 | "type": "image/png", 35 | "sizes": "192x192" 36 | }, 37 | { 38 | "src": "assets/icons/icon.png", 39 | "type": "image/png", 40 | "sizes": "256x256" 41 | }, 42 | { 43 | "src": "assets/icons/icon.png", 44 | "type": "image/png", 45 | "sizes": "512x512" 46 | } 47 | ] 48 | } -------------------------------------------------------------------------------- /service-worker.js: -------------------------------------------------------------------------------- 1 | let cacheName = "OpenGithubPWA";// 👈 any unique name 2 | 3 | let filesToCache = [ 4 | "/OpenPWA/", // 👈 your repository name , both slash are important 5 | "service-worker.js", 6 | "js/main.js", 7 | "js/install-handler.js", 8 | "js/settings.js", 9 | "css/main.css", 10 | "assets/icons/icon.png", 11 | "manifest.json" 12 | // add your assets here 13 | // ❗️❕donot add config.json here ❗️❕ 14 | ]; 15 | 16 | self.addEventListener("install", function (event) { 17 | event.waitUntil(caches.open(cacheName).then((cache) => { 18 | console.log('installed successfully') 19 | return cache.addAll(filesToCache); 20 | })); 21 | }); 22 | 23 | self.addEventListener('fetch', function (event) { 24 | 25 | if (event.request.url.includes('clean-cache')) { 26 | caches.delete(cacheName); 27 | console.log('Cache cleared') 28 | } 29 | 30 | event.respondWith(caches.match(event.request).then(function (response) { 31 | if (response) { 32 | console.log('served form cache') 33 | } else { 34 | console.log('Not serving from cache ', event.request.url) 35 | } 36 | return response || fetch(event.request); 37 | }) 38 | ); 39 | }); 40 | 41 | self.addEventListener('activate', function (e) { 42 | e.waitUntil( 43 | caches.keys().then(function (keyList) { 44 | return Promise.all(keyList.map(function (key) { 45 | if (key !== cacheName) { 46 | console.log('service worker: Removing old cache', key); 47 | return caches.delete(key); 48 | } 49 | })); 50 | }) 51 | ); 52 | return self.clients.claim(); 53 | }); 54 | -------------------------------------------------------------------------------- /js/main.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | window.addEventListener('load',main) 5 | function main(){ 6 | 7 | vaildateCacheIfOnline() 8 | .then(_=>{ 9 | 10 | }) 11 | 12 | } 13 | 14 | 15 | 16 | 17 | function vaildateCacheIfOnline(){ 18 | 19 | return new Promise((resolve,reject)=>{ 20 | 21 | fetch(`config.json?cacheBust=${new Date().getTime()}`) 22 | .then(response => { return response.json() }) 23 | .then(config => { 24 | 25 | let installedVersion = Settings.getVersion() 26 | if ( installedVersion== 0) { 27 | Settings.setVersion(config.version) 28 | document.querySelector('#version').innerHTML= `version ${config.version}`; 29 | return resolve(); 30 | } 31 | else if (installedVersion != config.version) { 32 | console.log('Cache Version mismatch') 33 | fetch(`config.json?clean-cache=true&cacheBust=${new Date().getTime()}`).then(_ => { 34 | //actually cleans the cache 35 | Settings.setVersion(config.version); 36 | window.location.reload(); 37 | 38 | return resolve(); // unnecessary 39 | }); 40 | 41 | }else{ 42 | // already updated 43 | console.log('Cache Updated') 44 | document.querySelector('#version').innerHTML= `version ${installedVersion}`; 45 | return resolve(); 46 | } 47 | }).catch(err=>{ 48 | console.log(err); 49 | return resolve(); 50 | //handle offline here 51 | }) 52 | }) 53 | 54 | } 55 | 56 | -------------------------------------------------------------------------------- /js/install-handler.js: -------------------------------------------------------------------------------- 1 | var deferredInstallPrompt = null; 2 | 3 | 4 | window.addEventListener('beforeinstallprompt', function (event) { 5 | event.preventDefault(); 6 | deferredInstallPrompt = event; 7 | showDownloadPrompt(); 8 | }); 9 | 10 | 11 | document.querySelector('.downloadButton').addEventListener('click', downloadButtonClicked) 12 | 13 | function downloadButtonClicked() { 14 | deferredInstallPrompt.prompt(); 15 | deferredInstallPrompt.userChoice 16 | .then(function (choiceResult) { 17 | if (choiceResult.outcome === 'accepted') { 18 | 19 | deferredInstallPrompt = null; 20 | document.querySelector('.downloadPrompt').style.display = 'none'; 21 | 22 | } else { 23 | console.log(choiceResult) 24 | } 25 | }) 26 | } 27 | 28 | function showDownloadPrompt() { 29 | document.querySelector('.downloadPrompt').style.display = 'grid'; 30 | } 31 | 32 | window.addEventListener('appinstalled', (evt) => { 33 | // Log install to analytics 34 | 35 | if (!isInStandaloneMode()) { 36 | alert('open in app'); 37 | } 38 | }); 39 | 40 | async function foo(){ 41 | if ('getInstalledRelatedApps' in window.navigator) { 42 | const relatedApps = await navigator.getInstalledRelatedApps(); 43 | relatedApps.forEach((app) => { 44 | //if your PWA exists in the array it is installed 45 | alert(app.platform, app.url); 46 | }); 47 | } 48 | } 49 | 50 | foo(); 51 | 52 | 53 | 54 | const isInStandaloneMode = () =>(window.matchMedia('(display-mode: standalone)').matches) || (window.navigator.standalone) || document.referrer.includes('android-app://'); 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |43 | This is a PWA , deployed using Github Pages (Free and Awesome) with cache bust mechanism. 44 |
45 |46 | Build your own pwa in 2 mins, follow the bellow steps 47 |
48 |54 | Check here for more information 55 |
56 |57 | At the beginning it might take some time to deploy, if it shows 404 wait for 5 mins. 58 |
59 |60 | If you dont see the download button bollow then delete the previous install. 61 |
62 |63 | Open in APP 64 |
65 | 66 |