Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F33096133
sw.js
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
8 KB
Referenced Files
None
Subscribers
None
sw.js
View Options
/* eslint-env serviceworker */
import
'virtual:pleroma-fe/service_worker_env'
import
{
storage
}
from
'src/lib/storage.js'
import
{
parseNotification
}
from
'./services/entity_normalizer/entity_normalizer.service.js'
import
{
prepareNotificationObject
}
from
'./services/notification_utils/notification_utils.js'
import
{
shouldCache
,
cacheKey
,
emojiCacheKey
}
from
'./services/sw/sw.js'
import
{
createI18n
}
from
'vue-i18n'
// 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
vuexState
=
await
storage
.
getItem
(
'vuex-lz'
)
const
locale
=
vuexState
.
config
.
interfaceLanguage
||
'en'
i18n
.
locale
=
locale
const
notificationsNativeArray
=
Object
.
entries
(
vuexState
.
config
.
notificationNative
)
state
.
webPushAlwaysShowNotifications
=
vuexState
.
config
.
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
(
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/plain
Expires
Tue, Jan 20, 10:06 AM (1 d, 1 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
972932
Default Alt Text
sw.js (8 KB)
Attached To
Mode
rPUFE pleroma-fe-upstream
Attached
Detach File
Event Timeline
Log In to Comment