Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85630668
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
9 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/sw.js b/src/sw.js
index b7629b5f29..908e865673 100644
--- a/src/sw.js
+++ b/src/sw.js
@@ -1,299 +1,303 @@
/* eslint-env serviceworker */
// biome-ignore: side effect import of assets list
import 'virtual:pleroma-fe/service_worker_env'
import { createI18n } from 'vue-i18n'
import { storage } from 'src/lib/storage.js'
import { INSTANCE_DEFAULT_CONFIG } from 'src/modules/default_config_state.js'
import { parseNotification } from 'src/services/entity_normalizer/entity_normalizer.service.js'
import { prepareNotificationObject } from 'src/services/notification_utils/notification_utils.js'
import { cacheKey, emojiCacheKey, shouldCache } from 'src/services/sw/sw.js'
// Collects all messages for service workers
// Needed because service workers cannot use dynamic imports
// See /build/sw_plugin.js for more information
import messages from 'virtual:pleroma-fe/service_worker_messages'
const i18n = createI18n({
// By default, use the browser locale, we will update it if neccessary
locale: 'en',
fallbackLocale: 'en',
messages,
})
const state = {
lastFocused: null,
notificationIds: new Set(),
allowedNotificationTypes: null,
}
function getWindowClients() {
return clients
.matchAll({ includeUncontrolled: true })
.then((clientList) => clientList.filter(({ type }) => type === 'window'))
}
const setSettings = async () => {
const piniaState = await storage.getItem('pinia-local-sync_config')
const locale = piniaState.prefsStorage.simple.interfaceLanguage || 'en'
i18n.locale = locale
const notificationsNativeArray = Object.entries(
piniaState.prefsStorage.simple.notificationNative ||
INSTANCE_DEFAULT_CONFIG.notificationNative,
)
state.webPushAlwaysShowNotifications =
piniaState.prefsStorage.simple.webPushAlwaysShowNotifications
state.allowedNotificationTypes = new Set(
notificationsNativeArray
.filter(([, v]) => v)
.map(([k]) => {
switch (k) {
case 'mentions':
return 'mention'
case 'statuses':
return 'status'
case 'likes':
return 'like'
case 'repeats':
return 'repeat'
case 'emojiReactions':
return 'pleroma:emoji_reaction'
case 'reports':
return 'pleroma:report'
case 'followRequest':
return 'follow_request'
case 'follows':
return 'follow'
case 'polls':
return 'poll'
default:
return k
}
}),
)
}
const showPushNotification = async (event) => {
const activeClients = await getWindowClients()
await setSettings()
// Only show push notifications if all tabs/windows are closed
if (state.webPushAlwaysShowNotifications || activeClients.length === 0) {
const data = event.data.json()
const url = `${self.registration.scope}api/v1/notifications/${data.notification_id}`
const notification = await fetch(url, {
headers: { Authorization: 'Bearer ' + data.access_token },
})
const notificationJson = await notification.json()
const parsedNotification = parseNotification(notificationJson)
const res = prepareNotificationObject(parsedNotification, i18n)
if (
state.webPushAlwaysShowNotifications ||
state.allowedNotificationTypes.has(parsedNotification.type)
) {
return self.registration.showNotification(res.title, res)
}
}
return Promise.resolve()
}
const cacheFiles = self.serviceWorkerOption.assets
const isEmoji = (req) => {
if (req.method !== 'GET') {
return false
}
const url = new URL(req.url)
return url.pathname.startsWith('/emoji/')
}
const isNotMedia = (req) => {
if (req.method !== 'GET') {
return false
}
const url = new URL(req.url)
return !url.pathname.startsWith('/media/')
}
const isAsset = (req) => {
const url = new URL(req.url)
return cacheFiles.includes(url.pathname)
}
const isSuccessful = (resp) => {
if (!resp.ok) {
return false
}
if (new URL(resp.url).pathname === '/index.html') {
// For index.html itself, there is no fallback possible.
return true
}
const type = resp.headers.get('Content-Type')
// Backend will revert to index.html if the file does not exist, so text/html for emojis and assets is a failure
return type && !type.includes('text/html')
}
self.addEventListener('install', async (event) => {
if (shouldCache) {
event.waitUntil(
(async () => {
// Do not preload i18n and emoji annotations to speed up loading
const shouldPreload = (route) => {
return (
!route.startsWith('/static/js/i18n/') &&
!route.startsWith('/static/js/emoji-annotations/')
)
}
const cache = await caches.open(cacheKey)
await Promise.allSettled(
cacheFiles.filter(shouldPreload).map(async (route) => {
// https://developer.mozilla.org/en-US/docs/Web/API/Cache/add
// originally we used addAll() but it will raise a problem in one edge case:
// when the file for the route is not found, backend will return index.html with code 200
// but it's wrong, and it's cached, so we end up with a bad cache.
// this can happen when you refresh when you are in the process of upgrading
// the frontend.
const resp = await fetch(route)
if (isSuccessful(resp)) {
await cache.put(route, resp)
}
}),
)
})(),
)
}
})
self.addEventListener('activate', async (event) => {
if (shouldCache) {
event.waitUntil(
(async () => {
const cache = await caches.open(cacheKey)
const keys = await cache.keys()
await Promise.all(
keys
.filter((request) => {
const url = new URL(request.url)
const shouldKeep = cacheFiles.includes(url.pathname)
return !shouldKeep
})
.map((k) => cache.delete(k)),
)
})(),
)
}
})
self.addEventListener('push', async (event) => {
if (event.data) {
// Supposedly, we HAVE to return a promise inside waitUntil otherwise it will
// show (extra) notification that website is updated in background
event.waitUntil(showPushNotification(event))
}
})
self.addEventListener('message', async (event) => {
await setSettings()
const { type, content } = event.data
+ if (self.location.origin !== event.origin) {
+ console.error('SW Message with strange origin received', event)
+ return
+ }
if (type === 'desktopNotification') {
const { title, ...rest } = content
const { tag, type } = rest
if (state.notificationIds.has(tag)) return
state.notificationIds.add(tag)
setTimeout(() => state.notificationIds.delete(tag), 10000)
if (state.allowedNotificationTypes.has(type)) {
self.registration.showNotification(title, rest)
}
}
if (type === 'desktopNotificationClose') {
const { id, all } = content
const search = all ? null : { tag: id }
const notifications = await self.registration.getNotifications(search)
notifications.forEach((n) => n.close())
}
if (type === 'updateFocus') {
state.lastFocused = event.source.id
const notifications = await self.registration.getNotifications()
notifications.forEach((n) => n.close())
}
})
self.addEventListener('notificationclick', (event) => {
event.notification.close()
event.waitUntil(
getWindowClients().then((list) => {
for (let i = 0; i < list.length; i++) {
const client = list[i]
client.postMessage({
type: 'notificationClicked',
id: event.notification.tag,
})
}
for (let i = 0; i < list.length; i++) {
const client = list[i]
if (state.lastFocused === null || client.id === state.lastFocused) {
if ('focus' in client) return client.focus()
}
}
if (clients.openWindow) return clients.openWindow('/')
}),
)
})
self.addEventListener('fetch', (event) => {
// Do not mess up with remote things
const isSameOrigin =
new URL(event.request.url).origin === self.location.origin
if (
shouldCache &&
event.request.method === 'GET' &&
isSameOrigin &&
isNotMedia(event.request)
) {
// this is a bit spammy
// console.debug('[Service worker] fetch:', event.request.url)
event.respondWith(
(async () => {
const r = await caches.match(event.request)
const isEmojiReq = isEmoji(event.request)
if (r && isSuccessful(r)) {
console.debug('[Service worker] already cached:', event.request.url)
return r
}
try {
const response = await fetch(event.request)
if (
response.ok &&
isSuccessful(response) &&
(isEmojiReq || isAsset(event.request))
) {
console.debug(
`[Service worker] caching ${isEmojiReq ? 'emoji' : 'asset'}:`,
event.request.url,
)
const cache = await caches.open(
isEmojiReq ? emojiCacheKey : cacheKey,
)
await cache.put(event.request.clone(), response.clone())
}
return response
} catch (e) {
console.error('[Service worker] error when caching emoji:', e)
throw e
}
})(),
)
}
})
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Aug 9, 6:06 PM (1 d, 13 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1724742
Default Alt Text
(9 KB)
Attached To
Mode
rPUFE pleroma-fe-upstream
Attached
Detach File
Event Timeline
Log In to Comment