<project version="4">
<component name="ChangeListManager">
<list default="true" id="352ce63a-b52a-41a2-979b-becda7920939" name="Default" comment=".">
- <change afterPath="$PROJECT_DIR$/scripts/lib/netflix.php" afterDir="false" />
+ <change afterPath="$PROJECT_DIR$/cache-polyfill.js" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/index.php" beforeDir="false" afterPath="$PROJECT_DIR$/index.php" afterDir="false" />
<change beforePath="$PROJECT_DIR$/js/webapp.js" beforeDir="false" afterPath="$PROJECT_DIR$/js/webapp.js" afterDir="false" />
- <change beforePath="$PROJECT_DIR$/scripts/lib/scenes.php" beforeDir="false" afterPath="$PROJECT_DIR$/scripts/lib/scenes.php" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/manifest.php" beforeDir="false" afterPath="$PROJECT_DIR$/manifest.php" afterDir="false" />
<change beforePath="$PROJECT_DIR$/service-worker.js" beforeDir="false" afterPath="$PROJECT_DIR$/service-worker.js" afterDir="false" />
+ <change beforePath="$PROJECT_DIR$/service-worker.js.template" beforeDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option value="Less File" />
<option value="Setup Script" />
<option value="Python Script" />
- <option value="JavaScript File" />
<option value="HTML File" />
+ <option value="JavaScript File" />
</list>
</option>
</component>
<option name="Make" enabled="true" />
</method>
</configuration>
- <configuration default="true" type="ArquillianJUnit" factoryName="" nameIsGenerated="true">
- <option name="arquillianRunConfiguration">
- <value>
- <option name="containerStateName" value="" />
- </value>
- </option>
- <option name="TEST_OBJECT" value="class" />
- <method v="2">
- <option name="Make" enabled="true" />
- </method>
- </configuration>
<configuration default="true" type="ArquillianTestNG" factoryName="">
<option name="arquillianRunConfiguration">
<value>
<workItem from="1602862886793" duration="1014000" />
<workItem from="1603095373591" duration="1368000" />
<workItem from="1603270756890" duration="3366000" />
- <workItem from="1603558842019" duration="12945000" />
- </task>
- <task id="LOCAL-00305" summary=".">
- <created>1582130758226</created>
- <option name="number" value="00305" />
- <option name="presentableId" value="LOCAL-00305" />
- <option name="project" value="LOCAL" />
- <updated>1582130758226</updated>
+ <workItem from="1603558842019" duration="15438000" />
+ <workItem from="1603646440968" duration="3050000" />
</task>
<task id="LOCAL-00306" summary=".">
<created>1582220578115</created>
<option name="project" value="LOCAL" />
<updated>1603626112636</updated>
</task>
- <option name="localTasksCounter" value="354" />
+ <task id="LOCAL-00354" summary=".">
+ <created>1603643895376</created>
+ <option name="number" value="00354" />
+ <option name="presentableId" value="LOCAL-00354" />
+ <option name="project" value="LOCAL" />
+ <updated>1603643895376</updated>
+ </task>
+ <option name="localTasksCounter" value="355" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<screen x="0" y="0" width="2560" height="1040" />
</state>
<state x="872" y="161" key="#Plugins/0.0.2560.1040@0.0.2560.1040" timestamp="1596008801840" />
- <state x="198" y="0" width="745" height="567" key="CommitChangelistDialog2" timestamp="1603626109154">
+ <state x="198" y="0" width="745" height="567" key="CommitChangelistDialog2" timestamp="1603643887312">
<screen x="0" y="0" width="2560" height="1040" />
</state>
<state x="701" y="75" key="CommitChangelistDialog2/0.0.1920.1160@0.0.1920.1160" timestamp="1602171117745" />
- <state x="198" y="0" width="745" height="567" key="CommitChangelistDialog2/0.0.2560.1040@0.0.2560.1040" timestamp="1603626109154" />
+ <state x="198" y="0" width="745" height="567" key="CommitChangelistDialog2/0.0.2560.1040@0.0.2560.1040" timestamp="1603643887312" />
<state x="1114" y="443" key="NewPhpFileDialog" timestamp="1603637447680">
<screen x="0" y="0" width="2560" height="1040" />
</state>
--- /dev/null
+/*
+ Source: https://github.com/coonsta/cache-polyfill
+ Author: https://github.com/coonsta
+*/
+
+if (!Cache.prototype.add) {
+ Cache.prototype.add = function add(request) {
+ return this.addAll([request]);
+ };
+}
+
+if (!Cache.prototype.addAll) {
+ Cache.prototype.addAll = function addAll(requests) {
+ var cache = this;
+
+ // Since DOMExceptions are not constructable:
+ function NetworkError(message) {
+ this.name = 'NetworkError';
+ this.code = 19;
+ this.message = message;
+ }
+ NetworkError.prototype = Object.create(Error.prototype);
+
+ return Promise.resolve().then(function() {
+ if (arguments.length < 1) throw new TypeError();
+
+ // Simulate sequence<(Request or USVString)> binding:
+ var sequence = [];
+
+ requests = requests.map(function(request) {
+ if (request instanceof Request) {
+ return request;
+ }
+ else {
+ return String(request); // may throw TypeError
+ }
+ });
+
+ return Promise.all(
+ requests.map(function(request) {
+ if (typeof request === 'string') {
+ request = new Request(request);
+ }
+
+ var scheme = new URL(request.url).protocol;
+
+ if (scheme !== 'http:' && scheme !== 'https:') {
+ throw new NetworkError("Invalid scheme");
+ }
+
+ return fetch(request.clone());
+ })
+ );
+ }).then(function(responses) {
+ // TODO: check that requests don't overwrite one another
+ // (don't think this is possible to polyfill due to opaque responses)
+ return Promise.all(
+ responses.map(function(response, i) {
+ return cache.put(requests[i], response);
+ })
+ );
+ }).then(function() {
+ return undefined;
+ });
+ };
+}
+
+if (!CacheStorage.prototype.match) {
+ // This is probably vulnerable to race conditions (removing caches etc)
+ CacheStorage.prototype.match = function match(request, opts) {
+ var caches = this;
+
+ return this.keys().then(function(cacheNames) {
+ var match;
+
+ return cacheNames.reduce(function(chain, cacheName) {
+ return chain.then(function() {
+ return match || caches.open(cacheName).then(function(cache) {
+ return cache.match(request, opts);
+ }).then(function(response) {
+ match = response;
+ return match;
+ });
+ });
+ }, Promise.resolve());
+ });
+ };
+}
</main>
</div>
<?php
-if ($android) {
+if (true || $android) {
echo '<script src="' . relativePath('js/webapp.js') . '"></script>' . "\n";
} ?>
<script>
});
}
-
-/* Only register a service worker if it's supported */
-if ('serviceWorker' in navigator) {
- window.addEventListener('load', function() {
- console.log('👍', 'navigator.serviceWorker is supported');
- navigator.serviceWorker.register('/service-worker.js');
+function checkForPageUpdate(registration) {
+ // onupdatefound will fire on first time install and when serviceWorker.js file changes
+ registration.addEventListener("updatefound", function() {
+ // To check if service worker is already installed and controlling the page or not
+ if (navigator.serviceWorker.controller) {
+ var installingSW = registration.installing;
+ installingSW.onstatechange = function() {
+ console.info("Service Worker State :", installingSW.state);
+ switch(installingSW.state) {
+ case 'installed':
+ // Now new contents will be added to cache and old contents will be remove so
+ // this is perfect time to show user that page content is updated.
+ toast('Site is updated. Refresh the page.', 5000);
+ break;
+ case 'redundant':
+ throw new Error('The installing service worker became redundant.');
+ }
+ }
+ }
});
}
+
+if ("serviceWorker" in navigator) {
+ navigator.serviceWorker.register('./service-worker.js', {scope: "./"}) //setting scope of sw
+ .then(function (registration) {
+ console.info('Service worker is registered!');
+ checkForPageUpdate(registration); // To check if new content is updated or not
+ }) .catch(function (error) {
+ console.error('Service worker failed ', error);
+ });
+}
<?php
+$host = 'https://' . $_SERVER['HTTP_HOST'] . '/';
header('Content-type: application/manifest+json');
$manifest = [
'name' => $_GET['n'],
'orientation' => 'landscape',
'background_color' => '#0a4a9e',
'theme_color' => '#0a4a9e',
- 'start_url' => '/',
- 'scope' => '/',
+ 'start_url' => './index.php',
'icons' => [],
'lang' => 'fr',
'share_target' => [
- 'action' => 'https://' . $_SERVER['HTTP_HOST'] . '/scripts/share.php',
+ 'action' => './scripts/share.php',
'method' => 'POST',
'enctype' => 'application/x-www-form-urlencoded',
'params' => [
$icons = [36, 48, 72, 96, 144, 192, 256, 512, 1024];
foreach ($icons as $size) {
- $manifest['icons'][] = ['src' => '/images/favicon/android-icon-' . $size . 'x' . $size . '.png',
+ $manifest['icons'][] = ['src' => './images/favicon/android-icon-' . $size . 'x' . $size . '.png',
'sizes' => $size . 'x' . $size,
'type' => 'image/png',
'density' => $size / 48];
+//Cache polyfil to support cacheAPI in all browsers
+importScripts('./cache-polyfill.js');
+
+var cacheName = 'cache-v4';
+
+//Files to save in cache
+var files = [
+ './',
+ './index.php', //SW treats query string as new request
+ // 'https://fonts.googleapis.com/css?family=Roboto:200,300,400,500,700', //caching 3rd party content
+ // './css/styles.css',
+ // './images/icons/android-chrome-192x192.png',
+ // './images/push-on.png',
+ // './images/push-off.png',
+ // './images/icons/favicon-16x16.png',
+ // './images/icons/favicon-32x32.png',
+ // './js/main.js',
+ // './js/app.js',
+ // './js/offline.js',
+ // './js/push.js',
+ // './js/sync.js',
+ // './js/toast.js',
+ // './js/share.js',
+ // './js/menu.js',
+ // './manifest.json'
+];
+
+//Adding `install` event listener
self.addEventListener('install', (event) => {
- console.log('👷', 'install', event);
- self.skipWaiting();
+ console.info('Event: Install');
+
+ event.waitUntil(
+ caches.open(cacheName)
+ .then((cache) => {
+ //[] of files to cache & if any of the file not present `addAll` will fail
+ return cache.addAll(files)
+ .then(() => {
+ console.info('All files are cached');
+ return self.skipWaiting(); //To forces the waiting service worker to become the active service worker
+ })
+ .catch((error) => {
+ console.error('Failed to cache', error);
+ })
+ })
+ );
});
-self.addEventListener('activate', (event) => {
- console.log('👷', 'activate', event);
- return self.clients.claim();
+/*
+ FETCH EVENT: triggered for every request made by index page, after install.
+*/
+
+//Adding `fetch` event listener
+self.addEventListener('fetch', (event) => {
+ console.info('Event: Fetch');
+
+ var request = event.request;
+ var url = new URL(request.url);
+ if (url.origin === location.origin) {
+ // Static files cache
+ event.respondWith(cacheFirst(request));
+ } else {
+ // Dynamic API cache
+ event.respondWith(networkFirst(request));
+ }
+
+ // // Checking for navigation preload response
+ // if (event.preloadResponse) {
+ // console.info('Using navigation preload');
+ // return response;
+ // }
});
-// self.addEventListener('fetch', function(event) {
-// // console.log('👷', 'fetch', event);
-// event.respondWith(fetch(event.request));
-// });
\ No newline at end of file
+async function cacheFirst(request) {
+ const cachedResponse = await caches.match(request);
+ return cachedResponse || fetch(request);
+}
+
+async function networkFirst(request) {
+ const dynamicCache = await caches.open(cacheName);
+ try {
+ const networkResponse = await fetch(request);
+ // Cache the dynamic API response
+ dynamicCache.put(request, networkResponse.clone()).catch((err) => {
+ console.warn(request.url + ': ' + err.message);
+ });
+ return networkResponse;
+ } catch (err) {
+ const cachedResponse = await dynamicCache.match(request);
+ return cachedResponse;
+ }
+}
+
+/*
+ ACTIVATE EVENT: triggered once after registering, also used to clean up caches.
+*/
+
+//Adding `activate` event listener
+self.addEventListener('activate', (event) => {
+ console.info('Event: Activate');
+
+ //Navigation preload is help us make parallel request while service worker is booting up.
+ //Enable - chrome://flags/#enable-service-worker-navigation-preload
+ //Support - Chrome 57 beta (behing the flag)
+ //More info - https://developers.google.com/web/updates/2017/02/navigation-preload#the-problem
+
+ // Check if navigationPreload is supported or not
+ // if (self.registration.navigationPreload) {
+ // self.registration.navigationPreload.enable();
+ // }
+ // else if (!self.registration.navigationPreload) {
+ // console.info('Your browser does not support navigation preload.');
+ // }
+
+ //Remove old and unwanted caches
+ event.waitUntil(
+ caches.keys().then((cacheNames) => {
+ return Promise.all(
+ cacheNames.map((cache) => {
+ if (cache !== cacheName) {
+ return caches.delete(cache); //Deleting the old cache (cache v1)
+ }
+ })
+ );
+ })
+ .then(function () {
+ console.info("Old caches are cleared!");
+ // To tell the service worker to activate current one
+ // instead of waiting for the old one to finish.
+ return self.clients.claim();
+ })
+ );
+});
+++ /dev/null
-const CACHE_NAME = 'mpwa-cache-v1';
-const urlsToCache = $cached;
-
-// Listen for the install event, which fires when the service worker is installing
-self.addEventListener('install', event => {
- // Ensures the install event doesn't complete until after the cache promise resolves
- // This is so we don't move on to other events until the critical initial cache is done
- event.waitUntil(
- // Open a named cache, then add all the specified URLs to it
- caches.open(CACHE_NAME).then(cache => cache.addAll(urlsToCache))
- );
-});
-
-// Listen for the activate event, which is fired after installation
-// Activate is when the service worker actually takes over from the previous
-// version, which is a good time to clean up old caches
-self.addEventListener('activate', event => {
- console.log('Finally active. Ready to serve!');
- event.waitUntil(
- // Get the keys of all the old caches
- caches
- .keys()
- // Ensure we don't resolve until all the promises do (i.e. each key has been deleted)
- .then(keys =>
- Promise.all(
- keys
- // Remove any cache that matches the current cache name
- .filter(key => key !== CACHE_NAME)
- // Map over the array of old cache names and delete them all
- .map(key => caches.delete(key))
- )
- )
- );
-});
-
-// Listen for browser fetch events. These fire any time the browser tries to load
-// any outside resources
-self.addEventListener('fetch', function(event) {
- // This lets us control the response
- // We pass in a promise that resolves with a response object
- event.respondWith(
- // Check whether we have a matching response for this request in our cache
- caches.match(event.request).then(response => {
- // It's in the cache! Serve the response straight from there
- if (response) {
- console.log('Serving response from the cache');
- return response;
- }
- // If it's not in the cache we make a fetch request for the resource
- return (
- fetch(event.request)
- // Then we open our cache
- .then(response => caches.open(CACHE_NAME))
- // Then we put the request into the cache, so we have it offline next time
- .then(cache => {
- // We have to clone the response as response streams can only be read once
- // This way we can put one copy in the cache and return the other to the browser
- cache.put(event.request, response.clone());
- return response;
- })
- .catch(response => {
- console.log('Fetch failed, sorry.');
- })
- );
- })
- );
-});