You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

164 line
4.1KB

  1. const State = Object.seal({
  2. username: null,
  3. websocket: null,
  4. online: [],
  5. messages: [],
  6. get isConnected() {
  7. return State.websocket && State.websocket.readyState === 1
  8. },
  9. })
  10. /*
  11. *
  12. * SIGNALING
  13. *
  14. */
  15. addEventListener('resize', () => m.redraw())
  16. const wire = (message) => State.websocket.send(JSON.stringify(message))
  17. const signal = (message) => dispatchEvent(new CustomEvent(message.kind, {detail: message}))
  18. const listen = (kind, handler) => {
  19. addEventListener(kind, handler)
  20. }
  21. listen('login', ({detail}) => {
  22. State.username = detail.value
  23. State.messages = []
  24. })
  25. listen('logout', ({detail}) => {
  26. State.online = []
  27. })
  28. listen('state', ({detail}) => {
  29. delete detail.ts
  30. delete detail.kind
  31. Object.assign(State, detail)
  32. })
  33. listen('post', ({detail}) => {
  34. State.messages.push(detail)
  35. m.redraw()
  36. })
  37. const doNotLog = new Set(['login', 'state', 'post', 'peerInfo', 'join', 'leave'])
  38. /*
  39. *
  40. * ALERTS
  41. *
  42. */
  43. State.unseen = 0
  44. listen('post', () => {State.unseen += !document.hasFocus(); updateTitle()})
  45. listen('focus', () => {State.unseen = 0; updateTitle()})
  46. const updateTitle = () => {
  47. document.title = location.href.split('//')[1] + (State.unseen ? ` (${State.unseen})` : ``)
  48. }
  49. /*
  50. *
  51. * UTILS
  52. *
  53. */
  54. const autoFocus = (vnode) => {
  55. vnode.dom.focus()
  56. }
  57. /*
  58. *
  59. * BASE
  60. *
  61. */
  62. const Base = {
  63. sendLogin: (e) => {
  64. e.preventDefault()
  65. const username = e.target.username.value
  66. localStorage.username = username
  67. connect(username)
  68. },
  69. sendLogout: (e) => {
  70. e.preventDefault()
  71. wire({kind: 'logout'})
  72. signal({kind: 'logout'})
  73. },
  74. view() {
  75. const attrs = {
  76. oncreate: autoFocus,
  77. name: 'username',
  78. autocomplete: 'off',
  79. value: localStorage.username,
  80. }
  81. const mainStyle = {
  82. display: 'grid',
  83. gridTemplateRows: 'auto 1fr',
  84. height: '100vh',
  85. overflow: 'hidden',
  86. }
  87. const headerStyle = {
  88. display: 'grid',
  89. gridAutoFlow: 'column',
  90. justifyItems: 'start',
  91. marginRight: 'auto',
  92. }
  93. return m('main', {style: mainStyle},
  94. m('header', {style: headerStyle},
  95. State.isConnected ? null : m('form.login',
  96. {onsubmit: Base.sendLogin},
  97. m('input', attrs),
  98. m('button', 'Login'),
  99. ),
  100. State.isConnected ? m('form.logout',
  101. {onsubmit: Base.sendLogout},
  102. m('button', 'Logout'),
  103. m('input[readonly]', {value: location}),
  104. ) : null,
  105. State.isConnected ? m(VideoConfig) : null,
  106. m('span.error', State.info),
  107. ),
  108. State.isConnected ? m(StreamContainer) : null,
  109. )
  110. },
  111. }
  112. m.mount(document.body, Base)
  113. /*
  114. *
  115. * WEBSOCKET
  116. *
  117. */
  118. const connect = (username) => {
  119. const wsUrl = location.href.replace('http', 'ws')
  120. State.websocket = new WebSocket(wsUrl)
  121. State.websocket.onopen = (e) => {
  122. wire({kind: 'login', value: username})
  123. }
  124. State.websocket.onmessage = (e) => {
  125. const message = JSON.parse(e.data)
  126. if(message.online) {
  127. const difference = (l1, l2) => l1.filter(u => !l2.includes(u))
  128. difference(message.online, State.online).forEach(username => {
  129. signal({kind: 'post', ts: message.ts, value: `${username} joined`})
  130. signal({kind: 'join', username: username})
  131. })
  132. difference(State.online, message.online).forEach(username => {
  133. signal({kind: 'post', ts: message.ts, value: `${username} left`})
  134. signal({kind: 'leave', username: username})
  135. })
  136. }
  137. if(!doNotLog.has(message.kind)) {
  138. console.log(message)
  139. }
  140. signal(message)
  141. m.redraw()
  142. }
  143. State.websocket.onclose = (e) => {
  144. State.online.forEach(signalPeerStop)
  145. if(!e.wasClean) {
  146. setTimeout(connect, 1000, username)
  147. }
  148. m.redraw()
  149. }
  150. }
  151. if(localStorage.username) {
  152. connect(localStorage.username)
  153. }