@@ -2,6 +2,7 @@ import ServiceWorkerStorage from 'serviceworker-storage'
2
2
import { numWithUnits } from '@/lib/format'
3
3
import { CLEAR_NOTIFICATIONS , clearAppBadge , setAppBadge } from '@/lib/badge'
4
4
import { ACTION_PORT , DELETE_SUBSCRIPTION , MESSAGE_PORT , STORE_OS , STORE_SUBSCRIPTION , SYNC_SUBSCRIPTION } from '@/components/serviceworker'
5
+ // import { getLogger } from '@/lib/logger'
5
6
6
7
// we store existing push subscriptions to keep them in sync with server
7
8
const storage = new ServiceWorkerStorage ( 'sw:storage' , 1 )
@@ -28,70 +29,34 @@ const log = (message, level = 'info', context) => {
28
29
}
29
30
30
31
export function onPush ( sw ) {
31
- return async ( event ) => {
32
- const payload = event . data ?. json ( )
32
+ return ( event ) => {
33
+ // const logger = getLogger('sw:push', ['onPush'])
34
+ let payload = event . data ?. json ( )
33
35
if ( ! payload ) return
34
36
const { tag } = payload . options
35
- event . waitUntil ( ( async ( ) => {
36
- const iOS = await getOS ( ) === 'iOS'
37
- // generate random ID for every incoming push for better tracing in logs
38
- const nid = crypto . randomUUID ( )
39
- log ( `[sw:push] ${ nid } - received notification with tag ${ tag } ` )
40
-
41
- // due to missing proper tag support in Safari on iOS, we can't rely on the tag built-in filter.
42
- // we therefore fetch all notifications with the same tag and manually filter them, too.
43
- // see https://bugs.webkit.org/show_bug.cgi?id=258922
44
- const notifications = await sw . registration . getNotifications ( { tag } )
45
- log ( `[sw:push] ${ nid } - found ${ notifications . length } ${ tag } notifications` )
46
- log ( `[sw:push] ${ nid } - built-in tag filter: ${ JSON . stringify ( notifications . map ( ( { tag } ) => tag ) ) } ` )
47
-
48
- // we're not sure if the built-in tag filter actually filters by tag on iOS
49
- // or if it just returns all currently displayed notifications (?)
50
- const filtered = notifications . filter ( ( { tag : nTag } ) => nTag === tag )
51
- log ( `[sw:push] ${ nid } - found ${ filtered . length } ${ tag } notifications after manual tag filter` )
52
- log ( `[sw:push] ${ nid } - manual tag filter: ${ JSON . stringify ( filtered . map ( ( { tag } ) => tag ) ) } ` )
53
-
54
- if ( immediatelyShowNotification ( tag ) ) {
55
- // we can't rely on the tag property to replace notifications on Safari on iOS.
56
- // we therefore close them manually and then we display the notification.
57
- log ( `[sw:push] ${ nid } - ${ tag } notifications replace previous notifications` )
58
- setAppBadge ( sw , ++ activeCount )
59
- // due to missing proper tag support in Safari on iOS, we can't rely on the tag property to replace notifications.
60
- // see https://bugs.webkit.org/show_bug.cgi?id=258922 for more information
61
- // we therefore fetch all notifications with the same tag (+ manual filter),
62
- // close them and then we display the notification.
63
- const notifications = await sw . registration . getNotifications ( { tag } )
64
- // we only close notifications manually on iOS because we don't want to degrade android UX just because iOS is behind in their support.
65
- if ( iOS ) {
66
- log ( `[sw:push] ${ nid } - closing existing notifications` )
67
- notifications . filter ( ( { tag : nTag } ) => nTag === tag ) . forEach ( n => n . close ( ) )
37
+ const nid = crypto . randomUUID ( )
38
+
39
+ // iOS requirement: group all promises
40
+ const promises = [ ]
41
+
42
+ // On immediate notifications we update the counter
43
+ if ( immediatelyShowNotification ( tag ) ) {
44
+ // logger.info(`[${nid}] showing immediate notification with title: ${payload.title}`)
45
+ promises . push ( setAppBadge ( sw , ++ activeCount ) )
46
+ } else { // Check if there are already notifications with the same tag and merge them
47
+ // logger.info(`[${nid}] checking for existing notification with tag ${tag}`)
48
+ promises . push ( sw . registration . getNotifications ( { tag } ) . then ( ( notifications ) => {
49
+ if ( notifications . length ) {
50
+ // logger.info(`[${nid}] found ${notifications.length} notifications with tag ${tag}`)
51
+ payload = mergeNotification ( event , sw , payload , notifications , tag , nid )
68
52
}
69
- log ( `[sw:push] ${ nid } - show notification with title "${ payload . title } "` )
70
- return await sw . registration . showNotification ( payload . title , payload . options )
71
- }
72
-
73
- // according to the spec, there should only be zero or one notification since we used a tag filter
74
- // handle zero case here
75
- if ( notifications . length === 0 ) {
76
- // incoming notification is first notification with this tag
77
- log ( `[sw:push] ${ nid } - no existing ${ tag } notifications found` )
78
- setAppBadge ( sw , ++ activeCount )
79
- log ( `[sw:push] ${ nid } - show notification with title "${ payload . title } "` )
80
- return await sw . registration . showNotification ( payload . title , payload . options )
81
- }
82
-
83
- // handle unexpected case here
84
- if ( notifications . length > 1 ) {
85
- log ( `[sw:push] ${ nid } - more than one notification with tag ${ tag } found` , 'error' )
86
- // due to missing proper tag support in Safari on iOS,
87
- // we only acknowledge this error in our logs and don't bail here anymore
88
- // see https://bugs.webkit.org/show_bug.cgi?id=258922 for more information
89
- log ( `[sw:push] ${ nid } - skip bail -- merging notifications with tag ${ tag } manually` )
90
- // return null
91
- }
53
+ } ) )
54
+ }
92
55
93
- return await mergeAndShowNotification ( sw , payload , notifications , tag , nid , iOS )
94
- } ) ( ) )
56
+ // Apple requirement: wait for all promises to resolve
57
+ event . waitUntil ( Promise . all ( promises ) . then ( ( ) => {
58
+ sw . registration . showNotification ( payload . title , payload . options )
59
+ } ) )
95
60
}
96
61
}
97
62
@@ -100,22 +65,22 @@ export function onPush (sw) {
100
65
const immediatelyShowNotification = ( tag ) =>
101
66
! tag || [ 'TIP' , 'FORWARDEDTIP' , 'EARN' , 'STREAK' , 'TERRITORY_TRANSFER' ] . includes ( tag . split ( '-' ) [ 0 ] )
102
67
103
- const mergeAndShowNotification = async ( sw , payload , currentNotifications , tag , nid , iOS ) => {
68
+ const mergeNotification = ( event , sw , payload , currentNotifications , tag , nid ) => {
69
+ // const logger = getLogger('sw:push:mergeNotification', ['mergeNotification'])
104
70
// sanity check
105
71
const otherTagNotifications = currentNotifications . filter ( ( { tag : nTag } ) => nTag !== tag )
106
72
if ( otherTagNotifications . length > 0 ) {
107
73
// we can't recover from this here. bail.
108
- const message = `[sw:push] ${ nid } - bailing -- more than one notification with tag ${ tag } found after manual filter`
109
- log ( message , 'error' )
74
+ // logger.error(`${nid} - bailing -- more than one notification with tag ${tag} found after manual filter`)
110
75
return
111
76
}
112
77
113
78
const { data : incomingData } = payload . options
114
- log ( `[sw:push] ${ nid } - incoming payload.options.data: ${ JSON . stringify ( incomingData ) } ` )
79
+ // logger.info (`[sw:push] ${nid} - incoming payload.options.data: ${JSON.stringify(incomingData)}`)
115
80
116
81
// we can ignore everything after the first dash in the tag for our control flow
117
82
const compareTag = tag . split ( '-' ) [ 0 ]
118
- log ( `[sw:push] ${ nid } - using ${ compareTag } for control flow` )
83
+ // logger.info (`[sw:push] ${nid} - using ${compareTag} for control flow`)
119
84
120
85
// merge notifications into single notification payload
121
86
// ---
@@ -124,22 +89,18 @@ const mergeAndShowNotification = async (sw, payload, currentNotifications, tag,
124
89
// tags that need to know the sum of sats of notifications with same tag for merging
125
90
const SUM_SATS_TAGS = [ 'DEPOSIT' , 'WITHDRAWAL' ]
126
91
// this should reflect the amount of notifications that were already merged before
127
- let initialAmount = currentNotifications [ 0 ] ?. data ?. amount || 1
128
- if ( iOS ) initialAmount = 1
129
- log ( `[sw:push] ${ nid } - initial amount: ${ initialAmount } ` )
130
- const mergedPayload = currentNotifications . reduce ( ( acc , { data } ) => {
131
- let newAmount , newSats
132
- if ( AMOUNT_TAGS . includes ( compareTag ) ) {
133
- newAmount = acc . amount + 1
134
- }
135
- if ( SUM_SATS_TAGS . includes ( compareTag ) ) {
136
- newSats = acc . sats + data . sats
137
- }
138
- const newPayload = { ...data , amount : newAmount , sats : newSats }
139
- return newPayload
140
- } , { ...incomingData , amount : initialAmount } )
92
+ const initialAmount = currentNotifications [ 0 ] ?. data ?. amount || 1
93
+ const initialSats = currentNotifications [ 0 ] ?. data ?. sats || 0
94
+ // logger.info(`[sw:push] ${nid} - initial amount: ${initialAmount}`)
95
+ // logger.info(`[sw:push] ${nid} - initial sats: ${initialSats}`)
96
+
97
+ const mergedPayload = {
98
+ ...incomingData ,
99
+ amount : initialAmount + 1 ,
100
+ sats : initialSats + incomingData . sats
101
+ }
141
102
142
- log ( `[sw:push] ${ nid } - merged payload: ${ JSON . stringify ( mergedPayload ) } ` )
103
+ // logger.info (`[sw:push] ${nid} - merged payload: ${JSON.stringify(mergedPayload)}`)
143
104
144
105
// calculate title from merged payload
145
106
const { amount, followeeName, subName, subType, sats } = mergedPayload
@@ -167,23 +128,18 @@ const mergeAndShowNotification = async (sw, payload, currentNotifications, tag,
167
128
title = `${ numWithUnits ( sats , { abbreviate : false , unitSingular : 'sat was' , unitPlural : 'sats were' } ) } withdrawn from your account`
168
129
}
169
130
}
170
- log ( `[sw:push] ${ nid } - calculated title: ${ title } ` )
171
-
172
- // close all current notifications before showing new one to "merge" notifications
173
- // we only do this on iOS because we don't want to degrade android UX just because iOS is behind in their support.
174
- if ( iOS ) {
175
- log ( `[sw:push] ${ nid } - closing existing notifications` )
176
- currentNotifications . forEach ( n => n . close ( ) )
177
- }
131
+ // logger.info(`[sw:push] ${nid} - calculated title: ${title}`)
178
132
179
133
const options = { icon : payload . options ?. icon , tag, data : { url : '/notifications' , ...mergedPayload } }
180
- log ( `[sw:push] ${ nid } - show notification with title "${ title } "` )
181
- return await sw . registration . showNotification ( title , options )
134
+ // logger.info (`[sw:push] ${nid} - show notification with title "${title}"`)
135
+ return { title, options }
182
136
}
183
137
184
138
export function onNotificationClick ( sw ) {
185
139
return ( event ) => {
140
+ // const logger = getLogger('sw:onNotificationClick', ['onNotificationClick'])
186
141
const url = event . notification . data ?. url
142
+ // logger.info(`[sw:onNotificationClick] clicked notification with url ${url}`)
187
143
if ( url ) {
188
144
event . waitUntil ( sw . clients . openWindow ( url ) )
189
145
}
@@ -202,10 +158,11 @@ export function onPushSubscriptionChange (sw) {
202
158
// `isSync` is passed if function was called because of 'SYNC_SUBSCRIPTION' event
203
159
// this makes sure we can differentiate between 'pushsubscriptionchange' events and our custom 'SYNC_SUBSCRIPTION' event
204
160
return async ( event , isSync ) => {
161
+ // const logger = getLogger('sw:onPushSubscriptionChange', ['onPushSubscriptionChange'])
205
162
let { oldSubscription, newSubscription } = event
206
163
// https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/pushsubscriptionchange_event
207
164
// fallbacks since browser may not set oldSubscription and newSubscription
208
- log ( '[sw:handlePushSubscriptionChange] invoked' )
165
+ // logger.info ('[sw:handlePushSubscriptionChange] invoked')
209
166
oldSubscription ??= await storage . getItem ( 'subscription' )
210
167
newSubscription ??= await sw . registration . pushManager . getSubscription ( )
211
168
if ( ! newSubscription ) {
@@ -214,17 +171,17 @@ export function onPushSubscriptionChange (sw) {
214
171
// see https://github.com/stackernews/stacker.news/issues/411#issuecomment-1790675861
215
172
// NOTE: this is only run on IndexedDB subscriptions stored under service worker version 2 since this is not backwards compatible
216
173
// see discussion in https://github.com/stackernews/stacker.news/pull/597
217
- log ( '[sw:handlePushSubscriptionChange] service worker lost subscription' )
174
+ // logger.info ('[sw:handlePushSubscriptionChange] service worker lost subscription')
218
175
actionChannelPort ?. postMessage ( { action : 'RESUBSCRIBE' } )
219
176
return
220
177
}
221
178
// no subscription exists at the moment
222
- log ( '[sw:handlePushSubscriptionChange] no existing subscription found' )
179
+ // logger.info ('[sw:handlePushSubscriptionChange] no existing subscription found')
223
180
return
224
181
}
225
182
if ( oldSubscription ?. endpoint === newSubscription . endpoint ) {
226
183
// subscription did not change. no need to sync with server
227
- log ( '[sw:handlePushSubscriptionChange] old subscription matches existing subscription' )
184
+ // logger.info ('[sw:handlePushSubscriptionChange] old subscription matches existing subscription')
228
185
return
229
186
}
230
187
// convert keys from ArrayBuffer to string
@@ -249,7 +206,7 @@ export function onPushSubscriptionChange (sw) {
249
206
} ,
250
207
body
251
208
} )
252
- log ( '[sw:handlePushSubscriptionChange] synced push subscription with server' , 'info' , { endpoint : variables . endpoint , oldEndpoint : variables . oldEndpoint } )
209
+ // logger.info ('[sw:handlePushSubscriptionChange] synced push subscription with server', 'info', { endpoint: variables.endpoint, oldEndpoint: variables.oldEndpoint })
253
210
await storage . setItem ( 'subscription' , JSON . parse ( JSON . stringify ( newSubscription ) ) )
254
211
}
255
212
}
0 commit comments