diff --git a/packages/hoppscotch-app/components/realtime/Mqtt.vue b/packages/hoppscotch-app/components/realtime/Mqtt.vue index 968c6f987..613b10eda 100644 --- a/packages/hoppscotch-app/components/realtime/Mqtt.vue +++ b/packages/hoppscotch-app/components/realtime/Mqtt.vue @@ -161,6 +161,22 @@ import debounce from "lodash/debounce" import { logHoppRequestRunToAnalytics } from "~/helpers/fb/analytics" import { useSetting } from "~/newstore/settings" import useWindowSize from "~/helpers/utils/useWindowSize" +import { + MQTTEndpoint$, + setMQTTEndpoint, + MQTTConnectingState$, + MQTTConnectionState$, + setMQTTConnectingState, + setMQTTConnectionState, + MQTTSubscriptionState$, + setMQTTSubscriptionState, + MQTTSocket$, + setMQTTSocket, + MQTTLog$, + setMQTTLog, + addMQTTLogLine, +} from "~/newstore/MQTTSession" +import { useStream } from "~/helpers/utils/composables" export default defineComponent({ components: { Splitpanes, Pane }, @@ -170,21 +186,33 @@ export default defineComponent({ SIDEBAR: useSetting("SIDEBAR"), COLUMN_LAYOUT: useSetting("COLUMN_LAYOUT"), SIDEBAR_ON_LEFT: useSetting("SIDEBAR_ON_LEFT"), + url: useStream(MQTTEndpoint$, "", setMQTTEndpoint), + connectionState: useStream( + MQTTConnectionState$, + false, + setMQTTConnectionState + ), + connectingState: useStream( + MQTTConnectingState$, + false, + setMQTTConnectingState + ), + subscriptionState: useStream( + MQTTSubscriptionState$, + false, + setMQTTSubscriptionState + ), + log: useStream(MQTTLog$, null, setMQTTLog), + client: useStream(MQTTSocket$, null, setMQTTSocket), } }, data() { return { - url: "wss://test.mosquitto.org:8081", isUrlValid: true, - client: null, pub_topic: "", sub_topic: "", msg: "", - connectionState: false, - connectingState: false, - log: null, manualDisconnect: false, - subscriptionState: false, username: "", password: "", } @@ -261,7 +289,7 @@ export default defineComponent({ onConnectionFailure() { this.connectingState = false this.connectionState = false - this.log.push({ + addMQTTLogLine({ payload: this.$t("error.something_went_wrong"), source: "info", color: "#ff5555", @@ -271,7 +299,7 @@ export default defineComponent({ onConnectionSuccess() { this.connectingState = false this.connectionState = true - this.log.push({ + addMQTTLogLine({ payload: this.$t("state.connected_to", { name: this.url }), source: "info", color: "var(--accent-color)", @@ -280,7 +308,7 @@ export default defineComponent({ this.$toast.success(this.$t("state.connected")) }, onMessageArrived({ payloadString, destinationName }) { - this.log.push({ + addMQTTLogLine({ payload: `Message: ${payloadString} arrived on topic: ${destinationName}`, source: "info", color: "var(--accent-color)", @@ -297,7 +325,7 @@ export default defineComponent({ disconnect() { this.manualDisconnect = true this.client.disconnect() - this.log.push({ + addMQTTLogLine({ payload: this.$t("state.disconnected_from", { name: this.url }), source: "info", color: "#ff5555", @@ -318,14 +346,14 @@ export default defineComponent({ publish() { try { this.client.publish(this.pub_topic, this.msg, 0, false) - this.log.push({ + addMQTTLogLine({ payload: `Published message: ${this.msg} to topic: ${this.pub_topic}`, ts: new Date().toLocaleTimeString(), source: "info", color: "var(--accent-color)", }) } catch (e) { - this.log.push({ + addMQTTLogLine({ payload: this.$t("error.something_went_wrong") + `while publishing msg: ${this.msg} to topic: ${this.pub_topic}`, @@ -349,7 +377,7 @@ export default defineComponent({ onFailure: this.usubFailure, }) } catch (e) { - this.log.push({ + addMQTTLogLine({ payload: this.$t("error.something_went_wrong") + `while subscribing to topic: ${this.sub_topic}`, @@ -361,7 +389,7 @@ export default defineComponent({ }, usubSuccess() { this.subscriptionState = !this.subscriptionState - this.log.push({ + addMQTTLogLine({ payload: `Successfully ` + (this.subscriptionState ? "subscribed" : "unsubscribed") + @@ -372,7 +400,7 @@ export default defineComponent({ }) }, usubFailure() { - this.log.push({ + addMQTTLogLine({ payload: `Failed to ` + (this.subscriptionState ? "unsubscribe" : "subscribe") + diff --git a/packages/hoppscotch-app/components/realtime/Socketio.vue b/packages/hoppscotch-app/components/realtime/Socketio.vue index b6405bb53..14a81d543 100644 --- a/packages/hoppscotch-app/components/realtime/Socketio.vue +++ b/packages/hoppscotch-app/components/realtime/Socketio.vue @@ -89,7 +89,7 @@ class="hide-scrollbar !overflow-auto" > - + @@ -188,6 +188,24 @@ import debounce from "lodash/debounce" import { logHoppRequestRunToAnalytics } from "~/helpers/fb/analytics" import { useSetting } from "~/newstore/settings" import useWindowSize from "~/helpers/utils/useWindowSize" +import { + SIOEndpoint$, + setSIOEndpoint, + SIOVersion$, + setSIOVersion, + SIOPath$, + setSIOPath, + SIOConnectionState$, + SIOConnectingState$, + setSIOConnectionState, + setSIOConnectingState, + SIOSocket$, + setSIOSocket, + SIOLog$, + setSIOLog, + addSIOLogLine, +} from "~/newstore/SocketIOSession" +import { useStream } from "~/helpers/utils/composables" const socketIoClients = { v4: ClientV4, @@ -204,20 +222,27 @@ export default defineComponent({ COLUMN_LAYOUT: useSetting("COLUMN_LAYOUT"), SIDEBAR_ON_LEFT: useSetting("SIDEBAR_ON_LEFT"), socketIoClients, + url: useStream(SIOEndpoint$, "", setSIOEndpoint), + clientVersion: useStream(SIOVersion$, "", setSIOVersion), + path: useStream(SIOPath$, "", setSIOPath), + connectingState: useStream( + SIOConnectingState$, + false, + setSIOConnectingState + ), + connectionState: useStream( + SIOConnectionState$, + false, + setSIOConnectionState + ), + io: useStream(SIOSocket$, null, setSIOSocket), + log: useStream(SIOLog$, [], setSIOLog), } }, data() { return { - // default version is set to v4 - clientVersion: "v4", - url: "wss://hoppscotch-socketio.herokuapp.com", - path: "/socket.io", isUrlValid: true, - connectingState: false, - connectionState: false, - io: null, communication: { - log: null, eventName: "", inputs: [""], }, @@ -267,7 +292,7 @@ export default defineComponent({ }, connect() { this.connectingState = true - this.communication.log = [ + this.log = [ { payload: this.$t("state.connecting_to", { name: this.url }), source: "info", @@ -286,7 +311,7 @@ export default defineComponent({ this.io.on("connect", () => { this.connectingState = false this.connectionState = true - this.communication.log = [ + this.log = [ { payload: this.$t("state.connected_to", { name: this.url }), source: "info", @@ -298,7 +323,7 @@ export default defineComponent({ }) this.io.on("*", ({ data }) => { const [eventName, message] = data - this.communication.log.push({ + addSIOLogLine({ payload: `[${eventName}] ${message ? JSON.stringify(message) : ""}`, source: "server", ts: new Date().toLocaleTimeString(), @@ -316,7 +341,7 @@ export default defineComponent({ this.io.on("disconnect", () => { this.connectingState = false this.connectionState = false - this.communication.log.push({ + addSIOLogLine({ payload: this.$t("state.disconnected_from", { name: this.url }), source: "info", color: "#ff5555", @@ -340,14 +365,14 @@ export default defineComponent({ this.disconnect() this.connectingState = false this.connectionState = false - this.communication.log.push({ + addSIOLogLine({ payload: this.$t("error.something_went_wrong"), source: "info", color: "#ff5555", ts: new Date().toLocaleTimeString(), }) if (error !== null) - this.communication.log.push({ + addSIOLogLine({ payload: error, source: "info", color: "#ff5555", @@ -369,14 +394,14 @@ export default defineComponent({ if (this.io) { this.io.emit(eventName, ...messages, (data) => { // receive response from server - this.communication.log.push({ + addSIOLogLine({ payload: `[${eventName}] ${JSON.stringify(data)}`, source: "server", ts: new Date().toLocaleTimeString(), }) }) - this.communication.log.push({ + addSIOLogLine({ payload: `[${eventName}] ${JSON.stringify(messages)}`, source: "client", ts: new Date().toLocaleTimeString(), diff --git a/packages/hoppscotch-app/components/realtime/Sse.vue b/packages/hoppscotch-app/components/realtime/Sse.vue index 80ff715b6..9a882a184 100644 --- a/packages/hoppscotch-app/components/realtime/Sse.vue +++ b/packages/hoppscotch-app/components/realtime/Sse.vue @@ -48,7 +48,7 @@ @@ -64,26 +64,47 @@ import "splitpanes/dist/splitpanes.css" import debounce from "lodash/debounce" import { logHoppRequestRunToAnalytics } from "~/helpers/fb/analytics" import { useSetting } from "~/newstore/settings" +import { + SSEEndpoint$, + setSSEEndpoint, + SSEEventType$, + setSSEEventType, + SSESocket$, + setSSESocket, + SSEConnectingState$, + SSEConnectionState$, + setSSEConnectionState, + setSSEConnectingState, + SSELog$, + setSSELog, + addSSELogLine, +} from "~/newstore/SSESession" +import { useStream } from "~/helpers/utils/composables" export default defineComponent({ components: { Splitpanes, Pane }, setup() { return { COLUMN_LAYOUT: useSetting("COLUMN_LAYOUT"), + connectionSSEState: useStream( + SSEConnectionState$, + false, + setSSEConnectionState + ), + connectingState: useStream( + SSEConnectingState$, + false, + setSSEConnectingState + ), + server: useStream(SSEEndpoint$, "", setSSEEndpoint), + eventType: useStream(SSEEventType$, "", setSSEEventType), + sse: useStream(SSESocket$, null, setSSESocket), + log: useStream(SSELog$, [], setSSELog), } }, data() { return { - connectionSSEState: false, - connectingState: false, - server: "https://express-eventsource.herokuapp.com/events", isUrlValid: true, - sse: null, - events: { - log: null, - input: "", - }, - eventType: "data", } }, computed: { @@ -120,7 +141,7 @@ export default defineComponent({ }, start() { this.connectingState = true - this.events.log = [ + this.log = [ { payload: this.$t("state.connecting_to", { name: this.server }), source: "info", @@ -133,7 +154,7 @@ export default defineComponent({ this.sse.onopen = () => { this.connectingState = false this.connectionSSEState = true - this.events.log = [ + this.log = [ { payload: this.$t("state.connected_to", { name: this.server }), source: "info", @@ -148,7 +169,7 @@ export default defineComponent({ } this.sse.onclose = () => { this.connectionSSEState = false - this.events.log.push({ + addSSELogLine({ payload: this.$t("state.disconnected_from", { name: this.server, }), @@ -159,7 +180,7 @@ export default defineComponent({ this.$toast.error(this.$t("state.disconnected")) } this.sse.addEventListener(this.eventType, ({ data }) => { - this.events.log.push({ + addSSELogLine({ payload: data, source: "server", ts: new Date().toLocaleTimeString(), @@ -170,7 +191,7 @@ export default defineComponent({ this.$toast.error(this.$t("error.something_went_wrong")) } } else { - this.events.log = [ + this.log = [ { payload: this.$t("error.browser_support_sse"), source: "info", @@ -187,14 +208,14 @@ export default defineComponent({ handleSSEError(error) { this.stop() this.connectionSSEState = false - this.events.log.push({ + addSSELogLine({ payload: this.$t("error.something_went_wrong"), source: "info", color: "#ff5555", ts: new Date().toLocaleTimeString(), }) if (error !== null) - this.events.log.push({ + addSSELogLine({ payload: error, source: "info", color: "#ff5555", diff --git a/packages/hoppscotch-app/components/realtime/Websocket.vue b/packages/hoppscotch-app/components/realtime/Websocket.vue index 1be933a5f..7aeb841cf 100644 --- a/packages/hoppscotch-app/components/realtime/Websocket.vue +++ b/packages/hoppscotch-app/components/realtime/Websocket.vue @@ -76,6 +76,12 @@ name="message" type="text" autocomplete="off" + @change=" + updateProtocol(index, { + value: $event.target.value, + active: protocol.active, + }) + " /> @@ -133,10 +140,7 @@ class="hide-scrollbar !overflow-auto" > - + @@ -191,6 +195,26 @@ import debounce from "lodash/debounce" import { logHoppRequestRunToAnalytics } from "~/helpers/fb/analytics" import useWindowSize from "~/helpers/utils/useWindowSize" import { useSetting } from "~/newstore/settings" +import { + setWSEndpoint, + WSEndpoint$, + WSProtocols$, + setWSProtocols, + addWSProtocol, + deleteWSProtocol, + updateWSProtocol, + deleteAllWSProtocols, + WSSocket$, + setWSSocket, + setWSConnectionState, + setWSConnectingState, + WSConnectionState$, + WSConnectingState$, + addWSLogLine, + WSLog$, + setWSLog, +} from "~/newstore/WebSocketSession" +import { useStream } from "~/helpers/utils/composables" export default defineComponent({ components: { Splitpanes, Pane }, @@ -200,21 +224,29 @@ export default defineComponent({ SIDEBAR: useSetting("SIDEBAR"), COLUMN_LAYOUT: useSetting("COLUMN_LAYOUT"), SIDEBAR_ON_LEFT: useSetting("SIDEBAR_ON_LEFT"), + url: useStream(WSEndpoint$, "", setWSEndpoint), + protocols: useStream(WSProtocols$, [], setWSProtocols), + connectionState: useStream( + WSConnectionState$, + false, + setWSConnectionState + ), + connectingState: useStream( + WSConnectingState$, + false, + setWSConnectingState + ), + socket: useStream(WSSocket$, null, setWSSocket), + log: useStream(WSLog$, [], setWSLog), } }, data() { return { - connectionState: false, - connectingState: false, - url: "wss://hoppscotch-websocket.herokuapp.com", isUrlValid: true, - socket: null, communication: { - log: null, input: "", }, currentIndex: -1, // index of the message log array to put in input box - protocols: [], activeProtocols: [], } }, @@ -251,7 +283,7 @@ export default defineComponent({ }, methods: { clearContent() { - this.protocols = [] + deleteAllWSProtocols() }, debouncer: debounce(function () { this.worker.postMessage({ type: "ws", url: this.url }) @@ -266,7 +298,7 @@ export default defineComponent({ else return this.disconnect() }, connect() { - this.communication.log = [ + this.log = [ { payload: this.$t("state.connecting_to", { name: this.url }), source: "info", @@ -279,7 +311,7 @@ export default defineComponent({ this.socket.onopen = () => { this.connectingState = false this.connectionState = true - this.communication.log = [ + this.log = [ { payload: this.$t("state.connected_to", { name: this.url }), source: "info", @@ -294,7 +326,7 @@ export default defineComponent({ } this.socket.onclose = () => { this.connectionState = false - this.communication.log.push({ + addWSLogLine({ payload: this.$t("state.disconnected_from", { name: this.url }), source: "info", color: "#ff5555", @@ -303,7 +335,7 @@ export default defineComponent({ this.$toast.error(this.$t("state.disconnected")) } this.socket.onmessage = ({ data }) => { - this.communication.log.push({ + addWSLogLine({ payload: data, source: "server", ts: new Date().toLocaleTimeString(), @@ -328,14 +360,14 @@ export default defineComponent({ handleError(error) { this.disconnect() this.connectionState = false - this.communication.log.push({ + addWSLogLine({ payload: this.$t("error.something_went_wrong"), source: "info", color: "#ff5555", ts: new Date().toLocaleTimeString(), }) if (error !== null) - this.communication.log.push({ + addWSLogLine({ payload: error, source: "info", color: "#ff5555", @@ -345,7 +377,7 @@ export default defineComponent({ sendMessage() { const message = this.communication.input this.socket.send(message) - this.communication.log.push({ + addWSLogLine({ payload: message, source: "client", ts: new Date().toLocaleTimeString(), @@ -353,7 +385,7 @@ export default defineComponent({ this.communication.input = "" }, walkHistory(direction) { - const clientMessages = this.communication.log.filter( + const clientMessages = this.log.filter( ({ source }) => source === "client" ) const length = clientMessages.length @@ -389,11 +421,11 @@ export default defineComponent({ } }, addProtocol() { - this.protocols.push({ value: "", active: true }) + addWSProtocol({ value: "", active: true }) }, deleteProtocol({ index }) { const oldProtocols = this.protocols.slice() - this.$delete(this.protocols, index) + deleteWSProtocol(index) this.$toast.success(this.$t("state.deleted"), { action: { text: this.$t("action.undo"), @@ -405,6 +437,9 @@ export default defineComponent({ }, }) }, + updateProtocol(index, updated) { + updateWSProtocol(index, updated) + }, }, }) diff --git a/packages/hoppscotch-app/helpers/types/HoppRealtimeLog.ts b/packages/hoppscotch-app/helpers/types/HoppRealtimeLog.ts new file mode 100644 index 000000000..4132a487d --- /dev/null +++ b/packages/hoppscotch-app/helpers/types/HoppRealtimeLog.ts @@ -0,0 +1,8 @@ +export type HoppRealtimeLogLine = { + payload: string + source: string + color?: string + ts: string +} + +export type HoppRealtimeLog = HoppRealtimeLogLine[] diff --git a/packages/hoppscotch-app/newstore/MQTTSession.ts b/packages/hoppscotch-app/newstore/MQTTSession.ts new file mode 100644 index 000000000..e67382709 --- /dev/null +++ b/packages/hoppscotch-app/newstore/MQTTSession.ts @@ -0,0 +1,190 @@ +import { pluck, distinctUntilChanged } from "rxjs/operators" +import { Client as MQTTClient } from "paho-mqtt" +import DispatchingStore, { defineDispatchers } from "./DispatchingStore" +import { + HoppRealtimeLog, + HoppRealtimeLogLine, +} from "~/helpers/types/HoppRealtimeLog" + +type HoppMQTTRequest = { + endpoint: string +} + +type HoppMQTTSession = { + request: HoppMQTTRequest + connectingState: boolean + connectionState: boolean + subscriptionState: boolean + log: HoppRealtimeLog + socket: MQTTClient | null +} + +const defaultMQTTRequest: HoppMQTTRequest = { + endpoint: "wss://test.mosquitto.org:8081", +} + +const defaultMQTTSession: HoppMQTTSession = { + request: defaultMQTTRequest, + connectionState: false, + connectingState: false, + subscriptionState: false, + socket: null, + log: [], +} + +const dispatchers = defineDispatchers({ + setRequest( + _: HoppMQTTSession, + { newRequest }: { newRequest: HoppMQTTRequest } + ) { + return { + request: newRequest, + } + }, + setEndpoint(_: HoppMQTTSession, { newEndpoint }: { newEndpoint: string }) { + return { + request: { + endpoint: newEndpoint, + }, + } + }, + setSocket(_: HoppMQTTSession, { socket }: { socket: MQTTClient }) { + return { + socket, + } + }, + setConnectionState(_: HoppMQTTSession, { state }: { state: boolean }) { + return { + connectionState: state, + } + }, + setConnectingState(_: HoppMQTTSession, { state }: { state: boolean }) { + return { + connectingState: state, + } + }, + setSubscriptionState(_: HoppMQTTSession, { state }: { state: boolean }) { + return { + subscriptionState: state, + } + }, + setLog(_: HoppMQTTSession, { log }: { log: HoppRealtimeLog }) { + return { + log, + } + }, + addLogLine(curr: HoppMQTTSession, { line }: { line: HoppRealtimeLogLine }) { + return { + log: [...curr.log, line], + } + }, +}) + +const MQTTSessionStore = new DispatchingStore(defaultMQTTSession, dispatchers) + +export function setMQTTRequest(newRequest?: HoppMQTTRequest) { + MQTTSessionStore.dispatch({ + dispatcher: "setRequest", + payload: { + newRequest: newRequest ?? defaultMQTTRequest, + }, + }) +} + +export function setMQTTEndpoint(newEndpoint: string) { + MQTTSessionStore.dispatch({ + dispatcher: "setEndpoint", + payload: { + newEndpoint, + }, + }) +} + +export function setMQTTSocket(socket: MQTTClient) { + MQTTSessionStore.dispatch({ + dispatcher: "setSocket", + payload: { + socket, + }, + }) +} + +export function setMQTTConnectionState(state: boolean) { + MQTTSessionStore.dispatch({ + dispatcher: "setConnectionState", + payload: { + state, + }, + }) +} + +export function setMQTTConnectingState(state: boolean) { + MQTTSessionStore.dispatch({ + dispatcher: "setConnectingState", + payload: { + state, + }, + }) +} + +export function setMQTTSubscriptionState(state: boolean) { + MQTTSessionStore.dispatch({ + dispatcher: "setSubscriptionState", + payload: { + state, + }, + }) +} + +export function setMQTTLog(log: HoppRealtimeLog) { + MQTTSessionStore.dispatch({ + dispatcher: "setLog", + payload: { + log, + }, + }) +} + +export function addMQTTLogLine(line: HoppRealtimeLogLine) { + MQTTSessionStore.dispatch({ + dispatcher: "addLogLine", + payload: { + line, + }, + }) +} + +export const MQTTRequest$ = MQTTSessionStore.subject$.pipe( + pluck("request"), + distinctUntilChanged() +) + +export const MQTTEndpoint$ = MQTTSessionStore.subject$.pipe( + pluck("request", "endpoint"), + distinctUntilChanged() +) + +export const MQTTConnectingState$ = MQTTSessionStore.subject$.pipe( + pluck("connectingState"), + distinctUntilChanged() +) + +export const MQTTConnectionState$ = MQTTSessionStore.subject$.pipe( + pluck("connectionState"), + distinctUntilChanged() +) + +export const MQTTSubscriptionState$ = MQTTSessionStore.subject$.pipe( + pluck("subscriptionState"), + distinctUntilChanged() +) + +export const MQTTSocket$ = MQTTSessionStore.subject$.pipe( + pluck("socket"), + distinctUntilChanged() +) + +export const MQTTLog$ = MQTTSessionStore.subject$.pipe( + pluck("log"), + distinctUntilChanged() +) diff --git a/packages/hoppscotch-app/newstore/SSESession.ts b/packages/hoppscotch-app/newstore/SSESession.ts new file mode 100644 index 000000000..fec8770d7 --- /dev/null +++ b/packages/hoppscotch-app/newstore/SSESession.ts @@ -0,0 +1,192 @@ +import { pluck, distinctUntilChanged } from "rxjs/operators" +import DispatchingStore, { defineDispatchers } from "./DispatchingStore" +import { + HoppRealtimeLog, + HoppRealtimeLogLine, +} from "~/helpers/types/HoppRealtimeLog" + +type HoppSSERequest = { + endpoint: string + eventType: string +} + +type HoppSSESession = { + request: HoppSSERequest + connectingState: boolean + connectionState: boolean + log: HoppRealtimeLog + socket: EventSource | null +} + +const defaultSSERequest: HoppSSERequest = { + endpoint: "https://express-eventsource.herokuapp.com/events", + eventType: "data", +} + +const defaultSSESession: HoppSSESession = { + request: defaultSSERequest, + connectionState: false, + connectingState: false, + socket: null, + log: [], +} + +const dispatchers = defineDispatchers({ + setRequest( + _: HoppSSESession, + { newRequest }: { newRequest: HoppSSERequest } + ) { + return { + request: newRequest, + } + }, + setEndpoint(curr: HoppSSESession, { newEndpoint }: { newEndpoint: string }) { + return { + request: { + eventType: curr.request.eventType, + endpoint: newEndpoint, + }, + } + }, + setEventType(curr: HoppSSESession, { newType }: { newType: string }) { + return { + request: { + endpoint: curr.request.endpoint, + eventType: newType, + }, + } + }, + setSocket(_: HoppSSESession, { socket }: { socket: EventSource }) { + return { + socket, + } + }, + setConnectionState(_: HoppSSESession, { state }: { state: boolean }) { + return { + connectionState: state, + } + }, + setConnectingState(_: HoppSSESession, { state }: { state: boolean }) { + return { + connectingState: state, + } + }, + setLog(_: HoppSSESession, { log }: { log: HoppRealtimeLog }) { + return { + log, + } + }, + addLogLine(curr: HoppSSESession, { line }: { line: HoppRealtimeLogLine }) { + return { + log: [...curr.log, line], + } + }, +}) + +const SSESessionStore = new DispatchingStore(defaultSSESession, dispatchers) + +export function setSSERequest(newRequest?: HoppSSERequest) { + SSESessionStore.dispatch({ + dispatcher: "setRequest", + payload: { + newRequest: newRequest ?? defaultSSERequest, + }, + }) +} + +export function setSSEEndpoint(newEndpoint: string) { + SSESessionStore.dispatch({ + dispatcher: "setEndpoint", + payload: { + newEndpoint, + }, + }) +} + +export function setSSEEventType(newType: string) { + SSESessionStore.dispatch({ + dispatcher: "setEventType", + payload: { + newType, + }, + }) +} + +export function setSSESocket(socket: EventSource) { + SSESessionStore.dispatch({ + dispatcher: "setSocket", + payload: { + socket, + }, + }) +} + +export function setSSEConnectionState(state: boolean) { + SSESessionStore.dispatch({ + dispatcher: "setConnectionState", + payload: { + state, + }, + }) +} +export function setSSEConnectingState(state: boolean) { + SSESessionStore.dispatch({ + dispatcher: "setConnectingState", + payload: { + state, + }, + }) +} + +export function setSSELog(log: HoppRealtimeLog) { + SSESessionStore.dispatch({ + dispatcher: "setLog", + payload: { + log, + }, + }) +} + +export function addSSELogLine(line: HoppRealtimeLogLine) { + SSESessionStore.dispatch({ + dispatcher: "addLogLine", + payload: { + line, + }, + }) +} + +export const SSERequest$ = SSESessionStore.subject$.pipe( + pluck("request"), + distinctUntilChanged() +) + +export const SSEEndpoint$ = SSESessionStore.subject$.pipe( + pluck("request", "endpoint"), + distinctUntilChanged() +) + +export const SSEEventType$ = SSESessionStore.subject$.pipe( + pluck("request", "eventType"), + distinctUntilChanged() +) + +export const SSEConnectingState$ = SSESessionStore.subject$.pipe( + pluck("connectingState"), + distinctUntilChanged() +) + +export const SSEConnectionState$ = SSESessionStore.subject$.pipe( + pluck("connectionState"), + distinctUntilChanged() +) + +export const SSESocket$ = SSESessionStore.subject$.pipe( + pluck("socket"), + distinctUntilChanged() +) + +export const SSELog$ = SSESessionStore.subject$.pipe( + pluck("log"), + distinctUntilChanged() +) diff --git a/packages/hoppscotch-app/newstore/SocketIOSession.ts b/packages/hoppscotch-app/newstore/SocketIOSession.ts new file mode 100644 index 000000000..6d7240487 --- /dev/null +++ b/packages/hoppscotch-app/newstore/SocketIOSession.ts @@ -0,0 +1,221 @@ +import { pluck, distinctUntilChanged } from "rxjs/operators" +import { Socket as SocketV2 } from "socket.io-client-v2" +import { Socket as SocketV3 } from "socket.io-client-v3" +import { Socket as SocketV4 } from "socket.io-client-v4" +import DispatchingStore, { defineDispatchers } from "./DispatchingStore" +import { + HoppRealtimeLog, + HoppRealtimeLogLine, +} from "~/helpers/types/HoppRealtimeLog" + +type SocketIO = SocketV2 | SocketV3 | SocketV4 + +type HoppSIORequest = { + endpoint: string + path: string + version: string +} + +type HoppSIOSession = { + request: HoppSIORequest + connectingState: boolean + connectionState: boolean + log: HoppRealtimeLog + socket: SocketIO | null +} + +const defaultSIORequest: HoppSIORequest = { + endpoint: "wss://hoppscotch-socketio.herokuapp.com", + path: "/socket.io", + version: "v4", +} + +const defaultSIOSession: HoppSIOSession = { + request: defaultSIORequest, + connectionState: false, + connectingState: false, + socket: null, + log: [], +} + +const dispatchers = defineDispatchers({ + setRequest( + _: HoppSIOSession, + { newRequest }: { newRequest: HoppSIORequest } + ) { + return { + request: newRequest, + } + }, + setEndpoint(curr: HoppSIOSession, { newEndpoint }: { newEndpoint: string }) { + return { + request: { + ...curr.request, + endpoint: newEndpoint, + }, + } + }, + setPath(curr: HoppSIOSession, { newPath }: { newPath: string }) { + return { + request: { + ...curr.request, + path: newPath, + }, + } + }, + setVersion(curr: HoppSIOSession, { newVersion }: { newVersion: string }) { + return { + request: { + ...curr.request, + version: newVersion, + }, + } + }, + setSocket(_: HoppSIOSession, { socket }: { socket: SocketIO }) { + return { + socket, + } + }, + setConnectionState(_: HoppSIOSession, { state }: { state: boolean }) { + return { + connectionState: state, + } + }, + setConnectingState(_: HoppSIOSession, { state }: { state: boolean }) { + return { + connectingState: state, + } + }, + setLog(_: HoppSIOSession, { log }: { log: HoppRealtimeLog }) { + return { + log, + } + }, + addLogLine(curr: HoppSIOSession, { line }: { line: HoppRealtimeLogLine }) { + return { + log: [...curr.log, line], + } + }, +}) + +const SIOSessionStore = new DispatchingStore(defaultSIOSession, dispatchers) + +export function setSIORequest(newRequest?: HoppSIORequest) { + SIOSessionStore.dispatch({ + dispatcher: "setRequest", + payload: { + newRequest: newRequest ?? defaultSIORequest, + }, + }) +} + +export function setSIOEndpoint(newEndpoint: string) { + SIOSessionStore.dispatch({ + dispatcher: "setEndpoint", + payload: { + newEndpoint, + }, + }) +} + +export function setSIOVersion(newVersion: string) { + SIOSessionStore.dispatch({ + dispatcher: "setVersion", + payload: { + newVersion, + }, + }) +} + +export function setSIOPath(newPath: string) { + SIOSessionStore.dispatch({ + dispatcher: "setPath", + payload: { + newPath, + }, + }) +} + +export function setSIOSocket(socket: SocketIO) { + SIOSessionStore.dispatch({ + dispatcher: "setSocket", + payload: { + socket, + }, + }) +} + +export function setSIOConnectionState(state: boolean) { + SIOSessionStore.dispatch({ + dispatcher: "setConnectionState", + payload: { + state, + }, + }) +} +export function setSIOConnectingState(state: boolean) { + SIOSessionStore.dispatch({ + dispatcher: "setConnectingState", + payload: { + state, + }, + }) +} + +export function setSIOLog(log: HoppRealtimeLog) { + SIOSessionStore.dispatch({ + dispatcher: "setLog", + payload: { + log, + }, + }) +} + +export function addSIOLogLine(line: HoppRealtimeLogLine) { + SIOSessionStore.dispatch({ + dispatcher: "addLogLine", + payload: { + line, + }, + }) +} + +export const SIORequest$ = SIOSessionStore.subject$.pipe( + pluck("request"), + distinctUntilChanged() +) + +export const SIOEndpoint$ = SIOSessionStore.subject$.pipe( + pluck("request", "endpoint"), + distinctUntilChanged() +) + +export const SIOVersion$ = SIOSessionStore.subject$.pipe( + pluck("request", "version"), + distinctUntilChanged() +) + +export const SIOPath$ = SIOSessionStore.subject$.pipe( + pluck("request", "path"), + distinctUntilChanged() +) + +export const SIOConnectingState$ = SIOSessionStore.subject$.pipe( + pluck("connectingState"), + distinctUntilChanged() +) + +export const SIOConnectionState$ = SIOSessionStore.subject$.pipe( + pluck("connectionState"), + distinctUntilChanged() +) + +export const SIOSocket$ = SIOSessionStore.subject$.pipe( + pluck("socket"), + distinctUntilChanged() +) + +export const SIOLog$ = SIOSessionStore.subject$.pipe( + pluck("log"), + distinctUntilChanged() +) diff --git a/packages/hoppscotch-app/newstore/WebSocketSession.ts b/packages/hoppscotch-app/newstore/WebSocketSession.ts new file mode 100644 index 000000000..87e1c3232 --- /dev/null +++ b/packages/hoppscotch-app/newstore/WebSocketSession.ts @@ -0,0 +1,275 @@ +import { pluck, distinctUntilChanged } from "rxjs/operators" +import DispatchingStore, { defineDispatchers } from "./DispatchingStore" +import { + HoppRealtimeLog, + HoppRealtimeLogLine, +} from "~/helpers/types/HoppRealtimeLog" + +type HoppWSProtocol = { + value: string + active: boolean +} + +type HoppWSRequest = { + endpoint: string + protocols: HoppWSProtocol[] +} + +export type HoppWSSession = { + request: HoppWSRequest + connectingState: boolean + connectionState: boolean + log: HoppRealtimeLog + socket: WebSocket | null +} + +const defaultWSRequest: HoppWSRequest = { + endpoint: "wss://hoppscotch-websocket.herokuapp.com", + protocols: [], +} + +const defaultWSSession: HoppWSSession = { + request: defaultWSRequest, + connectionState: false, + connectingState: false, + socket: null, + log: [], +} + +const dispatchers = defineDispatchers({ + setRequest(_: HoppWSSession, { newRequest }: { newRequest: HoppWSRequest }) { + return { + request: newRequest, + } + }, + setEndpoint(curr: HoppWSSession, { newEndpoint }: { newEndpoint: string }) { + return { + request: { + protocols: curr.request.protocols, + endpoint: newEndpoint, + }, + } + }, + setProtocols( + curr: HoppWSSession, + { protocols }: { protocols: HoppWSProtocol[] } + ) { + return { + request: { + protocols, + endpoint: curr.request.endpoint, + }, + } + }, + addProtocol(curr: HoppWSSession, { protocol }: { protocol: HoppWSProtocol }) { + return { + request: { + endpoint: curr.request.endpoint, + protocols: [...curr.request.protocols, protocol], + }, + } + }, + deleteProtocol(curr: HoppWSSession, { index }: { index: number }) { + return { + request: { + endpoint: curr.request.endpoint, + protocols: curr.request.protocols.filter((_, idx) => index !== idx), + }, + } + }, + deleteAllProtocols(curr: HoppWSSession) { + return { + request: { + endpoint: curr.request.endpoint, + protocols: [], + }, + } + }, + updateProtocol( + curr: HoppWSSession, + { + index, + updatedProtocol, + }: { index: number; updatedProtocol: HoppWSProtocol } + ) { + return { + request: { + endpoint: curr.request.endpoint, + protocols: curr.request.protocols.map((proto, idx) => { + return index === idx ? updatedProtocol : proto + }), + }, + } + }, + setSocket(_: HoppWSSession, { socket }: { socket: WebSocket }) { + return { + socket, + } + }, + setConnectionState(_: HoppWSSession, { state }: { state: boolean }) { + return { + connectionState: state, + } + }, + setConnectingState(_: HoppWSSession, { state }: { state: boolean }) { + return { + connectingState: state, + } + }, + setLog(_: HoppWSSession, { log }: { log: HoppRealtimeLog }) { + return { + log, + } + }, + addLogLine(curr: HoppWSSession, { line }: { line: HoppRealtimeLogLine }) { + return { + log: [...curr.log, line], + } + }, +}) + +const WSSessionStore = new DispatchingStore(defaultWSSession, dispatchers) + +export function setWSRequest(newRequest?: HoppWSRequest) { + WSSessionStore.dispatch({ + dispatcher: "setRequest", + payload: { + newRequest: newRequest ?? defaultWSRequest, + }, + }) +} + +export function setWSEndpoint(newEndpoint: string) { + WSSessionStore.dispatch({ + dispatcher: "setEndpoint", + payload: { + newEndpoint, + }, + }) +} + +export function setWSProtocols(protocols: HoppWSProtocol[]) { + WSSessionStore.dispatch({ + dispatcher: "setProtocols", + payload: { + protocols, + }, + }) +} + +export function addWSProtocol(protocol: HoppWSProtocol) { + WSSessionStore.dispatch({ + dispatcher: "addProtocol", + payload: { + protocol, + }, + }) +} + +export function deleteWSProtocol(index: number) { + WSSessionStore.dispatch({ + dispatcher: "deleteProtocol", + payload: { + index, + }, + }) +} + +export function deleteAllWSProtocols() { + WSSessionStore.dispatch({ + dispatcher: "deleteAllProtocols", + payload: {}, + }) +} + +export function updateWSProtocol( + index: number, + updatedProtocol: HoppWSProtocol +) { + WSSessionStore.dispatch({ + dispatcher: "updateProtocol", + payload: { + index, + updatedProtocol, + }, + }) +} + +export function setWSSocket(socket: WebSocket) { + WSSessionStore.dispatch({ + dispatcher: "setSocket", + payload: { + socket, + }, + }) +} + +export function setWSConnectionState(state: boolean) { + WSSessionStore.dispatch({ + dispatcher: "setConnectionState", + payload: { + state, + }, + }) +} +export function setWSConnectingState(state: boolean) { + WSSessionStore.dispatch({ + dispatcher: "setConnectingState", + payload: { + state, + }, + }) +} + +export function setWSLog(log: HoppRealtimeLog) { + WSSessionStore.dispatch({ + dispatcher: "setLog", + payload: { + log, + }, + }) +} + +export function addWSLogLine(line: HoppRealtimeLogLine) { + WSSessionStore.dispatch({ + dispatcher: "addLogLine", + payload: { + line, + }, + }) +} + +export const WSRequest$ = WSSessionStore.subject$.pipe( + pluck("request"), + distinctUntilChanged() +) + +export const WSEndpoint$ = WSSessionStore.subject$.pipe( + pluck("request", "endpoint"), + distinctUntilChanged() +) + +export const WSProtocols$ = WSSessionStore.subject$.pipe( + pluck("request", "protocols"), + distinctUntilChanged() +) + +export const WSConnectingState$ = WSSessionStore.subject$.pipe( + pluck("connectingState"), + distinctUntilChanged() +) + +export const WSConnectionState$ = WSSessionStore.subject$.pipe( + pluck("connectionState"), + distinctUntilChanged() +) + +export const WSSocket$ = WSSessionStore.subject$.pipe( + pluck("socket"), + distinctUntilChanged() +) + +export const WSLog$ = WSSessionStore.subject$.pipe( + pluck("log"), + distinctUntilChanged() +) diff --git a/packages/hoppscotch-app/newstore/localpersistence.ts b/packages/hoppscotch-app/newstore/localpersistence.ts index 112d008db..58436bc79 100644 --- a/packages/hoppscotch-app/newstore/localpersistence.ts +++ b/packages/hoppscotch-app/newstore/localpersistence.ts @@ -40,6 +40,10 @@ import { setCurrentEnvironment, } from "./environments" import { restRequest$, setRESTRequest } from "./RESTSession" +import { WSRequest$, setWSRequest } from "./WebSocketSession" +import { SIORequest$, setSIORequest } from "./SocketIOSession" +import { SSERequest$, setSSERequest } from "./SSESession" +import { MQTTRequest$, setMQTTRequest } from "./MQTTSession" import { translateToNewRequest } from "~/helpers/types/HoppRESTRequest" function checkAndMigrateOldSettings() { @@ -209,6 +213,54 @@ function setupSelectedEnvPersistence() { }) } +function setupWebsocketPersistence() { + const request = JSON.parse( + window.localStorage.getItem("WebsocketRequest") || "null" + ) + + setWSRequest(request) + + WSRequest$.subscribe((req) => { + window.localStorage.setItem("WebsocketRequest", JSON.stringify(req)) + }) +} + +function setupSocketIOPersistence() { + const request = JSON.parse( + window.localStorage.getItem("SocketIORequest") || "null" + ) + + setSIORequest(request) + + SIORequest$.subscribe((req) => { + window.localStorage.setItem("SocketIORequest", JSON.stringify(req)) + }) +} + +function setupSSEPersistence() { + const request = JSON.parse( + window.localStorage.getItem("SSERequest") || "null" + ) + + setSSERequest(request) + + SSERequest$.subscribe((req) => { + window.localStorage.setItem("SSERequest", JSON.stringify(req)) + }) +} + +function setupMQTTPersistence() { + const request = JSON.parse( + window.localStorage.getItem("MQTTRequest") || "null" + ) + + setMQTTRequest(request) + + MQTTRequest$.subscribe((req) => { + window.localStorage.setItem("MQTTRequest", JSON.stringify(req)) + }) +} + function setupGlobalEnvsPersistence() { const globals: Environment["variables"] = JSON.parse( window.localStorage.getItem("globalEnv") || "[]" @@ -246,6 +298,10 @@ export function setupLocalPersistence() { setupGlobalEnvsPersistence() setupEnvironmentsPersistence() setupSelectedEnvPersistence() + setupWebsocketPersistence() + setupSocketIOPersistence() + setupSSEPersistence() + setupMQTTPersistence() } /**