Remove service worker logger (#2265)
* Remove service worker logger * Use async/await for togglePushSubscription * Remove commented out logger calls in service worker * Remove message channel between service worker and app The listener for messages from the service worker was removed in a previous commit. * Remove unused OS detection for service worker
This commit is contained in:
parent
06df4b7a8c
commit
3a27057781
@ -1,17 +0,0 @@
|
||||
import { WebClient, LogLevel } from '@slack/web-api'
|
||||
|
||||
const slackClient = global.slackClient || (() => {
|
||||
if (!process.env.SLACK_BOT_TOKEN && !process.env.SLACK_CHANNEL_ID) {
|
||||
console.warn('SLACK_* env vars not set, skipping slack setup')
|
||||
return null
|
||||
}
|
||||
console.log('initing slack client')
|
||||
const client = new WebClient(process.env.SLACK_BOT_TOKEN, {
|
||||
logLevel: LogLevel.INFO
|
||||
})
|
||||
return client
|
||||
})()
|
||||
|
||||
if (process.env.NODE_ENV === 'development') global.slackClient = slackClient
|
||||
|
||||
export default slackClient
|
@ -86,7 +86,7 @@ export default gql`
|
||||
|
||||
input SettingsInput {
|
||||
autoDropBolt11s: Boolean!
|
||||
diagnostics: Boolean!
|
||||
diagnostics: Boolean @deprecated
|
||||
noReferralLinks: Boolean!
|
||||
fiatCurrency: String!
|
||||
satsFilter: Int!
|
||||
@ -162,7 +162,7 @@ export default gql`
|
||||
mirrors SettingsInput
|
||||
"""
|
||||
autoDropBolt11s: Boolean!
|
||||
diagnostics: Boolean!
|
||||
diagnostics: Boolean @deprecated
|
||||
noReferralLinks: Boolean!
|
||||
fiatCurrency: String!
|
||||
satsFilter: Int!
|
||||
|
@ -3,7 +3,6 @@ import { StaticLayout } from './layout'
|
||||
import styles from '@/styles/error.module.css'
|
||||
import Image from 'react-bootstrap/Image'
|
||||
import copy from 'clipboard-copy'
|
||||
import { LoggerContext } from './logger'
|
||||
import Button from 'react-bootstrap/Button'
|
||||
import { useToast } from './toast'
|
||||
import { decodeMinifiedStackTrace } from '@/lib/stacktrace'
|
||||
@ -36,8 +35,6 @@ class ErrorBoundary extends Component {
|
||||
// You can use your own error logging service here
|
||||
console.log({ error, errorInfo })
|
||||
this.setState({ errorInfo })
|
||||
const logger = this.context
|
||||
logger?.error(this.getErrorDetails())
|
||||
}
|
||||
|
||||
render () {
|
||||
@ -59,8 +56,6 @@ class ErrorBoundary extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
ErrorBoundary.contextType = LoggerContext
|
||||
|
||||
export default ErrorBoundary
|
||||
|
||||
// This button is a functional component so we can use `useToast` hook, which
|
||||
|
@ -1,119 +0,0 @@
|
||||
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'
|
||||
import { useMe } from './me'
|
||||
import fancyNames from '@/lib/fancy-names.json'
|
||||
|
||||
const generateFancyName = () => {
|
||||
// 100 adjectives * 100 nouns * 10000 = 100M possible names
|
||||
const pickRandom = (array) => array[Math.floor(Math.random() * array.length)]
|
||||
const adj = pickRandom(fancyNames.adjectives)
|
||||
const noun = pickRandom(fancyNames.nouns)
|
||||
const id = Math.floor(Math.random() * fancyNames.maxSuffix)
|
||||
return `${adj}-${noun}-${id}`
|
||||
}
|
||||
|
||||
export function detectOS () {
|
||||
if (!window.navigator) return ''
|
||||
|
||||
const userAgent = window.navigator.userAgent
|
||||
const platform = window.navigator.userAgentData?.platform || window.navigator.platform
|
||||
const macosPlatforms = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K']
|
||||
const windowsPlatforms = ['Win32', 'Win64', 'Windows', 'WinCE']
|
||||
const iosPlatforms = ['iPhone', 'iPad', 'iPod']
|
||||
let os = null
|
||||
|
||||
if (macosPlatforms.indexOf(platform) !== -1) {
|
||||
os = 'Mac OS'
|
||||
} else if (iosPlatforms.indexOf(platform) !== -1) {
|
||||
os = 'iOS'
|
||||
} else if (windowsPlatforms.indexOf(platform) !== -1) {
|
||||
os = 'Windows'
|
||||
} else if (/Android/.test(userAgent)) {
|
||||
os = 'Android'
|
||||
} else if (/Linux/.test(platform)) {
|
||||
os = 'Linux'
|
||||
}
|
||||
|
||||
return os
|
||||
}
|
||||
|
||||
export const LoggerContext = createContext()
|
||||
|
||||
export const LoggerProvider = ({ children }) => {
|
||||
return (
|
||||
<ServiceWorkerLoggerProvider>
|
||||
{children}
|
||||
</ServiceWorkerLoggerProvider>
|
||||
)
|
||||
}
|
||||
|
||||
const ServiceWorkerLoggerContext = createContext()
|
||||
|
||||
function ServiceWorkerLoggerProvider ({ children }) {
|
||||
const { me } = useMe()
|
||||
const [name, setName] = useState()
|
||||
const [os, setOS] = useState()
|
||||
|
||||
useEffect(() => {
|
||||
let name = window.localStorage.getItem('fancy-name')
|
||||
if (!name) {
|
||||
name = generateFancyName()
|
||||
window.localStorage.setItem('fancy-name', name)
|
||||
}
|
||||
setName(name)
|
||||
setOS(detectOS())
|
||||
}, [])
|
||||
|
||||
const log = useCallback(level => {
|
||||
return async (message, context) => {
|
||||
if (!me || !me.privates?.diagnostics) return
|
||||
const env = {
|
||||
userAgent: window.navigator.userAgent,
|
||||
// os may not be initialized yet
|
||||
os: os || detectOS()
|
||||
}
|
||||
const body = {
|
||||
level,
|
||||
env,
|
||||
// name may be undefined if it wasn't stored in local storage yet
|
||||
// we fallback to local storage since on page reloads, the name may wasn't fetched from local storage yet
|
||||
name: name || window.localStorage.getItem('fancy-name'),
|
||||
message,
|
||||
context
|
||||
}
|
||||
await fetch('/api/log', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'Content-type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
}).catch(console.error)
|
||||
}
|
||||
}, [me?.privates?.diagnostics, name, os])
|
||||
|
||||
const logger = useMemo(() => ({
|
||||
info: log('info'),
|
||||
warn: log('warn'),
|
||||
error: log('error'),
|
||||
name
|
||||
}), [log, name])
|
||||
|
||||
useEffect(() => {
|
||||
// for communication between app and service worker
|
||||
const channel = new MessageChannel()
|
||||
navigator?.serviceWorker?.controller?.postMessage({ action: 'MESSAGE_PORT' }, [channel.port2])
|
||||
channel.port1.onmessage = (event) => {
|
||||
const { message, level, context } = Object.assign({ level: 'info' }, event.data)
|
||||
logger[level](message, context)
|
||||
}
|
||||
}, [logger])
|
||||
|
||||
return (
|
||||
<ServiceWorkerLoggerContext.Provider value={logger}>
|
||||
{children}
|
||||
</ServiceWorkerLoggerContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useServiceWorkerLogger () {
|
||||
return useContext(ServiceWorkerLoggerContext)
|
||||
}
|
@ -1,20 +1,17 @@
|
||||
import { createContext, useContext, useEffect, useState, useCallback, useMemo } from 'react'
|
||||
import { Workbox } from 'workbox-window'
|
||||
import { gql, useMutation } from '@apollo/client'
|
||||
import { detectOS, useServiceWorkerLogger } from './logger'
|
||||
|
||||
const applicationServerKey = process.env.NEXT_PUBLIC_VAPID_PUBKEY
|
||||
|
||||
const ServiceWorkerContext = createContext()
|
||||
|
||||
// message types for communication between app and service worker
|
||||
export const MESSAGE_PORT = 'MESSAGE_PORT' // message to exchange message channel on which service worker will send messages back to app
|
||||
export const ACTION_PORT = 'ACTION_PORT' // message to exchange action channel on which service worker will send actions back to app
|
||||
export const SYNC_SUBSCRIPTION = 'SYNC_SUBSCRIPTION' // trigger onPushSubscriptionChange event in service worker manually
|
||||
export const RESUBSCRIBE = 'RESUBSCRIBE' // trigger resubscribing to push notifications (sw -> app)
|
||||
export const DELETE_SUBSCRIPTION = 'DELETE_SUBSCRIPTION' // delete subscription in IndexedDB (app -> sw)
|
||||
export const STORE_SUBSCRIPTION = 'STORE_SUBSCRIPTION' // store subscription in IndexedDB (app -> sw)
|
||||
export const STORE_OS = 'STORE_OS' // store OS in service worker
|
||||
|
||||
export const ServiceWorkerProvider = ({ children }) => {
|
||||
const [registration, setRegistration] = useState(null)
|
||||
@ -44,7 +41,6 @@ export const ServiceWorkerProvider = ({ children }) => {
|
||||
}
|
||||
}
|
||||
`)
|
||||
const logger = useServiceWorkerLogger()
|
||||
|
||||
// I am not entirely sure if this is needed since at least in Brave,
|
||||
// using `registration.pushManager.subscribe` also prompts the user.
|
||||
@ -77,7 +73,6 @@ export const ServiceWorkerProvider = ({ children }) => {
|
||||
// see https://stackoverflow.com/a/69624651
|
||||
let pushSubscription = await registration.pushManager.subscribe(subscribeOptions)
|
||||
const { endpoint } = pushSubscription
|
||||
logger.info('subscribed to push notifications', { endpoint })
|
||||
// convert keys from ArrayBuffer to string
|
||||
pushSubscription = JSON.parse(JSON.stringify(pushSubscription))
|
||||
// Send subscription to service worker to save it so we can use it later during `pushsubscriptionchange`
|
||||
@ -86,7 +81,6 @@ export const ServiceWorkerProvider = ({ children }) => {
|
||||
action: STORE_SUBSCRIPTION,
|
||||
subscription: pushSubscription
|
||||
})
|
||||
logger.info('sent STORE_SUBSCRIPTION to service worker', { endpoint })
|
||||
// send subscription to server
|
||||
const variables = {
|
||||
endpoint,
|
||||
@ -94,34 +88,28 @@ export const ServiceWorkerProvider = ({ children }) => {
|
||||
auth: pushSubscription.keys.auth
|
||||
}
|
||||
await savePushSubscription({ variables })
|
||||
logger.info('sent push subscription to server', { endpoint })
|
||||
}
|
||||
|
||||
const unsubscribeFromPushNotifications = async (subscription) => {
|
||||
await subscription.unsubscribe()
|
||||
const { endpoint } = subscription
|
||||
logger.info('unsubscribed from push notifications', { endpoint })
|
||||
await deletePushSubscription({ variables: { endpoint } })
|
||||
// also delete push subscription in IndexedDB so we can tell if the user disabled push subscriptions
|
||||
// or we lost the push subscription due to a bug
|
||||
navigator.serviceWorker.controller.postMessage({ action: DELETE_SUBSCRIPTION })
|
||||
logger.info('deleted push subscription from server', { endpoint })
|
||||
}
|
||||
|
||||
const togglePushSubscription = useCallback(async () => {
|
||||
const pushSubscription = await registration.pushManager.getSubscription()
|
||||
if (pushSubscription) {
|
||||
return unsubscribeFromPushNotifications(pushSubscription)
|
||||
return await unsubscribeFromPushNotifications(pushSubscription)
|
||||
}
|
||||
await subscribeToPushNotifications()
|
||||
// request persistent storage: https://web.dev/learn/pwa/offline-data#data_persistence
|
||||
const persisted = await navigator?.storage?.persisted?.()
|
||||
if (!persisted && navigator?.storage?.persist) {
|
||||
return await navigator.storage.persist()
|
||||
}
|
||||
return subscribeToPushNotifications().then(async () => {
|
||||
// request persistent storage: https://web.dev/learn/pwa/offline-data#data_persistence
|
||||
const persisted = await navigator?.storage?.persisted?.()
|
||||
if (!persisted && navigator?.storage?.persist) {
|
||||
return navigator.storage.persist().then(persistent => {
|
||||
logger.info('persistent storage:', persistent)
|
||||
}).catch(logger.error)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
@ -133,13 +121,11 @@ export const ServiceWorkerProvider = ({ children }) => {
|
||||
setPermission({ notification: 'Notification' in window ? window.Notification.permission : 'denied' })
|
||||
|
||||
if (!('serviceWorker' in navigator)) {
|
||||
logger.info('device does not support service worker')
|
||||
return
|
||||
}
|
||||
|
||||
const wb = new Workbox('/sw.js', { scope: '/' })
|
||||
wb.register().then(registration => {
|
||||
logger.info('service worker registration successful')
|
||||
setRegistration(registration)
|
||||
})
|
||||
}, [])
|
||||
@ -158,10 +144,7 @@ export const ServiceWorkerProvider = ({ children }) => {
|
||||
// since (a lot of) browsers don't support the pushsubscriptionchange event,
|
||||
// we sync with server manually by checking on every page reload if the push subscription changed.
|
||||
// see https://medium.com/@madridserginho/how-to-handle-webpush-api-pushsubscriptionchange-event-in-modern-browsers-6e47840d756f
|
||||
navigator?.serviceWorker?.controller?.postMessage?.({ action: STORE_OS, os: detectOS() })
|
||||
logger.info('sent STORE_OS to service worker: ', detectOS())
|
||||
navigator?.serviceWorker?.controller?.postMessage?.({ action: SYNC_SUBSCRIPTION })
|
||||
logger.info('sent SYNC_SUBSCRIPTION to service worker')
|
||||
}, [registration, permission.notification])
|
||||
|
||||
const contextValue = useMemo(() => ({
|
||||
|
@ -23,7 +23,6 @@ ${STREAK_FIELDS}
|
||||
photoId
|
||||
privates {
|
||||
autoDropBolt11s
|
||||
diagnostics
|
||||
noReferralLinks
|
||||
fiatCurrency
|
||||
autoWithdrawMaxFeePercent
|
||||
@ -97,7 +96,6 @@ export const SETTINGS_FIELDS = gql`
|
||||
imgproxyOnly
|
||||
showImagesAndVideos
|
||||
hideWalletBalance
|
||||
diagnostics
|
||||
noReferralLinks
|
||||
nostrPubkey
|
||||
nostrCrossposting
|
||||
|
@ -472,7 +472,6 @@ export const settingsSchema = object().shape({
|
||||
hideNostr: boolean(),
|
||||
hideTwitter: boolean(),
|
||||
hideWalletBalance: boolean(),
|
||||
diagnostics: boolean(),
|
||||
noReferralLinks: boolean(),
|
||||
hideIsContributor: boolean(),
|
||||
disableFreebies: boolean().nullable(),
|
||||
|
@ -16,7 +16,6 @@ import { ServiceWorkerProvider } from '@/components/serviceworker'
|
||||
import { SSR } from '@/lib/constants'
|
||||
import NProgress from 'nprogress'
|
||||
import 'nprogress/nprogress.css'
|
||||
import { LoggerProvider } from '@/components/logger'
|
||||
import { ChainFeeProvider } from '@/components/chain-fee.js'
|
||||
import dynamic from 'next/dynamic'
|
||||
import { HasNewNotesProvider } from '@/components/use-has-new-notes'
|
||||
@ -114,28 +113,26 @@ export default function MyApp ({ Component, pageProps: { ...props } }) {
|
||||
<MeProvider me={me}>
|
||||
<WalletsProvider>
|
||||
<HasNewNotesProvider>
|
||||
<LoggerProvider>
|
||||
<WebLnProvider>
|
||||
<ServiceWorkerProvider>
|
||||
<PriceProvider price={price}>
|
||||
<FireworksProvider>
|
||||
<ToastProvider>
|
||||
<ShowModalProvider>
|
||||
<BlockHeightProvider blockHeight={blockHeight}>
|
||||
<ChainFeeProvider chainFee={chainFee}>
|
||||
<ErrorBoundary>
|
||||
<Component ssrData={ssrData} {...otherProps} />
|
||||
{!router?.query?.disablePrompt && <PWAPrompt copyBody='This website has app functionality. Add it to your home screen to use it in fullscreen and receive notifications. In Safari:' promptOnVisit={2} />}
|
||||
</ErrorBoundary>
|
||||
</ChainFeeProvider>
|
||||
</BlockHeightProvider>
|
||||
</ShowModalProvider>
|
||||
</ToastProvider>
|
||||
</FireworksProvider>
|
||||
</PriceProvider>
|
||||
</ServiceWorkerProvider>
|
||||
</WebLnProvider>
|
||||
</LoggerProvider>
|
||||
<WebLnProvider>
|
||||
<ServiceWorkerProvider>
|
||||
<PriceProvider price={price}>
|
||||
<FireworksProvider>
|
||||
<ToastProvider>
|
||||
<ShowModalProvider>
|
||||
<BlockHeightProvider blockHeight={blockHeight}>
|
||||
<ChainFeeProvider chainFee={chainFee}>
|
||||
<ErrorBoundary>
|
||||
<Component ssrData={ssrData} {...otherProps} />
|
||||
{!router?.query?.disablePrompt && <PWAPrompt copyBody='This website has app functionality. Add it to your home screen to use it in fullscreen and receive notifications. In Safari:' promptOnVisit={2} />}
|
||||
</ErrorBoundary>
|
||||
</ChainFeeProvider>
|
||||
</BlockHeightProvider>
|
||||
</ShowModalProvider>
|
||||
</ToastProvider>
|
||||
</FireworksProvider>
|
||||
</PriceProvider>
|
||||
</ServiceWorkerProvider>
|
||||
</WebLnProvider>
|
||||
</HasNewNotesProvider>
|
||||
</WalletsProvider>
|
||||
</MeProvider>
|
||||
|
@ -1,25 +0,0 @@
|
||||
import models from '@/api/models'
|
||||
import slackClient from '@/api/slack'
|
||||
|
||||
const channelId = process.env.SLACK_CHANNEL_ID
|
||||
|
||||
const toKV = (obj) => {
|
||||
return obj ? Object.entries(obj).reduce((text, [k, v]) => text + ` ${k}=${v}`, '').trimStart() : '-'
|
||||
}
|
||||
|
||||
const slackPostMessage = ({ id, level, name, message, env, context }) => {
|
||||
const text = `\`${new Date().toISOString()}\` | \`${id} [${level}] ${name}\` | ${message} | ${toKV(context)} | ${toKV({ os: env.os })}`
|
||||
return slackClient.chat.postMessage({ channel: channelId, text })
|
||||
}
|
||||
|
||||
export default async (req, res) => {
|
||||
const { level, name, message, env, context } = req.body
|
||||
if (!name) return res.status(400).json({ status: 400, message: 'name required' })
|
||||
if (!message) return res.status(400).json({ status: 400, message: 'message required' })
|
||||
|
||||
const { id } = await models.log.create({ data: { level: level.toUpperCase(), name, message, env, context } })
|
||||
|
||||
if (slackClient) slackPostMessage({ id, ...req.body }).catch(console.error)
|
||||
|
||||
return res.status(200).json({ status: 200, message: 'ok' })
|
||||
}
|
@ -24,7 +24,6 @@ import { useShowModal } from '@/components/modal'
|
||||
import { authErrorMessage } from '@/components/login'
|
||||
import { NostrAuth } from '@/components/nostr-auth'
|
||||
import { useToast } from '@/components/toast'
|
||||
import { useServiceWorkerLogger } from '@/components/logger'
|
||||
import { useMe } from '@/components/me'
|
||||
import { INVOICE_RETENTION_DAYS, ZAP_UNDO_DELAY_MS } from '@/lib/constants'
|
||||
import { OverlayTrigger, Tooltip } from 'react-bootstrap'
|
||||
@ -102,7 +101,6 @@ export default function Settings ({ ssrData }) {
|
||||
})
|
||||
}
|
||||
})
|
||||
const logger = useServiceWorkerLogger()
|
||||
|
||||
const { data } = useQuery(SETTINGS)
|
||||
const { settings: { privates: settings } } = useMemo(() => data ?? ssrData, [data, ssrData])
|
||||
@ -156,7 +154,6 @@ export default function Settings ({ ssrData }) {
|
||||
nostrRelays: settings?.nostrRelays?.length ? settings?.nostrRelays : [''],
|
||||
hideBookmarks: settings?.hideBookmarks,
|
||||
hideWalletBalance: settings?.hideWalletBalance,
|
||||
diagnostics: settings?.diagnostics,
|
||||
hideIsContributor: settings?.hideIsContributor,
|
||||
noReferralLinks: settings?.noReferralLinks,
|
||||
proxyReceive: settings?.proxyReceive,
|
||||
@ -505,29 +502,6 @@ export default function Settings ({ ssrData }) {
|
||||
name='imgproxyOnly'
|
||||
groupClassName='mb-0'
|
||||
/>
|
||||
<Checkbox
|
||||
label={
|
||||
<div className='d-flex align-items-center'>allow anonymous diagnostics
|
||||
<Info>
|
||||
<ul>
|
||||
<li>collect and send back anonymous diagnostics data</li>
|
||||
<li>this information is used to fix bugs</li>
|
||||
<li>this information includes:
|
||||
<ul><li>timestamps</li></ul>
|
||||
<ul><li>a randomly generated fancy name</li></ul>
|
||||
<ul><li>your user agent</li></ul>
|
||||
<ul><li>your operating system</li></ul>
|
||||
</li>
|
||||
<li>this information can not be traced back to you without your fancy name</li>
|
||||
<li>fancy names are generated in your browser</li>
|
||||
</ul>
|
||||
<div className='text-muted fst-italic'>your fancy name: {logger.name}</div>
|
||||
</Info>
|
||||
</div>
|
||||
}
|
||||
name='diagnostics'
|
||||
groupClassName='mb-0'
|
||||
/>
|
||||
<Checkbox
|
||||
label={<>don't create referral links on copy</>}
|
||||
name='noReferralLinks'
|
||||
|
@ -117,7 +117,7 @@ model User {
|
||||
followees UserSubscription[] @relation("followee")
|
||||
hideWelcomeBanner Boolean @default(false)
|
||||
hideWalletRecvPrompt Boolean @default(false)
|
||||
diagnostics Boolean @default(false)
|
||||
diagnostics Boolean @default(false) @ignore
|
||||
hideIsContributor Boolean @default(false)
|
||||
lnAddr String?
|
||||
autoWithdrawMaxFeePercent Float?
|
||||
@ -1214,6 +1214,8 @@ model Log {
|
||||
context Json?
|
||||
|
||||
@@index([createdAt, name], map: "Log.name_index")
|
||||
|
||||
@@ignore
|
||||
}
|
||||
|
||||
model TerritoryTransfer {
|
||||
|
@ -1,38 +1,20 @@
|
||||
import ServiceWorkerStorage from 'serviceworker-storage'
|
||||
import { numWithUnits } from '@/lib/format'
|
||||
import { CLEAR_NOTIFICATIONS, clearAppBadge, setAppBadge } from '@/lib/badge'
|
||||
import { ACTION_PORT, DELETE_SUBSCRIPTION, MESSAGE_PORT, STORE_OS, STORE_SUBSCRIPTION, SYNC_SUBSCRIPTION } from '@/components/serviceworker'
|
||||
// import { getLogger } from '@/lib/logger'
|
||||
import { ACTION_PORT, DELETE_SUBSCRIPTION, STORE_SUBSCRIPTION, SYNC_SUBSCRIPTION } from '@/components/serviceworker'
|
||||
|
||||
// we store existing push subscriptions and OS to keep them in sync with server
|
||||
const storage = new ServiceWorkerStorage('sw:storage', 1)
|
||||
|
||||
// for communication between app and service worker
|
||||
// see https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel
|
||||
let messageChannelPort
|
||||
let actionChannelPort
|
||||
|
||||
// operating system. the value will be received via a STORE_OS message from app since service workers don't have access to window.navigator
|
||||
let os = ''
|
||||
async function getOS () {
|
||||
if (!os) {
|
||||
os = await storage.getItem('os') || ''
|
||||
}
|
||||
return os
|
||||
}
|
||||
|
||||
// current push notification count for badge purposes
|
||||
let activeCount = 0
|
||||
|
||||
// message event listener for communication between app and service worker
|
||||
const log = (message, level = 'info', context) => {
|
||||
messageChannelPort?.postMessage({ level, message, context })
|
||||
}
|
||||
|
||||
export function onPush (sw) {
|
||||
return (event) => {
|
||||
// in case of push notifications, make sure that the logger has an HTTPS endpoint
|
||||
// const logger = getLogger('sw:push', ['onPush'])
|
||||
let payload = event.data?.json()
|
||||
if (!payload) return // ignore push events without payload, like isTrusted events
|
||||
const { tag } = payload.options
|
||||
@ -43,15 +25,11 @@ export function onPush (sw) {
|
||||
|
||||
// On immediate notifications we update the counter
|
||||
if (immediatelyShowNotification(tag)) {
|
||||
// logger.info(`[${nid}] showing immediate notification with title: ${payload.title}`)
|
||||
promises.push(setAppBadge(sw, ++activeCount))
|
||||
} else {
|
||||
// logger.info(`[${nid}] checking for existing notification with tag ${tag}`)
|
||||
// Check if there are already notifications with the same tag and merge them
|
||||
promises.push(sw.registration.getNotifications({ tag }).then((notifications) => {
|
||||
// logger.info(`[${nid}] found ${notifications.length} notifications with tag ${tag}`)
|
||||
if (notifications.length) {
|
||||
// logger.info(`[${nid}] found ${notifications.length} notifications with tag ${tag}`)
|
||||
payload = mergeNotification(event, sw, payload, notifications, tag, nid)
|
||||
}
|
||||
}))
|
||||
@ -71,22 +49,16 @@ const immediatelyShowNotification = (tag) =>
|
||||
|
||||
// merge notifications with the same tag
|
||||
const mergeNotification = (event, sw, payload, currentNotifications, tag, nid) => {
|
||||
// const logger = getLogger('sw:push:mergeNotification', ['mergeNotification'])
|
||||
|
||||
// sanity check
|
||||
const otherTagNotifications = currentNotifications.filter(({ tag: nTag }) => nTag !== tag)
|
||||
if (otherTagNotifications.length > 0) {
|
||||
// we can't recover from this here. bail.
|
||||
// logger.error(`${nid} - bailing -- more than one notification with tag ${tag} found after manual filter`)
|
||||
return
|
||||
}
|
||||
|
||||
const { data: incomingData } = payload.options
|
||||
// logger.info(`[sw:push] ${nid} - incoming payload.options.data: ${JSON.stringify(incomingData)}`)
|
||||
|
||||
// we can ignore everything after the first dash in the tag for our control flow
|
||||
const compareTag = tag.split('-')[0]
|
||||
// logger.info(`[sw:push] ${nid} - using ${compareTag} for control flow`)
|
||||
|
||||
// merge notifications into single notification payload
|
||||
// ---
|
||||
@ -97,8 +69,6 @@ const mergeNotification = (event, sw, payload, currentNotifications, tag, nid) =
|
||||
// this should reflect the amount of notifications that were already merged before
|
||||
const initialAmount = currentNotifications.length || 1
|
||||
const initialSats = currentNotifications[0]?.data?.sats || 0
|
||||
// logger.info(`[sw:push] ${nid} - initial amount: ${initialAmount}`)
|
||||
// logger.info(`[sw:push] ${nid} - initial sats: ${initialSats}`)
|
||||
|
||||
// currentNotifications.reduce causes iOS to sum n notifications + initialAmount which is already n notifications
|
||||
const mergedPayload = {
|
||||
@ -108,8 +78,6 @@ const mergeNotification = (event, sw, payload, currentNotifications, tag, nid) =
|
||||
sats: initialSats + incomingData.sats
|
||||
}
|
||||
|
||||
// logger.info(`[sw:push] ${nid} - merged payload: ${JSON.stringify(mergedPayload)}`)
|
||||
|
||||
// calculate title from merged payload
|
||||
const { amount, followeeName, subName, subType, sats } = mergedPayload
|
||||
let title = ''
|
||||
@ -136,10 +104,8 @@ const mergeNotification = (event, sw, payload, currentNotifications, tag, nid) =
|
||||
title = `${numWithUnits(sats, { abbreviate: false, unitSingular: 'sat was', unitPlural: 'sats were' })} withdrawn from your account`
|
||||
}
|
||||
}
|
||||
// logger.info(`[sw:push] ${nid} - calculated title: ${title}`)
|
||||
|
||||
const options = { icon: payload.options?.icon, tag, data: { ...mergedPayload } }
|
||||
// logger.info(`[sw:push] ${nid} - show notification with title "${title}"`)
|
||||
return { title, options } // send the new, merged, payload
|
||||
}
|
||||
|
||||
@ -147,9 +113,7 @@ const mergeNotification = (event, sw, payload, currentNotifications, tag, nid) =
|
||||
export function onNotificationClick (sw) {
|
||||
return (event) => {
|
||||
const promises = []
|
||||
// const logger = getLogger('sw:onNotificationClick', ['onNotificationClick'])
|
||||
const url = event.notification.data?.url
|
||||
// logger.info(`[sw:onNotificationClick] clicked notification with url ${url}`)
|
||||
if (url) {
|
||||
promises.push(sw.clients.openWindow(url))
|
||||
}
|
||||
@ -169,11 +133,9 @@ export function onPushSubscriptionChange (sw) {
|
||||
// `isSync` is passed if function was called because of 'SYNC_SUBSCRIPTION' event
|
||||
// this makes sure we can differentiate between 'pushsubscriptionchange' events and our custom 'SYNC_SUBSCRIPTION' event
|
||||
return async (event, isSync) => {
|
||||
// const logger = getLogger('sw:onPushSubscriptionChange', ['onPushSubscriptionChange'])
|
||||
let { oldSubscription, newSubscription } = event
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/pushsubscriptionchange_event
|
||||
// fallbacks since browser may not set oldSubscription and newSubscription
|
||||
// logger.info('[sw:handlePushSubscriptionChange] invoked')
|
||||
oldSubscription ??= await storage.getItem('subscription')
|
||||
newSubscription ??= await sw.registration.pushManager.getSubscription()
|
||||
if (!newSubscription) {
|
||||
@ -182,17 +144,14 @@ export function onPushSubscriptionChange (sw) {
|
||||
// see https://github.com/stackernews/stacker.news/issues/411#issuecomment-1790675861
|
||||
// NOTE: this is only run on IndexedDB subscriptions stored under service worker version 2 since this is not backwards compatible
|
||||
// see discussion in https://github.com/stackernews/stacker.news/pull/597
|
||||
// logger.info('[sw:handlePushSubscriptionChange] service worker lost subscription')
|
||||
actionChannelPort?.postMessage({ action: 'RESUBSCRIBE' })
|
||||
return
|
||||
}
|
||||
// no subscription exists at the moment
|
||||
// logger.info('[sw:handlePushSubscriptionChange] no existing subscription found')
|
||||
return
|
||||
}
|
||||
if (oldSubscription?.endpoint === newSubscription.endpoint) {
|
||||
// subscription did not change. no need to sync with server
|
||||
// logger.info('[sw:handlePushSubscriptionChange] old subscription matches existing subscription')
|
||||
// subscription did not change. no need to sync with server
|
||||
return
|
||||
}
|
||||
// convert keys from ArrayBuffer to string
|
||||
@ -217,7 +176,6 @@ export function onPushSubscriptionChange (sw) {
|
||||
},
|
||||
body
|
||||
})
|
||||
// logger.info('[sw:handlePushSubscriptionChange] synced push subscription with server', 'info', { endpoint: variables.endpoint, oldEndpoint: variables.oldEndpoint })
|
||||
await storage.setItem('subscription', JSON.parse(JSON.stringify(newSubscription)))
|
||||
}
|
||||
}
|
||||
@ -228,18 +186,7 @@ export function onMessage (sw) {
|
||||
actionChannelPort = event.ports[0]
|
||||
return
|
||||
}
|
||||
if (event.data.action === STORE_OS) {
|
||||
event.waitUntil(storage.setItem('os', event.data.os))
|
||||
return
|
||||
}
|
||||
if (event.data.action === MESSAGE_PORT) {
|
||||
messageChannelPort = event.ports[0]
|
||||
}
|
||||
log('[sw:message] received message', 'info', { action: event.data.action })
|
||||
const currentOS = event.waitUntil(getOS())
|
||||
log('[sw:message] stored os: ' + currentOS, 'info', { action: event.data.action })
|
||||
if (event.data.action === STORE_SUBSCRIPTION) {
|
||||
log('[sw:message] storing subscription in IndexedDB', 'info', { endpoint: event.data.subscription.endpoint })
|
||||
return event.waitUntil(storage.setItem('subscription', { ...event.data.subscription, swVersion: 2 }))
|
||||
}
|
||||
if (event.data.action === SYNC_SUBSCRIPTION) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user