diff --git a/client/css/bootstrap.css b/client/css/bootstrap.css index 5f41c698..768f6dac 100644 --- a/client/css/bootstrap.css +++ b/client/css/bootstrap.css @@ -44,7 +44,7 @@ audio:not([controls]) { } [hidden], template { - display: none; + display: none !important; } a { background: transparent; diff --git a/client/js/loading-error-handlers.js b/client/js/loading-error-handlers.js index 324fce68..79523547 100644 --- a/client/js/loading-error-handlers.js +++ b/client/js/loading-error-handlers.js @@ -60,4 +60,9 @@ }; window.addEventListener("error", window.g_LoungeErrorHandler); + + // Trigger early service worker registration + if ("serviceWorker" in navigator) { + navigator.serviceWorker.register("service-worker.js"); + } })(); diff --git a/client/js/socket-events/configuration.js b/client/js/socket-events/configuration.js index bf117370..3289dfeb 100644 --- a/client/js/socket-events/configuration.js +++ b/client/js/socket-events/configuration.js @@ -8,6 +8,18 @@ const webpush = require("../webpush"); const connect = $("#connect"); const utils = require("../utils"); +window.addEventListener("beforeinstallprompt", (installPromptEvent) => { + $("#webapp-install-button") + .on("click", function() { + if (installPromptEvent && installPromptEvent.prompt) { + installPromptEvent.prompt(); + } + + $(this).prop("hidden", true); + }) + .prop("hidden", false); +}); + socket.on("configuration", function(data) { if (options.initialized) { // Likely a reconnect, request sync for possibly missed settings. diff --git a/client/service-worker.js b/client/service-worker.js index cd0af83f..94aa49dd 100644 --- a/client/service-worker.js +++ b/client/service-worker.js @@ -2,6 +2,62 @@ /* global clients */ "use strict"; +const excludedPathsFromCache = /^(?:socket\.io|storage|uploads|cdn-cgi)\//; + +self.addEventListener("install", function() { + self.skipWaiting(); +}); + +self.addEventListener("activate", function(event) { + event.waitUntil(self.clients.claim()); +}); + +self.addEventListener("fetch", function(event) { + if (event.request.method !== "GET") { + return; + } + + const url = event.request.url; + const scope = self.registration.scope; + + // Skip cross-origin requests + if (!url.startsWith(scope)) { + return; + } + + const path = url.substring(scope.length); + + // Skip ignored paths + if (excludedPathsFromCache.test(path)) { + return; + } + + const uri = new URL(url); + uri.hash = ""; + uri.search = ""; + + event.respondWith(networkOrCache(uri)); +}); + +function networkOrCache(uri) { + return caches.open("thelounge").then(function(cache) { + // Despite the "no-cache" name, it is a conditional request if proper headers are set + return fetch(uri, {cache: "no-cache"}) + .then(function(response) { + if (response.ok) { + return cache.put(uri, response.clone()).then(function() { + return response; + }); + } + }) + .catch(function() { + return cache.match(uri).then(function(matching) { + return matching || Promise.reject("request-not-in-cache"); + }); + }); + }); +} + self.addEventListener("message", function(event) { showNotification(event, event.data); }); diff --git a/client/views/windows/settings.tpl b/client/views/windows/settings.tpl index 0443d1cf..ed9b9e93 100644 --- a/client/views/windows/settings.tpl +++ b/client/views/windows/settings.tpl @@ -15,6 +15,8 @@
+

Native app

+
diff --git a/src/server.js b/src/server.js index f87e3107..bb4f609d 100644 --- a/src/server.js +++ b/src/server.js @@ -39,15 +39,17 @@ module.exports = function() { (Node.js ${colors.green(process.versions.node)} on ${colors.green(process.platform)} ${process.arch})`); log.info(`Configuration file: ${colors.green(Helper.getConfigPath())}`); + const staticOptions = { + redirect: false, + maxAge: 86400 * 1000, + }; + const app = express() .disable("x-powered-by") .use(allRequests) .use(index) - .use(express.static("public")) - .use("/storage/", express.static(Helper.getStoragePath(), { - redirect: false, - maxAge: 86400 * 1000, - })); + .use(express.static(path.join(__dirname, "..", "public"), staticOptions)) + .use("/storage/", express.static(Helper.getStoragePath(), staticOptions)); // This route serves *installed themes only*. Local themes are served directly // from the `public/themes/` folder as static assets, without entering this