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 } export 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 ( {children} ) } export function useLogger () { return useContext(LoggerContext) }