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 {
|
input SettingsInput {
|
||||||
autoDropBolt11s: Boolean!
|
autoDropBolt11s: Boolean!
|
||||||
diagnostics: Boolean!
|
diagnostics: Boolean @deprecated
|
||||||
noReferralLinks: Boolean!
|
noReferralLinks: Boolean!
|
||||||
fiatCurrency: String!
|
fiatCurrency: String!
|
||||||
satsFilter: Int!
|
satsFilter: Int!
|
||||||
@ -162,7 +162,7 @@ export default gql`
|
|||||||
mirrors SettingsInput
|
mirrors SettingsInput
|
||||||
"""
|
"""
|
||||||
autoDropBolt11s: Boolean!
|
autoDropBolt11s: Boolean!
|
||||||
diagnostics: Boolean!
|
diagnostics: Boolean @deprecated
|
||||||
noReferralLinks: Boolean!
|
noReferralLinks: Boolean!
|
||||||
fiatCurrency: String!
|
fiatCurrency: String!
|
||||||
satsFilter: Int!
|
satsFilter: Int!
|
||||||
|
@ -3,7 +3,6 @@ import { StaticLayout } from './layout'
|
|||||||
import styles from '@/styles/error.module.css'
|
import styles from '@/styles/error.module.css'
|
||||||
import Image from 'react-bootstrap/Image'
|
import Image from 'react-bootstrap/Image'
|
||||||
import copy from 'clipboard-copy'
|
import copy from 'clipboard-copy'
|
||||||
import { LoggerContext } from './logger'
|
|
||||||
import Button from 'react-bootstrap/Button'
|
import Button from 'react-bootstrap/Button'
|
||||||
import { useToast } from './toast'
|
import { useToast } from './toast'
|
||||||
import { decodeMinifiedStackTrace } from '@/lib/stacktrace'
|
import { decodeMinifiedStackTrace } from '@/lib/stacktrace'
|
||||||
@ -36,8 +35,6 @@ class ErrorBoundary extends Component {
|
|||||||
// You can use your own error logging service here
|
// You can use your own error logging service here
|
||||||
console.log({ error, errorInfo })
|
console.log({ error, errorInfo })
|
||||||
this.setState({ errorInfo })
|
this.setState({ errorInfo })
|
||||||
const logger = this.context
|
|
||||||
logger?.error(this.getErrorDetails())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
@ -59,8 +56,6 @@ class ErrorBoundary extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorBoundary.contextType = LoggerContext
|
|
||||||
|
|
||||||
export default ErrorBoundary
|
export default ErrorBoundary
|
||||||
|
|
||||||
// This button is a functional component so we can use `useToast` hook, which
|
// 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 { createContext, useContext, useEffect, useState, useCallback, useMemo } from 'react'
|
||||||
import { Workbox } from 'workbox-window'
|
import { Workbox } from 'workbox-window'
|
||||||
import { gql, useMutation } from '@apollo/client'
|
import { gql, useMutation } from '@apollo/client'
|
||||||
import { detectOS, useServiceWorkerLogger } from './logger'
|
|
||||||
|
|
||||||
const applicationServerKey = process.env.NEXT_PUBLIC_VAPID_PUBKEY
|
const applicationServerKey = process.env.NEXT_PUBLIC_VAPID_PUBKEY
|
||||||
|
|
||||||
const ServiceWorkerContext = createContext()
|
const ServiceWorkerContext = createContext()
|
||||||
|
|
||||||
// message types for communication between app and service worker
|
// 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 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 SYNC_SUBSCRIPTION = 'SYNC_SUBSCRIPTION' // trigger onPushSubscriptionChange event in service worker manually
|
||||||
export const RESUBSCRIBE = 'RESUBSCRIBE' // trigger resubscribing to push notifications (sw -> app)
|
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 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_SUBSCRIPTION = 'STORE_SUBSCRIPTION' // store subscription in IndexedDB (app -> sw)
|
||||||
export const STORE_OS = 'STORE_OS' // store OS in service worker
|
|
||||||
|
|
||||||
export const ServiceWorkerProvider = ({ children }) => {
|
export const ServiceWorkerProvider = ({ children }) => {
|
||||||
const [registration, setRegistration] = useState(null)
|
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,
|
// I am not entirely sure if this is needed since at least in Brave,
|
||||||
// using `registration.pushManager.subscribe` also prompts the user.
|
// using `registration.pushManager.subscribe` also prompts the user.
|
||||||
@ -77,7 +73,6 @@ export const ServiceWorkerProvider = ({ children }) => {
|
|||||||
// see https://stackoverflow.com/a/69624651
|
// see https://stackoverflow.com/a/69624651
|
||||||
let pushSubscription = await registration.pushManager.subscribe(subscribeOptions)
|
let pushSubscription = await registration.pushManager.subscribe(subscribeOptions)
|
||||||
const { endpoint } = pushSubscription
|
const { endpoint } = pushSubscription
|
||||||
logger.info('subscribed to push notifications', { endpoint })
|
|
||||||
// convert keys from ArrayBuffer to string
|
// convert keys from ArrayBuffer to string
|
||||||
pushSubscription = JSON.parse(JSON.stringify(pushSubscription))
|
pushSubscription = JSON.parse(JSON.stringify(pushSubscription))
|
||||||
// Send subscription to service worker to save it so we can use it later during `pushsubscriptionchange`
|
// 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,
|
action: STORE_SUBSCRIPTION,
|
||||||
subscription: pushSubscription
|
subscription: pushSubscription
|
||||||
})
|
})
|
||||||
logger.info('sent STORE_SUBSCRIPTION to service worker', { endpoint })
|
|
||||||
// send subscription to server
|
// send subscription to server
|
||||||
const variables = {
|
const variables = {
|
||||||
endpoint,
|
endpoint,
|
||||||
@ -94,35 +88,29 @@ export const ServiceWorkerProvider = ({ children }) => {
|
|||||||
auth: pushSubscription.keys.auth
|
auth: pushSubscription.keys.auth
|
||||||
}
|
}
|
||||||
await savePushSubscription({ variables })
|
await savePushSubscription({ variables })
|
||||||
logger.info('sent push subscription to server', { endpoint })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const unsubscribeFromPushNotifications = async (subscription) => {
|
const unsubscribeFromPushNotifications = async (subscription) => {
|
||||||
await subscription.unsubscribe()
|
await subscription.unsubscribe()
|
||||||
const { endpoint } = subscription
|
const { endpoint } = subscription
|
||||||
logger.info('unsubscribed from push notifications', { endpoint })
|
|
||||||
await deletePushSubscription({ variables: { endpoint } })
|
await deletePushSubscription({ variables: { endpoint } })
|
||||||
// also delete push subscription in IndexedDB so we can tell if the user disabled push subscriptions
|
// 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
|
// or we lost the push subscription due to a bug
|
||||||
navigator.serviceWorker.controller.postMessage({ action: DELETE_SUBSCRIPTION })
|
navigator.serviceWorker.controller.postMessage({ action: DELETE_SUBSCRIPTION })
|
||||||
logger.info('deleted push subscription from server', { endpoint })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const togglePushSubscription = useCallback(async () => {
|
const togglePushSubscription = useCallback(async () => {
|
||||||
const pushSubscription = await registration.pushManager.getSubscription()
|
const pushSubscription = await registration.pushManager.getSubscription()
|
||||||
if (pushSubscription) {
|
if (pushSubscription) {
|
||||||
return unsubscribeFromPushNotifications(pushSubscription)
|
return await unsubscribeFromPushNotifications(pushSubscription)
|
||||||
}
|
}
|
||||||
return subscribeToPushNotifications().then(async () => {
|
await subscribeToPushNotifications()
|
||||||
// request persistent storage: https://web.dev/learn/pwa/offline-data#data_persistence
|
// request persistent storage: https://web.dev/learn/pwa/offline-data#data_persistence
|
||||||
const persisted = await navigator?.storage?.persisted?.()
|
const persisted = await navigator?.storage?.persisted?.()
|
||||||
if (!persisted && navigator?.storage?.persist) {
|
if (!persisted && navigator?.storage?.persist) {
|
||||||
return navigator.storage.persist().then(persistent => {
|
return await navigator.storage.persist()
|
||||||
logger.info('persistent storage:', persistent)
|
|
||||||
}).catch(logger.error)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSupport({
|
setSupport({
|
||||||
@ -133,13 +121,11 @@ export const ServiceWorkerProvider = ({ children }) => {
|
|||||||
setPermission({ notification: 'Notification' in window ? window.Notification.permission : 'denied' })
|
setPermission({ notification: 'Notification' in window ? window.Notification.permission : 'denied' })
|
||||||
|
|
||||||
if (!('serviceWorker' in navigator)) {
|
if (!('serviceWorker' in navigator)) {
|
||||||
logger.info('device does not support service worker')
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const wb = new Workbox('/sw.js', { scope: '/' })
|
const wb = new Workbox('/sw.js', { scope: '/' })
|
||||||
wb.register().then(registration => {
|
wb.register().then(registration => {
|
||||||
logger.info('service worker registration successful')
|
|
||||||
setRegistration(registration)
|
setRegistration(registration)
|
||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
@ -158,10 +144,7 @@ export const ServiceWorkerProvider = ({ children }) => {
|
|||||||
// since (a lot of) browsers don't support the pushsubscriptionchange event,
|
// 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.
|
// 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
|
// 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 })
|
navigator?.serviceWorker?.controller?.postMessage?.({ action: SYNC_SUBSCRIPTION })
|
||||||
logger.info('sent SYNC_SUBSCRIPTION to service worker')
|
|
||||||
}, [registration, permission.notification])
|
}, [registration, permission.notification])
|
||||||
|
|
||||||
const contextValue = useMemo(() => ({
|
const contextValue = useMemo(() => ({
|
||||||
|
@ -23,7 +23,6 @@ ${STREAK_FIELDS}
|
|||||||
photoId
|
photoId
|
||||||
privates {
|
privates {
|
||||||
autoDropBolt11s
|
autoDropBolt11s
|
||||||
diagnostics
|
|
||||||
noReferralLinks
|
noReferralLinks
|
||||||
fiatCurrency
|
fiatCurrency
|
||||||
autoWithdrawMaxFeePercent
|
autoWithdrawMaxFeePercent
|
||||||
@ -97,7 +96,6 @@ export const SETTINGS_FIELDS = gql`
|
|||||||
imgproxyOnly
|
imgproxyOnly
|
||||||
showImagesAndVideos
|
showImagesAndVideos
|
||||||
hideWalletBalance
|
hideWalletBalance
|
||||||
diagnostics
|
|
||||||
noReferralLinks
|
noReferralLinks
|
||||||
nostrPubkey
|
nostrPubkey
|
||||||
nostrCrossposting
|
nostrCrossposting
|
||||||
|
@ -472,7 +472,6 @@ export const settingsSchema = object().shape({
|
|||||||
hideNostr: boolean(),
|
hideNostr: boolean(),
|
||||||
hideTwitter: boolean(),
|
hideTwitter: boolean(),
|
||||||
hideWalletBalance: boolean(),
|
hideWalletBalance: boolean(),
|
||||||
diagnostics: boolean(),
|
|
||||||
noReferralLinks: boolean(),
|
noReferralLinks: boolean(),
|
||||||
hideIsContributor: boolean(),
|
hideIsContributor: boolean(),
|
||||||
disableFreebies: boolean().nullable(),
|
disableFreebies: boolean().nullable(),
|
||||||
|
@ -16,7 +16,6 @@ import { ServiceWorkerProvider } from '@/components/serviceworker'
|
|||||||
import { SSR } from '@/lib/constants'
|
import { SSR } from '@/lib/constants'
|
||||||
import NProgress from 'nprogress'
|
import NProgress from 'nprogress'
|
||||||
import 'nprogress/nprogress.css'
|
import 'nprogress/nprogress.css'
|
||||||
import { LoggerProvider } from '@/components/logger'
|
|
||||||
import { ChainFeeProvider } from '@/components/chain-fee.js'
|
import { ChainFeeProvider } from '@/components/chain-fee.js'
|
||||||
import dynamic from 'next/dynamic'
|
import dynamic from 'next/dynamic'
|
||||||
import { HasNewNotesProvider } from '@/components/use-has-new-notes'
|
import { HasNewNotesProvider } from '@/components/use-has-new-notes'
|
||||||
@ -114,7 +113,6 @@ export default function MyApp ({ Component, pageProps: { ...props } }) {
|
|||||||
<MeProvider me={me}>
|
<MeProvider me={me}>
|
||||||
<WalletsProvider>
|
<WalletsProvider>
|
||||||
<HasNewNotesProvider>
|
<HasNewNotesProvider>
|
||||||
<LoggerProvider>
|
|
||||||
<WebLnProvider>
|
<WebLnProvider>
|
||||||
<ServiceWorkerProvider>
|
<ServiceWorkerProvider>
|
||||||
<PriceProvider price={price}>
|
<PriceProvider price={price}>
|
||||||
@ -135,7 +133,6 @@ export default function MyApp ({ Component, pageProps: { ...props } }) {
|
|||||||
</PriceProvider>
|
</PriceProvider>
|
||||||
</ServiceWorkerProvider>
|
</ServiceWorkerProvider>
|
||||||
</WebLnProvider>
|
</WebLnProvider>
|
||||||
</LoggerProvider>
|
|
||||||
</HasNewNotesProvider>
|
</HasNewNotesProvider>
|
||||||
</WalletsProvider>
|
</WalletsProvider>
|
||||||
</MeProvider>
|
</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 { authErrorMessage } from '@/components/login'
|
||||||
import { NostrAuth } from '@/components/nostr-auth'
|
import { NostrAuth } from '@/components/nostr-auth'
|
||||||
import { useToast } from '@/components/toast'
|
import { useToast } from '@/components/toast'
|
||||||
import { useServiceWorkerLogger } from '@/components/logger'
|
|
||||||
import { useMe } from '@/components/me'
|
import { useMe } from '@/components/me'
|
||||||
import { INVOICE_RETENTION_DAYS, ZAP_UNDO_DELAY_MS } from '@/lib/constants'
|
import { INVOICE_RETENTION_DAYS, ZAP_UNDO_DELAY_MS } from '@/lib/constants'
|
||||||
import { OverlayTrigger, Tooltip } from 'react-bootstrap'
|
import { OverlayTrigger, Tooltip } from 'react-bootstrap'
|
||||||
@ -102,7 +101,6 @@ export default function Settings ({ ssrData }) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const logger = useServiceWorkerLogger()
|
|
||||||
|
|
||||||
const { data } = useQuery(SETTINGS)
|
const { data } = useQuery(SETTINGS)
|
||||||
const { settings: { privates: settings } } = useMemo(() => data ?? ssrData, [data, ssrData])
|
const { settings: { privates: settings } } = useMemo(() => data ?? ssrData, [data, ssrData])
|
||||||
@ -156,7 +154,6 @@ export default function Settings ({ ssrData }) {
|
|||||||
nostrRelays: settings?.nostrRelays?.length ? settings?.nostrRelays : [''],
|
nostrRelays: settings?.nostrRelays?.length ? settings?.nostrRelays : [''],
|
||||||
hideBookmarks: settings?.hideBookmarks,
|
hideBookmarks: settings?.hideBookmarks,
|
||||||
hideWalletBalance: settings?.hideWalletBalance,
|
hideWalletBalance: settings?.hideWalletBalance,
|
||||||
diagnostics: settings?.diagnostics,
|
|
||||||
hideIsContributor: settings?.hideIsContributor,
|
hideIsContributor: settings?.hideIsContributor,
|
||||||
noReferralLinks: settings?.noReferralLinks,
|
noReferralLinks: settings?.noReferralLinks,
|
||||||
proxyReceive: settings?.proxyReceive,
|
proxyReceive: settings?.proxyReceive,
|
||||||
@ -505,29 +502,6 @@ export default function Settings ({ ssrData }) {
|
|||||||
name='imgproxyOnly'
|
name='imgproxyOnly'
|
||||||
groupClassName='mb-0'
|
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
|
<Checkbox
|
||||||
label={<>don't create referral links on copy</>}
|
label={<>don't create referral links on copy</>}
|
||||||
name='noReferralLinks'
|
name='noReferralLinks'
|
||||||
|
@ -117,7 +117,7 @@ model User {
|
|||||||
followees UserSubscription[] @relation("followee")
|
followees UserSubscription[] @relation("followee")
|
||||||
hideWelcomeBanner Boolean @default(false)
|
hideWelcomeBanner Boolean @default(false)
|
||||||
hideWalletRecvPrompt Boolean @default(false)
|
hideWalletRecvPrompt Boolean @default(false)
|
||||||
diagnostics Boolean @default(false)
|
diagnostics Boolean @default(false) @ignore
|
||||||
hideIsContributor Boolean @default(false)
|
hideIsContributor Boolean @default(false)
|
||||||
lnAddr String?
|
lnAddr String?
|
||||||
autoWithdrawMaxFeePercent Float?
|
autoWithdrawMaxFeePercent Float?
|
||||||
@ -1214,6 +1214,8 @@ model Log {
|
|||||||
context Json?
|
context Json?
|
||||||
|
|
||||||
@@index([createdAt, name], map: "Log.name_index")
|
@@index([createdAt, name], map: "Log.name_index")
|
||||||
|
|
||||||
|
@@ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
model TerritoryTransfer {
|
model TerritoryTransfer {
|
||||||
|
@ -1,38 +1,20 @@
|
|||||||
import ServiceWorkerStorage from 'serviceworker-storage'
|
import ServiceWorkerStorage from 'serviceworker-storage'
|
||||||
import { numWithUnits } from '@/lib/format'
|
import { numWithUnits } from '@/lib/format'
|
||||||
import { CLEAR_NOTIFICATIONS, clearAppBadge, setAppBadge } from '@/lib/badge'
|
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 { ACTION_PORT, DELETE_SUBSCRIPTION, STORE_SUBSCRIPTION, SYNC_SUBSCRIPTION } from '@/components/serviceworker'
|
||||||
// import { getLogger } from '@/lib/logger'
|
|
||||||
|
|
||||||
// we store existing push subscriptions and OS to keep them in sync with server
|
// we store existing push subscriptions and OS to keep them in sync with server
|
||||||
const storage = new ServiceWorkerStorage('sw:storage', 1)
|
const storage = new ServiceWorkerStorage('sw:storage', 1)
|
||||||
|
|
||||||
// for communication between app and service worker
|
// for communication between app and service worker
|
||||||
// see https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel
|
// see https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel
|
||||||
let messageChannelPort
|
|
||||||
let actionChannelPort
|
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
|
// current push notification count for badge purposes
|
||||||
let activeCount = 0
|
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) {
|
export function onPush (sw) {
|
||||||
return (event) => {
|
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()
|
let payload = event.data?.json()
|
||||||
if (!payload) return // ignore push events without payload, like isTrusted events
|
if (!payload) return // ignore push events without payload, like isTrusted events
|
||||||
const { tag } = payload.options
|
const { tag } = payload.options
|
||||||
@ -43,15 +25,11 @@ export function onPush (sw) {
|
|||||||
|
|
||||||
// On immediate notifications we update the counter
|
// On immediate notifications we update the counter
|
||||||
if (immediatelyShowNotification(tag)) {
|
if (immediatelyShowNotification(tag)) {
|
||||||
// logger.info(`[${nid}] showing immediate notification with title: ${payload.title}`)
|
|
||||||
promises.push(setAppBadge(sw, ++activeCount))
|
promises.push(setAppBadge(sw, ++activeCount))
|
||||||
} else {
|
} else {
|
||||||
// logger.info(`[${nid}] checking for existing notification with tag ${tag}`)
|
|
||||||
// Check if there are already notifications with the same tag and merge them
|
// Check if there are already notifications with the same tag and merge them
|
||||||
promises.push(sw.registration.getNotifications({ tag }).then((notifications) => {
|
promises.push(sw.registration.getNotifications({ tag }).then((notifications) => {
|
||||||
// logger.info(`[${nid}] found ${notifications.length} notifications with tag ${tag}`)
|
|
||||||
if (notifications.length) {
|
if (notifications.length) {
|
||||||
// logger.info(`[${nid}] found ${notifications.length} notifications with tag ${tag}`)
|
|
||||||
payload = mergeNotification(event, sw, payload, notifications, tag, nid)
|
payload = mergeNotification(event, sw, payload, notifications, tag, nid)
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
@ -71,22 +49,16 @@ const immediatelyShowNotification = (tag) =>
|
|||||||
|
|
||||||
// merge notifications with the same tag
|
// merge notifications with the same tag
|
||||||
const mergeNotification = (event, sw, payload, currentNotifications, tag, nid) => {
|
const mergeNotification = (event, sw, payload, currentNotifications, tag, nid) => {
|
||||||
// const logger = getLogger('sw:push:mergeNotification', ['mergeNotification'])
|
|
||||||
|
|
||||||
// sanity check
|
// sanity check
|
||||||
const otherTagNotifications = currentNotifications.filter(({ tag: nTag }) => nTag !== tag)
|
const otherTagNotifications = currentNotifications.filter(({ tag: nTag }) => nTag !== tag)
|
||||||
if (otherTagNotifications.length > 0) {
|
if (otherTagNotifications.length > 0) {
|
||||||
// we can't recover from this here. bail.
|
// we can't recover from this here. bail.
|
||||||
// logger.error(`${nid} - bailing -- more than one notification with tag ${tag} found after manual filter`)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data: incomingData } = payload.options
|
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
|
// we can ignore everything after the first dash in the tag for our control flow
|
||||||
const compareTag = tag.split('-')[0]
|
const compareTag = tag.split('-')[0]
|
||||||
// logger.info(`[sw:push] ${nid} - using ${compareTag} for control flow`)
|
|
||||||
|
|
||||||
// merge notifications into single notification payload
|
// 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
|
// this should reflect the amount of notifications that were already merged before
|
||||||
const initialAmount = currentNotifications.length || 1
|
const initialAmount = currentNotifications.length || 1
|
||||||
const initialSats = currentNotifications[0]?.data?.sats || 0
|
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
|
// currentNotifications.reduce causes iOS to sum n notifications + initialAmount which is already n notifications
|
||||||
const mergedPayload = {
|
const mergedPayload = {
|
||||||
@ -108,8 +78,6 @@ const mergeNotification = (event, sw, payload, currentNotifications, tag, nid) =
|
|||||||
sats: initialSats + incomingData.sats
|
sats: initialSats + incomingData.sats
|
||||||
}
|
}
|
||||||
|
|
||||||
// logger.info(`[sw:push] ${nid} - merged payload: ${JSON.stringify(mergedPayload)}`)
|
|
||||||
|
|
||||||
// calculate title from merged payload
|
// calculate title from merged payload
|
||||||
const { amount, followeeName, subName, subType, sats } = mergedPayload
|
const { amount, followeeName, subName, subType, sats } = mergedPayload
|
||||||
let title = ''
|
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`
|
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 } }
|
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
|
return { title, options } // send the new, merged, payload
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,9 +113,7 @@ const mergeNotification = (event, sw, payload, currentNotifications, tag, nid) =
|
|||||||
export function onNotificationClick (sw) {
|
export function onNotificationClick (sw) {
|
||||||
return (event) => {
|
return (event) => {
|
||||||
const promises = []
|
const promises = []
|
||||||
// const logger = getLogger('sw:onNotificationClick', ['onNotificationClick'])
|
|
||||||
const url = event.notification.data?.url
|
const url = event.notification.data?.url
|
||||||
// logger.info(`[sw:onNotificationClick] clicked notification with url ${url}`)
|
|
||||||
if (url) {
|
if (url) {
|
||||||
promises.push(sw.clients.openWindow(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
|
// `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
|
// this makes sure we can differentiate between 'pushsubscriptionchange' events and our custom 'SYNC_SUBSCRIPTION' event
|
||||||
return async (event, isSync) => {
|
return async (event, isSync) => {
|
||||||
// const logger = getLogger('sw:onPushSubscriptionChange', ['onPushSubscriptionChange'])
|
|
||||||
let { oldSubscription, newSubscription } = event
|
let { oldSubscription, newSubscription } = event
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/pushsubscriptionchange_event
|
// https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/pushsubscriptionchange_event
|
||||||
// fallbacks since browser may not set oldSubscription and newSubscription
|
// fallbacks since browser may not set oldSubscription and newSubscription
|
||||||
// logger.info('[sw:handlePushSubscriptionChange] invoked')
|
|
||||||
oldSubscription ??= await storage.getItem('subscription')
|
oldSubscription ??= await storage.getItem('subscription')
|
||||||
newSubscription ??= await sw.registration.pushManager.getSubscription()
|
newSubscription ??= await sw.registration.pushManager.getSubscription()
|
||||||
if (!newSubscription) {
|
if (!newSubscription) {
|
||||||
@ -182,17 +144,14 @@ export function onPushSubscriptionChange (sw) {
|
|||||||
// see https://github.com/stackernews/stacker.news/issues/411#issuecomment-1790675861
|
// 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
|
// 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
|
// see discussion in https://github.com/stackernews/stacker.news/pull/597
|
||||||
// logger.info('[sw:handlePushSubscriptionChange] service worker lost subscription')
|
|
||||||
actionChannelPort?.postMessage({ action: 'RESUBSCRIBE' })
|
actionChannelPort?.postMessage({ action: 'RESUBSCRIBE' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// no subscription exists at the moment
|
// no subscription exists at the moment
|
||||||
// logger.info('[sw:handlePushSubscriptionChange] no existing subscription found')
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (oldSubscription?.endpoint === newSubscription.endpoint) {
|
if (oldSubscription?.endpoint === newSubscription.endpoint) {
|
||||||
// subscription did not change. no need to sync with server
|
// subscription did not change. no need to sync with server
|
||||||
// logger.info('[sw:handlePushSubscriptionChange] old subscription matches existing subscription')
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// convert keys from ArrayBuffer to string
|
// convert keys from ArrayBuffer to string
|
||||||
@ -217,7 +176,6 @@ export function onPushSubscriptionChange (sw) {
|
|||||||
},
|
},
|
||||||
body
|
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)))
|
await storage.setItem('subscription', JSON.parse(JSON.stringify(newSubscription)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -228,18 +186,7 @@ export function onMessage (sw) {
|
|||||||
actionChannelPort = event.ports[0]
|
actionChannelPort = event.ports[0]
|
||||||
return
|
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) {
|
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 }))
|
return event.waitUntil(storage.setItem('subscription', { ...event.data.subscription, swVersion: 2 }))
|
||||||
}
|
}
|
||||||
if (event.data.action === SYNC_SUBSCRIPTION) {
|
if (event.data.action === SYNC_SUBSCRIPTION) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user