|  | const State = Object.seal({
    username: null,
    websocket: null,
    online: [],
    get isConnected() {
        return State.websocket && State.websocket.readyState === 1
    },
})
const params = (new URL(document.location)).searchParams
/*
*
* SIGNALING
*
*/
addEventListener('resize', () => m.redraw())
const wire = (message) => State.websocket.send(JSON.stringify(message))
const signal = (message) => dispatchEvent(new CustomEvent(message.kind, {detail: message}))
const listen = (kind, handler) => {
    addEventListener(kind, handler)
}
listen('login', ({detail}) => {
    State.username = detail.value
})
listen('logout', ({detail}) => {
    State.online = []
})
listen('state', ({detail}) => {
    delete detail.ts
    delete detail.kind
    Object.assign(State, detail)
})
const doNotLog = new Set(['login', 'state', 'post', 'peerInfo'])
/*
*
* UTILS
*
*/
const autoFocus = (vnode) => {
    vnode.dom.focus()
}
/*
*
* WEBSOCKET
*
*/
const connect = (username) => {
    const wsUrl = location.href.replace('http', 'ws')
    State.websocket = new WebSocket(wsUrl)
    State.websocket.onopen = (e) => {
        wire({kind: 'login', value: username})
    }
    State.websocket.onmessage = (e) => {
        const message = JSON.parse(e.data)
        if(message.online) {
            const difference = (l1, l2) => l1.filter(u => !l2.includes(u))
            difference(message.online, State.online).forEach(username => {
                if(username === State.username) return
                signal({kind: 'post', ts: message.ts, value: `${username} joined`})
            })
            difference(State.online, message.online).forEach(username => {
                if(username === State.username) return
                signal({kind: 'post', ts: message.ts, value: `${username} left`})
            })
        }
        if(!doNotLog.has(message.kind)) {
            console.log(message)
        }
        signal(message)
        m.redraw()
    }
    State.websocket.onclose = (e) => {
        State.online.forEach(signalPeerStop)
        if(!e.wasClean) {
            setTimeout(connect, 1000, username)
        }
        m.redraw()
    }
}
/*
*
* BASE
*
*/
const Settings = {
    get(key) {
        try {
            return JSON.parse(localStorage.getItem(key))
        }
        catch(error) {
            return null
        }
    },
    set(key, value) {
        localStorage.setItem(key, JSON.stringify(value))
    },
    multiField: ([key, options]) => {
        let current = Settings.get(key)
        if(!options.includes(current)) {
            Settings.set(key, options[0])
        }
        return m('.field',
            m('label', key),
            options.map(value => {
                const style = {
                    fontWeight: Settings.get(key) == value ? 'bold' : 'unset'
                }
                const onclick = () => Settings.set(key, value)
                return m('button', {style, onclick}, `${value}`)
            })
        )
    },
    view() {
        return m('.settings',
            Object.entries({
                blackBars: [true, false],
            }).map(Settings.multiField)
        )
    },
}
const Base = {
    oncreate: () => {
        const randomName = ('' + Math.random()).substring(2)
        connect(localStorage.username || randomName)
    },
    sendLogin: (e) => {
        e.preventDefault()
        const username = e.target.username.value
        localStorage.username = username
        connect(username)
    },
    sendLogout: (e) => {
        e.preventDefault()
        wire({kind: 'logout'})
        signal({kind: 'logout'})
    },
    view() {
        const attrs = {
            oncreate: autoFocus,
            name: 'username',
            autocomplete: 'off',
            value: localStorage.username,
        }
        const mainStyle = {
            position: 'fixed',
            width: '100%',
            display: 'grid',
            gridTemplateRows: 'auto 1fr',
            height: window.innerHeight + 'px',
            overflow: 'hidden',
        }
        const headerStyle = {
            display: 'grid',
            gridAutoFlow: 'column',
            justifyItems: 'start',
            marginRight: 'auto',
        }
        return m('main', {style: mainStyle},
            m('header', {style: headerStyle},
                State.isConnected ? [
                    m('button', {onclick: Base.sendLogout}, 'settings'),
                    m(VideoConfig),
                    m(ChatConfig),
                ] : [
                    m('form.login',
                        {onsubmit: Base.sendLogin},
                        m('input', attrs),
                        m('button', 're-join'),
                    ),
                ],
                m('span.error', State.info),
            ),
            State.isConnected ? [
                m(StreamContainer),
                m(Chat),
            ] : m(Settings),
        )
    },
}
m.mount(document.body, Base)
 |