diff --git a/api/slack/index.js b/api/slack/index.js deleted file mode 100644 index cd1f220d..00000000 --- a/api/slack/index.js +++ /dev/null @@ -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 diff --git a/api/typeDefs/user.js b/api/typeDefs/user.js index 03fe5551..1e715591 100644 --- a/api/typeDefs/user.js +++ b/api/typeDefs/user.js @@ -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! diff --git a/components/error-boundary.js b/components/error-boundary.js index 0fe6554c..82ebf626 100644 --- a/components/error-boundary.js +++ b/components/error-boundary.js @@ -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 diff --git a/components/logger.js b/components/logger.js deleted file mode 100644 index b9c1e510..00000000 --- a/components/logger.js +++ /dev/null @@ -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 ( - - {children} - - ) -} - -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 ( - - {children} - - ) -} - -export function useServiceWorkerLogger () { - return useContext(ServiceWorkerLoggerContext) -} diff --git a/components/serviceworker.js b/components/serviceworker.js index 58d6745f..77edabee 100644 --- a/components/serviceworker.js +++ b/components/serviceworker.js @@ -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(() => ({ diff --git a/fragments/users.js b/fragments/users.js index ba3734eb..86b594ba 100644 --- a/fragments/users.js +++ b/fragments/users.js @@ -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 diff --git a/lib/validate.js b/lib/validate.js index 5bc4fe7d..e103c4b8 100644 --- a/lib/validate.js +++ b/lib/validate.js @@ -472,7 +472,6 @@ export const settingsSchema = object().shape({ hideNostr: boolean(), hideTwitter: boolean(), hideWalletBalance: boolean(), - diagnostics: boolean(), noReferralLinks: boolean(), hideIsContributor: boolean(), disableFreebies: boolean().nullable(), diff --git a/pages/_app.js b/pages/_app.js index 34d9fc86..efc15347 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -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 } }) { - - - - - - - - - - - - {!router?.query?.disablePrompt && } - - - - - - - - - - + + + + + + + + + + + {!router?.query?.disablePrompt && } + + + + + + + + + diff --git a/pages/api/log/index.js b/pages/api/log/index.js deleted file mode 100644 index 18ffbc35..00000000 --- a/pages/api/log/index.js +++ /dev/null @@ -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' }) -} diff --git a/pages/settings/index.js b/pages/settings/index.js index 201d8c3d..93cbc599 100644 --- a/pages/settings/index.js +++ b/pages/settings/index.js @@ -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' /> - allow anonymous diagnostics - - -
your fancy name: {logger.name}
-
- - } - name='diagnostics' - groupClassName='mb-0' - /> don't create referral links on copy} name='noReferralLinks' diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a1be3ede..a3922145 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -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 { diff --git a/sw/eventListener.js b/sw/eventListener.js index fcec05be..c5a79f6c 100644 --- a/sw/eventListener.js +++ b/sw/eventListener.js @@ -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) {