aboutsummaryrefslogtreecommitdiff
path: root/src/sw.js
blob: 8d790446a8115f22ee5116fffd72b466b18e2951 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/* eslint-env serviceworker */

import localForage from 'localforage'
import { parseNotification } from './services/entity_normalizer/entity_normalizer.service.js'
import { prepareNotificationObject } from './services/notification_utils/notification_utils.js'
import { createI18n } from 'vue-i18n'
import messages from './i18n/service_worker_messages.js'

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 localForage.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(([k, 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()
}

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('/')
  }))
})