Mqtt.vue 12 KB

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