Sse.vue 6.8 KB

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