Skip to content

Commit

Permalink
Improve GI_VERSION update logic (#2568)
Browse files Browse the repository at this point in the history
* Improve GI_VERSION update logic

Closes #2562.

* Feedback
  • Loading branch information
corrideat authored Jan 31, 2025
1 parent 16893d5 commit 301cb86
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 49 deletions.
35 changes: 31 additions & 4 deletions frontend/controller/backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import type { JSONObject } from '~/shared/types.js'

import sbp from '@sbp/sbp'
import { HOURS_MILLIS } from '~/frontend/model/contracts/shared/time.js'
import { NOTIFICATION_TYPE } from '~/shared/pubsub.js'
import { handleFetchResult } from './utils/misc.js'
import { PUBSUB_INSTANCE } from './instance-keys.js'
Expand All @@ -14,14 +15,40 @@ const languageFileMap = new Map([
])

sbp('okTurtles.events/on', NOTIFICATION_TYPE.VERSION_INFO, (versionInfo) => {
if (versionInfo.GI_VERSION === process.env.GI_VERSION) {
// No refresh necessary, we're already at the latest version
sessionStorage.removeItem(NOTIFICATION_TYPE.VERSION_INFO)
return
}

console.info('New Group Income version available:', versionInfo)
// Prevent the client from trying to reconnect when the page starts unloading.
sbp('okTurtles.data/get', PUBSUB_INSTANCE).destroy()
// TODO: allow the user to manually reload the page later.
try {
// Store the current VERSION_INFO in session storage to prevent infinite
// reload loops
const existingSerialized = sessionStorage.getItem(NOTIFICATION_TYPE.VERSION_INFO)
if (existingSerialized) {
const existingVersionInfo = JSON.parse(existingSerialized)
if (
Array.isArray(existingVersionInfo) &&
!(Date.now() - existingVersionInfo[0] >= 2.5 * HOURS_MILLIS) &&
versionInfo.GI_VERSION === existingVersionInfo[1].GI_VERSION
) {
console.warn('[NOTIFICATION_TYPE.VERSION_INFO] A different Group Income version is available, but reloading has failed to address it', { existingVersionInfo, versionInfo })
return
}
}

sessionStorage.setItem(NOTIFICATION_TYPE.VERSION_INFO, JSON.stringify([Date.now(), versionInfo]))
} catch (e) {
console.error('[NOTIFICATION_TYPE.VERSION_INFO] Error in handler', e)
}
// Prevent the client from trying to reconnect when the page starts unloading.
sbp('okTurtles.data/get', PUBSUB_INSTANCE)?.destroy()
window.location.reload()
})

sbp('sbp/selectors/register', {
export default (sbp('sbp/selectors/register', {
/**
* Fetches a JSON object containing translation strings for a given language.
*
Expand All @@ -39,4 +66,4 @@ sbp('sbp/selectors/register', {
.then(handleFetchResult('json'))
}
}
})
}): string[])
79 changes: 40 additions & 39 deletions frontend/controller/service-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,44 @@ sbp('sbp/selectors/register', {
await swRegistration.update()
setInterval(() => sbp('service-worker/update'), HOURS_MILLIS)

navigator.serviceWorker.addEventListener('message', event => {
const data = event.data
const silentEmit = sbp('sbp/selectors/fn', 'okTurtles.events/emit')

if (typeof data === 'object' && data.type) {
switch (data.type) {
case 'pong':
break
case 'event': {
sbp('okTurtles.events/emit', event.data.subtype, ...deserializer(event.data.data))
break
}
case 'navigate': {
if (data.groupID) {
sbp('state/vuex/commit', 'setCurrentGroupId', { contractID: data.groupID })
}
sbp('controller/router').push({ path: data.path }).catch(console.warn)
break
}
// `sbp` invocations from the SW to the app. Used by the
// `notificationclick` handler for notifications that have an
// `sbpInvocation` instead of a `linkTo` property.
case 'sbp': {
sbp(...deserializer(event.data.data))
break
}
case CAPTURED_LOGS: {
// Emit silently to avoid flooding logs with event emitted entries
silentEmit(CAPTURED_LOGS, ...deserializer(event.data.data))
break
}
default:
console.error('[sw] Received unknown message type from the service worker:', data)
break
}
}
})

// Send a 'ready' message to the SW and wait back for a response
// This way we ensure that Chelonia has been set up
await new Promise((resolve, reject) => {
Expand All @@ -71,7 +109,8 @@ sbp('sbp/selectors/register', {
navigator.serviceWorker.ready.then((worker) => {
worker.active.postMessage({
type: 'ready',
port: messageChannel.port2
port: messageChannel.port2,
GI_VERSION: process.env.GI_VERSION
}, [messageChannel.port2])
}).catch((e) => {
reject(e)
Expand Down Expand Up @@ -110,44 +149,6 @@ sbp('sbp/selectors/register', {
// there are open tabs, which makes it faster and smoother to interact
// with contracts than if the service worker had to be restarted.
setInterval(() => navigator.serviceWorker.controller?.postMessage({ type: 'ping' }), 5000)

navigator.serviceWorker.addEventListener('message', event => {
const data = event.data
const silentEmit = sbp('sbp/selectors/fn', 'okTurtles.events/emit')

if (typeof data === 'object' && data.type) {
switch (data.type) {
case 'pong':
break
case 'event': {
sbp('okTurtles.events/emit', event.data.subtype, ...deserializer(event.data.data))
break
}
case 'navigate': {
if (data.groupID) {
sbp('state/vuex/commit', 'setCurrentGroupId', { contractID: data.groupID })
}
sbp('controller/router').push({ path: data.path }).catch(console.warn)
break
}
// `sbp` invocations from the SW to the app. Used by the
// `notificationclick` handler for notifications that have an
// `sbpInvocation` instead of a `linkTo` property.
case 'sbp': {
sbp(...deserializer(event.data.data))
break
}
case CAPTURED_LOGS: {
// Emit silently to avoid flooding logs with event emitted entries
silentEmit(CAPTURED_LOGS, ...deserializer(event.data.data))
break
}
default:
console.error('[sw] Received unknown message type from the service worker:', data)
break
}
}
})
} catch (e) {
console.error('error setting up service worker:', e)
throw e
Expand Down
36 changes: 30 additions & 6 deletions frontend/controller/serviceworkers/sw-primary.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import { KV_KEYS } from '~/frontend/utils/constants.js'
import { CHELONIA_STATE_MODIFIED, LOGIN, LOGIN_ERROR, LOGOUT } from '~/frontend/utils/events.js'
import { GIMessage } from '~/shared/domains/chelonia/GIMessage.js'
import { Secret } from '~/shared/domains/chelonia/Secret.js'
import { CHELONIA_RESET, CONTRACTS_MODIFIED, CONTRACT_IS_SYNCING, EVENT_HANDLED } from '~/shared/domains/chelonia/events.js'
import { CHELONIA_RESET, CONTRACTS_MODIFIED, CONTRACT_IS_SYNCING, CONTRACT_REGISTERED, EVENT_HANDLED } from '~/shared/domains/chelonia/events.js'
import { NOTIFICATION_TYPE } from '~/shared/pubsub.js'
import { deserializer, serializer } from '~/shared/serdes/index.js'
import {
ACCEPTED_GROUP, CAPTURED_LOGS, CHATROOM_USER_STOP_TYPING,
Expand Down Expand Up @@ -103,11 +104,12 @@ sbp('okTurtles.events/on', CHELONIA_RESET, setupRootState)

// These are all of the events that will be forwarded to all open tabs and windows
;[
CHELONIA_RESET, CONTRACTS_MODIFIED, CONTRACT_IS_SYNCING, EVENT_HANDLED, LOGIN,
LOGIN_ERROR, LOGOUT, ACCEPTED_GROUP, CHATROOM_USER_STOP_TYPING,
CHATROOM_USER_TYPING, DELETED_CHATROOM, LEFT_CHATROOM, LEFT_GROUP,
JOINED_CHATROOM, JOINED_GROUP, KV_EVENT, MESSAGE_RECEIVE, MESSAGE_SEND,
NAMESPACE_REGISTRATION, NEW_LAST_LOGGED_IN,
CHELONIA_RESET, CONTRACTS_MODIFIED, CONTRACT_IS_SYNCING, CONTRACT_REGISTERED,
EVENT_HANDLED, LOGIN, LOGIN_ERROR, LOGOUT, ACCEPTED_GROUP,
CHATROOM_USER_STOP_TYPING, CHATROOM_USER_TYPING, DELETED_CHATROOM,
LEFT_CHATROOM, LEFT_GROUP, JOINED_CHATROOM, JOINED_GROUP, KV_EVENT,
NOTIFICATION_TYPE.VERSION_INFO,
MESSAGE_RECEIVE, MESSAGE_SEND, NAMESPACE_REGISTRATION, NEW_LAST_LOGGED_IN,
NEW_PREFERENCES, NEW_UNREAD_MESSAGES, NOTIFICATION_EMITTED,
NOTIFICATION_REMOVED, NOTIFICATION_STATUS_LOADED, OFFLINE, ONLINE,
PROPOSAL_ARCHIVED, SERIOUS_ERROR, SWITCH_GROUP
Expand Down Expand Up @@ -265,6 +267,11 @@ sbp('okTurtles.events/on', CHELONIA_RESET, () => {
sbp('gi.periodicNotifications/init')
})

let currentVersionInfo
sbp('okTurtles.events/on', NOTIFICATION_TYPE.VERSION_INFO, (versionInfo) => {
currentVersionInfo = versionInfo
})

sbp('okTurtles.data/set', 'API_URL', self.location.origin)
setupRootState()
const setupPromise = setupChelonia()
Expand Down Expand Up @@ -359,6 +366,23 @@ self.addEventListener('message', function (event) {
}).finally(() => {
port.close()
})

// If the window is outdated (different GI_VERSION), trigger an event
// of type 'NOTIFICATION_TYPE.VERSION_INFO'.
// This handles new SW clients that have an outdated
// `process.env.GI_VERSION` (for example, by having loaded a cached
// version of `main.js`).
if (
currentVersionInfo &&
event.source &&
event.data.GI_VERSION !== currentVersionInfo.GI_VERSION
) {
event.source.postMessage({
type: 'event',
subtype: NOTIFICATION_TYPE.VERSION_INFO,
data: [currentVersionInfo]
})
}
break
}
default:
Expand Down

0 comments on commit 301cb86

Please sign in to comment.