realtime persistence. (#1952)

* feat: websocket persistence

* feat: socketio persistence

* feat: sse persistence

* feat: mqtt persistence

* fix: added types

* fix: typescript issues

Co-authored-by: Andrew Bastin <andrewbastin.k@gmail.com>
This commit is contained in:
0xc0Der
2021-12-02 16:50:24 +02:00
committed by GitHub
parent a508909471
commit cc81242294
10 changed files with 1124 additions and 73 deletions

View File

@@ -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()
)

View File

@@ -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()
)

View File

@@ -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()
)

View File

@@ -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()
)

View File

@@ -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()
}
/**