Mqtt.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. <template>
  2. <AppPaneLayout>
  3. <template #primary>
  4. <div
  5. class="sticky top-0 z-10 flex flex-shrink-0 p-4 overflow-x-auto space-x-2 bg-primary hide-scrollbar"
  6. >
  7. <div class="inline-flex flex-1 space-x-2">
  8. <input
  9. id="mqtt-url"
  10. v-model="url"
  11. type="url"
  12. autocomplete="off"
  13. spellcheck="false"
  14. class="w-full px-4 py-2 border rounded bg-primaryLight border-divider text-secondaryDark"
  15. :placeholder="$t('mqtt.url')"
  16. :disabled="connectionState"
  17. @keyup.enter="validUrl ? toggleConnection() : null"
  18. />
  19. <ButtonPrimary
  20. id="connect"
  21. :disabled="!validUrl"
  22. class="w-32"
  23. :label="
  24. connectionState ? $t('action.disconnect') : $t('action.connect')
  25. "
  26. :loading="connectingState"
  27. @click.native="toggleConnection"
  28. />
  29. </div>
  30. <div class="flex space-x-4">
  31. <input
  32. id="mqtt-username"
  33. v-model="username"
  34. type="text"
  35. spellcheck="false"
  36. class="input"
  37. :placeholder="$t('authorization.username')"
  38. />
  39. <input
  40. id="mqtt-password"
  41. v-model="password"
  42. type="password"
  43. spellcheck="false"
  44. class="input"
  45. :placeholder="$t('authorization.password')"
  46. />
  47. </div>
  48. </div>
  49. </template>
  50. <template #secondary>
  51. <RealtimeLog :title="$t('mqtt.log')" :log="log" />
  52. </template>
  53. <template #sidebar>
  54. <div class="flex items-center justify-between p-4">
  55. <label for="pub_topic" class="font-semibold text-secondaryLight">
  56. {{ $t("mqtt.topic") }}
  57. </label>
  58. </div>
  59. <div class="flex px-4">
  60. <input
  61. id="pub_topic"
  62. v-model="pub_topic"
  63. class="input"
  64. :placeholder="$t('mqtt.topic_name')"
  65. type="text"
  66. autocomplete="off"
  67. spellcheck="false"
  68. />
  69. </div>
  70. <div class="flex items-center justify-between p-4">
  71. <label for="mqtt-message" class="font-semibold text-secondaryLight">
  72. {{ $t("mqtt.communication") }}
  73. </label>
  74. </div>
  75. <div class="flex px-4 space-x-2">
  76. <input
  77. id="mqtt-message"
  78. v-model="msg"
  79. class="input"
  80. type="text"
  81. autocomplete="off"
  82. :placeholder="$t('mqtt.message')"
  83. spellcheck="false"
  84. />
  85. <ButtonPrimary
  86. id="publish"
  87. name="get"
  88. :disabled="!canpublish"
  89. :label="$t('mqtt.publish')"
  90. @click.native="publish"
  91. />
  92. </div>
  93. <div
  94. class="flex items-center justify-between p-4 mt-4 border-t border-dividerLight"
  95. >
  96. <label for="sub_topic" class="font-semibold text-secondaryLight">
  97. {{ $t("mqtt.topic") }}
  98. </label>
  99. </div>
  100. <div class="flex px-4 space-x-2">
  101. <input
  102. id="sub_topic"
  103. v-model="sub_topic"
  104. type="text"
  105. autocomplete="off"
  106. :placeholder="$t('mqtt.topic_name')"
  107. spellcheck="false"
  108. class="input"
  109. />
  110. <ButtonPrimary
  111. id="subscribe"
  112. name="get"
  113. :disabled="!cansubscribe"
  114. :label="
  115. subscriptionState ? $t('mqtt.unsubscribe') : $t('mqtt.subscribe')
  116. "
  117. reverse
  118. @click.native="toggleSubscription"
  119. />
  120. </div>
  121. </template>
  122. </AppPaneLayout>
  123. </template>
  124. <script>
  125. import { defineComponent } from "@nuxtjs/composition-api"
  126. import Paho from "paho-mqtt"
  127. import debounce from "lodash/debounce"
  128. import { logHoppRequestRunToAnalytics } from "~/helpers/fb/analytics"
  129. import {
  130. MQTTEndpoint$,
  131. setMQTTEndpoint,
  132. MQTTConnectingState$,
  133. MQTTConnectionState$,
  134. setMQTTConnectingState,
  135. setMQTTConnectionState,
  136. MQTTSubscriptionState$,
  137. setMQTTSubscriptionState,
  138. MQTTSocket$,
  139. setMQTTSocket,
  140. MQTTLog$,
  141. setMQTTLog,
  142. addMQTTLogLine,
  143. } from "~/newstore/MQTTSession"
  144. import { useStream } from "~/helpers/utils/composables"
  145. export default defineComponent({
  146. setup() {
  147. return {
  148. url: useStream(MQTTEndpoint$, "", setMQTTEndpoint),
  149. connectionState: useStream(
  150. MQTTConnectionState$,
  151. false,
  152. setMQTTConnectionState
  153. ),
  154. connectingState: useStream(
  155. MQTTConnectingState$,
  156. false,
  157. setMQTTConnectingState
  158. ),
  159. subscriptionState: useStream(
  160. MQTTSubscriptionState$,
  161. false,
  162. setMQTTSubscriptionState
  163. ),
  164. log: useStream(MQTTLog$, null, setMQTTLog),
  165. client: useStream(MQTTSocket$, null, setMQTTSocket),
  166. }
  167. },
  168. data() {
  169. return {
  170. isUrlValid: true,
  171. pub_topic: "",
  172. sub_topic: "",
  173. msg: "",
  174. manualDisconnect: false,
  175. username: "",
  176. password: "",
  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. created() {
  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.pathname !== "/" ? parseUrl.pathname : ""
  225. }`,
  226. parseUrl.port !== "" ? Number(parseUrl.port) : 8081,
  227. "hoppscotch"
  228. )
  229. const connectOptions = {
  230. onSuccess: this.onConnectionSuccess,
  231. onFailure: this.onConnectionFailure,
  232. useSSL: parseUrl.protocol !== "ws:",
  233. }
  234. if (this.username !== "") {
  235. connectOptions.userName = this.username
  236. }
  237. if (this.password !== "") {
  238. connectOptions.password = this.password
  239. }
  240. this.client.connect(connectOptions)
  241. this.client.onConnectionLost = this.onConnectionLost
  242. this.client.onMessageArrived = this.onMessageArrived
  243. logHoppRequestRunToAnalytics({
  244. platform: "mqtt",
  245. })
  246. },
  247. onConnectionFailure() {
  248. this.connectingState = false
  249. this.connectionState = false
  250. addMQTTLogLine({
  251. payload: this.$t("error.something_went_wrong"),
  252. source: "info",
  253. color: "#ff5555",
  254. ts: new Date().toLocaleTimeString(),
  255. })
  256. },
  257. onConnectionSuccess() {
  258. this.connectingState = false
  259. this.connectionState = true
  260. addMQTTLogLine({
  261. payload: this.$t("state.connected_to", { name: this.url }),
  262. source: "info",
  263. color: "var(--accent-color)",
  264. ts: new Date().toLocaleTimeString(),
  265. })
  266. this.$toast.success(this.$t("state.connected"))
  267. },
  268. onMessageArrived({ payloadString, destinationName }) {
  269. addMQTTLogLine({
  270. payload: `Message: ${payloadString} arrived on topic: ${destinationName}`,
  271. source: "info",
  272. color: "var(--accent-color)",
  273. ts: new Date().toLocaleTimeString(),
  274. })
  275. },
  276. toggleConnection() {
  277. if (this.connectionState) {
  278. this.disconnect()
  279. } else {
  280. this.connect()
  281. }
  282. },
  283. disconnect() {
  284. this.manualDisconnect = true
  285. this.client.disconnect()
  286. addMQTTLogLine({
  287. payload: this.$t("state.disconnected_from", { name: this.url }),
  288. source: "info",
  289. color: "#ff5555",
  290. ts: new Date().toLocaleTimeString(),
  291. })
  292. },
  293. onConnectionLost() {
  294. this.connectingState = false
  295. this.connectionState = false
  296. if (this.manualDisconnect) {
  297. this.$toast.error(this.$t("state.disconnected"))
  298. } else {
  299. this.$toast.error(this.$t("error.something_went_wrong"))
  300. }
  301. this.manualDisconnect = false
  302. this.subscriptionState = false
  303. },
  304. publish() {
  305. try {
  306. this.client.publish(this.pub_topic, this.msg, 0, false)
  307. addMQTTLogLine({
  308. payload: `Published message: ${this.msg} to topic: ${this.pub_topic}`,
  309. ts: new Date().toLocaleTimeString(),
  310. source: "info",
  311. color: "var(--accent-color)",
  312. })
  313. } catch (e) {
  314. addMQTTLogLine({
  315. payload:
  316. this.$t("error.something_went_wrong") +
  317. `while publishing msg: ${this.msg} to topic: ${this.pub_topic}`,
  318. source: "info",
  319. color: "#ff5555",
  320. ts: new Date().toLocaleTimeString(),
  321. })
  322. }
  323. },
  324. toggleSubscription() {
  325. if (this.subscriptionState) {
  326. this.unsubscribe()
  327. } else {
  328. this.subscribe()
  329. }
  330. },
  331. subscribe() {
  332. try {
  333. this.client.subscribe(this.sub_topic, {
  334. onSuccess: this.usubSuccess,
  335. onFailure: this.usubFailure,
  336. })
  337. } catch (e) {
  338. addMQTTLogLine({
  339. payload:
  340. this.$t("error.something_went_wrong") +
  341. `while subscribing to topic: ${this.sub_topic}`,
  342. source: "info",
  343. color: "#ff5555",
  344. ts: new Date().toLocaleTimeString(),
  345. })
  346. }
  347. },
  348. usubSuccess() {
  349. this.subscriptionState = !this.subscriptionState
  350. addMQTTLogLine({
  351. payload:
  352. `Successfully ` +
  353. (this.subscriptionState ? "subscribed" : "unsubscribed") +
  354. ` to topic: ${this.sub_topic}`,
  355. source: "info",
  356. color: "var(--accent-color)",
  357. ts: new Date().toLocaleTimeString(),
  358. })
  359. },
  360. usubFailure() {
  361. addMQTTLogLine({
  362. payload:
  363. `Failed to ` +
  364. (this.subscriptionState ? "unsubscribe" : "subscribe") +
  365. ` to topic: ${this.sub_topic}`,
  366. source: "info",
  367. color: "#ff5555",
  368. ts: new Date().toLocaleTimeString(),
  369. })
  370. },
  371. unsubscribe() {
  372. this.client.unsubscribe(this.sub_topic, {
  373. onSuccess: this.usubSuccess,
  374. onFailure: this.usubFailure,
  375. })
  376. },
  377. },
  378. })
  379. </script>