Commit 903ad14b authored by Phyks's avatar Phyks

Add a service worker and cache assets

Assets are served from the local cache preferably. They are fetched from
the network if not available. This new addition also enables the "Add to
homescreen" in Chrome/Chromium.

Fix #22.
parent 80b650c2
......@@ -43,6 +43,7 @@ module.exports = {
}
],
'no-bitwise': 'off',
'no-unused-expressions': ["error", { "allowShortCircuit": true }],
'function-paren-newline': 'off',
}
}
......@@ -8,6 +8,7 @@ const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const ServiceWorkerWebpackPlugin = require("serviceworker-webpack-plugin");
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
......@@ -189,6 +190,9 @@ module.exports = {
child_process: 'empty',
},
plugins: [
new ServiceWorkerWebpackPlugin({
entry: path.join(__dirname, '../src/sw.js'),
}),
new VueLoaderPlugin(),
new MiniCssExtractPlugin({
filename: utils.assetsPath('css/[name].[contenthash:4].css'),
......
......@@ -63,6 +63,8 @@
</template>
<script>
import runtime from 'serviceworker-webpack-plugin/lib/runtime';
import { DELAY_BETWEEN_API_BATCH_REQUESTS } from '@/constants';
import ReportErrorModal from '@/components/ReportErrorModal.vue';
......@@ -136,6 +138,13 @@ export default {
});
},
},
mounted() {
if ('serviceWorker' in navigator) {
runtime.register().catch((error) => {
console.log(`Registration failed with ${error}.`);
});
}
},
};
</script>
......
......@@ -46,6 +46,7 @@ import compassIcon from '@/assets/compass.svg';
import compassNorthIcon from '@/assets/compassNorth.svg';
import unknownMarkerIcon from '@/assets/unknownMarker.svg';
import * as constants from '@/constants';
import REPORT_TYPES from '@/report-types';
import { distance } from '@/tools';
const MAIN_VECTOR_LAYER_NAME = 'MAIN';
......@@ -177,7 +178,7 @@ export default {
? constants.LARGE_ICON_SCALE
: constants.NORMAL_ICON_SCALE
),
src: constants.REPORT_TYPES[marker.type].marker,
src: REPORT_TYPES[marker.type].marker,
}),
}));
// Add the marker to the map and keep a reference to it
......
......@@ -49,7 +49,8 @@
</template>
<script>
import { REPORT_TYPES, REPORT_ALARM_VIBRATION_SEQUENCE } from '@/constants';
import { REPORT_ALARM_VIBRATION_SEQUENCE } from '@/constants';
import REPORT_TYPES from '@/report-types';
import { distanceInWordsToNow } from '@/tools/date';
import beepSound from '@/assets/beep.mp3';
......
......@@ -22,7 +22,8 @@
</template>
<script>
import { REPORT_TYPES, REPORT_TYPES_ORDER } from '@/constants';
import { REPORT_TYPES_ORDER } from '@/constants';
import REPORT_TYPES from '@/report-types';
import ReportErrorModal from '@/components/ReportErrorModal.vue';
import ReportTile from './ReportTile.vue';
......
......@@ -8,7 +8,8 @@
</template>
<script>
import { REPORT_TYPES, REPORT_TYPES_ORDER } from '@/constants';
import { REPORT_TYPES_ORDER } from '@/constants';
import REPORT_TYPES from '@/report-types';
export default {
data() {
......
import accidentMarker from '@/assets/accidentMarker.svg';
import gcumMarker from '@/assets/gcumMarker.svg';
import interruptMarker from '@/assets/interruptMarker.svg';
import miscMarker from '@/assets/miscMarker.svg';
import obstacleMarker from '@/assets/obstacleMarker.svg';
import potholeMarker from '@/assets/potholeMarker.svg';
import accidentIcon from '@/assets/accident.svg';
import gcumIcon from '@/assets/gcum.svg';
import interruptIcon from '@/assets/interrupt.svg';
import miscIcon from '@/assets/misc.svg';
import obstacleIcon from '@/assets/obstacle.svg';
import potholeIcon from '@/assets/pothole.svg';
export const VERSION = '0.3';
export const NORMAL_ICON_SCALE = 0.625;
export const LARGE_ICON_SCALE = 1.0;
export const ICON_ANCHOR = [0.5, 1.0];
export const REPORT_TYPES = {
accident: {
description: 'reportLabels.accidentDescription',
label: 'reportLabels.accident',
image: accidentIcon,
marker: accidentMarker,
markerLarge: accidentMarker,
},
gcum: {
description: 'reportLabels.gcumDescription',
label: 'reportLabels.gcum',
image: gcumIcon,
marker: gcumMarker,
markerLarge: gcumMarker,
},
interrupt: {
description: 'reportLabels.interruptDescription',
label: 'reportLabels.interrupt',
image: interruptIcon,
marker: interruptMarker,
markerLarge: interruptMarker,
},
misc: {
description: 'reportLabels.miscDescription',
label: 'reportLabels.misc',
image: miscIcon,
marker: miscMarker,
markerLarge: miscMarker,
},
obstacle: {
description: 'reportLabels.obstacleDescription',
label: 'reportLabels.obstacle',
image: obstacleIcon,
marker: obstacleMarker,
markerLarge: obstacleMarker,
},
pothole: {
description: 'reportLabels.potholeDescription',
label: 'reportLabels.pothole',
image: potholeIcon,
marker: potholeMarker,
markerLarge: potholeMarker,
},
};
// Display order of the report types
export const REPORT_TYPES_ORDER = ['gcum', 'interrupt', 'obstacle', 'pothole', 'accident', 'misc'];
......
import accidentMarker from '@/assets/accidentMarker.svg';
import gcumMarker from '@/assets/gcumMarker.svg';
import interruptMarker from '@/assets/interruptMarker.svg';
import miscMarker from '@/assets/miscMarker.svg';
import obstacleMarker from '@/assets/obstacleMarker.svg';
import potholeMarker from '@/assets/potholeMarker.svg';
import accidentIcon from '@/assets/accident.svg';
import gcumIcon from '@/assets/gcum.svg';
import interruptIcon from '@/assets/interrupt.svg';
import miscIcon from '@/assets/misc.svg';
import obstacleIcon from '@/assets/obstacle.svg';
import potholeIcon from '@/assets/pothole.svg';
export default {
accident: {
description: 'reportLabels.accidentDescription',
label: 'reportLabels.accident',
image: accidentIcon,
marker: accidentMarker,
markerLarge: accidentMarker,
},
gcum: {
description: 'reportLabels.gcumDescription',
label: 'reportLabels.gcum',
image: gcumIcon,
marker: gcumMarker,
markerLarge: gcumMarker,
},
interrupt: {
description: 'reportLabels.interruptDescription',
label: 'reportLabels.interrupt',
image: interruptIcon,
marker: interruptMarker,
markerLarge: interruptMarker,
},
misc: {
description: 'reportLabels.miscDescription',
label: 'reportLabels.misc',
image: miscIcon,
marker: miscMarker,
markerLarge: miscMarker,
},
obstacle: {
description: 'reportLabels.obstacleDescription',
label: 'reportLabels.obstacle',
image: obstacleIcon,
marker: obstacleMarker,
markerLarge: obstacleMarker,
},
pothole: {
description: 'reportLabels.potholeDescription',
label: 'reportLabels.pothole',
image: potholeIcon,
marker: potholeMarker,
markerLarge: potholeMarker,
},
};
import { VERSION as CACHE_NAME } from '@/constants';
const DEBUG = (process.env.NODE_ENV !== 'production');
// Define the assets to cache
const { assets } = global.serviceWorkerOption;
let assetsToCache = [...assets, './'];
assetsToCache = assetsToCache.map(
path => new URL(path, global.location).toString(),
);
assetsToCache = assetsToCache.filter(
// Remove some assets from cache, such as Webpack hot-reload stuff
url => !url.endsWith('hot-update.json'),
);
// Define the locations from which we allow caching
const ALLOW_CACHING_FROM = [
global.location.origin,
];
global.self.addEventListener('install', (event) => {
DEBUG && console.log('SW: installing…');
event.waitUntil(
global.caches.open(CACHE_NAME)
.then((cache) => {
DEBUG && console.log('SW: cache opened.');
cache.addAll(assetsToCache).then(
() => {
if (DEBUG) {
console.log(`SW: cached assets ${assetsToCache}.`);
console.log('SW: successfully installed!');
}
return global.self.skipWaiting(); // Immediately update the SW
},
);
}),
);
});
global.self.addEventListener('activate', (event) => {
DEBUG && console.log('SW: activating…');
event.waitUntil(
// Delete all caches but the current one
global.caches.keys().then(
cacheNames => Promise.all(
cacheNames.map((cacheName) => {
if (cacheName.indexOf(CACHE_NAME) !== 0) {
DEBUG && console.log(`SW: Deleting unused cache ${cacheName}.`);
return global.caches.delete(cacheName);
}
return null;
}),
).then(() => DEBUG && console.log('SW: activated!')),
),
);
});
global.self.addEventListener('fetch', (event) => {
const { request } = event;
// Do not touch requests which are not GET
if (request.method !== 'GET') {
DEBUG && console.log(`SW: ignore non-GET request: ${request.method}`);
return;
}
// Do not touch requests from a different origin
const requestURL = new URL(request.url);
if (ALLOW_CACHING_FROM.indexOf(requestURL.origin) === -1) {
DEBUG && console.log(`SW: ignore different origin ${requestURL.origin}`);
return;
}
// Never touch requests going from / to the API
if (requestURL.pathname.startsWith('/api')) {
// Note that if API is on a different location, it will be ignored by
// the previous rule.
DEBUG && console.log(`SW: ignore API call ${requestURL.pathname}`);
return;
}
// For the other requests, try to match it in the cache, otherwise do a
// network call
const resource = global.caches.open(CACHE_NAME).then(
cache => cache.match(request).then(
(response) => {
if (response) {
DEBUG && console.log(`SW: serving ${request.url} from cache`);
return response;
}
DEBUG && console.log(`SW: no match in cache for ${request.url}, using network`);
return fetch(request);
},
),
);
event.respondWith(resource);
});
......@@ -46,6 +46,15 @@ server {
expires max; # Max caching for font files
}
# No caching for the service worker file
location = /sw.js {
add_header Last-Modified $date_gmt;
add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
if_modified_since off;
expires off;
etag off;
}
# Proxy pass the API calls to the server part
location /api {
limit_req zone=cycloAPI burst=3 nodelay; # Add rate-limiting on top of the API
......
......@@ -7413,6 +7413,12 @@ serve-static@1.13.2:
parseurl "~1.3.2"
send "0.16.2"
serviceworker-webpack-plugin@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/serviceworker-webpack-plugin/-/serviceworker-webpack-plugin-1.0.1.tgz#481863288487e92da01d49745336c72ef8a6136b"
dependencies:
minimatch "^3.0.4"
set-blocking@^2.0.0, set-blocking@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment