Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

163 lines
4.4KB

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