Sse.vue 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. <template>
  2. <AppPaneLayout>
  3. <template #primary>
  4. <div
  5. class="sticky top-0 z-10 flex flex-shrink-0 p-4 overflow-x-auto space-x-2 bg-primary hide-scrollbar"
  6. >
  7. <div class="inline-flex flex-1 space-x-2">
  8. <div class="flex flex-1">
  9. <input
  10. id="server"
  11. v-model="server"
  12. type="url"
  13. autocomplete="off"
  14. :class="{ error: !serverValid }"
  15. class="flex flex-1 w-full px-4 py-2 border rounded-l bg-primaryLight border-divider text-secondaryDark"
  16. :placeholder="$t('sse.url')"
  17. :disabled="connectionSSEState"
  18. @keyup.enter="serverValid ? toggleSSEConnection() : null"
  19. />
  20. <label
  21. for="event-type"
  22. class="px-4 py-2 font-semibold truncate border-t border-b bg-primaryLight border-divider text-secondaryLight"
  23. >
  24. {{ $t("sse.event_type") }}
  25. </label>
  26. <input
  27. id="event-type"
  28. v-model="eventType"
  29. class="flex flex-1 w-full px-4 py-2 border rounded-r bg-primaryLight border-divider text-secondaryDark"
  30. spellcheck="false"
  31. :disabled="connectionSSEState"
  32. @keyup.enter="serverValid ? toggleSSEConnection() : null"
  33. />
  34. </div>
  35. <ButtonPrimary
  36. id="start"
  37. :disabled="!serverValid"
  38. name="start"
  39. class="w-32"
  40. :label="
  41. !connectionSSEState ? $t('action.start') : $t('action.stop')
  42. "
  43. :loading="connectingState"
  44. @click.native="toggleSSEConnection"
  45. />
  46. </div>
  47. </div>
  48. </template>
  49. <template #secondary>
  50. <RealtimeLog :title="$t('sse.log')" :log="log" />
  51. </template>
  52. </AppPaneLayout>
  53. </template>
  54. <script>
  55. import { defineComponent } from "@nuxtjs/composition-api"
  56. import "splitpanes/dist/splitpanes.css"
  57. import debounce from "lodash/debounce"
  58. import { logHoppRequestRunToAnalytics } from "~/helpers/fb/analytics"
  59. import {
  60. SSEEndpoint$,
  61. setSSEEndpoint,
  62. SSEEventType$,
  63. setSSEEventType,
  64. SSESocket$,
  65. setSSESocket,
  66. SSEConnectingState$,
  67. SSEConnectionState$,
  68. setSSEConnectionState,
  69. setSSEConnectingState,
  70. SSELog$,
  71. setSSELog,
  72. addSSELogLine,
  73. } from "~/newstore/SSESession"
  74. import { useStream } from "~/helpers/utils/composables"
  75. export default defineComponent({
  76. setup() {
  77. return {
  78. connectionSSEState: useStream(
  79. SSEConnectionState$,
  80. false,
  81. setSSEConnectionState
  82. ),
  83. connectingState: useStream(
  84. SSEConnectingState$,
  85. false,
  86. setSSEConnectingState
  87. ),
  88. server: useStream(SSEEndpoint$, "", setSSEEndpoint),
  89. eventType: useStream(SSEEventType$, "", setSSEEventType),
  90. sse: useStream(SSESocket$, null, setSSESocket),
  91. log: useStream(SSELog$, [], setSSELog),
  92. }
  93. },
  94. data() {
  95. return {
  96. isUrlValid: true,
  97. }
  98. },
  99. computed: {
  100. serverValid() {
  101. return this.isUrlValid
  102. },
  103. },
  104. watch: {
  105. server() {
  106. this.debouncer()
  107. },
  108. },
  109. created() {
  110. if (process.browser) {
  111. this.worker = this.$worker.createRejexWorker()
  112. this.worker.addEventListener("message", this.workerResponseHandler)
  113. }
  114. },
  115. destroyed() {
  116. this.worker.terminate()
  117. },
  118. methods: {
  119. debouncer: debounce(function () {
  120. this.worker.postMessage({ type: "sse", url: this.server })
  121. }, 1000),
  122. workerResponseHandler({ data }) {
  123. if (data.url === this.url) this.isUrlValid = data.result
  124. },
  125. toggleSSEConnection() {
  126. // If it is connecting:
  127. if (!this.connectionSSEState) return this.start()
  128. // Otherwise, it's disconnecting.
  129. else return this.stop()
  130. },
  131. start() {
  132. this.connectingState = true
  133. this.log = [
  134. {
  135. payload: this.$t("state.connecting_to", { name: this.server }),
  136. source: "info",
  137. color: "var(--accent-color)",
  138. },
  139. ]
  140. if (typeof EventSource !== "undefined") {
  141. try {
  142. this.sse = new EventSource(this.server)
  143. this.sse.onopen = () => {
  144. this.connectingState = false
  145. this.connectionSSEState = true
  146. this.log = [
  147. {
  148. payload: this.$t("state.connected_to", { name: this.server }),
  149. source: "info",
  150. color: "var(--accent-color)",
  151. ts: new Date().toLocaleTimeString(),
  152. },
  153. ]
  154. this.$toast.success(this.$t("state.connected"))
  155. }
  156. this.sse.onerror = () => {
  157. this.handleSSEError()
  158. }
  159. this.sse.onclose = () => {
  160. this.connectionSSEState = false
  161. addSSELogLine({
  162. payload: this.$t("state.disconnected_from", {
  163. name: this.server,
  164. }),
  165. source: "info",
  166. color: "#ff5555",
  167. ts: new Date().toLocaleTimeString(),
  168. })
  169. this.$toast.error(this.$t("state.disconnected"))
  170. }
  171. this.sse.addEventListener(this.eventType, ({ data }) => {
  172. addSSELogLine({
  173. payload: data,
  174. source: "server",
  175. ts: new Date().toLocaleTimeString(),
  176. })
  177. })
  178. } catch (e) {
  179. this.handleSSEError(e)
  180. this.$toast.error(this.$t("error.something_went_wrong"))
  181. }
  182. } else {
  183. this.log = [
  184. {
  185. payload: this.$t("error.browser_support_sse"),
  186. source: "info",
  187. color: "#ff5555",
  188. ts: new Date().toLocaleTimeString(),
  189. },
  190. ]
  191. }
  192. logHoppRequestRunToAnalytics({
  193. platform: "sse",
  194. })
  195. },
  196. handleSSEError(error) {
  197. this.stop()
  198. this.connectionSSEState = false
  199. addSSELogLine({
  200. payload: this.$t("error.something_went_wrong"),
  201. source: "info",
  202. color: "#ff5555",
  203. ts: new Date().toLocaleTimeString(),
  204. })
  205. if (error !== null)
  206. addSSELogLine({
  207. payload: error,
  208. source: "info",
  209. color: "#ff5555",
  210. ts: new Date().toLocaleTimeString(),
  211. })
  212. },
  213. stop() {
  214. this.sse.close()
  215. this.sse.onclose()
  216. },
  217. },
  218. })
  219. </script>