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.

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