Merge branch 'progressive-web-application' into 'develop'
Progressive web application See merge request funkwhale/funkwhale!983
This commit is contained in:
commit
ebf774d5ab
|
@ -117,6 +117,13 @@ FUNKWHALE_SPA_HTML_CACHE_DURATION = env.int(
|
|||
FUNKWHALE_EMBED_URL = env(
|
||||
"FUNKWHALE_EMBED_URL", default=FUNKWHALE_URL + "/front/embed.html"
|
||||
)
|
||||
FUNKWHALE_SPA_REWRITE_MANIFEST = env.bool(
|
||||
"FUNKWHALE_SPA_REWRITE_MANIFEST", default=True
|
||||
)
|
||||
FUNKWHALE_SPA_REWRITE_MANIFEST_URL = env.bool(
|
||||
"FUNKWHALE_SPA_REWRITE_MANIFEST_URL", default=None
|
||||
)
|
||||
|
||||
APP_NAME = "Funkwhale"
|
||||
|
||||
# XXX: deprecated, see #186
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import html
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import xml.sax.saxutils
|
||||
|
||||
|
@ -9,6 +11,8 @@ from django.core.cache import caches
|
|||
from django import urls
|
||||
from rest_framework import views
|
||||
|
||||
from funkwhale_api.federation import utils as federation_utils
|
||||
|
||||
from . import preferences
|
||||
from . import session
|
||||
from . import throttling
|
||||
|
@ -26,6 +30,13 @@ def should_fallback_to_spa(path):
|
|||
def serve_spa(request):
|
||||
html = get_spa_html(settings.FUNKWHALE_SPA_HTML_ROOT)
|
||||
head, tail = html.split("</head>", 1)
|
||||
if settings.FUNKWHALE_SPA_REWRITE_MANIFEST:
|
||||
new_url = (
|
||||
settings.FUNKWHALE_SPA_REWRITE_MANIFEST_URL
|
||||
or federation_utils.full_url(urls.reverse("api:v1:instance:spa-manifest"))
|
||||
)
|
||||
head = replace_manifest_url(head, new_url)
|
||||
|
||||
if not preferences.get("common__api_authentication_required"):
|
||||
try:
|
||||
request_tags = get_request_head_tags(request) or []
|
||||
|
@ -66,17 +77,34 @@ def serve_spa(request):
|
|||
return http.HttpResponse(head + tail)
|
||||
|
||||
|
||||
MANIFEST_LINK_REGEX = re.compile(r"<link .*rel=(?:'|\")?manifest(?:'|\")?.*>")
|
||||
|
||||
|
||||
def replace_manifest_url(head, new_url):
|
||||
replacement = '<link rel=manifest href="{}">'.format(new_url)
|
||||
head = MANIFEST_LINK_REGEX.sub(replacement, head)
|
||||
return head
|
||||
|
||||
|
||||
def get_spa_html(spa_url):
|
||||
return get_spa_file(spa_url, "index.html")
|
||||
|
||||
|
||||
def get_spa_file(spa_url, name):
|
||||
if spa_url.startswith("/"):
|
||||
# XXX: spa_url is an absolute path to index.html, on the local disk.
|
||||
# However, we may want to access manifest.json or other files as well, so we
|
||||
# strip the filename
|
||||
path = os.path.join(os.path.dirname(spa_url), name)
|
||||
# we try to open a local file
|
||||
with open(spa_url) as f:
|
||||
with open(path) as f:
|
||||
return f.read()
|
||||
cache_key = "spa-html:{}".format(spa_url)
|
||||
cache_key = "spa-file:{}:{}".format(spa_url, name)
|
||||
cached = caches["local"].get(cache_key)
|
||||
if cached:
|
||||
return cached
|
||||
|
||||
response = session.get_session().get(utils.join_url(spa_url, "index.html"),)
|
||||
response = session.get_session().get(utils.join_url(spa_url, name),)
|
||||
response.raise_for_status()
|
||||
content = response.text
|
||||
caches["local"].set(cache_key, content, settings.FUNKWHALE_SPA_HTML_CACHE_DURATION)
|
||||
|
|
|
@ -9,4 +9,5 @@ admin_router.register(r"admin/settings", views.AdminSettings, "admin-settings")
|
|||
urlpatterns = [
|
||||
url(r"^nodeinfo/2.0/?$", views.NodeInfo.as_view(), name="nodeinfo-2.0"),
|
||||
url(r"^settings/?$", views.InstanceSettings.as_view(), name="settings"),
|
||||
url(r"^spa-manifest.json", views.SpaManifest.as_view(), name="spa-manifest"),
|
||||
] + admin_router.urls
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
import json
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from dynamic_preferences.api import serializers
|
||||
from dynamic_preferences.api import viewsets as preferences_viewsets
|
||||
from dynamic_preferences.registries import global_preferences_registry
|
||||
from rest_framework import views
|
||||
from rest_framework.response import Response
|
||||
|
||||
from funkwhale_api.common import middleware
|
||||
from funkwhale_api.common import preferences
|
||||
from funkwhale_api.users.oauth import permissions as oauth_permissions
|
||||
|
||||
from . import nodeinfo
|
||||
|
@ -39,3 +45,23 @@ class NodeInfo(views.APIView):
|
|||
def get(self, request, *args, **kwargs):
|
||||
data = nodeinfo.get()
|
||||
return Response(data, status=200, content_type=NODEINFO_2_CONTENT_TYPE)
|
||||
|
||||
|
||||
class SpaManifest(views.APIView):
|
||||
permission_classes = []
|
||||
authentication_classes = []
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
existing_manifest = middleware.get_spa_file(
|
||||
settings.FUNKWHALE_SPA_HTML_ROOT, "manifest.json"
|
||||
)
|
||||
parsed_manifest = json.loads(existing_manifest)
|
||||
parsed_manifest["short_name"] = settings.APP_NAME
|
||||
instance_name = preferences.get("instance__name")
|
||||
if instance_name:
|
||||
parsed_manifest["short_name"] = instance_name
|
||||
parsed_manifest["name"] = instance_name
|
||||
instance_description = preferences.get("instance__short_description")
|
||||
if instance_description:
|
||||
parsed_manifest["description"] = instance_description
|
||||
return Response(parsed_manifest, status=200)
|
||||
|
|
|
@ -2,6 +2,9 @@ import time
|
|||
import pytest
|
||||
|
||||
from django.http import HttpResponse
|
||||
from django.urls import reverse
|
||||
|
||||
from funkwhale_api.federation import utils as federation_utils
|
||||
|
||||
from funkwhale_api.common import middleware
|
||||
from funkwhale_api.common import throttling
|
||||
|
@ -112,7 +115,7 @@ def test_get_default_head_tags(preferences, settings):
|
|||
|
||||
|
||||
def test_get_spa_html_from_cache(local_cache):
|
||||
local_cache.set("spa-html:http://test", "hello world")
|
||||
local_cache.set("spa-file:http://test:index.html", "hello world")
|
||||
|
||||
assert middleware.get_spa_html("http://test") == "hello world"
|
||||
|
||||
|
@ -124,16 +127,16 @@ def test_get_spa_html_from_http(local_cache, r_mock, mocker, settings):
|
|||
|
||||
assert middleware.get_spa_html("http://test") == "hello world"
|
||||
cache_set.assert_called_once_with(
|
||||
"spa-html:{}".format(url),
|
||||
"spa-file:{}:index.html".format(url),
|
||||
"hello world",
|
||||
settings.FUNKWHALE_SPA_HTML_CACHE_DURATION,
|
||||
)
|
||||
|
||||
|
||||
def test_get_spa_html_from_disk(tmpfile):
|
||||
with open(tmpfile.name, "wb") as f:
|
||||
f.write(b"hello world")
|
||||
assert middleware.get_spa_html(tmpfile.name) == "hello world"
|
||||
def test_get_spa_html_from_disk(tmp_path):
|
||||
index = tmp_path / "index.html"
|
||||
index.write_bytes(b"hello world")
|
||||
assert middleware.get_spa_html(str(index)) == "hello world"
|
||||
|
||||
|
||||
def test_get_route_head_tags(mocker, settings):
|
||||
|
@ -225,3 +228,97 @@ def test_throttle_status_middleware_returns_proper_response(mocker):
|
|||
|
||||
response = m(request)
|
||||
assert response.status_code == 429
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"link, new_url, expected",
|
||||
[
|
||||
(
|
||||
"<link rel=manifest href=/manifest.json>",
|
||||
"custom_url",
|
||||
'<link rel=manifest href="custom_url">',
|
||||
),
|
||||
(
|
||||
"<link href=/manifest.json rel=manifest>",
|
||||
"custom_url",
|
||||
'<link rel=manifest href="custom_url">',
|
||||
),
|
||||
(
|
||||
'<link href="/manifest.json" rel=manifest>',
|
||||
"custom_url",
|
||||
'<link rel=manifest href="custom_url">',
|
||||
),
|
||||
(
|
||||
'<link href=/manifest.json rel="manifest">',
|
||||
"custom_url",
|
||||
'<link rel=manifest href="custom_url">',
|
||||
),
|
||||
(
|
||||
"<link href='/manifest.json' rel=manifest>",
|
||||
"custom_url",
|
||||
'<link rel=manifest href="custom_url">',
|
||||
),
|
||||
(
|
||||
"<link href=/manifest.json rel='manifest'>",
|
||||
"custom_url",
|
||||
'<link rel=manifest href="custom_url">',
|
||||
),
|
||||
# not matching
|
||||
(
|
||||
"<link href=/manifest.json rel=notmanifest>",
|
||||
"custom_url",
|
||||
"<link href=/manifest.json rel=notmanifest>",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_rewrite_manifest_json_url(link, new_url, expected, mocker, settings):
|
||||
settings.FUNKWHALE_SPA_REWRITE_MANIFEST = True
|
||||
settings.FUNKWHALE_SPA_REWRITE_MANIFEST_URL = new_url
|
||||
spa_html = "<html><head>{}</head></html>".format(link)
|
||||
request = mocker.Mock(path="/")
|
||||
mocker.patch.object(middleware, "get_spa_html", return_value=spa_html)
|
||||
mocker.patch.object(
|
||||
middleware, "get_default_head_tags", return_value=[],
|
||||
)
|
||||
response = middleware.serve_spa(request)
|
||||
|
||||
assert response.status_code == 200
|
||||
expected_html = "<html><head>{}\n\n</head></html>".format(expected)
|
||||
assert response.content == expected_html.encode()
|
||||
|
||||
|
||||
def test_rewrite_manifest_json_url_rewrite_disabled(mocker, settings):
|
||||
settings.FUNKWHALE_SPA_REWRITE_MANIFEST = False
|
||||
settings.FUNKWHALE_SPA_REWRITE_MANIFEST_URL = "custom_url"
|
||||
spa_html = "<html><head><link href=/manifest.json rel=manifest></head></html>"
|
||||
request = mocker.Mock(path="/")
|
||||
mocker.patch.object(middleware, "get_spa_html", return_value=spa_html)
|
||||
mocker.patch.object(
|
||||
middleware, "get_default_head_tags", return_value=[],
|
||||
)
|
||||
response = middleware.serve_spa(request)
|
||||
|
||||
assert response.status_code == 200
|
||||
expected_html = (
|
||||
"<html><head><link href=/manifest.json rel=manifest>\n\n</head></html>"
|
||||
)
|
||||
assert response.content == expected_html.encode()
|
||||
|
||||
|
||||
def test_rewrite_manifest_json_url_rewrite_default_url(mocker, settings):
|
||||
settings.FUNKWHALE_SPA_REWRITE_MANIFEST = True
|
||||
settings.FUNKWHALE_SPA_REWRITE_MANIFEST_URL = None
|
||||
spa_html = "<html><head><link href=/manifest.json rel=manifest></head></html>"
|
||||
expected_url = federation_utils.full_url(reverse("api:v1:instance:spa-manifest"))
|
||||
request = mocker.Mock(path="/")
|
||||
mocker.patch.object(middleware, "get_spa_html", return_value=spa_html)
|
||||
mocker.patch.object(
|
||||
middleware, "get_default_head_tags", return_value=[],
|
||||
)
|
||||
response = middleware.serve_spa(request)
|
||||
|
||||
assert response.status_code == 200
|
||||
expected_html = '<html><head><link rel=manifest href="{}">\n\n</head></html>'.format(
|
||||
expected_url
|
||||
)
|
||||
assert response.content == expected_html.encode()
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import json
|
||||
|
||||
from django.urls import reverse
|
||||
|
||||
|
||||
|
@ -37,3 +39,25 @@ def test_admin_settings_correct_permission(db, logged_in_api_client, preferences
|
|||
|
||||
assert response.status_code == 200
|
||||
assert len(response.data) == len(preferences.all())
|
||||
|
||||
|
||||
def test_manifest_endpoint(api_client, mocker, preferences, tmp_path, settings):
|
||||
settings.FUNKWHALE_SPA_HTML_ROOT = str(tmp_path / "index.html")
|
||||
preferences["instance__name"] = "Test pod"
|
||||
preferences["instance__short_description"] = "Test description"
|
||||
base_payload = {
|
||||
"foo": "bar",
|
||||
}
|
||||
manifest = tmp_path / "manifest.json"
|
||||
expected = {
|
||||
"foo": "bar",
|
||||
"name": "Test pod",
|
||||
"short_name": "Test pod",
|
||||
"description": "Test description",
|
||||
}
|
||||
manifest.write_bytes(json.dumps(base_payload).encode())
|
||||
|
||||
url = reverse("api:v1:instance:spa-manifest")
|
||||
response = api_client.get(url)
|
||||
assert response.status_code == 200
|
||||
assert response.data == expected
|
||||
|
|
|
@ -2,13 +2,15 @@
|
|||
"name": "front",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"description": "Funkwhale front-end",
|
||||
"author": "Eliot Berriot <contact@eliotberriot.com>",
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve --port ${VUE_PORT:-8000} --host ${VUE_HOST:-0.0.0.0}",
|
||||
"build": "scripts/i18n-compile.sh && vue-cli-service build",
|
||||
"test:unit": "vue-cli-service test:unit",
|
||||
"lint": "vue-cli-service lint",
|
||||
"i18n-extract": "scripts/i18n-extract.sh",
|
||||
"i18n-compile": "scripts/i18n-compile.sh",
|
||||
"test:unit": "vue-cli-service test:unit"
|
||||
"i18n-extract": "scripts/i18n-extract.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.18.0",
|
||||
|
@ -23,6 +25,7 @@
|
|||
"masonry-layout": "^4.2.2",
|
||||
"moment": "^2.22.2",
|
||||
"qs": "^6.7.0",
|
||||
"register-service-worker": "^1.6.2",
|
||||
"sanitize-html": "^1.20.1",
|
||||
"showdown": "^1.8.6",
|
||||
"vue": "^2.6.10",
|
||||
|
@ -40,6 +43,7 @@
|
|||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "^3.0.0",
|
||||
"@vue/cli-plugin-eslint": "^3.0.0",
|
||||
"@vue/cli-plugin-pwa": "^4.1.2",
|
||||
"@vue/cli-plugin-unit-mocha": "^3.0.0",
|
||||
"@vue/cli-service": "^3.0.0",
|
||||
"@vue/test-utils": "^1.0.0-beta.20",
|
||||
|
@ -104,7 +108,5 @@
|
|||
"iOS >= 9",
|
||||
"Android >= 4",
|
||||
"not dead"
|
||||
],
|
||||
"author": "Eliot Berriot <contact@eliotberriot.com>",
|
||||
"description": "Funkwhale front-end"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -11,7 +11,16 @@
|
|||
<template>
|
||||
<sidebar></sidebar>
|
||||
<set-instance-modal @update:show="showSetInstanceModal = $event" :show="showSetInstanceModal"></set-instance-modal>
|
||||
<service-messages v-if="messages.length > 0"/>
|
||||
<service-messages>
|
||||
<message key="refreshApp" class="large info" v-if="serviceWorker.updateAvailable">
|
||||
<p>
|
||||
<translate translate-context="App/Message/Paragraph">A new version of the app is available.</translate>
|
||||
</p>
|
||||
<button class="ui basic button" @click.stop="updateApp">
|
||||
<translate translate-context="App/Message/Button">Update</translate>
|
||||
</button>
|
||||
</message>
|
||||
</service-messages>
|
||||
<transition name="queue">
|
||||
<queue @touch-progress="$refs.player.setCurrentTime($event)" v-if="$store.state.ui.queueFocused"></queue>
|
||||
</transition>
|
||||
|
@ -62,10 +71,23 @@ export default {
|
|||
bridge: null,
|
||||
instanceUrl: null,
|
||||
showShortcutsModal: false,
|
||||
showSetInstanceModal: false
|
||||
showSetInstanceModal: false,
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
|
||||
if (navigator.serviceWorker) {
|
||||
navigator.serviceWorker.addEventListener(
|
||||
'controllerchange', () => {
|
||||
if (this.serviceWorker.refreshing) return;
|
||||
this.$store.commit('ui/serviceWorker', {
|
||||
refreshing: true
|
||||
})
|
||||
window.location.reload();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
this.openWebsocket()
|
||||
let self = this
|
||||
if (!this.$store.state.ui.selectedLanguage) {
|
||||
|
@ -238,6 +260,11 @@ export default {
|
|||
parts.push(this.$store.state.instance.settings.instance.name.value || 'Funkwhale')
|
||||
document.title = parts.join(' – ')
|
||||
},
|
||||
updateApp () {
|
||||
this.$store.commit('ui/serviceWorker', {updateAvailable: false})
|
||||
if (!this.serviceWorker.registration || !this.serviceWorker.registration.waiting) { return; }
|
||||
this.serviceWorker.registration.waiting.postMessage('skipWaiting');
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
|
@ -246,6 +273,7 @@ export default {
|
|||
playing: state => state.player.playing,
|
||||
bufferProgress: state => state.player.bufferProgress,
|
||||
isLoadingAudio: state => state.player.isLoadingAudio,
|
||||
serviceWorker: state => state.ui.serviceWorker,
|
||||
}),
|
||||
...mapGetters({
|
||||
hasNext: "queue/hasNext",
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
<message v-for="message in displayedMessages" :key="String(message.date)" :class="['large', getLevel(message)]">
|
||||
<p>{{ message.content }}</p>
|
||||
</message>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -276,6 +276,15 @@ export default {
|
|||
if (this.currentTrack) {
|
||||
this.getSound(this.currentTrack)
|
||||
}
|
||||
// Add controls for notification drawer
|
||||
if ('mediaSession' in navigator) {
|
||||
navigator.mediaSession.setActionHandler('play', this.togglePlay);
|
||||
navigator.mediaSession.setActionHandler('pause', this.togglePlay);
|
||||
navigator.mediaSession.setActionHandler('seekforward', this.seekForward);
|
||||
navigator.mediaSession.setActionHandler('seekbackward', this.seekBackward);
|
||||
navigator.mediaSession.setActionHandler('nexttrack', this.next);
|
||||
navigator.mediaSession.setActionHandler('previoustrack', this.previous);
|
||||
}
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.dummyAudio.unload()
|
||||
|
@ -380,6 +389,7 @@ export default {
|
|||
self.$store.commit('player/resetErrorCount')
|
||||
self.$store.commit('player/errored', false)
|
||||
self.$store.commit('player/duration', this.duration())
|
||||
|
||||
},
|
||||
onloaderror: function (sound, error) {
|
||||
self.removeFromCache(this)
|
||||
|
@ -484,6 +494,12 @@ export default {
|
|||
this.$store.dispatch('player/updateProgress', position)
|
||||
}
|
||||
},
|
||||
seekForward () {
|
||||
this.seek (5)
|
||||
},
|
||||
seekBackward () {
|
||||
this.seek (-5)
|
||||
},
|
||||
observeProgress: function (enable) {
|
||||
let self = this
|
||||
if (enable) {
|
||||
|
@ -684,6 +700,23 @@ export default {
|
|||
this.playTimeout = setTimeout(async () => {
|
||||
await self.loadSound(newValue, oldValue)
|
||||
}, 500);
|
||||
// If the session is playing as a PWA, populate the notification
|
||||
// with details from the track
|
||||
if ('mediaSession' in navigator) {
|
||||
navigator.mediaSession.metadata = new MediaMetadata({
|
||||
title: this.currentTrack.title,
|
||||
artist: this.currentTrack.artist.name,
|
||||
album: this.currentTrack.album.title,
|
||||
artwork: [
|
||||
{ src: this.currentTrack.album.cover.original, sizes: '96x96', type: 'image/png' },
|
||||
{ src: this.currentTrack.album.cover.original, sizes: '128x128', type: 'image/png' },
|
||||
{ src: this.currentTrack.album.cover.original, sizes: '192x192', type: 'image/png' },
|
||||
{ src: this.currentTrack.album.cover.original, sizes: '256x256', type: 'image/png' },
|
||||
{ src: this.currentTrack.album.cover.original, sizes: '384x384', type: 'image/png' },
|
||||
{ src: this.currentTrack.album.cover.original, sizes: '512x512', type: 'image/png' },
|
||||
]
|
||||
});
|
||||
}
|
||||
},
|
||||
immediate: false
|
||||
},
|
||||
|
|
|
@ -20,6 +20,7 @@ import locales from '@/locales'
|
|||
|
||||
import filters from '@/filters' // eslint-disable-line
|
||||
import globals from '@/components/globals' // eslint-disable-line
|
||||
import './registerServiceWorker'
|
||||
|
||||
sync(store, router)
|
||||
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/* eslint-disable no-console */
|
||||
|
||||
import { register } from 'register-service-worker'
|
||||
|
||||
import store from './store'
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
register(`${process.env.BASE_URL}service-worker.js`, {
|
||||
ready () {
|
||||
console.log(
|
||||
'App is being served from cache by a service worker.'
|
||||
)
|
||||
},
|
||||
registered (registration) {
|
||||
console.log('Service worker has been registered.')
|
||||
// check for updates every 2 hours
|
||||
var checkInterval = 1000 * 60 * 60 * 2
|
||||
// var checkInterval = 1000 * 5
|
||||
setInterval(() => {
|
||||
console.log('Checking for service worker update…')
|
||||
registration.update();
|
||||
}, checkInterval);
|
||||
},
|
||||
cached () {
|
||||
console.log('Content has been cached for offline use.')
|
||||
},
|
||||
updatefound () {
|
||||
console.log('New content is downloading.')
|
||||
},
|
||||
updated (registration) {
|
||||
console.log('New content is available; please refresh!')
|
||||
store.commit('ui/serviceWorker', {updateAvailable: true, registration: registration})
|
||||
},
|
||||
offline () {
|
||||
console.log('No internet connection found. App is running in offline mode.')
|
||||
},
|
||||
error (error) {
|
||||
console.error('Error during service worker registration:', error)
|
||||
}
|
||||
})
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// This is the code piece that GenerateSW mode can't provide for us.
|
||||
// This code listens for the user's confirmation to update the app.
|
||||
self.addEventListener('message', (e) => {
|
||||
if (!e.data) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (e.data) {
|
||||
case 'skipWaiting':
|
||||
self.skipWaiting();
|
||||
break;
|
||||
default:
|
||||
// NOOP
|
||||
break;
|
||||
}
|
||||
});
|
||||
workbox.core.clientsClaim();
|
||||
|
||||
// The precaching code provided by Workbox.
|
||||
self.__precacheManifest = [].concat(self.__precacheManifest || []);
|
||||
console.log('Files to be cached by service worker [before filtering]', self.__precacheManifest.length);
|
||||
var excludedUrlsPrefix = [
|
||||
'/js/locale-',
|
||||
'/js/moment-locale-',
|
||||
'/js/admin',
|
||||
'/css/admin',
|
||||
];
|
||||
self.__precacheManifest = self.__precacheManifest.filter((e) => {
|
||||
return !excludedUrlsPrefix.some(prefix => e.url.startsWith(prefix))
|
||||
});
|
||||
console.log('Files to be cached by service worker [after filtering]', self.__precacheManifest.length);
|
||||
// workbox.precaching.suppressWarnings(); // Only used with Vue CLI 3 and Workbox v3.
|
||||
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
|
|
@ -68,6 +68,11 @@ export default {
|
|||
ordering: "creation_date",
|
||||
},
|
||||
},
|
||||
serviceWorker: {
|
||||
refreshing: false,
|
||||
registration: null,
|
||||
updateAvailable: false,
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
showInstanceSupportMessage: (state, getters, rootState) => {
|
||||
|
@ -160,6 +165,10 @@ export default {
|
|||
orderingDirection: (state, {route, value}) => {
|
||||
state.routePreferences[route].orderingDirection = value
|
||||
},
|
||||
|
||||
serviceWorker: (state, value) => {
|
||||
state.serviceWorker = {...state.serviceWorker, ...value}
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
fetchUnreadNotifications ({commit}, payload) {
|
||||
|
|
|
@ -32,6 +32,46 @@ plugins.push(
|
|||
module.exports = {
|
||||
baseUrl: process.env.BASE_URL || '/front/',
|
||||
productionSourceMap: false,
|
||||
// Add settings for manifest file
|
||||
pwa: {
|
||||
name: 'Funkwhale',
|
||||
themeColor: '#f2711c',
|
||||
msTileColor: '#000000',
|
||||
appleMobileWebAppCapable: 'yes',
|
||||
appleMobileWebAppStatusBarStyle: 'black',
|
||||
display: 'minimal-ui',
|
||||
workboxPluginMode: 'InjectManifest',
|
||||
manifestOptions: {
|
||||
start_url: '.',
|
||||
description: 'A social platform to enjoy and share music',
|
||||
scope: "/",
|
||||
categories: ["music"],
|
||||
icons: [
|
||||
{
|
||||
'src': 'favicon.png',
|
||||
'sizes': '192x192',
|
||||
'type': 'image/png'
|
||||
}, {
|
||||
'src': 'favicon.png',
|
||||
'sizes': '512x512',
|
||||
'type': 'image/png'
|
||||
},
|
||||
]
|
||||
},
|
||||
workboxOptions: {
|
||||
importWorkboxFrom: 'local',
|
||||
// swSrc is required in InjectManifest mode.
|
||||
swSrc: 'src/service-worker.js',
|
||||
swDest: 'service-worker.js',
|
||||
},
|
||||
iconPaths: {
|
||||
favicon32: 'favicon.png',
|
||||
favicon16: 'favicon.png',
|
||||
appleTouchIcon: 'favicon.png',
|
||||
maskIcon: 'favicon.png',
|
||||
msTileImage: 'favicon.png'
|
||||
}
|
||||
},
|
||||
pages: {
|
||||
embed: {
|
||||
entry: 'src/embed.js',
|
||||
|
|
635
front/yarn.lock
635
front/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue