From 2ed709796a18b5f1c8479bee0737639560eaf81c Mon Sep 17 00:00:00 2001 From: Anwarul Islam Date: Sun, 27 Nov 2022 03:13:24 +0600 Subject: [PATCH] MQTT Revamp (#2381) Co-authored-by: Liyas Thomas --- packages/hoppscotch-app/locales/en.json | 32 +- packages/hoppscotch-app/src/components.d.ts | 5 + .../src/components/graphql/RequestOptions.vue | 2 +- .../src/components/graphql/Response.vue | 14 + .../src/components/http/Request.vue | 8 +- .../src/components/http/ResponseMeta.vue | 4 +- .../src/components/realtime/Communication.vue | 46 +- .../components/realtime/ConnectionConfig.vue | 135 ++++++ .../src/components/realtime/Log.vue | 47 ++ .../src/components/realtime/LogEntry.vue | 1 + .../src/components/realtime/Subscription.vue | 138 ++++++ .../src/components/smart/Window.vue | 49 ++ .../src/components/smart/Windows.vue | 274 +++++++++++ .../hoppscotch-app/src/helpers/actions.ts | 2 +- .../hoppscotch-app/src/helpers/keybindings.ts | 10 +- .../src/helpers/realtime/MQTTConnection.ts | 85 +++- .../hoppscotch-app/src/helpers/shortcuts.ts | 2 +- .../src/newstore/MQTTSession.ts | 148 +++++- .../src/pages/realtime/mqtt.vue | 457 +++++++++++------- .../src/pages/realtime/socketio.vue | 9 +- .../hoppscotch-app/src/pages/realtime/sse.vue | 5 +- .../src/pages/realtime/websocket.vue | 6 +- pnpm-lock.yaml | 10 +- 23 files changed, 1280 insertions(+), 209 deletions(-) create mode 100644 packages/hoppscotch-app/src/components/realtime/ConnectionConfig.vue create mode 100644 packages/hoppscotch-app/src/components/realtime/Subscription.vue create mode 100644 packages/hoppscotch-app/src/components/smart/Window.vue create mode 100644 packages/hoppscotch-app/src/components/smart/Windows.vue diff --git a/packages/hoppscotch-app/locales/en.json b/packages/hoppscotch-app/locales/en.json index 986525df6..632312c58 100644 --- a/packages/hoppscotch-app/locales/en.json +++ b/packages/hoppscotch-app/locales/en.json @@ -161,6 +161,7 @@ "body": "This request does not have a body", "collection": "Collection is empty", "collections": "Collections are empty", + "subscription": "Subscriptions are empty", "documentation": "Connect to a GraphQL endpoint to view documentation", "endpoint": "Endpoint cannot be empty", "environments": "Environments are empty", @@ -312,6 +313,8 @@ "import_export": "Import / Export" }, "mqtt": { + "new": "New Subscription", + "invalid_topic": "Please provide a topic for the subscription", "communication": "Communication", "log": "Log", "message": "Message", @@ -321,7 +324,23 @@ "topic_name": "Topic Name", "topic_title": "Publish / Subscribe topic", "unsubscribe": "Unsubscribe", - "url": "URL" + "url": "URL", + "client_id": "Client ID", + "qos": "QoS", + "color": "Color", + "connection_not_authorized": "This MQTT connection does not use any authentication.", + "not_connected": "Please start a MQTT connection first.", + "already_subscribed": "You are already subscribed to this topic.", + "connection_config": "Connection Config", + "keep_alive": "Keep Alive", + "clean_session": "Clean Session", + "ssl": "SSL", + "lw_qos": "Last-Will QoS", + "lw_retain": "Last-Will Retain", + "lw_topic": "Last-Will Topic", + "lw_message": "Last-Will Message", + "clear_input": "Clear input", + "clear_input_on_send": "Clear input on send" }, "navigation": { "doc": "Docs", @@ -530,6 +549,15 @@ "title": "Theme" } }, + "shortcodes": { + "actions": "Actions", + "created_on": "Created on", + "deleted": "Shortcode deleted", + "method": "Method", + "not_found": "Shortcode not found", + "short_code": "Short code", + "url": "URL" + }, "show": { "code": "Show code", "collection": "Expand Collection Panel", @@ -539,7 +567,7 @@ "socketio": { "communication": "Communication", "connection_not_authorized": "This SocketIO connection does not use any authentication.", - "event_name": "Event Name", + "event_name": "Event/Topic Name", "events": "Events", "log": "Log", "url": "URL" diff --git a/packages/hoppscotch-app/src/components.d.ts b/packages/hoppscotch-app/src/components.d.ts index 4eab571d2..8e2b111d3 100644 --- a/packages/hoppscotch-app/src/components.d.ts +++ b/packages/hoppscotch-app/src/components.d.ts @@ -106,6 +106,7 @@ declare module '@vue/runtime-core' { IconLucideLayers: typeof import('~icons/lucide/layers')['default'] IconLucideLoader: typeof import('~icons/lucide/loader')['default'] IconLucideMinus: typeof import('~icons/lucide/minus')['default'] + IconLucideRss: typeof import('~icons/lucide/rss')['default'] IconLucideSearch: typeof import('~icons/lucide/search')['default'] IconLucideUser: typeof import('~icons/lucide/user')['default'] IconLucideUsers: typeof import('~icons/lucide/users')['default'] @@ -121,8 +122,10 @@ declare module '@vue/runtime-core' { ProfilePicture: typeof import('./components/profile/Picture.vue')['default'] ProfileShortcode: typeof import('./components/profile/Shortcode.vue')['default'] RealtimeCommunication: typeof import('./components/realtime/Communication.vue')['default'] + RealtimeConnectionConfig: typeof import('./components/realtime/ConnectionConfig.vue')['default'] RealtimeLog: typeof import('./components/realtime/Log.vue')['default'] RealtimeLogEntry: typeof import('./components/realtime/LogEntry.vue')['default'] + RealtimeSubscription: typeof import('./components/realtime/Subscription.vue')['default'] SmartAccentModePicker: typeof import('./components/smart/AccentModePicker.vue')['default'] SmartAnchor: typeof import('./components/smart/Anchor.vue')['default'] SmartAutoComplete: typeof import('./components/smart/AutoComplete.vue')['default'] @@ -146,6 +149,8 @@ declare module '@vue/runtime-core' { SmartTab: typeof import('./components/smart/Tab.vue')['default'] SmartTabs: typeof import('./components/smart/Tabs.vue')['default'] SmartToggle: typeof import('./components/smart/Toggle.vue')['default'] + SmartWindow: typeof import('./components/smart/Window.vue')['default'] + SmartWindows: typeof import('./components/smart/Windows.vue')['default'] TabPrimary: typeof import('./components/tab/Primary.vue')['default'] TabSecondary: typeof import('./components/tab/Secondary.vue')['default'] Teams: typeof import('./components/teams/index.vue')['default'] diff --git a/packages/hoppscotch-app/src/components/graphql/RequestOptions.vue b/packages/hoppscotch-app/src/components/graphql/RequestOptions.vue index e7b9c6831..479209886 100644 --- a/packages/hoppscotch-app/src/components/graphql/RequestOptions.vue +++ b/packages/hoppscotch-app/src/components/graphql/RequestOptions.vue @@ -21,7 +21,7 @@ v-tippy="{ theme: 'tooltip', delay: [500, 20], allowHTML: true }" :title="`${t( 'request.run' - )} ${getSpecialKey()}G`" + )} ${getSpecialKey()}`" :label="`${t('request.run')}`" :icon="IconPlay" class="rounded-none !text-accent !hover:text-accentDark" diff --git a/packages/hoppscotch-app/src/components/graphql/Response.vue b/packages/hoppscotch-app/src/components/graphql/Response.vue index fdea347fe..8caa39097 100644 --- a/packages/hoppscotch-app/src/components/graphql/Response.vue +++ b/packages/hoppscotch-app/src/components/graphql/Response.vue @@ -48,6 +48,12 @@ >
+ + {{ t("shortcut.request.send_request") }} + + + {{ t("shortcut.general.show_all") }} + {{ t("shortcut.general.command_menu") }} @@ -56,6 +62,14 @@
+
+ {{ getSpecialKey() }} + +
+
+ {{ getSpecialKey() }} + K +
/
diff --git a/packages/hoppscotch-app/src/components/http/Request.vue b/packages/hoppscotch-app/src/components/http/Request.vue index 12414f78b..793841404 100644 --- a/packages/hoppscotch-app/src/components/http/Request.vue +++ b/packages/hoppscotch-app/src/components/http/Request.vue @@ -16,7 +16,7 @@
-
+
{{ t("shortcut.request.send_request") }} @@ -24,7 +24,7 @@
{{ getSpecialKey() }} - G +
{{ getSpecialKey() }} diff --git a/packages/hoppscotch-app/src/components/realtime/Communication.vue b/packages/hoppscotch-app/src/components/realtime/Communication.vue index c25e147a1..1cf25e550 100644 --- a/packages/hoppscotch-app/src/components/realtime/Communication.vue +++ b/packages/hoppscotch-app/src/components/realtime/Communication.vue @@ -1,10 +1,15 @@ @@ -61,8 +106,10 @@ import IconTrash from "~icons/lucide/trash" import IconArrowUp from "~icons/lucide/arrow-up" import IconArrowDown from "~icons/lucide/arrow-down" import IconChevronsDown from "~icons/lucide/chevron-down" +import IconExternalLink from "~icons/lucide/external-link" import { useThrottleFn, useScroll } from "@vueuse/core" import { useI18n } from "@composables/i18n" +import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils" export type LogEntryData = { prefix?: string diff --git a/packages/hoppscotch-app/src/components/realtime/LogEntry.vue b/packages/hoppscotch-app/src/components/realtime/LogEntry.vue index b1082517c..5dbfe6176 100644 --- a/packages/hoppscotch-app/src/components/realtime/LogEntry.vue +++ b/packages/hoppscotch-app/src/components/realtime/LogEntry.vue @@ -371,6 +371,7 @@ const icon = computed(() => markRaw(ICONS[props.entry.source].icon)) .realtime-log { @apply text-secondary; @apply overflow-hidden; + @apply hover:cursor-nsResize; &, span { diff --git a/packages/hoppscotch-app/src/components/realtime/Subscription.vue b/packages/hoppscotch-app/src/components/realtime/Subscription.vue new file mode 100644 index 000000000..ed49661c2 --- /dev/null +++ b/packages/hoppscotch-app/src/components/realtime/Subscription.vue @@ -0,0 +1,138 @@ + + + diff --git a/packages/hoppscotch-app/src/components/smart/Window.vue b/packages/hoppscotch-app/src/components/smart/Window.vue new file mode 100644 index 000000000..ff6a82a63 --- /dev/null +++ b/packages/hoppscotch-app/src/components/smart/Window.vue @@ -0,0 +1,49 @@ + + + diff --git a/packages/hoppscotch-app/src/components/smart/Windows.vue b/packages/hoppscotch-app/src/components/smart/Windows.vue new file mode 100644 index 000000000..ef1db4ba9 --- /dev/null +++ b/packages/hoppscotch-app/src/components/smart/Windows.vue @@ -0,0 +1,274 @@ + + + + + diff --git a/packages/hoppscotch-app/src/helpers/actions.ts b/packages/hoppscotch-app/src/helpers/actions.ts index deb0dbe3b..d73805689 100644 --- a/packages/hoppscotch-app/src/helpers/actions.ts +++ b/packages/hoppscotch-app/src/helpers/actions.ts @@ -68,7 +68,7 @@ type HoppActionWithArgs = keyof HoppActionArgs /** * HoppActions which do not require arguments for their invocation */ -type HoppActionWithNoArgs = Exclude +export type HoppActionWithNoArgs = Exclude /** * Resolves the argument type for a given HoppAction diff --git a/packages/hoppscotch-app/src/helpers/keybindings.ts b/packages/hoppscotch-app/src/helpers/keybindings.ts index f754e1ab3..d20953ab6 100644 --- a/packages/hoppscotch-app/src/helpers/keybindings.ts +++ b/packages/hoppscotch-app/src/helpers/keybindings.ts @@ -1,5 +1,5 @@ import { onBeforeUnmount, onMounted } from "vue" -import { HoppAction, invokeAction } from "./actions" +import { HoppActionWithNoArgs, invokeAction } from "./actions" import { isAppleDevice } from "./platformutils" import { isDOMElement, isTypableElement } from "./utils/dom" @@ -23,7 +23,7 @@ type Key = | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" | "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "up" | "down" | "left" - | "right" | "/" | "?" | "." + | "right" | "/" | "?" | "." | "enter" /* eslint-enable */ type ModifierBasedShortcutKey = `${ModifierKeys}-${Key}` @@ -34,9 +34,9 @@ type ShortcutKey = ModifierBasedShortcutKey | SingleCharacterShortcutKey export const bindings: { // eslint-disable-next-line no-unused-vars - [_ in ShortcutKey]?: HoppAction + [_ in ShortcutKey]?: HoppActionWithNoArgs } = { - "ctrl-g": "request.send-cancel", + "ctrl-enter": "request.send-cancel", "ctrl-i": "request.reset", "ctrl-u": "request.copy-link", "ctrl-s": "request.save", @@ -136,6 +136,8 @@ function getPressedKey(ev: KeyboardEvent): Key | null { // Check if period if (val === ".") return "." + if (val === "enter") return "enter" + // If no other cases match, this is not a valid key return null } diff --git a/packages/hoppscotch-app/src/helpers/realtime/MQTTConnection.ts b/packages/hoppscotch-app/src/helpers/realtime/MQTTConnection.ts index 978663d57..a5aefea99 100644 --- a/packages/hoppscotch-app/src/helpers/realtime/MQTTConnection.ts +++ b/packages/hoppscotch-app/src/helpers/realtime/MQTTConnection.ts @@ -2,6 +2,17 @@ import Paho, { ConnectionOptions } from "paho-mqtt" import { BehaviorSubject, Subject } from "rxjs" import { logHoppRequestRunToAnalytics } from "../fb/analytics" +export type MQTTConnectionConfig = { + username?: string + password?: string + keepAlive?: string + cleanSession?: boolean + lwTopic?: string + lwMessage: string + lwQos: 2 | 1 | 0 + lwRetain: boolean +} + export type MQTTMessage = { topic: string; message: string } export type MQTTError = | { type: "CONNECTION_NOT_ESTABLISHED"; value: unknown } @@ -21,12 +32,22 @@ export type MQTTEvent = { time: number } & ( | { type: "ERROR"; error: MQTTError } ) +export type MQTTTopic = { + name: string + color: string + qos: 2 | 1 | 0 +} + export type ConnectionState = "CONNECTING" | "CONNECTED" | "DISCONNECTED" +export const QOS_VALUES = [2, 1, 0] as const + export class MQTTConnection { + subscribing$ = new BehaviorSubject(false) subscriptionState$ = new BehaviorSubject(false) connectionState$ = new BehaviorSubject("DISCONNECTED") event$: Subject = new Subject() + subscribedTopics$ = new BehaviorSubject([]) private mqttClient: Paho.Client | undefined private manualDisconnect = false @@ -35,7 +56,7 @@ export class MQTTConnection { this.event$.next(event) } - connect(url: string, username: string, password: string) { + connect(url: string, clientID: string, config: MQTTConnectionConfig) { try { this.connectionState$.next("CONNECTING") @@ -49,19 +70,34 @@ export class MQTTConnection { this.mqttClient = new Paho.Client( `${hostname + (pathname !== "/" ? pathname : "")}`, port !== "" ? Number(port) : 8081, - "hoppscotch" + clientID ?? "hoppscotch" ) const connectOptions: ConnectionOptions = { onSuccess: this.onConnectionSuccess.bind(this), onFailure: this.onConnectionFailure.bind(this), + timeout: 3, + keepAliveInterval: Number(config.keepAlive) ?? 60, + cleanSession: config.cleanSession ?? true, useSSL: parseUrl.protocol !== "ws:", } - if (username !== "") { + + const { username, password, lwTopic, lwMessage, lwQos, lwRetain } = config + + if (username) { connectOptions.userName = username } - if (password !== "") { + if (password) { connectOptions.password = password } + + if (lwTopic?.length) { + const willmsg = new Paho.Message(lwMessage) + willmsg.qos = lwQos + willmsg.destinationName = lwTopic + willmsg.retained = lwRetain + connectOptions.willMessage = willmsg + } + this.mqttClient.connect(connectOptions) this.mqttClient.onConnectionLost = this.onConnectionLost.bind(this) this.mqttClient.onMessageArrived = this.onMessageArrived.bind(this) @@ -112,6 +148,7 @@ export class MQTTConnection { } this.manualDisconnect = false this.subscriptionState$.next(false) + this.subscribedTopics$.next([]) } onMessageArrived({ @@ -170,34 +207,45 @@ export class MQTTConnection { } } - subscribe(topic: string) { + subscribe(topic: MQTTTopic) { + this.subscribing$.next(true) try { - this.mqttClient?.subscribe(topic, { - onSuccess: this.usubSuccess.bind(this, topic), - onFailure: this.usubFailure.bind(this, topic), + this.mqttClient?.subscribe(topic.name, { + onSuccess: this.subSuccess.bind(this, topic), + onFailure: this.usubFailure.bind(this, topic.name), + qos: topic.qos, }) } catch (e) { + this.subscribing$.next(false) this.addEvent({ time: Date.now(), type: "ERROR", error: { type: "SUBSCRIPTION_FAILED", - topic, + topic: topic.name, }, }) } } - usubSuccess(topic: string) { + subSuccess(topic: MQTTTopic) { + this.subscribing$.next(false) this.subscriptionState$.next(!this.subscriptionState$.value) + this.addSubscription(topic) this.addEvent({ time: Date.now(), type: "SUBSCRIBED", - topic, + topic: topic.name, }) } + usubSuccess(topic: string) { + this.subscribing$.next(false) + this.removeSubscription(topic) + } + usubFailure(topic: string) { + this.subscribing$.next(false) this.addEvent({ time: Date.now(), type: "ERROR", @@ -215,6 +263,21 @@ export class MQTTConnection { }) } + addSubscription(topic: MQTTTopic) { + const subscriptions = this.subscribedTopics$.getValue() + subscriptions.push({ + name: topic.name, + color: topic.color, + qos: topic.qos, + }) + this.subscribedTopics$.next(subscriptions) + } + + removeSubscription(topic: string) { + const subscriptions = this.subscribedTopics$.getValue() + this.subscribedTopics$.next(subscriptions.filter((t) => t.name !== topic)) + } + disconnect() { this.manualDisconnect = true this.mqttClient?.disconnect() diff --git a/packages/hoppscotch-app/src/helpers/shortcuts.ts b/packages/hoppscotch-app/src/helpers/shortcuts.ts index 182f49674..d99ab0a04 100644 --- a/packages/hoppscotch-app/src/helpers/shortcuts.ts +++ b/packages/hoppscotch-app/src/helpers/shortcuts.ts @@ -34,7 +34,7 @@ export default [ section: "shortcut.request.title", shortcuts: [ { - keys: [getPlatformSpecialKey(), "G"], + keys: [getPlatformSpecialKey(), "Enter"], label: "shortcut.request.send_request", }, { diff --git a/packages/hoppscotch-app/src/newstore/MQTTSession.ts b/packages/hoppscotch-app/src/newstore/MQTTSession.ts index cad1e8d68..d328901bb 100644 --- a/packages/hoppscotch-app/src/newstore/MQTTSession.ts +++ b/packages/hoppscotch-app/src/newstore/MQTTSession.ts @@ -6,8 +6,17 @@ import { HoppRealtimeLogLine, } from "~/helpers/types/HoppRealtimeLog" +type MQTTTab = { + id: string + name: string + color: string + removable: boolean + logs: HoppRealtimeLog[] +} + type HoppMQTTRequest = { endpoint: string + clientID: string } type HoppMQTTSession = { @@ -15,10 +24,21 @@ type HoppMQTTSession = { subscriptionState: boolean log: HoppRealtimeLog socket: MQTTConnection + tabs: MQTTTab[] + currentTabId: string } const defaultMQTTRequest: HoppMQTTRequest = { endpoint: "wss://test.mosquitto.org:8081", + clientID: "hoppscotch", +} + +const defaultTab: MQTTTab = { + id: "all", + name: "All Topics", + color: "var(--accent-color)", + removable: false, + logs: [], } const defaultMQTTSession: HoppMQTTSession = { @@ -26,6 +46,8 @@ const defaultMQTTSession: HoppMQTTSession = { subscriptionState: false, socket: new MQTTConnection(), log: [], + tabs: [defaultTab], + currentTabId: defaultTab.id, } const dispatchers = defineDispatchers({ @@ -37,13 +59,22 @@ const dispatchers = defineDispatchers({ request: newRequest, } }, - setEndpoint(_: HoppMQTTSession, { newEndpoint }: { newEndpoint: string }) { + setEndpoint(curr: HoppMQTTSession, { newEndpoint }: { newEndpoint: string }) { return { request: { + clientID: curr.request.clientID, endpoint: newEndpoint, }, } }, + setClientID(curr: HoppMQTTSession, { newClientID }: { newClientID: string }) { + return { + request: { + endpoint: curr.request.endpoint, + clientID: newClientID, + }, + } + }, setConn(_: HoppMQTTSession, { socket }: { socket: MQTTConnection }) { return { socket, @@ -64,6 +95,47 @@ const dispatchers = defineDispatchers({ log: [...curr.log, line], } }, + setTabs(_: HoppMQTTSession, { tabs }: { tabs: MQTTTab[] }) { + return { + tabs, + } + }, + addTab(curr: HoppMQTTSession, { tab }: { tab: MQTTTab }) { + return { + tabs: [...curr.tabs, tab], + } + }, + setCurrentTabId(_: HoppMQTTSession, { tabId }: { tabId: string }) { + return { + currentTabId: tabId, + } + }, + setCurrentTabLog( + _: HoppMQTTSession, + { log, tabId }: { log: HoppRealtimeLog[]; tabId: string } + ) { + const newTabs = _.tabs.map((tab) => { + if (tab.id === tabId) tab.logs = log + return tab + }) + + return { + tabs: newTabs, + } + }, + addCurrentTabLogLine( + _: HoppMQTTSession, + { line, tabId }: { tabId: string; line: HoppRealtimeLog } + ) { + const newTabs = _.tabs.map((tab) => { + if (tab.id === tabId) tab.logs = [...tab.logs, line] + return tab + }) + + return { + tabs: newTabs, + } + }, }) const MQTTSessionStore = new DispatchingStore(defaultMQTTSession, dispatchers) @@ -86,6 +158,15 @@ export function setMQTTEndpoint(newEndpoint: string) { }) } +export function setMQTTClientID(newClientID: string) { + MQTTSessionStore.dispatch({ + dispatcher: "setClientID", + payload: { + newClientID, + }, + }) +} + export function setMQTTConn(socket: MQTTConnection) { MQTTSessionStore.dispatch({ dispatcher: "setConn", @@ -122,6 +203,56 @@ export function addMQTTLogLine(line: HoppRealtimeLogLine) { }) } +export function setMQTTTabs(tabs: MQTTTab[]) { + MQTTSessionStore.dispatch({ + dispatcher: "setTabs", + payload: { + tabs, + }, + }) +} + +export function addMQTTTab(tab: MQTTTab) { + MQTTSessionStore.dispatch({ + dispatcher: "addTab", + payload: { + tab, + }, + }) +} + +export function setCurrentTab(tabId: string) { + MQTTSessionStore.dispatch({ + dispatcher: "setCurrentTabId", + payload: { + tabId, + }, + }) +} + +export function setMQTTCurrentTabLog(tabId: string, log: HoppRealtimeLog) { + MQTTSessionStore.dispatch({ + dispatcher: "setCurrentTabLog", + payload: { + tabId, + log, + }, + }) +} + +export function addMQTTCurrentTabLogLine( + tabId: string, + line: HoppRealtimeLogLine +) { + MQTTSessionStore.dispatch({ + dispatcher: "addCurrentTabLogLine", + payload: { + tabId, + line, + }, + }) +} + export const MQTTRequest$ = MQTTSessionStore.subject$.pipe( pluck("request"), distinctUntilChanged() @@ -132,6 +263,11 @@ export const MQTTEndpoint$ = MQTTSessionStore.subject$.pipe( distinctUntilChanged() ) +export const MQTTClientID$ = MQTTSessionStore.subject$.pipe( + pluck("request", "clientID"), + distinctUntilChanged() +) + export const MQTTConnectingState$ = MQTTSessionStore.subject$.pipe( pluck("connectingState"), distinctUntilChanged() @@ -156,3 +292,13 @@ export const MQTTLog$ = MQTTSessionStore.subject$.pipe( pluck("log"), distinctUntilChanged() ) + +export const MQTTTabs$ = MQTTSessionStore.subject$.pipe( + pluck("tabs"), + distinctUntilChanged() +) + +export const MQTTCurrentTab$ = MQTTSessionStore.subject$.pipe( + pluck("currentTabId"), + distinctUntilChanged() +) diff --git a/packages/hoppscotch-app/src/pages/realtime/mqtt.vue b/packages/hoppscotch-app/src/pages/realtime/mqtt.vue index f24078a5a..f36a2a414 100644 --- a/packages/hoppscotch-app/src/pages/realtime/mqtt.vue +++ b/packages/hoppscotch-app/src/pages/realtime/mqtt.vue @@ -2,23 +2,42 @@ - - + diff --git a/packages/hoppscotch-app/src/pages/realtime/socketio.vue b/packages/hoppscotch-app/src/pages/realtime/socketio.vue index b1db74830..820e4340f 100644 --- a/packages/hoppscotch-app/src/pages/realtime/socketio.vue +++ b/packages/hoppscotch-app/src/pages/realtime/socketio.vue @@ -40,7 +40,7 @@ :label="`Client ${version}`" @click=" () => { - onSelectVersion(version) + onSelectVersion(version as SIOClientVersion) hide() } " @@ -107,6 +107,8 @@ @@ -242,7 +244,7 @@ @@ -286,6 +288,7 @@ import { } from "~/newstore/SocketIOSession" import { useColorMode } from "@composables/theming" import RegexWorker from "@workers/regex?worker" +import { LogEntryData } from "~/components/realtime/Log.vue" const t = useI18n() const colorMode = useColorMode() @@ -397,7 +400,7 @@ onMounted(() => { case "DISCONNECTED": addSIOLogLine({ payload: t("state.disconnected_from", { name: url.value }).toString(), - source: "info", + source: "disconnected", color: "#ff5555", ts: event.time, }) diff --git a/packages/hoppscotch-app/src/pages/realtime/sse.vue b/packages/hoppscotch-app/src/pages/realtime/sse.vue index a1de215ef..11b7e1826 100644 --- a/packages/hoppscotch-app/src/pages/realtime/sse.vue +++ b/packages/hoppscotch-app/src/pages/realtime/sse.vue @@ -57,7 +57,7 @@ @@ -88,6 +88,7 @@ import { } from "@composables/stream" import { SSEConnection } from "@helpers/realtime/SSEConnection" import RegexWorker from "@workers/regex?worker" +import { LogEntryData } from "~/components/realtime/Log.vue" const t = useI18n() const toast = useToast() @@ -170,7 +171,7 @@ onMounted(() => { payload: t("state.disconnected_from", { name: server.value, }).toString(), - source: "info", + source: "disconnected", color: "#ff5555", ts: event.time, }) diff --git a/packages/hoppscotch-app/src/pages/realtime/websocket.vue b/packages/hoppscotch-app/src/pages/realtime/websocket.vue index 9095ea13e..14b130359 100644 --- a/packages/hoppscotch-app/src/pages/realtime/websocket.vue +++ b/packages/hoppscotch-app/src/pages/realtime/websocket.vue @@ -48,6 +48,7 @@ > @@ -178,7 +179,7 @@ @@ -220,6 +221,7 @@ import { useToast } from "@composables/toast" import { useColorMode } from "@composables/theming" import { WSConnection, WSErrorMessage } from "@helpers/realtime/WSConnection" import RegexWorker from "@workers/regex?worker" +import { LogEntryData } from "~/components/realtime/Log.vue" const t = useI18n() const toast = useToast() @@ -349,7 +351,7 @@ onMounted(() => { case "DISCONNECTED": addWSLogLine({ payload: t("state.disconnected_from", { name: url.value }).toString(), - source: "info", + source: "disconnected", color: "#ff5555", ts: event.time, }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c2274f1d3..43a6b10dd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6670,7 +6670,7 @@ packages: engines: {node: '>=6'} /escape-string-regexp/1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + resolution: {integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=} engines: {node: '>=0.8.0'} /escape-string-regexp/2.0.0: @@ -7918,7 +7918,7 @@ packages: wrappy: 1.0.2 /inherits/2.0.3: - resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==} + resolution: {integrity: sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=} dev: false /inherits/2.0.4: @@ -9366,7 +9366,7 @@ packages: dev: false /memory-fs/0.3.0: - resolution: {integrity: sha512-QTNXnl79X97kZ9jJk/meJrtDuvgvRakX5LU7HZW1L7MsXHuSTwoMIzN9tOLLH3Xfsj/gbsSqX/ovnsqz246zKQ==} + resolution: {integrity: sha1-e8xrYp46Q+hx1+Kaymrop/FcuyA=} dependencies: errno: 0.1.8 readable-stream: 2.3.7 @@ -9537,7 +9537,7 @@ packages: dev: true /ms/2.0.0: - resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + resolution: {integrity: sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=} dev: false /ms/2.1.2: @@ -10191,7 +10191,7 @@ packages: dev: false /prr/1.0.1: - resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} + resolution: {integrity: sha1-0/wRS6BplaRexok/SEzrHXj19HY=} dev: false /psl/1.9.0: