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.
This commit is contained in:
parent
80b650c2a9
commit
903ad14bbc
@ -43,6 +43,7 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
'no-bitwise': 'off',
|
'no-bitwise': 'off',
|
||||||
|
'no-unused-expressions': ["error", { "allowShortCircuit": true }],
|
||||||
'function-paren-newline': 'off',
|
'function-paren-newline': 'off',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ const CopyWebpackPlugin = require('copy-webpack-plugin')
|
|||||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||||
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
|
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
|
||||||
|
const ServiceWorkerWebpackPlugin = require("serviceworker-webpack-plugin");
|
||||||
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
|
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
|
||||||
const VueLoaderPlugin = require('vue-loader/lib/plugin')
|
const VueLoaderPlugin = require('vue-loader/lib/plugin')
|
||||||
|
|
||||||
@ -189,6 +190,9 @@ module.exports = {
|
|||||||
child_process: 'empty',
|
child_process: 'empty',
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
new ServiceWorkerWebpackPlugin({
|
||||||
|
entry: path.join(__dirname, '../src/sw.js'),
|
||||||
|
}),
|
||||||
new VueLoaderPlugin(),
|
new VueLoaderPlugin(),
|
||||||
new MiniCssExtractPlugin({
|
new MiniCssExtractPlugin({
|
||||||
filename: utils.assetsPath('css/[name].[contenthash:4].css'),
|
filename: utils.assetsPath('css/[name].[contenthash:4].css'),
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
"nosleep.js": "^0.7.0",
|
"nosleep.js": "^0.7.0",
|
||||||
"ol": "^5.1.3",
|
"ol": "^5.1.3",
|
||||||
"roboto-fontface": "^0.9.0",
|
"roboto-fontface": "^0.9.0",
|
||||||
|
"serviceworker-webpack-plugin": "^1.0.1",
|
||||||
"vue": "^2.5.2",
|
"vue": "^2.5.2",
|
||||||
"vue-i18n": "^8.0.0",
|
"vue-i18n": "^8.0.0",
|
||||||
"vue-router": "^3.0.1",
|
"vue-router": "^3.0.1",
|
||||||
|
@ -63,6 +63,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import runtime from 'serviceworker-webpack-plugin/lib/runtime';
|
||||||
|
|
||||||
import { DELAY_BETWEEN_API_BATCH_REQUESTS } from '@/constants';
|
import { DELAY_BETWEEN_API_BATCH_REQUESTS } from '@/constants';
|
||||||
|
|
||||||
import ReportErrorModal from '@/components/ReportErrorModal.vue';
|
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>
|
</script>
|
||||||
|
|
||||||
|
@ -46,6 +46,7 @@ import compassIcon from '@/assets/compass.svg';
|
|||||||
import compassNorthIcon from '@/assets/compassNorth.svg';
|
import compassNorthIcon from '@/assets/compassNorth.svg';
|
||||||
import unknownMarkerIcon from '@/assets/unknownMarker.svg';
|
import unknownMarkerIcon from '@/assets/unknownMarker.svg';
|
||||||
import * as constants from '@/constants';
|
import * as constants from '@/constants';
|
||||||
|
import REPORT_TYPES from '@/report-types';
|
||||||
import { distance } from '@/tools';
|
import { distance } from '@/tools';
|
||||||
|
|
||||||
const MAIN_VECTOR_LAYER_NAME = 'MAIN';
|
const MAIN_VECTOR_LAYER_NAME = 'MAIN';
|
||||||
@ -177,7 +178,7 @@ export default {
|
|||||||
? constants.LARGE_ICON_SCALE
|
? constants.LARGE_ICON_SCALE
|
||||||
: constants.NORMAL_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
|
// Add the marker to the map and keep a reference to it
|
||||||
|
@ -49,7 +49,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<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 { distanceInWordsToNow } from '@/tools/date';
|
||||||
import beepSound from '@/assets/beep.mp3';
|
import beepSound from '@/assets/beep.mp3';
|
||||||
|
|
||||||
|
@ -22,7 +22,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<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 ReportErrorModal from '@/components/ReportErrorModal.vue';
|
||||||
import ReportTile from './ReportTile.vue';
|
import ReportTile from './ReportTile.vue';
|
||||||
|
@ -8,7 +8,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { REPORT_TYPES, REPORT_TYPES_ORDER } from '@/constants';
|
import { REPORT_TYPES_ORDER } from '@/constants';
|
||||||
|
import REPORT_TYPES from '@/report-types';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
|
@ -1,65 +1,9 @@
|
|||||||
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 VERSION = '0.3';
|
||||||
|
|
||||||
export const NORMAL_ICON_SCALE = 0.625;
|
export const NORMAL_ICON_SCALE = 0.625;
|
||||||
export const LARGE_ICON_SCALE = 1.0;
|
export const LARGE_ICON_SCALE = 1.0;
|
||||||
export const ICON_ANCHOR = [0.5, 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
|
// Display order of the report types
|
||||||
export const REPORT_TYPES_ORDER = ['gcum', 'interrupt', 'obstacle', 'pothole', 'accident', 'misc'];
|
export const REPORT_TYPES_ORDER = ['gcum', 'interrupt', 'obstacle', 'pothole', 'accident', 'misc'];
|
||||||
|
|
||||||
|
57
src/report-types.js
Normal file
57
src/report-types.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
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,
|
||||||
|
},
|
||||||
|
};
|
99
src/sw.js
Normal file
99
src/sw.js
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
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
|
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
|
# Proxy pass the API calls to the server part
|
||||||
location /api {
|
location /api {
|
||||||
limit_req zone=cycloAPI burst=3 nodelay; # Add rate-limiting on top of the 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"
|
parseurl "~1.3.2"
|
||||||
send "0.16.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:
|
set-blocking@^2.0.0, set-blocking@~2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
|
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
|
||||||
|
Loading…
Reference in New Issue
Block a user