Add setting to send diagnostics back to SN (#463)
* Add diagnostics settings & endpoint Stackers can now help us to identify and fix bugs by enabling diagnostics. This will send anonymized data to us. For now, this is only used to send events around push notifications. * Send diagnostics to slack * Detect OS * Diagnostics data is only pseudonymous, not anonymous It's only pseudonymous since with additional knowledge (which stacker uses which fancy name), we could trace the events back to individual stackers. Data is only anonymous if this is not possible - it must be irreversible. * Check if window.navigator is defined * Use Slack SDK * Catch errors of slack requests --------- Co-authored-by: ekzyis <ek@stacker.news> Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
This commit is contained in:
parent
e5852ee0b1
commit
3a7c3f7af2
|
@ -66,3 +66,7 @@ DATABASE_URL="postgresql://sn:password@db:5432/stackernews?schema=public"
|
|||
POSTGRES_PASSWORD=password
|
||||
POSTGRES_USER=sn
|
||||
POSTGRES_DB=stackernews
|
||||
|
||||
# slack
|
||||
SLACK_BOT_TOKEN=
|
||||
SLACK_CHANNEL_ID=
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
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
|
|
@ -25,7 +25,7 @@ export default gql`
|
|||
noteInvites: Boolean!, noteJobIndicator: Boolean!, noteCowboyHat: Boolean!, hideInvoiceDesc: Boolean!,
|
||||
hideFromTopUsers: Boolean!, hideCowboyHat: Boolean!, clickToLoadImg: Boolean!,
|
||||
wildWestMode: Boolean!, greeterMode: Boolean!, nostrPubkey: String, nostrRelays: [String!], hideBookmarks: Boolean!,
|
||||
noteForwardedSats: Boolean!, hideWalletBalance: Boolean!, hideIsContributor: Boolean!): User
|
||||
noteForwardedSats: Boolean!, hideWalletBalance: Boolean!, hideIsContributor: Boolean!, diagnostics: Boolean!): User
|
||||
setPhoto(photoId: ID!): Int!
|
||||
upsertBio(bio: String!): User!
|
||||
setWalkthrough(tipPopover: Boolean, upvotePopover: Boolean): Boolean
|
||||
|
@ -87,6 +87,7 @@ export default gql`
|
|||
hideBookmarks: Boolean!
|
||||
hideWelcomeBanner: Boolean!
|
||||
hideWalletBalance: Boolean!
|
||||
diagnostics: Boolean!
|
||||
clickToLoadImg: Boolean!
|
||||
wildWestMode: Boolean!
|
||||
greeterMode: Boolean!
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
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}`
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
const LoggerContext = createContext()
|
||||
|
||||
export function LoggerProvider ({ 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.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?.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 (
|
||||
<LoggerContext.Provider value={logger}>
|
||||
{children}
|
||||
</LoggerContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useLogger () {
|
||||
return useContext(LoggerContext)
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import { createContext, useContext, useEffect, useState, useCallback } from 'react'
|
||||
import { Workbox } from 'workbox-window'
|
||||
import { gql, useMutation } from '@apollo/client'
|
||||
import { useLogger } from './logger'
|
||||
|
||||
const applicationServerKey = process.env.NEXT_PUBLIC_VAPID_PUBKEY
|
||||
|
||||
|
@ -34,6 +35,7 @@ export const ServiceWorkerProvider = ({ children }) => {
|
|||
}
|
||||
}
|
||||
`)
|
||||
const logger = useLogger()
|
||||
|
||||
// I am not entirely sure if this is needed since at least in Brave,
|
||||
// using `registration.pushManager.subscribe` also prompts the user.
|
||||
|
@ -60,6 +62,8 @@ export const ServiceWorkerProvider = ({ children }) => {
|
|||
// Brave users must enable a flag in brave://settings/privacy first
|
||||
// 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`
|
||||
|
@ -68,24 +72,30 @@ 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: pushSubscription.endpoint,
|
||||
endpoint,
|
||||
p256dh: pushSubscription.keys.p256dh,
|
||||
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 } })
|
||||
logger.info('deleted push subscription from server', { endpoint })
|
||||
}
|
||||
|
||||
const togglePushSubscription = useCallback(async () => {
|
||||
const pushSubscription = await registration.pushManager.getSubscription()
|
||||
if (pushSubscription) return unsubscribeFromPushNotifications(pushSubscription)
|
||||
if (pushSubscription) {
|
||||
return unsubscribeFromPushNotifications(pushSubscription)
|
||||
}
|
||||
return subscribeToPushNotifications()
|
||||
})
|
||||
|
||||
|
@ -100,12 +110,17 @@ export const ServiceWorkerProvider = ({ children }) => {
|
|||
// 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: 'SYNC_SUBSCRIPTION' })
|
||||
logger.info('sent SYNC_SUBSCRIPTION to service worker')
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!support.serviceWorker) return
|
||||
if (!support.serviceWorker) {
|
||||
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)
|
||||
})
|
||||
}, [support.serviceWorker])
|
||||
|
|
|
@ -31,6 +31,7 @@ export const ME = gql`
|
|||
hideFromTopUsers
|
||||
hideCowboyHat
|
||||
clickToLoadImg
|
||||
diagnostics
|
||||
wildWestMode
|
||||
greeterMode
|
||||
lastCheckedJobs
|
||||
|
@ -62,6 +63,7 @@ export const SETTINGS_FIELDS = gql`
|
|||
hideIsContributor
|
||||
clickToLoadImg
|
||||
hideWalletBalance
|
||||
diagnostics
|
||||
nostrPubkey
|
||||
nostrRelays
|
||||
wildWestMode
|
||||
|
@ -91,14 +93,14 @@ mutation setSettings($tipDefault: Int!, $turboTipping: Boolean!, $fiatCurrency:
|
|||
$noteInvites: Boolean!, $noteJobIndicator: Boolean!, $noteCowboyHat: Boolean!, $hideInvoiceDesc: Boolean!,
|
||||
$hideFromTopUsers: Boolean!, $hideCowboyHat: Boolean!, $clickToLoadImg: Boolean!,
|
||||
$wildWestMode: Boolean!, $greeterMode: Boolean!, $nostrPubkey: String, $nostrRelays: [String!], $hideBookmarks: Boolean!,
|
||||
$noteForwardedSats: Boolean!, $hideWalletBalance: Boolean!, $hideIsContributor: Boolean!) {
|
||||
$noteForwardedSats: Boolean!, $hideWalletBalance: Boolean!, $hideIsContributor: Boolean!, $diagnostics: Boolean!) {
|
||||
setSettings(tipDefault: $tipDefault, turboTipping: $turboTipping, fiatCurrency: $fiatCurrency,
|
||||
noteItemSats: $noteItemSats, noteEarning: $noteEarning, noteAllDescendants: $noteAllDescendants,
|
||||
noteMentions: $noteMentions, noteDeposits: $noteDeposits, noteInvites: $noteInvites,
|
||||
noteJobIndicator: $noteJobIndicator, noteCowboyHat: $noteCowboyHat, hideInvoiceDesc: $hideInvoiceDesc,
|
||||
hideFromTopUsers: $hideFromTopUsers, hideCowboyHat: $hideCowboyHat, clickToLoadImg: $clickToLoadImg,
|
||||
wildWestMode: $wildWestMode, greeterMode: $greeterMode, nostrPubkey: $nostrPubkey, nostrRelays: $nostrRelays, hideBookmarks: $hideBookmarks,
|
||||
noteForwardedSats: $noteForwardedSats, hideWalletBalance: $hideWalletBalance, hideIsContributor: $hideIsContributor) {
|
||||
noteForwardedSats: $noteForwardedSats, hideWalletBalance: $hideWalletBalance, hideIsContributor: $hideIsContributor, diagnostics: $diagnostics) {
|
||||
...SettingsFields
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,207 @@
|
|||
{
|
||||
"adjectives": [
|
||||
"mighty",
|
||||
"radiant",
|
||||
"whimsical",
|
||||
"cosmic",
|
||||
"enchanted",
|
||||
"electric",
|
||||
"serene",
|
||||
"vibrant",
|
||||
"fierce",
|
||||
"mystical",
|
||||
"playful",
|
||||
"daring",
|
||||
"soothing",
|
||||
"galactic",
|
||||
"exuberant",
|
||||
"harmonious",
|
||||
"energetic",
|
||||
"tranquil",
|
||||
"sparkling",
|
||||
"majestic",
|
||||
"luminous",
|
||||
"brave",
|
||||
"blissful",
|
||||
"captivating",
|
||||
"ethereal",
|
||||
"dynamic",
|
||||
"spirited",
|
||||
"graceful",
|
||||
"magical",
|
||||
"adventurous",
|
||||
"resplendent",
|
||||
"serendipitous",
|
||||
"tenacious",
|
||||
"whirlwind",
|
||||
"jubilant",
|
||||
"enigmatic",
|
||||
"mystifying",
|
||||
"zephyr",
|
||||
"celestial",
|
||||
"enthralling",
|
||||
"curious",
|
||||
"infinite",
|
||||
"radiant",
|
||||
"mesmerizing",
|
||||
"vibrant",
|
||||
"euphoric",
|
||||
"awe-inspiring",
|
||||
"phenomenal",
|
||||
"serene",
|
||||
"bewitching",
|
||||
"impulsive",
|
||||
"thrilling",
|
||||
"eclectic",
|
||||
"vivacious",
|
||||
"spirited",
|
||||
"enchanting",
|
||||
"dynamic",
|
||||
"brilliant",
|
||||
"harmonic",
|
||||
"charismatic",
|
||||
"courageous",
|
||||
"tenacious",
|
||||
"lively",
|
||||
"bewildering",
|
||||
"whimsical",
|
||||
"enveloping",
|
||||
"playful",
|
||||
"captivating",
|
||||
"inspiring",
|
||||
"zenith",
|
||||
"majestic",
|
||||
"dazzling",
|
||||
"resilient",
|
||||
"celestial",
|
||||
"resplendent",
|
||||
"enigmatic",
|
||||
"tranquil",
|
||||
"ethereal",
|
||||
"exquisite",
|
||||
"radiating",
|
||||
"breathtaking",
|
||||
"rhapsodic",
|
||||
"melodic",
|
||||
"phenomenal",
|
||||
"enchanted",
|
||||
"invincible",
|
||||
"serendipitous",
|
||||
"kaleidoscopic",
|
||||
"intriguing",
|
||||
"spellbinding",
|
||||
"thriving",
|
||||
"thrilling",
|
||||
"reverie",
|
||||
"exhilarating",
|
||||
"invigorating",
|
||||
"resolute",
|
||||
"audacious",
|
||||
"empowering",
|
||||
"jubilant",
|
||||
"timeless"
|
||||
],
|
||||
"nouns": [
|
||||
"summer",
|
||||
"phoenix",
|
||||
"echo",
|
||||
"voyager",
|
||||
"starlight",
|
||||
"harmony",
|
||||
"nova",
|
||||
"dreamer",
|
||||
"cascade",
|
||||
"celestial",
|
||||
"dragon",
|
||||
"whisper",
|
||||
"serenade",
|
||||
"avalanche",
|
||||
"pinnacle",
|
||||
"odyssey",
|
||||
"enigma",
|
||||
"zenith",
|
||||
"mirage",
|
||||
"symphony",
|
||||
"nebula",
|
||||
"infinity",
|
||||
"serenity",
|
||||
"radiance",
|
||||
"horizon",
|
||||
"eclipse",
|
||||
"solstice",
|
||||
"aria",
|
||||
"tornado",
|
||||
"aurora",
|
||||
"mirage",
|
||||
"quasar",
|
||||
"cascade",
|
||||
"seraph",
|
||||
"nebula",
|
||||
"solitude",
|
||||
"paradigm",
|
||||
"infinity",
|
||||
"melody",
|
||||
"nebula",
|
||||
"radiance",
|
||||
"odyssey",
|
||||
"seraph",
|
||||
"melody",
|
||||
"aria",
|
||||
"zenith",
|
||||
"eclipse",
|
||||
"tornado",
|
||||
"solstice",
|
||||
"celestia",
|
||||
"phoenix",
|
||||
"voyager",
|
||||
"starlight",
|
||||
"dreamer",
|
||||
"cascade",
|
||||
"aurora",
|
||||
"serenity",
|
||||
"echo",
|
||||
"serenade",
|
||||
"pinnacle",
|
||||
"symphony",
|
||||
"harmony",
|
||||
"quasar",
|
||||
"horizon",
|
||||
"enigma",
|
||||
"mirage",
|
||||
"nebula",
|
||||
"solitude",
|
||||
"radiance",
|
||||
"odyssey",
|
||||
"zenith",
|
||||
"aria",
|
||||
"melody",
|
||||
"celestia",
|
||||
"seraph",
|
||||
"infinity",
|
||||
"eclipse",
|
||||
"tornado",
|
||||
"aurora",
|
||||
"paradigm",
|
||||
"solstice",
|
||||
"phoenix",
|
||||
"voyager",
|
||||
"starlight",
|
||||
"dreamer",
|
||||
"cascade",
|
||||
"nebula",
|
||||
"serenade",
|
||||
"pinnacle",
|
||||
"symphony",
|
||||
"harmony",
|
||||
"zenith",
|
||||
"mirage",
|
||||
"eclipse",
|
||||
"quasar",
|
||||
"radiance",
|
||||
"serenity",
|
||||
"aurora",
|
||||
"tornado",
|
||||
"horizon"
|
||||
],
|
||||
"maxSuffix": 10000
|
||||
}
|
|
@ -225,6 +225,7 @@ export const settingsSchema = object({
|
|||
({ max, value }) => `${Math.abs(max - value.length)} too many`),
|
||||
hideBookmarks: boolean(),
|
||||
hideWalletBalance: boolean(),
|
||||
diagnostics: boolean(),
|
||||
hideIsContributor: boolean()
|
||||
})
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
"@noble/curves": "^1.1.0",
|
||||
"@opensearch-project/opensearch": "^2.3.1",
|
||||
"@prisma/client": "^5.1.1",
|
||||
"@slack/web-api": "^6.9.0",
|
||||
"acorn": "^8.10.0",
|
||||
"ajv": "^8.12.0",
|
||||
"async-retry": "^1.3.1",
|
||||
|
@ -3071,6 +3072,54 @@
|
|||
"url": "https://github.com/sindresorhus/is?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@slack/logger": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@slack/logger/-/logger-3.0.0.tgz",
|
||||
"integrity": "sha512-DTuBFbqu4gGfajREEMrkq5jBhcnskinhr4+AnfJEk48zhVeEv3XnUKGIX98B74kxhYsIMfApGGySTn7V3b5yBA==",
|
||||
"dependencies": {
|
||||
"@types/node": ">=12.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12.13.0",
|
||||
"npm": ">= 6.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@slack/types": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@slack/types/-/types-2.8.0.tgz",
|
||||
"integrity": "sha512-ghdfZSF0b4NC9ckBA8QnQgC9DJw2ZceDq0BIjjRSv6XAZBXJdWgxIsYz0TYnWSiqsKZGH2ZXbj9jYABZdH3OSQ==",
|
||||
"engines": {
|
||||
"node": ">= 12.13.0",
|
||||
"npm": ">= 6.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@slack/web-api": {
|
||||
"version": "6.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-6.9.0.tgz",
|
||||
"integrity": "sha512-RME5/F+jvQmZHkoP+ogrDbixq1Ms1mBmylzuWq4sf3f7GCpMPWoiZ+WqWk+sism3vrlveKWIgO9R4Qg9fiRyoQ==",
|
||||
"dependencies": {
|
||||
"@slack/logger": "^3.0.0",
|
||||
"@slack/types": "^2.8.0",
|
||||
"@types/is-stream": "^1.1.0",
|
||||
"@types/node": ">=12.0.0",
|
||||
"axios": "^0.27.2",
|
||||
"eventemitter3": "^3.1.0",
|
||||
"form-data": "^2.5.0",
|
||||
"is-electron": "2.2.2",
|
||||
"is-stream": "^1.1.0",
|
||||
"p-queue": "^6.6.1",
|
||||
"p-retry": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12.13.0",
|
||||
"npm": ">= 6.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@slack/web-api/node_modules/eventemitter3": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz",
|
||||
"integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q=="
|
||||
},
|
||||
"node_modules/@surma/rollup-plugin-off-main-thread": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz",
|
||||
|
@ -3312,6 +3361,14 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz",
|
||||
"integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ=="
|
||||
},
|
||||
"node_modules/@types/is-stream": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/is-stream/-/is-stream-1.1.0.tgz",
|
||||
"integrity": "sha512-jkZatu4QVbR60mpIzjINmtS1ZF4a/FqdTUTBeQDVOQ2PYyidtwFKr0B5G6ERukKwliq+7mIXvxyppwzG5EgRYg==",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/json-schema": {
|
||||
"version": "7.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz",
|
||||
|
@ -3446,6 +3503,11 @@
|
|||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/retry": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
|
||||
"integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="
|
||||
},
|
||||
"node_modules/@types/scheduler": {
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
|
||||
|
@ -4073,6 +4135,28 @@
|
|||
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz",
|
||||
"integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg=="
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
|
||||
"integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.14.9",
|
||||
"form-data": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axios/node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-plugin-inline-react-svg": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-inline-react-svg/-/babel-plugin-inline-react-svg-2.0.2.tgz",
|
||||
|
@ -7121,6 +7205,25 @@
|
|||
"integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/for-each": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
|
||||
|
@ -8268,6 +8371,11 @@
|
|||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/is-electron": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz",
|
||||
"integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg=="
|
||||
},
|
||||
"node_modules/is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
|
@ -13116,6 +13224,14 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/p-finally": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
|
||||
"integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/p-limit": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
|
||||
|
@ -13175,6 +13291,44 @@
|
|||
"url": "https://github.com/sindresorhus/p-memoize?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/p-queue": {
|
||||
"version": "6.6.2",
|
||||
"resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz",
|
||||
"integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==",
|
||||
"dependencies": {
|
||||
"eventemitter3": "^4.0.4",
|
||||
"p-timeout": "^3.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/p-retry": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz",
|
||||
"integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==",
|
||||
"dependencies": {
|
||||
"@types/retry": "0.12.0",
|
||||
"retry": "^0.13.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/p-timeout": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz",
|
||||
"integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==",
|
||||
"dependencies": {
|
||||
"p-finally": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/p-try": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
||||
|
@ -20218,6 +20372,44 @@
|
|||
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
|
||||
"integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="
|
||||
},
|
||||
"@slack/logger": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@slack/logger/-/logger-3.0.0.tgz",
|
||||
"integrity": "sha512-DTuBFbqu4gGfajREEMrkq5jBhcnskinhr4+AnfJEk48zhVeEv3XnUKGIX98B74kxhYsIMfApGGySTn7V3b5yBA==",
|
||||
"requires": {
|
||||
"@types/node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"@slack/types": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@slack/types/-/types-2.8.0.tgz",
|
||||
"integrity": "sha512-ghdfZSF0b4NC9ckBA8QnQgC9DJw2ZceDq0BIjjRSv6XAZBXJdWgxIsYz0TYnWSiqsKZGH2ZXbj9jYABZdH3OSQ=="
|
||||
},
|
||||
"@slack/web-api": {
|
||||
"version": "6.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-6.9.0.tgz",
|
||||
"integrity": "sha512-RME5/F+jvQmZHkoP+ogrDbixq1Ms1mBmylzuWq4sf3f7GCpMPWoiZ+WqWk+sism3vrlveKWIgO9R4Qg9fiRyoQ==",
|
||||
"requires": {
|
||||
"@slack/logger": "^3.0.0",
|
||||
"@slack/types": "^2.8.0",
|
||||
"@types/is-stream": "^1.1.0",
|
||||
"@types/node": ">=12.0.0",
|
||||
"axios": "^0.27.2",
|
||||
"eventemitter3": "^3.1.0",
|
||||
"form-data": "^2.5.0",
|
||||
"is-electron": "2.2.2",
|
||||
"is-stream": "^1.1.0",
|
||||
"p-queue": "^6.6.1",
|
||||
"p-retry": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"eventemitter3": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz",
|
||||
"integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@surma/rollup-plugin-off-main-thread": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz",
|
||||
|
@ -20449,6 +20641,14 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz",
|
||||
"integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ=="
|
||||
},
|
||||
"@types/is-stream": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/is-stream/-/is-stream-1.1.0.tgz",
|
||||
"integrity": "sha512-jkZatu4QVbR60mpIzjINmtS1ZF4a/FqdTUTBeQDVOQ2PYyidtwFKr0B5G6ERukKwliq+7mIXvxyppwzG5EgRYg==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/json-schema": {
|
||||
"version": "7.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz",
|
||||
|
@ -20582,6 +20782,11 @@
|
|||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/retry": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
|
||||
"integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="
|
||||
},
|
||||
"@types/scheduler": {
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
|
||||
|
@ -21109,6 +21314,27 @@
|
|||
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz",
|
||||
"integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg=="
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
|
||||
"integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.14.9",
|
||||
"form-data": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"requires": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"babel-plugin-inline-react-svg": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-inline-react-svg/-/babel-plugin-inline-react-svg-2.0.2.tgz",
|
||||
|
@ -23331,6 +23557,11 @@
|
|||
"integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
|
||||
"dev": true
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
|
||||
},
|
||||
"for-each": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
|
||||
|
@ -24142,6 +24373,11 @@
|
|||
"resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz",
|
||||
"integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw=="
|
||||
},
|
||||
"is-electron": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz",
|
||||
"integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg=="
|
||||
},
|
||||
"is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
|
@ -27140,6 +27376,11 @@
|
|||
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz",
|
||||
"integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg=="
|
||||
},
|
||||
"p-finally": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
|
||||
"integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow=="
|
||||
},
|
||||
"p-limit": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
|
||||
|
@ -27175,6 +27416,32 @@
|
|||
"type-fest": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"p-queue": {
|
||||
"version": "6.6.2",
|
||||
"resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz",
|
||||
"integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==",
|
||||
"requires": {
|
||||
"eventemitter3": "^4.0.4",
|
||||
"p-timeout": "^3.2.0"
|
||||
}
|
||||
},
|
||||
"p-retry": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz",
|
||||
"integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==",
|
||||
"requires": {
|
||||
"@types/retry": "0.12.0",
|
||||
"retry": "^0.13.1"
|
||||
}
|
||||
},
|
||||
"p-timeout": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz",
|
||||
"integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==",
|
||||
"requires": {
|
||||
"p-finally": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"p-try": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
"@noble/curves": "^1.1.0",
|
||||
"@opensearch-project/opensearch": "^2.3.1",
|
||||
"@prisma/client": "^5.1.1",
|
||||
"@slack/web-api": "^6.9.0",
|
||||
"acorn": "^8.10.0",
|
||||
"ajv": "^8.12.0",
|
||||
"async-retry": "^1.3.1",
|
||||
|
|
|
@ -16,6 +16,7 @@ import { ServiceWorkerProvider } from '../components/serviceworker'
|
|||
import { SSR } from '../lib/constants'
|
||||
import NProgress from 'nprogress'
|
||||
import 'nprogress/nprogress.css'
|
||||
import { LoggerProvider } from '../components/logger'
|
||||
|
||||
NProgress.configure({
|
||||
showSpinner: false
|
||||
|
@ -88,19 +89,21 @@ function MyApp ({ Component, pageProps: { ...props } }) {
|
|||
<PlausibleProvider domain='stacker.news' trackOutboundLinks>
|
||||
<ApolloProvider client={client}>
|
||||
<MeProvider me={me}>
|
||||
<ServiceWorkerProvider>
|
||||
<PriceProvider price={price}>
|
||||
<LightningProvider>
|
||||
<ToastProvider>
|
||||
<ShowModalProvider>
|
||||
<BlockHeightProvider blockHeight={blockHeight}>
|
||||
<Component ssrData={ssrData} {...otherProps} />
|
||||
</BlockHeightProvider>
|
||||
</ShowModalProvider>
|
||||
</ToastProvider>
|
||||
</LightningProvider>
|
||||
</PriceProvider>
|
||||
</ServiceWorkerProvider>
|
||||
<LoggerProvider>
|
||||
<ServiceWorkerProvider>
|
||||
<PriceProvider price={price}>
|
||||
<LightningProvider>
|
||||
<ToastProvider>
|
||||
<ShowModalProvider>
|
||||
<BlockHeightProvider blockHeight={blockHeight}>
|
||||
<Component ssrData={ssrData} {...otherProps} />
|
||||
</BlockHeightProvider>
|
||||
</ShowModalProvider>
|
||||
</ToastProvider>
|
||||
</LightningProvider>
|
||||
</PriceProvider>
|
||||
</ServiceWorkerProvider>
|
||||
</LoggerProvider>
|
||||
</MeProvider>
|
||||
</ApolloProvider>
|
||||
</PlausibleProvider>
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
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 }) => {
|
||||
if (!slackClient) return
|
||||
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 } })
|
||||
|
||||
slackPostMessage({ id, ...req.body }).catch(console.error)
|
||||
|
||||
return res.status(200).json({ status: 200, message: 'ok' })
|
||||
}
|
|
@ -23,6 +23,7 @@ import { useShowModal } from '../components/modal'
|
|||
import { authErrorMessage } from '../components/login'
|
||||
import { NostrAuth } from '../components/nostr-auth'
|
||||
import { useToast } from '../components/toast'
|
||||
import { useLogger } from '../components/logger'
|
||||
import { useMe } from '../components/me'
|
||||
|
||||
export const getServerSideProps = getGetServerSideProps({ query: SETTINGS, authRequired: true })
|
||||
|
@ -47,6 +48,7 @@ export default function Settings ({ ssrData }) {
|
|||
}
|
||||
}
|
||||
)
|
||||
const logger = useLogger()
|
||||
|
||||
const { data } = useQuery(SETTINGS)
|
||||
const { settings } = data || ssrData
|
||||
|
@ -80,6 +82,7 @@ export default function Settings ({ ssrData }) {
|
|||
nostrRelays: settings?.nostrRelays?.length ? settings?.nostrRelays : [''],
|
||||
hideBookmarks: settings?.hideBookmarks,
|
||||
hideWalletBalance: settings?.hideWalletBalance,
|
||||
diagnostics: settings?.diagnostics,
|
||||
hideIsContributor: settings?.hideIsContributor
|
||||
}}
|
||||
schema={settingsSchema}
|
||||
|
@ -253,6 +256,29 @@ export default function Settings ({ ssrData }) {
|
|||
<Checkbox
|
||||
label={<>hide my bookmarks from other stackers</>}
|
||||
name='hideBookmarks'
|
||||
groupClassName='mb-0'
|
||||
/>
|
||||
<Checkbox
|
||||
label={
|
||||
<div className='d-flex align-items-center'>allow diagnostics
|
||||
<Info>
|
||||
<ul className='fw-bold'>
|
||||
<li>collect and send back diagnostics data</li>
|
||||
<li>this information is used to identify and 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'
|
||||
/>
|
||||
<div className='form-label'>content</div>
|
||||
<Checkbox
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
-- CreateEnum
|
||||
CREATE TYPE "LogLevel" AS ENUM ('DEBUG', 'INFO', 'WARN', 'ERROR');
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "users" ADD COLUMN "diagnostics" BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Log" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"level" "LogLevel" NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"message" TEXT NOT NULL,
|
||||
"env" JSONB,
|
||||
"context" JSONB,
|
||||
|
||||
CONSTRAINT "Log_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Log.name_index" ON "Log"("created_at", "name");
|
|
@ -90,6 +90,7 @@ model User {
|
|||
followers UserSubscription[] @relation("follower")
|
||||
followees UserSubscription[] @relation("followee")
|
||||
hideWelcomeBanner Boolean @default(false)
|
||||
diagnostics Boolean @default(false)
|
||||
hideIsContributor Boolean @default(false)
|
||||
|
||||
@@index([createdAt], map: "users.created_at_index")
|
||||
|
@ -561,6 +562,18 @@ model PushSubscription {
|
|||
@@index([userId], map: "PushSubscription.userId_index")
|
||||
}
|
||||
|
||||
model Log {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
level LogLevel
|
||||
name String
|
||||
message String
|
||||
env Json?
|
||||
context Json?
|
||||
|
||||
@@index([createdAt, name], map: "Log.name_index")
|
||||
}
|
||||
|
||||
enum EarnType {
|
||||
POST
|
||||
COMMENT
|
||||
|
@ -606,3 +619,10 @@ enum WithdrawlStatus {
|
|||
CONFIRMED
|
||||
UNKNOWN_FAILURE
|
||||
}
|
||||
|
||||
enum LogLevel {
|
||||
DEBUG
|
||||
INFO
|
||||
WARN
|
||||
ERROR
|
||||
}
|
15
sw/index.js
15
sw/index.js
|
@ -11,6 +11,7 @@ import ServiceWorkerStorage from 'serviceworker-storage'
|
|||
self.__WB_DISABLE_DEV_LOGS = true
|
||||
|
||||
const storage = new ServiceWorkerStorage('sw:storage', 1)
|
||||
let messageChannelPort
|
||||
|
||||
// preloading improves startup performance
|
||||
// https://developer.chrome.com/docs/workbox/modules/workbox-navigation-preload/
|
||||
|
@ -55,7 +56,9 @@ self.addEventListener('push', async function (event) {
|
|||
const notifications = await self.registration.getNotifications({ tag })
|
||||
// since we used a tag filter, there should only be zero or one notification
|
||||
if (notifications.length > 1) {
|
||||
console.error(`more than one notification with tag ${tag} found`)
|
||||
const message = `[sw:push] more than one notification with tag ${tag} found`
|
||||
messageChannelPort?.postMessage({ level: 'error', message })
|
||||
console.error(message)
|
||||
return null
|
||||
}
|
||||
if (notifications.length === 0) {
|
||||
|
@ -85,7 +88,12 @@ self.addEventListener('notificationclick', (event) => {
|
|||
|
||||
// https://medium.com/@madridserginho/how-to-handle-webpush-api-pushsubscriptionchange-event-in-modern-browsers-6e47840d756f
|
||||
self.addEventListener('message', (event) => {
|
||||
if (event.data.action === 'MESSAGE_PORT') {
|
||||
messageChannelPort = event.ports[0]
|
||||
}
|
||||
messageChannelPort?.postMessage({ message: '[sw:message] received message', context: { action: event.data.action } })
|
||||
if (event.data.action === 'STORE_SUBSCRIPTION') {
|
||||
messageChannelPort?.postMessage({ message: '[sw:message] storing subscription in IndexedDB', context: { endpoint: event.data.subscription.endpoint } })
|
||||
return event.waitUntil(storage.setItem('subscription', event.data.subscription))
|
||||
}
|
||||
if (event.data.action === 'SYNC_SUBSCRIPTION') {
|
||||
|
@ -96,14 +104,17 @@ self.addEventListener('message', (event) => {
|
|||
async function handlePushSubscriptionChange (oldSubscription, newSubscription) {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/pushsubscriptionchange_event
|
||||
// fallbacks since browser may not set oldSubscription and newSubscription
|
||||
messageChannelPort?.postMessage({ message: '[sw:handlePushSubscriptionChange] invoked' })
|
||||
oldSubscription ??= await storage.getItem('subscription')
|
||||
newSubscription ??= await self.registration.pushManager.getSubscription()
|
||||
if (!newSubscription) {
|
||||
// no subscription exists at the moment
|
||||
messageChannelPort?.postMessage({ message: '[sw:handlePushSubscriptionChange] no existing subscription found' })
|
||||
return
|
||||
}
|
||||
if (oldSubscription?.endpoint === newSubscription.endpoint) {
|
||||
// subscription did not change. no need to sync with server
|
||||
messageChannelPort?.postMessage({ message: '[sw:handlePushSubscriptionChange] old subscription matches existing subscription' })
|
||||
return
|
||||
}
|
||||
// convert keys from ArrayBuffer to string
|
||||
|
@ -128,9 +139,11 @@ async function handlePushSubscriptionChange (oldSubscription, newSubscription) {
|
|||
},
|
||||
body
|
||||
})
|
||||
messageChannelPort?.postMessage({ message: '[sw:handlePushSubscriptionChange] synced push subscription with server', context: { endpoint: variables.endpoint, oldEndpoint: variables.oldEndpoint } })
|
||||
await storage.setItem('subscription', JSON.parse(JSON.stringify(newSubscription)))
|
||||
}
|
||||
|
||||
self.addEventListener('pushsubscriptionchange', (event) => {
|
||||
messageChannelPort?.postMessage({ message: '[sw:pushsubscriptionchange] received event' })
|
||||
event.waitUntil(handlePushSubscriptionChange(event.oldSubscription, event.newSubscription))
|
||||
}, false)
|
||||
|
|
Loading…
Reference in New Issue