service worker

This commit is contained in:
cupcakearmy 2019-05-27 20:18:26 +02:00
parent 4b566cce32
commit c299177f2c
4 changed files with 125 additions and 4 deletions

View File

@ -1,19 +1,31 @@
import React from 'react'
import React, { useEffect } from 'react'
import Head from 'next/head'
import Link from 'next/link'
import { logout } from '../utils/auth'
import { getRandomSlogan } from '../utils/misc'
import { editableWhenOnline } from '../utils/hooks'
const Layout = ({ children }) => {
const title = getRandomSlogan()
const online = typeof window !== 'undefined'
? editableWhenOnline()
: true
useEffect(() => {
if (window && 'navigator' in window && 'serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
})
}
}, [])
return <React.Fragment>
<Head>
<title>{title}</title>
{/* https://www.flaticon.com/packs/zoo-20 */}
<link rel="stylesheet" href="/static/css/spectre.min.css"/>
<link rel="stylesheet" href="/static/css/spectre-exp.min.css"/>
<link rel="stylesheet" href="/static/css/spectre-icons.min.css"/>
@ -70,12 +82,21 @@ const Layout = ({ children }) => {
</div>
</main>
{!online && <div id="msg-offline" className="bg-dark text-center p-2">Offline</div>}
{/* language=CSS */}
<style jsx>{`
main {
padding: 6em 0;
}
#msg-offline {
position: fixed;
bottom: 0;
left: 0;
width: 100vw;
}
#navbar-container {
position: fixed;
top: 0;

View File

@ -22,7 +22,9 @@ app.prepare().then(() => {
const parsedUrl = parse(req.url, true)
const { pathname } = parsedUrl
if (pathname.startsWith('/api/'))
if (pathname === '/sw.js')
handle(req, res, parse('/static/sw.js', true))
else if (pathname.startsWith('/api/'))
proxy.web(req, res, { target }, error => console.log('Error!', error))
else
handle(req, res, parsedUrl)

74
www/static/sw.js Normal file
View File

@ -0,0 +1,74 @@
const VERSION = 1
const CACHE = `cache:obolus:${VERSION}`
const getCache = () => self.caches.open(CACHE)
const getPathFromUrl = url => url.replace(/^https?:\/\/[^\/]+/, '')
const _default = {
ttl: 24 * 60 * 60,
methods: ['GET'],
}
const NetworkFirst = () => event => new Promise(async (resolve, reject) => {
const cache = await getCache()
try {
const response = await fetch(event.request)
if (_default.methods.includes(event.request.method))
cache.put(event.request, response.clone())
resolve(response)
} catch (e) {
const cached = await cache.match(event.request)
resolve(cached)
}
})
const NetworkOnly = () => event => new Promise(async (resolve, reject) => {
const cache = await getCache()
const response = await fetch(event.request)
cache.put(event.request, response.clone())
resolve(response)
})
const CacheFirst = () => event => new Promise(async (resolve, reject) => {
const cache = await getCache()
const cached = await cache.match(event.request)
if (cached) resolve(cached)
else {
const response = await fetch(event.request)
cache.put(event.request, response.clone())
resolve(response)
}
})
const CacheOnly = () => event => new Promise(async (resolve, reject) => {
const cache = await getCache()
const cached = await cache.match(event.request)
resolve(cached)
})
const Rolling = () => event => {
}
const routes = [
{
match: /^\/api\//,
handler: NetworkFirst(),
},
{
match: /^\/static\/icons\//,
handler: CacheFirst(),
},
{
match: /.*/,
handler: NetworkFirst(),
},
]
self.addEventListener('fetch', event => {
const path = getPathFromUrl(event.request.url)
const route = routes.find(route => route.match.test(path))
if (route) {
event.respondWith(route.handler(event))
}
})

View File

@ -1,4 +1,4 @@
import React, { useEffect } from 'react'
import React, { useEffect, useState } from 'react'
export const useLocalStorageWatcher = (fn) => {
@ -9,4 +9,28 @@ export const useLocalStorageWatcher = (fn) => {
window.removeEventListener('storage', fn)
}
}, [])
}
export const editableWhenOnline = () => {
const [online, setOnline] = useState(window.navigator.onLine)
useEffect(() => {
const onlineHandler = () => setOnline(true)
const offlineHandler = () => setOnline(false)
window.addEventListener('online', onlineHandler)
window.addEventListener('offline', offlineHandler)
return () => {
window.removeEventListener('online', onlineHandler)
window.removeEventListener('offline', offlineHandler)
}
}, [])
useEffect(() => {
const elements = window.document.querySelectorAll('input, button')
for (const element of elements)
element.disabled = !online
}, [online])
return online
}