Sse.vue 6.8 KB

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