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.

167 lines
4.4KB

  1. const State = {
  2. websocket: null,
  3. posts: [],
  4. users: [],
  5. }
  6. /*
  7. *
  8. * WEBRTC
  9. *
  10. */
  11. const rpcCall = async (username) => {
  12. const constraints = {audio: true, video: true}
  13. const media = await navigator.mediaDevices.getUserMedia(constraints)
  14. const rpc = new RTCPeerConnection({iceServers: []})
  15. // rpc.oniceconnectionstatechange = (e) => {
  16. // console.log(e)
  17. // }
  18. // rpc.onicecandidate = (e) => {
  19. // e.candidate
  20. // const candidate = new RTCIceCandidate();
  21. // rpc.addIceCandidate(candidate);
  22. // }
  23. // rpc.ontrack = (e) => {
  24. // console.log(e)
  25. // // appendVideo(media)
  26. // }
  27. const makeOffer = async () => {
  28. rpc.addStream(media)
  29. const offer = await rpc.createOffer()
  30. await rpc.setLocalDescription(offer)
  31. return {msgType: 'offer', rsd: rpc.localDescription}
  32. }
  33. const makeAnswer = async (msg) => {
  34. const offer = new RTCSessionDescription(msg.rsd)
  35. await rpc.setRemoteDescription(offer)
  36. const answer = await rpc.createAnswer()
  37. await rpc.setLocalDescription(answer)
  38. return {msgType: 'answer', rsd: rpc.localDescription}
  39. }
  40. const finishShake = async (msg) => {
  41. const answer = new RTCSessionDescription(msg.rsd)
  42. await rpc.setRemoteDescription(answer)
  43. }
  44. finishShake(await makeAnswer(await makeOffer()))
  45. }
  46. /*
  47. *
  48. * GUI
  49. *
  50. */
  51. const autoFocus = (vnode) => {
  52. vnode.dom.focus()
  53. }
  54. const scrollIntoView = (vnode) => {
  55. vnode.dom.scrollIntoView()
  56. }
  57. const prettyTime = (ts) => {
  58. return ts.slice(11, 19)
  59. }
  60. const Login = {
  61. sendLogin: (e) => {
  62. e.preventDefault()
  63. const username = e.target.username.value
  64. localStorage.username = username
  65. connect(username)
  66. },
  67. sendLogout: (e) => {
  68. State.websocket.send(JSON.stringify({action: 'logout'}))
  69. State.posts = []
  70. },
  71. view() {
  72. return m('.login',
  73. m('form', {onsubmit: Login.sendLogin},
  74. m('input', {oncreate: autoFocus, name: 'username', autocomplete: 'off', value: localStorage.username}),
  75. m('button', 'Login'),
  76. ),
  77. State.kind === 'error' && m('.error', State.info),
  78. )
  79. },
  80. }
  81. const Chat = {
  82. sendPost: (e) => {
  83. e.preventDefault()
  84. const field = e.target.text
  85. State.websocket.send(JSON.stringify({action: 'post', text: field.value}))
  86. field.value = ''
  87. },
  88. view() {
  89. return m('.chat',
  90. m('.posts',
  91. State.posts.map(post => m('.post', {oncreate: scrollIntoView},
  92. m('.ts', prettyTime(post.ts)),
  93. m('.source', post.source),
  94. m('.text', post.text),
  95. )),
  96. ),
  97. m('form.actions', {onsubmit: Chat.sendPost},
  98. m('input', {oncreate: autoFocus, name: 'text', autocomplete: 'off'}),
  99. m('button', 'Send'),
  100. ),
  101. m('.users',
  102. m('button', {onclick: Login.sendLogout}, 'Logout'),
  103. m('ul.user-list', State.users.map(username => m('li', username))),
  104. ),
  105. )
  106. },
  107. }
  108. const Main = {
  109. view() {
  110. const connected = State.websocket && State.websocket.readyState === 1
  111. return connected ? m(Chat) : m(Login)
  112. },
  113. }
  114. m.mount(document.body, Main)
  115. /*
  116. *
  117. * WEBSOCKETS
  118. *
  119. */
  120. const connect = (username) => {
  121. const wsUrl = location.href.replace('http', 'ws')
  122. State.websocket = new WebSocket(wsUrl)
  123. State.websocket.onopen = (e) => {
  124. State.websocket.send(JSON.stringify({action: 'login', username}))
  125. }
  126. State.websocket.onmessage = (e) => {
  127. const message = JSON.parse(e.data)
  128. if(message.kind === 'post') {
  129. State.posts.push(message)
  130. }
  131. else if(message.kind === 'update') {
  132. Object.assign(State, message)
  133. if(message.info) {
  134. State.posts.push({ts: message.ts, source: '~', text: message.info})
  135. }
  136. }
  137. else if(message.kind === 'error') {
  138. Object.assign(State, message)
  139. Login.sendLogout()
  140. }
  141. else {
  142. console.log(message)
  143. }
  144. m.redraw()
  145. }
  146. State.websocket.onclose = (e) => {
  147. if(!e.wasClean) {
  148. setTimeout(connect, 1000, username)
  149. }
  150. m.redraw()
  151. }
  152. }
  153. if(localStorage.username) {
  154. connect(localStorage.username)
  155. }