Sse.vue 6.9 KB

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