Skip to content

Commit

Permalink
Merge 29efb80 into f981369
Browse files Browse the repository at this point in the history
  • Loading branch information
corrideat authored Jan 19, 2025
2 parents f981369 + 29efb80 commit 984a201
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 11 deletions.
24 changes: 19 additions & 5 deletions backend/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,8 @@ route.GET('/latestHEADinfo/{contractID}', {
})

route.GET('/time', {}, function (request, h) {
return new Date().toISOString()
return h.response(new Date().toISOString())
.header('Cache-Control', 'no-store')
})

// TODO: if the browser deletes our cache then not everyone
Expand Down Expand Up @@ -713,9 +714,13 @@ route.GET('/assets/{subpath*}', {
.etag(basename)
.header('Cache-Control', 'public,max-age=31536000,immutable')
}
// Files like `main.js` or `main.css` should be revalidated before use. Se we use the default headers.
// Files like `main.js` or `main.css` should be revalidated before use.
// We set a short 'stale-while-revalidate' value instead of 'no-cache' to
// signal to the app that it's fine to use old versions when offline or over
// unreliable connections.
// This should also be suitable for serving unversioned fonts and images.
return h.file(subpath)
.header('Cache-Control', 'public,max-age=604800,stale-while-revalidate=86400')
})

route.GET(staticServeConfig.routePath, {}, {
Expand Down Expand Up @@ -804,7 +809,13 @@ route.GET('/zkpp/{name}/auth_hash', {
try {
const challenge = await getChallenge(req.params['name'], req.query['b'])

return challenge || Boom.notFound()
if (!challenge) {
return Boom.notFound()
}

return h.response(challenge)
.header('Cache-Control', 'no-store')
.header('Content-Type', 'text/plain')
} catch (e) {
e.ip = req.headers['x-real-ip'] || req.info.remoteAddress
console.error(e, 'Error at GET /zkpp/{name}/auth_hash: ' + e.message)
Expand All @@ -828,7 +839,9 @@ route.GET('/zkpp/{name}/contract_hash', {
const salt = await getContractSalt(req.params['name'], req.query['r'], req.query['s'], req.query['sig'], req.query['hc'])

if (salt) {
return salt
return h.response(salt)
.header('Cache-Control', 'no-store')
.header('Content-Type', 'text/plain')
}
} catch (e) {
e.ip = req.headers['x-real-ip'] || req.info.remoteAddress
Expand All @@ -854,7 +867,8 @@ route.POST('/zkpp/{name}/updatePasswordHash', {
const result = await updateContractSalt(req.params['name'], req.payload['r'], req.payload['s'], req.payload['sig'], req.payload['hc'], req.payload['Ea'])

if (result) {
return result
return h.response(result)
.header('Content-type', 'text/plain')
}
} catch (e) {
e.ip = req.headers['x-real-ip'] || req.info.remoteAddress
Expand Down
2 changes: 1 addition & 1 deletion frontend/controller/actions/group.js
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ export default (sbp('sbp/selectors/register', {
removeEventHandler()
removeLogoutHandler()
// The event handler recursively calls this same selector
// A different path should be taken, since te event handler
// A different path should be taken, since the event handler
// should be called after the key request has been answered
// and processed
sbp('gi.actions/group/join', params).catch((e) => {
Expand Down
4 changes: 3 additions & 1 deletion frontend/controller/app/identity.js
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,9 @@ export default (sbp('sbp/selectors/register', {
})
} else {
try {
await sbp('chelonia/contract/sync', identityContractID)
if (navigator.onLine !== false) {
await sbp('chelonia/contract/sync', identityContractID)
}
} catch (e) {
// Since we're throwing or returning, the `await` below will not
// be used. In either case, login won't complete after this point,
Expand Down
1 change: 1 addition & 0 deletions frontend/controller/service-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ sbp('sbp/selectors/register', {
// have this function there. However, most examples perform this outside of the
// SW, and private testing showed that it's more reliable doing it here.
'service-worker/setup-push-subscription': async function (retryCount?: number) {
if (process.env.CI || window.Cypress) return
await sbp('okTurtles.eventQueue/queueEvent', 'service-worker/setup-push-subscription', async () => {
// Get the installed service-worker registration
const registration = await navigator.serviceWorker.ready
Expand Down
149 changes: 149 additions & 0 deletions frontend/controller/serviceworkers/cache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import sbp from '@sbp/sbp'
import { NOTIFICATION_TYPE } from '~/shared/pubsub.js'

const CACHE_VERSION = '1.0.0'
const CURRENT_CACHES = {
assets: `assets-cache_v${CACHE_VERSION}`
}

if (
typeof Cache === 'function' &&
typeof CacheStorage === 'function' &&
typeof caches === 'object' &&
(caches instanceof CacheStorage)
) {
sbp('okTurtles.events/on', NOTIFICATION_TYPE.VERSION_INFO, (data) => {
if (data.GI_VERSION !== process.env.GI_VERSION) {
caches.delete(CURRENT_CACHES.assets).catch(e => {
console.error('Error trying to delete cache', CURRENT_CACHES.assets)
})
}
})

const locationUrl = new URL(self.location)
const routerBase = locationUrl.searchParams.get('routerBase') ?? '/app'

self.addEventListener('install', (event) => {
event.waitUntil(
caches
.open(CURRENT_CACHES.assets)
.then((cache) =>
cache.addAll([
'/',
'/assets/pwa-manifest.webmanifest',
'/assets/images/group-income-icon-transparent.png',
'/assets/images/pwa-icons/group-income-icon-maskable_192x192.png',
'/assets/css/main.css',
'/assets/js/main.js',
`${routerBase}/`
]).catch(e => {
console.error('Error adding initial entries to cache', e)
})
)
)
}, false)

// Taken from the MDN example:
// <https://developer.mozilla.org/en-US/docs/Web/API/Cache>
self.addEventListener('activate', (event) => {
const expectedCacheNamesSet = new Set(Object.values(CURRENT_CACHES))

event.waitUntil(
caches.keys().then((cacheNames) =>
Promise.allSettled(
cacheNames.map((cacheName) => {
if (!expectedCacheNamesSet.has(cacheName)) {
// If this cache name isn't present in the set of
// "expected" cache names, then delete it.
console.log('Deleting out of date cache:', cacheName)
return caches.delete(cacheName)
}

return undefined
})
)
)
)
}, false)

self.addEventListener('fetch', function (event) {
console.debug(`[sw] fetch : ${event.request.method} - ${event.request.url}`)

if (!['GET', 'HEAD', 'OPTIONS'].includes(event.request.method)) {
return
}

let request = event.request

try {
const url = new URL(request.url)
if (url.pathname.startsWith('/file')) {
return
}

if (
['/eventsAfter/', '/name/', '/latestHEADinfo/', '/file/', '/kv/', '/zkpp/'].some(prefix => url.pathname.startsWith(prefix)) ||
url.pathname === '/time'
) {
return
}

// If the route starts with `${routerBase}/`, use `${routerBase}/` as the
// URL, since the HTML content is presumed to be the same.
// This is _crucial_ for the offline PWA to work, since currently the
// app uses different paths.
if (url.pathname.startsWith(`${routerBase}/`)) {
request = new Request(`${url.origin}${routerBase}/`)
}
} catch (e) {
return
}

event.respondWith(
caches.open(CURRENT_CACHES.assets).then((cache) => {
return cache
.match(request, { ignoreSearch: true, ignoreVary: true })
.then((cachedResponse) => {
if (cachedResponse) {
// If we're offline, return the cached response, if it exists
if (navigator.onLine === false) {
return cachedResponse
}
}

return fetch(event.request.clone?.() || request).then(async (response) => {
if (
// Save successful reponses
response.status >= 200 &&
response.status < 400 &&
response.status !== 206 && // Partial response
response.status !== 304 && // Not modified
// Which don't have a 'no-store' directive
!response.headers.get('cache-control')?.split(',').some(x => x.trim() === 'no-store')
) {
await cache.put(request, response.clone()).catch(e => {
console.error('Error adding request to cache')
})
} else if (response.status < 500) {
// For 5xx responses (server errors, we don't delete the cache
// entry. This is so that, in the event of a 5xx error,
// the offline app still works.)
await cache.delete(request)
}

return response
}).catch(e => {
if (cachedResponse) {
console.warn('Error while fetching', request, e)
// If there was a network error fetching, return the cached
// response, if it exists
return cachedResponse
}

throw e
})
})
})
)
}, false)
}
5 changes: 1 addition & 4 deletions frontend/controller/serviceworkers/sw-primary.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
NEW_UNREAD_MESSAGES, NOTIFICATION_EMITTED, NOTIFICATION_REMOVED,
NOTIFICATION_STATUS_LOADED, OFFLINE, ONLINE, SERIOUS_ERROR, SWITCH_GROUP
} from '../../utils/events.js'
import './cache.js'
import './push.js'
import './sw-namespace.js'

Expand Down Expand Up @@ -272,10 +273,6 @@ self.addEventListener('activate', function (event) {
event.waitUntil(setupPromise.then(() => self.clients.claim()))
})

self.addEventListener('fetch', function (event) {
console.debug(`[sw] fetch : ${event.request.method} - ${event.request.url}`)
})

// TODO: this doesn't persist data across browser restarts, so try to use
// the cache instead, or just localstorage. Investigate whether the service worker
// has the ability to access and clear the localstorage periodically.
Expand Down

0 comments on commit 984a201

Please sign in to comment.