Mqtt.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. <template>
  2. <Splitpanes
  3. class="smart-splitter"
  4. :dbl-click-splitter="false"
  5. :horizontal="!(windowInnerWidth.x.value >= 768)"
  6. >
  7. <Pane class="hide-scrollbar !overflow-auto">
  8. <Splitpanes
  9. class="smart-splitter"
  10. :dbl-click-splitter="false"
  11. :horizontal="COLUMN_LAYOUT"
  12. >
  13. <Pane class="hide-scrollbar !overflow-auto">
  14. <AppSection label="request">
  15. <div class="bg-primary flex p-4 top-0 z-10 sticky">
  16. <div class="space-x-2 flex-1 inline-flex">
  17. <input
  18. id="mqtt-url"
  19. v-model="url"
  20. v-focus
  21. type="url"
  22. autocomplete="off"
  23. spellcheck="false"
  24. class="
  25. bg-primaryLight
  26. border border-divider
  27. rounded
  28. text-secondaryDark
  29. w-full
  30. py-2
  31. px-4
  32. hover:border-dividerDark
  33. focus-visible:bg-transparent
  34. focus-visible:border-dividerDark
  35. "
  36. :placeholder="$t('mqtt.url')"
  37. />
  38. <ButtonPrimary
  39. id="connect"
  40. :disabled="!validUrl"
  41. class="w-32"
  42. :label="
  43. connectionState
  44. ? $t('action.disconnect')
  45. : $t('action.connect')
  46. "
  47. :loading="connectingState"
  48. @click.native="toggleConnection"
  49. />
  50. </div>
  51. </div>
  52. </AppSection>
  53. </Pane>
  54. <Pane class="hide-scrollbar !overflow-auto">
  55. <AppSection label="response">
  56. <RealtimeLog :title="$t('mqtt.log')" :log="log" />
  57. </AppSection>
  58. </Pane>
  59. </Splitpanes>
  60. </Pane>
  61. <Pane
  62. v-if="RIGHT_SIDEBAR"
  63. max-size="35"
  64. size="25"
  65. min-size="20"
  66. class="hide-scrollbar !overflow-auto"
  67. >
  68. <AppSection label="messages">
  69. <div class="flex flex-col flex-1 p-4 inline-flex">
  70. <label for="pub_topic" class="font-semibold text-secondaryLight">
  71. {{ $t("mqtt.topic") }}
  72. </label>
  73. </div>
  74. <div class="flex px-4">
  75. <input
  76. id="pub_topic"
  77. v-model="pub_topic"
  78. class="input"
  79. :placeholder="$t('mqtt.topic_name')"
  80. type="text"
  81. autocomplete="off"
  82. spellcheck="false"
  83. />
  84. </div>
  85. <div class="flex flex-1 p-4 items-center justify-between">
  86. <label for="mqtt-message" class="font-semibold text-secondaryLight">
  87. {{ $t("mqtt.communication") }}
  88. </label>
  89. </div>
  90. <div class="flex space-x-2 px-4">
  91. <input
  92. id="mqtt-message"
  93. v-model="msg"
  94. class="input"
  95. type="text"
  96. autocomplete="off"
  97. :placeholder="$t('mqtt.message')"
  98. spellcheck="false"
  99. />
  100. <ButtonPrimary
  101. id="publish"
  102. name="get"
  103. :disabled="!canpublish"
  104. :label="$t('mqtt.publish')"
  105. @click.native="publish"
  106. />
  107. </div>
  108. <div
  109. class="
  110. border-t border-dividerLight
  111. flex flex-col flex-1
  112. mt-4
  113. p-4
  114. inline-flex
  115. "
  116. >
  117. <label for="sub_topic" class="font-semibold text-secondaryLight">
  118. {{ $t("mqtt.topic") }}
  119. </label>
  120. </div>
  121. <div class="flex space-x-2 px-4">
  122. <input
  123. id="sub_topic"
  124. v-model="sub_topic"
  125. type="text"
  126. autocomplete="off"
  127. :placeholder="$t('mqtt.topic_name')"
  128. spellcheck="false"
  129. class="input"
  130. />
  131. <ButtonPrimary
  132. id="subscribe"
  133. name="get"
  134. :disabled="!cansubscribe"
  135. :label="
  136. subscriptionState ? $t('mqtt.unsubscribe') : $t('mqtt.subscribe')
  137. "
  138. reverse
  139. @click.native="toggleSubscription"
  140. />
  141. </div>
  142. </AppSection>
  143. </Pane>
  144. </Splitpanes>
  145. </template>
  146. <script>
  147. import { defineComponent } from "@nuxtjs/composition-api"
  148. import { Splitpanes, Pane } from "splitpanes"
  149. import "splitpanes/dist/splitpanes.css"
  150. import Paho from "paho-mqtt"
  151. import debounce from "lodash/debounce"
  152. import { logHoppRequestRunToAnalytics } from "~/helpers/fb/analytics"
  153. import { useSetting } from "~/newstore/settings"
  154. import useWindowSize from "~/helpers/utils/useWindowSize"
  155. export default defineComponent({
  156. components: { Splitpanes, Pane },
  157. setup() {
  158. return {
  159. windowInnerWidth: useWindowSize(),
  160. RIGHT_SIDEBAR: useSetting("RIGHT_SIDEBAR"),
  161. COLUMN_LAYOUT: useSetting("COLUMN_LAYOUT"),
  162. }
  163. },
  164. data() {
  165. return {
  166. url: "wss://test.mosquitto.org:8081",
  167. isUrlValid: true,
  168. client: null,
  169. pub_topic: "",
  170. sub_topic: "",
  171. msg: "",
  172. connectionState: false,
  173. connectingState: false,
  174. log: null,
  175. manualDisconnect: false,
  176. subscriptionState: false,
  177. }
  178. },
  179. computed: {
  180. validUrl() {
  181. return this.isUrlValid
  182. },
  183. canpublish() {
  184. return this.pub_topic !== "" && this.msg !== "" && this.connectionState
  185. },
  186. cansubscribe() {
  187. return this.sub_topic !== "" && this.connectionState
  188. },
  189. },
  190. watch: {
  191. url() {
  192. this.debouncer()
  193. },
  194. },
  195. mounted() {
  196. if (process.browser) {
  197. this.worker = this.$worker.createRejexWorker()
  198. this.worker.addEventListener("message", this.workerResponseHandler)
  199. }
  200. },
  201. destroyed() {
  202. this.worker.terminate()
  203. },
  204. methods: {
  205. debouncer: debounce(function () {
  206. this.worker.postMessage({ type: "ws", url: this.url })
  207. }, 1000),
  208. workerResponseHandler({ data }) {
  209. if (data.url === this.url) this.isUrlValid = data.result
  210. },
  211. connect() {
  212. this.connectingState = true
  213. this.log = [
  214. {
  215. payload: this.$t("state.connecting_to", { name: this.url }),
  216. source: "info",
  217. color: "var(--accent-color)",
  218. ts: new Date().toLocaleTimeString(),
  219. },
  220. ]
  221. const parseUrl = new URL(this.url)
  222. this.client = new Paho.Client(
  223. parseUrl.hostname,
  224. parseUrl.port !== "" ? Number(parseUrl.port) : 8081,
  225. "hoppscotch"
  226. )
  227. this.client.connect({
  228. onSuccess: this.onConnectionSuccess,
  229. onFailure: this.onConnectionFailure,
  230. useSSL: true,
  231. })
  232. this.client.onConnectionLost = this.onConnectionLost
  233. this.client.onMessageArrived = this.onMessageArrived
  234. logHoppRequestRunToAnalytics({
  235. platform: "mqtt",
  236. })
  237. },
  238. onConnectionFailure() {
  239. this.connectingState = false
  240. this.connectionState = false
  241. this.log.push({
  242. payload: this.$t("error.something_went_wrong"),
  243. source: "info",
  244. color: "#ff5555",
  245. ts: new Date().toLocaleTimeString(),
  246. })
  247. },
  248. onConnectionSuccess() {
  249. this.connectingState = false
  250. this.connectionState = true
  251. this.log.push({
  252. payload: this.$t("state.connected_to", { name: this.url }),
  253. source: "info",
  254. color: "var(--accent-color)",
  255. ts: new Date().toLocaleTimeString(),
  256. })
  257. this.$toast.success(this.$t("state.connected"), {
  258. icon: "sync",
  259. })
  260. },
  261. onMessageArrived({ payloadString, destinationName }) {
  262. this.log.push({
  263. payload: `Message: ${payloadString} arrived on topic: ${destinationName}`,
  264. source: "info",
  265. color: "var(--accent-color)",
  266. ts: new Date().toLocaleTimeString(),
  267. })
  268. },
  269. toggleConnection() {
  270. if (this.connectionState) {
  271. this.disconnect()
  272. } else {
  273. this.connect()
  274. }
  275. },
  276. disconnect() {
  277. this.manualDisconnect = true
  278. this.client.disconnect()
  279. this.log.push({
  280. payload: this.$t("state.disconnected_from", { name: this.url }),
  281. source: "info",
  282. color: "#ff5555",
  283. ts: new Date().toLocaleTimeString(),
  284. })
  285. },
  286. onConnectionLost() {
  287. this.connectingState = false
  288. this.connectionState = false
  289. if (this.manualDisconnect) {
  290. this.$toast.error(this.$t("state.disconnected"), {
  291. icon: "sync_disabled",
  292. })
  293. } else {
  294. this.$toast.error(this.$t("error.something_went_wrong"), {
  295. icon: "error_outline",
  296. })
  297. }
  298. this.manualDisconnect = false
  299. this.subscriptionState = false
  300. },
  301. publish() {
  302. try {
  303. this.client.publish(this.pub_topic, this.msg, 0, false)
  304. this.log.push({
  305. payload: `Published message: ${this.msg} to topic: ${this.pub_topic}`,
  306. ts: new Date().toLocaleTimeString(),
  307. source: "info",
  308. color: "var(--accent-color)",
  309. })
  310. } catch (e) {
  311. this.log.push({
  312. payload:
  313. this.$t("error.something_went_wrong") +
  314. `while publishing msg: ${this.msg} to topic: ${this.pub_topic}`,
  315. source: "info",
  316. color: "#ff5555",
  317. ts: new Date().toLocaleTimeString(),
  318. })
  319. }
  320. },
  321. toggleSubscription() {
  322. if (this.subscriptionState) {
  323. this.unsubscribe()
  324. } else {
  325. this.subscribe()
  326. }
  327. },
  328. subscribe() {
  329. try {
  330. this.client.subscribe(this.sub_topic, {
  331. onSuccess: this.usubSuccess,
  332. onFailure: this.usubFailure,
  333. })
  334. } catch (e) {
  335. this.log.push({
  336. payload:
  337. this.$t("error.something_went_wrong") +
  338. `while subscribing to topic: ${this.sub_topic}`,
  339. source: "info",
  340. color: "#ff5555",
  341. ts: new Date().toLocaleTimeString(),
  342. })
  343. }
  344. },
  345. usubSuccess() {
  346. this.subscriptionState = !this.subscriptionState
  347. this.log.push({
  348. payload:
  349. `Successfully ` +
  350. (this.subscriptionState ? "subscribed" : "unsubscribed") +
  351. ` to topic: ${this.sub_topic}`,
  352. source: "info",
  353. color: "var(--accent-color)",
  354. ts: new Date().toLocaleTimeString(),
  355. })
  356. },
  357. usubFailure() {
  358. this.log.push({
  359. payload:
  360. `Failed to ` +
  361. (this.subscriptionState ? "unsubscribe" : "subscribe") +
  362. ` to topic: ${this.sub_topic}`,
  363. source: "info",
  364. color: "#ff5555",
  365. ts: new Date().toLocaleTimeString(),
  366. })
  367. },
  368. unsubscribe() {
  369. this.client.unsubscribe(this.sub_topic, {
  370. onSuccess: this.usubSuccess,
  371. onFailure: this.usubFailure,
  372. })
  373. },
  374. },
  375. })
  376. </script>