|
- const State = Object.seal({
- username: null,
- websocket: null,
- online: [],
- get isConnected() {
- return State.websocket && State.websocket.readyState === 1
- },
- get others() {
- return State.online.filter(username => username != State.username)
- }
- })
- const rpcConfig = {iceServers: [{
- urls: ['stun:stun.pico.chat:5349', 'turn:turn.pico.chat:5349'],
- username: 'roderic',
- credential: 'tomodachi',
- }]}
- const params = (new URL(document.location)).searchParams
- /*
- *
- * SIGNALING
- *
- */
- addEventListener('resize', () => m.redraw())
- // message: {kind, value, target?}
- 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(['state'])
- /*
- *
- * 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 isNew = (username) => !State.online.includes(username)
- const isGone = (username) => !message.online.includes(username)
- const isAfter = (_, idx, ll) => ll.indexOf(State.username) < idx
- message.online.filter(isNew).filter(isAfter).forEach(username => {
- signal({kind: 'post', ts: message.ts, value: `${username} joined`})
- signal({kind: 'join', value: username})
- })
- State.online.filter(isGone).forEach(username => {
- signal({kind: 'post', ts: message.ts, value: `${username} left`})
- signal({kind: 'leave', value: username})
- })
- }
-
- if(!doNotLog.has(message.kind)) {
- console.log(message)
- }
- signal(message)
-
- m.redraw()
- }
-
- State.websocket.onclose = (e) => {
- if(!e.wasClean) {
- setTimeout(connect, 1000, username)
- }
- m.redraw()
- }
- }
- /*
- *
- * BASE
- *
- */
- const Shadow = {
- oncreate({dom, attrs}) {
- dom.listener = () => attrs.app.isOn = !attrs.app.isOn
- addEventListener(attrs.key, dom.listener)
- },
- onremove({dom, attrs}) {
- removeEventListener(attrs.key, dom.listener)
- },
- view({attrs}) {
- const style = {
- zIndex: 999,
- backgroundColor: 'rgba(0, 0, 0, 0.2)',
- visibility: attrs.app.isOn ? 'unset' : 'hidden',
- position: 'fixed',
- right: 0,
- top: 0,
- height: '100vh',
- width: '100vw',
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- }
- const onclick = ({target: {classList}}) => {
- classList.contains('shadow') && signal({kind: attrs.key})
- }
- return m('.shadow', {style, onclick}, m(attrs.app))
- },
- }
- 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,
- }
- return [
- m('header',
- State.isConnected
- ? Headers.map(h => m(...h))
- : [
- m('form.login',
- {onsubmit: Base.sendLogin},
- m('input', attrs),
- m('button', 're-join'),
- ),
- ],
- ),
- m('main',
- State.isConnected
- ? Apps.map(a => m(...a))
- : [
- m('span.error', State.info),
- m(Settings),
- ],
- ),
- ]
- },
- }
- const Headers = [
- ['button', {onclick: Base.sendLogout}, 'log-out'],
- ]
- const Apps = [
- ]
- addEventListener('load', () => m.mount(document.body, Base))
|