From 866eab76c43d9592746fd2d82c6d33e8de39e868 Mon Sep 17 00:00:00 2001 From: Maxence Charriere Date: Tue, 3 Dec 2024 11:53:23 +0100 Subject: [PATCH] Fix safari maskable (#1004) * fix maskable * update manifest --- pkg/app/gen/manifest.webmanifest | 20 ++++++++++---------- pkg/app/http.go | 2 +- pkg/app/scripts.go | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/pkg/app/gen/manifest.webmanifest b/pkg/app/gen/manifest.webmanifest index 4c8f72cb..078a8274 100644 --- a/pkg/app/gen/manifest.webmanifest +++ b/pkg/app/gen/manifest.webmanifest @@ -3,26 +3,26 @@ "name": "{{.Name}}", "description": "{{.Description}}", "icons": [ - { + {{if .MaskableIcon}}{ + "src": "{{.MaskableIcon}}", + "type": "image/png", + "purpose": "maskable", + "sizes": "512x512" + },{{end}} + {{if .SVGIcon}}{ "src": "{{.SVGIcon}}", "type": "image/svg+xml", "sizes": "any" - }, - { + },{{end}} + {{if .LargeIcon}}{ "src": "{{.LargeIcon}}", "type": "image/png", "sizes": "512x512" - }, + },{{end}} { "src": "{{.DefaultIcon}}", "type": "image/png", "sizes": "192x192" - }, - { - "src": "{{.MaskableIcon}}", - "type": "image/png", - "purpose": "maskable", - "sizes": "192x192" } ], "scope": "{{.Scope}}", diff --git a/pkg/app/http.go b/pkg/app/http.go index ccaa99ee..b3a8cf88 100644 --- a/pkg/app/http.go +++ b/pkg/app/http.go @@ -794,7 +794,7 @@ type Icon struct { SVG string // Maskable specifies the path or URL to an adaptive icon designed for various - // operating system shapes. This icon must be a PNG image with 192x192 pixels. + // operating system shapes. This icon must be a PNG image with 512x512 pixels. // // Used in PWA manifests and as meta tags for Apple browsers, these icons adapt // to device or browser shape requirements, avoiding unsightly cropping. diff --git a/pkg/app/scripts.go b/pkg/app/scripts.go index 5800de0c..d815c5f2 100644 --- a/pkg/app/scripts.go +++ b/pkg/app/scripts.go @@ -12,7 +12,7 @@ const ( appJS = "// -----------------------------------------------------------------------------\n// go-app\n// -----------------------------------------------------------------------------\nvar goappNav = function () {};\n\nvar goappUpdatedBeforeWasmLoaded = false;\nvar goappOnUpdate = function () {\n goappUpdatedBeforeWasmLoaded = true;\n};\n\nvar goappAppInstallChangedBeforeWasmLoaded = false;\nvar goappOnAppInstallChange = function () {\n goappAppInstallChangedBeforeWasmLoaded = true;\n};\n\nconst goappEnv = {{.Env}};\nconst goappLoadingLabel = \"{{.LoadingLabel}}\";\nconst goappWasmContentLength = \"{{.WasmContentLength}}\";\nconst goappWasmContentLengthHeader = \"{{.WasmContentLengthHeader}}\";\n\nlet goappServiceWorkerRegistration;\nlet deferredPrompt = null;\n\ngoappInitServiceWorker();\ngoappWatchForUpdate();\ngoappWatchForInstallable();\ngoappInitWebAssembly();\n\n// -----------------------------------------------------------------------------\n// Service Worker\n// -----------------------------------------------------------------------------\nasync function goappInitServiceWorker() {\n if (\"serviceWorker\" in navigator) {\n try {\n const registration = await navigator.serviceWorker.register(\n \"{{.WorkerJS}}\"\n );\n goappServiceWorkerRegistration = registration;\n goappSetupNotifyUpdate(registration);\n goappSetupPushNotification();\n } catch (err) {\n console.error(\"goapp service worker registration failed: \", err);\n }\n }\n}\n\n// -----------------------------------------------------------------------------\n// Update\n// -----------------------------------------------------------------------------\nfunction goappWatchForUpdate() {\n window.addEventListener(\"beforeinstallprompt\", (e) => {\n e.preventDefault();\n deferredPrompt = e;\n goappOnAppInstallChange();\n });\n}\n\nfunction goappSetupNotifyUpdate(registration) {\n registration.addEventListener(\"updatefound\", (event) => {\n const newSW = registration.installing;\n newSW.addEventListener(\"statechange\", (event) => {\n if (!navigator.serviceWorker.controller) {\n return;\n }\n\n switch (newSW.state) {\n case \"activated\":\n goappOnUpdate();\n }\n });\n });\n}\n\nfunction goappTryUpdate() {\n if (!goappServiceWorkerRegistration) {\n return;\n }\n goappServiceWorkerRegistration.update();\n}\n\n// -----------------------------------------------------------------------------\n// Install\n// -----------------------------------------------------------------------------\nfunction goappWatchForInstallable() {\n window.addEventListener(\"appinstalled\", () => {\n deferredPrompt = null;\n goappOnAppInstallChange();\n });\n}\n\nfunction goappIsAppInstallable() {\n return !goappIsAppInstalled() && deferredPrompt != null;\n}\n\nfunction goappIsAppInstalled() {\n const isStandalone = window.matchMedia(\"(display-mode: standalone)\").matches;\n return isStandalone || navigator.standalone;\n}\n\nasync function goappShowInstallPrompt() {\n deferredPrompt.prompt();\n await deferredPrompt.userChoice;\n deferredPrompt = null;\n}\n\n// -----------------------------------------------------------------------------\n// Environment\n// -----------------------------------------------------------------------------\nfunction goappGetenv(k) {\n return goappEnv[k];\n}\n\n// -----------------------------------------------------------------------------\n// Notifications\n// -----------------------------------------------------------------------------\nfunction goappSetupPushNotification() {\n navigator.serviceWorker.addEventListener(\"message\", (event) => {\n const msg = event.data.goapp;\n if (!msg) {\n return;\n }\n\n if (msg.type !== \"notification\") {\n return;\n }\n\n goappNav(msg.path);\n });\n}\n\nasync function goappSubscribePushNotifications(vapIDpublicKey) {\n try {\n const subscription =\n await goappServiceWorkerRegistration.pushManager.subscribe({\n userVisibleOnly: true,\n applicationServerKey: vapIDpublicKey,\n });\n return JSON.stringify(subscription);\n } catch (err) {\n console.error(err);\n return \"\";\n }\n}\n\nfunction goappNewNotification(jsonNotification) {\n let notification = JSON.parse(jsonNotification);\n\n const title = notification.title;\n delete notification.title;\n\n let path = notification.path;\n if (!path) {\n path = \"/\";\n }\n\n const webNotification = new Notification(title, notification);\n\n webNotification.onclick = () => {\n goappNav(path);\n webNotification.close();\n };\n}\n\n// -----------------------------------------------------------------------------\n// Keep Clean Body\n// -----------------------------------------------------------------------------\nfunction goappKeepBodyClean() {\n const body = document.body;\n const bodyChildrenCount = body.children.length;\n\n const mutationObserver = new MutationObserver(function (mutationList) {\n mutationList.forEach((mutation) => {\n switch (mutation.type) {\n case \"childList\":\n while (body.children.length > bodyChildrenCount) {\n body.removeChild(body.lastChild);\n }\n break;\n }\n });\n });\n\n mutationObserver.observe(document.body, {\n childList: true,\n });\n\n return () => mutationObserver.disconnect();\n}\n\n// -----------------------------------------------------------------------------\n// Web Assembly\n// -----------------------------------------------------------------------------\nasync function goappInitWebAssembly() {\n const loader = document.getElementById(\"app-wasm-loader\");\n\n if (!goappCanLoadWebAssembly()) {\n loader.remove();\n return;\n }\n\n let instantiateStreaming = WebAssembly.instantiateStreaming;\n if (!instantiateStreaming) {\n instantiateStreaming = async (resp, importObject) => {\n const source = await (await resp).arrayBuffer();\n return await WebAssembly.instantiate(source, importObject);\n };\n }\n\n const loaderIcon = document.getElementById(\"app-wasm-loader-icon\");\n const loaderLabel = document.getElementById(\"app-wasm-loader-label\");\n\n try {\n const showProgress = (progress) => {\n loaderLabel.innerText = goappLoadingLabel.replace(\"{progress}\", progress);\n };\n showProgress(0);\n\n const go = new Go();\n const wasm = await instantiateStreaming(\n fetchWithProgress(\"{{.Wasm}}\", showProgress),\n go.importObject\n );\n\n go.run(wasm.instance);\n loader.remove();\n } catch (err) {\n loaderIcon.className = \"goapp-logo\";\n loaderLabel.innerText = err;\n console.error(\"loading wasm failed: \", err);\n }\n}\n\nfunction goappCanLoadWebAssembly() {\n if (\n /bot|googlebot|crawler|spider|robot|crawling/i.test(navigator.userAgent)\n ) {\n return false;\n }\n\n const urlParams = new URLSearchParams(window.location.search);\n return urlParams.get(\"wasm\") !== \"false\";\n}\n\nasync function fetchWithProgress(url, progess) {\n const response = await fetch(url);\n\n let contentLength = goappWasmContentLength;\n if (contentLength <= 0) {\n try {\n contentLength = response.headers.get(goappWasmContentLengthHeader);\n } catch {}\n if (!goappWasmContentLengthHeader || !contentLength) {\n contentLength = response.headers.get(\"Content-Length\");\n }\n }\n\n const total = parseInt(contentLength, 10);\n let loaded = 0;\n\n const progressHandler = function (loaded, total) {\n progess(Math.round((loaded * 100) / total));\n };\n\n var res = new Response(\n new ReadableStream(\n {\n async start(controller) {\n var reader = response.body.getReader();\n for (;;) {\n var { done, value } = await reader.read();\n\n if (done) {\n progressHandler(total, total);\n break;\n }\n\n loaded += value.byteLength;\n progressHandler(loaded, total);\n controller.enqueue(value);\n }\n controller.close();\n },\n },\n {\n status: response.status,\n statusText: response.statusText,\n }\n )\n );\n\n for (var pair of response.headers.entries()) {\n res.headers.set(pair[0], pair[1]);\n }\n\n return res;\n}\n" - manifestJSON = "{\n \"short_name\": \"{{.ShortName}}\",\n \"name\": \"{{.Name}}\",\n \"description\": \"{{.Description}}\",\n \"icons\": [\n {\n \"src\": \"{{.SVGIcon}}\",\n \"type\": \"image/svg+xml\",\n \"sizes\": \"any\"\n },\n {\n \"src\": \"{{.LargeIcon}}\",\n \"type\": \"image/png\",\n \"sizes\": \"512x512\"\n },\n {\n \"src\": \"{{.DefaultIcon}}\",\n \"type\": \"image/png\",\n \"sizes\": \"192x192\"\n },\n {\n \"src\": \"{{.MaskableIcon}}\",\n \"type\": \"image/png\",\n \"purpose\": \"maskable\",\n \"sizes\": \"192x192\"\n }\n ],\n \"scope\": \"{{.Scope}}\",\n \"start_url\": \"{{.StartURL}}\",\n \"background_color\": \"{{.BackgroundColor}}\",\n \"theme_color\": \"{{.ThemeColor}}\",\n \"display\": \"standalone\"\n}" + manifestJSON = "{\n \"short_name\": \"{{.ShortName}}\",\n \"name\": \"{{.Name}}\",\n \"description\": \"{{.Description}}\",\n \"icons\": [\n {{if .MaskableIcon}}{\n \"src\": \"{{.MaskableIcon}}\",\n \"type\": \"image/png\",\n \"purpose\": \"maskable\",\n \"sizes\": \"512x512\"\n },{{end}}\n {{if .SVGIcon}}{\n \"src\": \"{{.SVGIcon}}\",\n \"type\": \"image/svg+xml\",\n \"sizes\": \"any\"\n },{{end}}\n {{if .LargeIcon}}{\n \"src\": \"{{.LargeIcon}}\",\n \"type\": \"image/png\",\n \"sizes\": \"512x512\"\n },{{end}}\n {\n \"src\": \"{{.DefaultIcon}}\",\n \"type\": \"image/png\",\n \"sizes\": \"192x192\"\n }\n ],\n \"scope\": \"{{.Scope}}\",\n \"start_url\": \"{{.StartURL}}\",\n \"background_color\": \"{{.BackgroundColor}}\",\n \"theme_color\": \"{{.ThemeColor}}\",\n \"display\": \"standalone\"\n}" appCSS = "/*------------------------------------------------------------------------------\n Loader\n------------------------------------------------------------------------------*/\n.goapp-app-info {\n position: fixed;\n top: 0;\n left: 0;\n z-index: 1000;\n width: 100vw;\n height: 100vh;\n overflow: hidden;\n\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen,\n Ubuntu, Cantarell, \"Open Sans\", \"Helvetica Neue\", sans-serif;\n font-size: 13px;\n font-weight: 400;\n color: white;\n background-color: #2d2c2c;\n}\n\n@media (prefers-color-scheme: light) {\n .goapp-app-info {\n color: black;\n background-color: #f6f6f6;\n }\n}\n\n.goapp-logo {\n width: 100px;\n height: 100px;\n user-select: none;\n -moz-user-select: none;\n -webkit-user-drag: none;\n -webkit-user-select: none;\n -ms-user-select: none;\n}\n\n.goapp-label {\n margin-top: 12px;\n font-size: 21px;\n font-weight: 100;\n letter-spacing: 1px;\n max-width: 480px;\n text-align: center;\n}\n\n.goapp-spin {\n animation: goapp-spin-frames 1.21s infinite linear;\n}\n\n@keyframes goapp-spin-frames {\n from {\n transform: rotate(0deg);\n }\n\n to {\n transform: rotate(360deg);\n }\n}\n\n/*------------------------------------------------------------------------------\n Not found\n------------------------------------------------------------------------------*/\n.goapp-notfound-title {\n display: flex;\n justify-content: center;\n align-items: center;\n font-size: 65pt;\n font-weight: 100;\n}\n" )