From b359bb6498fd13bdae9b23d2bf40e9fa52085c59 Mon Sep 17 00:00:00 2001
From: jo <ljonas@riseup.net>
Date: Sat, 17 Dec 2022 18:21:41 +0100
Subject: [PATCH] fix: timeout on spa manifest requests

The previous behaviour had a loop of requests between the front
app and the api when querying the pwa manifest.

This reduce the coupling around the pwa manifest file between the api
and the front app, by uplicating the files so each "service" has a copy
of it, while keeping them in sync and having the front pwa manifest as
single source of truth.

Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2291>
---
 .pre-commit-config.yaml                      | 10 ++++
 api/funkwhale_api/instance/pwa-manifest.json | 48 ++++++++++++++++++
 api/funkwhale_api/instance/views.py          | 21 ++++----
 api/pyproject.toml                           |  7 +--
 api/tests/instance/test_views.py             | 35 ++++++-------
 changes/changelog.d/2006.bugfix              |  1 +
 front/pwa-manifest.json                      | 48 ++++++++++++++++++
 front/vite.config.ts                         | 53 ++------------------
 scripts/sync-pwa-manifest.sh                 |  8 +++
 9 files changed, 151 insertions(+), 80 deletions(-)
 create mode 100644 api/funkwhale_api/instance/pwa-manifest.json
 create mode 100644 changes/changelog.d/2006.bugfix
 create mode 100644 front/pwa-manifest.json
 create mode 100755 scripts/sync-pwa-manifest.sh

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 31818be71..165de4765 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -62,3 +62,13 @@ repos:
     rev: v0.8.0.4
     hooks:
       - id: shellcheck
+
+  - repo: local
+    hooks:
+      - id: pwa-manifest.json
+        name: pwa-manifest.json
+        description: Sync pwa-manifest.json
+        entry: scripts/sync-pwa-manifest.sh
+        pass_filenames: false
+        language: script
+        files: pwa-manifest.json$
diff --git a/api/funkwhale_api/instance/pwa-manifest.json b/api/funkwhale_api/instance/pwa-manifest.json
new file mode 100644
index 000000000..815076139
--- /dev/null
+++ b/api/funkwhale_api/instance/pwa-manifest.json
@@ -0,0 +1,48 @@
+{
+  "name": "Funkwhale",
+  "categories": ["music", "entertainment"],
+  "short_name": "Funkwhale",
+  "description": "Your free and federated audio platform",
+  "icons": [
+    {
+      "src": "android-chrome-192x192.png",
+      "sizes": "192x192",
+      "type": "image/png"
+    },
+    {
+      "src": "android-chrome-512x512.png",
+      "sizes": "512x512",
+      "type": "image/png"
+    }
+  ],
+  "prefer_related_applications": true,
+  "related_applications": [
+    {
+      "platform": "play",
+      "url": "https://play.google.com/store/apps/details?id=audio.funkwhale.ffa",
+      "id": "audio.funkwhale.ffa"
+    },
+    {
+      "platform": "f-droid",
+      "url": "https://f-droid.org/en/packages/audio.funkwhale.ffa/",
+      "id": "audio.funkwhale.ffa"
+    }
+  ],
+  "shortcuts": [
+    {
+      "name": "Search",
+      "url": "/search",
+      "icons": []
+    },
+    {
+      "name": "Library",
+      "url": "/library",
+      "icons": []
+    },
+    {
+      "name": "Channels",
+      "url": "/subscriptions",
+      "icons": []
+    }
+  ]
+}
diff --git a/api/funkwhale_api/instance/views.py b/api/funkwhale_api/instance/views.py
index 2a3e7010d..ffde3db4a 100644
--- a/api/funkwhale_api/instance/views.py
+++ b/api/funkwhale_api/instance/views.py
@@ -1,8 +1,8 @@
 import json
 import logging
+from pathlib import Path
 
 from cache_memoize import cache_memoize
-from django.conf import settings
 from django.urls import reverse
 from django.utils.decorators import method_decorator
 from django.views.decorators.csrf import ensure_csrf_cookie
@@ -14,7 +14,7 @@ from rest_framework import generics, views
 from rest_framework.response import Response
 
 from funkwhale_api import __version__ as funkwhale_version
-from funkwhale_api.common import middleware, preferences
+from funkwhale_api.common import preferences
 from funkwhale_api.common.renderers import ActivityStreamRenderer
 from funkwhale_api.federation.actors import get_service_actor
 from funkwhale_api.federation.models import Domain
@@ -118,6 +118,10 @@ class NodeInfo(views.APIView):
         )
 
 
+PWA_MANIFEST_PATH = Path(__file__).parent / "pwa-manifest.json"
+PWA_MANIFEST: dict = json.loads(PWA_MANIFEST_PATH.read_text(encoding="utf-8"))
+
+
 class SpaManifest(generics.GenericAPIView):
     permission_classes = []
     authentication_classes = []
@@ -126,18 +130,15 @@ class SpaManifest(generics.GenericAPIView):
 
     @extend_schema(operation_id="get_spa_manifest")
     def get(self, request):
-        existing_manifest = middleware.get_spa_file(
-            settings.FUNKWHALE_SPA_HTML_ROOT, "manifest.json"
-        )
-        parsed_manifest = json.loads(existing_manifest)
+        manifest = PWA_MANIFEST.copy()
         instance_name = preferences.get("instance__name")
         if instance_name:
-            parsed_manifest["short_name"] = instance_name
-            parsed_manifest["name"] = instance_name
+            manifest["short_name"] = instance_name
+            manifest["name"] = instance_name
         instance_description = preferences.get("instance__short_description")
         if instance_description:
-            parsed_manifest["description"] = instance_description
-        serializer = self.get_serializer(parsed_manifest)
+            manifest["description"] = instance_description
+        serializer = self.get_serializer(manifest)
         return Response(
             serializer.data, status=200, content_type="application/manifest+json"
         )
diff --git a/api/pyproject.toml b/api/pyproject.toml
index ab94f224c..59ebc4766 100644
--- a/api/pyproject.toml
+++ b/api/pyproject.toml
@@ -14,9 +14,10 @@ packages = [
     { include = "config" },
 ]
 include = [
-    { path = "*.txt" },
-    { path = "*.png" },
-    { path = "*.html" },
+  { path = "*.html" },
+  { path = "*.json" },
+  { path = "*.png" },
+  { path = "*.txt" },
 ]
 exclude = ["tests"]
 
diff --git a/api/tests/instance/test_views.py b/api/tests/instance/test_views.py
index afb97ac77..f4a0d3d45 100644
--- a/api/tests/instance/test_views.py
+++ b/api/tests/instance/test_views.py
@@ -1,4 +1,4 @@
-import json
+from unittest import mock
 
 from django.urls import reverse
 
@@ -38,20 +38,21 @@ def test_admin_settings_correct_permission(db, logged_in_api_client, preferences
     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 = {}
-    manifest = tmp_path / "manifest.json"
-    expected = {
-        "name": "Test pod",
-        "short_name": "Test pod",
-        "description": "Test description",
-    }
-    manifest.write_bytes(json.dumps(base_payload).encode())
+def test_manifest_endpoint(api_client, preferences):
+    with mock.patch(
+        "funkwhale_api.instance.views.PWA_MANIFEST",
+        {"lang": "unchanged"},
+    ):
+        preferences["instance__name"] = "Test pod"
+        preferences["instance__short_description"] = "Test description"
+        expected = {
+            "lang": "unchanged",
+            "name": "Test pod",
+            "short_name": "Test pod",
+            "description": "Test description",
+        }
 
-    url = reverse("api:v1:instance:spa-manifest")
-    response = api_client.get(url)
-    assert response.status_code == 200
-    assert response.data == expected
+        url = reverse("api:v1:instance:spa-manifest")
+        response = api_client.get(url)
+        assert response.status_code == 200
+        assert response.data == expected
diff --git a/changes/changelog.d/2006.bugfix b/changes/changelog.d/2006.bugfix
new file mode 100644
index 000000000..3ac481a34
--- /dev/null
+++ b/changes/changelog.d/2006.bugfix
@@ -0,0 +1 @@
+Fix timeout on spa manifest requests
diff --git a/front/pwa-manifest.json b/front/pwa-manifest.json
new file mode 100644
index 000000000..815076139
--- /dev/null
+++ b/front/pwa-manifest.json
@@ -0,0 +1,48 @@
+{
+  "name": "Funkwhale",
+  "categories": ["music", "entertainment"],
+  "short_name": "Funkwhale",
+  "description": "Your free and federated audio platform",
+  "icons": [
+    {
+      "src": "android-chrome-192x192.png",
+      "sizes": "192x192",
+      "type": "image/png"
+    },
+    {
+      "src": "android-chrome-512x512.png",
+      "sizes": "512x512",
+      "type": "image/png"
+    }
+  ],
+  "prefer_related_applications": true,
+  "related_applications": [
+    {
+      "platform": "play",
+      "url": "https://play.google.com/store/apps/details?id=audio.funkwhale.ffa",
+      "id": "audio.funkwhale.ffa"
+    },
+    {
+      "platform": "f-droid",
+      "url": "https://f-droid.org/en/packages/audio.funkwhale.ffa/",
+      "id": "audio.funkwhale.ffa"
+    }
+  ],
+  "shortcuts": [
+    {
+      "name": "Search",
+      "url": "/search",
+      "icons": []
+    },
+    {
+      "name": "Library",
+      "url": "/library",
+      "icons": []
+    },
+    {
+      "name": "Channels",
+      "url": "/subscriptions",
+      "icons": []
+    }
+  ]
+}
diff --git a/front/vite.config.ts b/front/vite.config.ts
index 8045223b7..d9062160a 100644
--- a/front/vite.config.ts
+++ b/front/vite.config.ts
@@ -5,6 +5,8 @@ import VueI18n from '@intlify/vite-plugin-vue-i18n'
 import { VitePWA } from 'vite-plugin-pwa'
 import { resolve } from 'path'
 
+import manifest from './pwa-manifest.json'
+
 const port = +(process.env.VUE_PORT ?? 8080)
 
 // https://vitejs.dev/config/
@@ -35,56 +37,7 @@ export default defineConfig(({ mode }) => ({
         type: 'module',
         navigateFallback: 'index.html'
       },
-      manifest: {
-        name: 'Funkwhale',
-        categories: ['music', 'entertainment'],
-        start_url: undefined,
-        scope: undefined,
-        short_name: 'Funkwhale',
-        description: 'Your free and federated audio platform',
-        icons: [
-          {
-            src: 'android-chrome-192x192.png',
-            sizes: '192x192',
-            type: 'image/png'
-          },
-          {
-            src: 'android-chrome-512x512.png',
-            sizes: '512x512',
-            type: 'image/png'
-          }
-        ],
-        prefer_related_applications: true,
-        related_applications: [
-          {
-            platform: 'play',
-            url: 'https://play.google.com/store/apps/details?id=audio.funkwhale.ffa',
-            id: 'audio.funkwhale.ffa'
-          },
-          {
-            platform: 'f-droid',
-            url: 'https://f-droid.org/en/packages/audio.funkwhale.ffa/',
-            id: 'audio.funkwhale.ffa'
-          }
-        ],
-        shortcuts: [
-          {
-            name: 'Search',
-            url: '/search',
-            icons: []
-          },
-          {
-            name: 'Library',
-            url: '/library',
-            icons: []
-          },
-          {
-            name: 'Channels',
-            url: '/subscriptions',
-            icons: []
-          }
-        ]
-      }
+      manifest
     })
   ],
   server: {
diff --git a/scripts/sync-pwa-manifest.sh b/scripts/sync-pwa-manifest.sh
new file mode 100755
index 000000000..3d549e24c
--- /dev/null
+++ b/scripts/sync-pwa-manifest.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+
+set -eu
+
+SRC="front/pwa-manifest.json"
+DEST="api/funkwhale_api/instance/pwa-manifest.json"
+
+cp "$SRC" "$DEST"