Mqtt.vue 11 KB

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