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 @@
-
+
+
+
+
+
+
+ {{ t("shortcut.request.send_request") }}
+
+
+ {{ t("shortcut.general.show_all") }}
+
+
+ {{ t("shortcut.general.command_menu") }}
+
+
+ {{ t("shortcut.general.help_menu") }}
+
+
+
+
+ {{ getSpecialKey() }}
+ ↩
+
+
+ {{ getSpecialKey() }}
+ K
+
+
+ /
+
+
+ ?
+
+
+
+
+
@@ -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 @@
+
+
+
+
+
+
+ {{ t("mqtt.qos") }}
+
+
+
+
+
+
+
+ {
+ QoS = item
+ hide()
+ }
+ "
+ />
+
+
+
+
+
+
+ {{ t("mqtt.color") }}
+
+
+
+
+
+
+
+ {{ t("action.label") }}
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
-
-
-
+
+
+
-
-
-
- {{ t("mqtt.topic") }}
-
-
-
-
-
-
-
- {{ t("mqtt.communication") }}
-
-
-
-
-
-
-
+
-
- {{ t("mqtt.topic") }}
-
+
+
+
+
+
+
+
+
+
+
-
-
+
![]()
-
+ {{ t("empty.subscription") }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ topic.name }}
+
+
+
+
+
+
+
+
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: