refactor: move persistence logic into a dedicated service (#3493)
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
import {
|
||||
Environment,
|
||||
HoppCollection,
|
||||
HoppGQLRequest,
|
||||
HoppRESTRequest,
|
||||
} from "@hoppscotch/data"
|
||||
|
||||
import { HoppGQLDocument } from "~/helpers/graphql/document"
|
||||
import { HoppRESTDocument } from "~/helpers/rest/document"
|
||||
import { GQLHistoryEntry, RESTHistoryEntry } from "~/newstore/history"
|
||||
import { SettingsDef, getDefaultSettings } from "~/newstore/settings"
|
||||
import { PersistableTabState } from "~/services/tab"
|
||||
|
||||
type VUEX_DATA = {
|
||||
postwoman: {
|
||||
settings?: SettingsDef
|
||||
collections?: HoppCollection<HoppRESTRequest>[]
|
||||
collectionsGraphql?: HoppCollection<HoppGQLRequest>[]
|
||||
environments?: Environment[]
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS = getDefaultSettings()
|
||||
|
||||
export const REST_COLLECTIONS_MOCK: HoppCollection<HoppRESTRequest>[] = [
|
||||
{
|
||||
v: 1,
|
||||
name: "Echo",
|
||||
folders: [],
|
||||
requests: [
|
||||
{
|
||||
v: "1",
|
||||
endpoint: "https://echo.hoppscotch.io",
|
||||
name: "Echo test",
|
||||
params: [],
|
||||
headers: [],
|
||||
method: "GET",
|
||||
auth: { authType: "none", authActive: true },
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
body: { contentType: null, body: null },
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export const GQL_COLLECTIONS_MOCK: HoppCollection<HoppGQLRequest>[] = [
|
||||
{
|
||||
v: 1,
|
||||
name: "Echo",
|
||||
folders: [],
|
||||
requests: [
|
||||
{
|
||||
v: 2,
|
||||
name: "Echo test",
|
||||
url: "https://echo.hoppscotch.io/graphql",
|
||||
headers: [],
|
||||
variables: '{\n "id": "1"\n}',
|
||||
query: "query Request { url }",
|
||||
auth: { authType: "none", authActive: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export const ENVIRONMENTS_MOCK: Environment[] = [
|
||||
{
|
||||
name: "globals",
|
||||
variables: [
|
||||
{
|
||||
key: "test-global-key",
|
||||
value: "test-global-value",
|
||||
},
|
||||
],
|
||||
},
|
||||
{ name: "Test", variables: [{ key: "test-key", value: "test-value" }] },
|
||||
]
|
||||
|
||||
export const SELECTED_ENV_INDEX_MOCK = {
|
||||
type: "MY_ENV",
|
||||
index: 1,
|
||||
}
|
||||
|
||||
export const WEBSOCKET_REQUEST_MOCK = {
|
||||
endpoint: "wss://echo-websocket.hoppscotch.io",
|
||||
protocols: [],
|
||||
}
|
||||
|
||||
export const SOCKET_IO_REQUEST_MOCK = {
|
||||
endpoint: "wss://echo-socketio.hoppscotch.io",
|
||||
path: "/socket.io",
|
||||
version: "v4",
|
||||
}
|
||||
|
||||
export const SSE_REQUEST_MOCK = {
|
||||
endpoint: "https://express-eventsource.herokuapp.com/events",
|
||||
eventType: "data",
|
||||
}
|
||||
|
||||
export const MQTT_REQUEST_MOCK = {
|
||||
endpoint: "wss://test.mosquitto.org:8081",
|
||||
clientID: "hoppscotch",
|
||||
}
|
||||
|
||||
export const GLOBAL_ENV_MOCK: Environment["variables"] = [
|
||||
{ key: "test-key", value: "test-value" },
|
||||
]
|
||||
|
||||
export const VUEX_DATA_MOCK: VUEX_DATA = {
|
||||
postwoman: {
|
||||
settings: { ...DEFAULT_SETTINGS, THEME_COLOR: "purple" },
|
||||
collections: REST_COLLECTIONS_MOCK,
|
||||
collectionsGraphql: GQL_COLLECTIONS_MOCK,
|
||||
environments: ENVIRONMENTS_MOCK,
|
||||
},
|
||||
}
|
||||
|
||||
export const REST_HISTORY_MOCK: RESTHistoryEntry[] = [
|
||||
{
|
||||
v: 1,
|
||||
request: {
|
||||
auth: { authType: "none", authActive: true },
|
||||
body: { contentType: null, body: null },
|
||||
endpoint: "https://echo.hoppscotch.io",
|
||||
headers: [],
|
||||
method: "GET",
|
||||
name: "Untitled",
|
||||
params: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
v: "1",
|
||||
},
|
||||
responseMeta: { duration: 807, statusCode: 200 },
|
||||
star: false,
|
||||
updatedOn: new Date("2023-11-07T05:27:32.951Z"),
|
||||
},
|
||||
]
|
||||
|
||||
export const GQL_HISTORY_MOCK: GQLHistoryEntry[] = [
|
||||
{
|
||||
v: 1,
|
||||
request: {
|
||||
v: 2,
|
||||
name: "Untitled",
|
||||
url: "https://echo.hoppscotch.io/graphql",
|
||||
query: "query Request { url }",
|
||||
headers: [],
|
||||
variables: "",
|
||||
auth: { authType: "none", authActive: true },
|
||||
},
|
||||
response: '{"data":{"url":"/graphql"}}',
|
||||
star: false,
|
||||
updatedOn: new Date("2023-11-07T05:28:21.073Z"),
|
||||
},
|
||||
]
|
||||
|
||||
export const GQL_TAB_STATE_MOCK: PersistableTabState<HoppGQLDocument> = {
|
||||
lastActiveTabID: "5edbe8d4-65c9-4381-9354-5f1bf05d8ccc",
|
||||
orderedDocs: [
|
||||
{
|
||||
tabID: "5edbe8d4-65c9-4381-9354-5f1bf05d8ccc",
|
||||
doc: {
|
||||
request: {
|
||||
v: 2,
|
||||
name: "Untitled",
|
||||
url: "https://echo.hoppscotch.io/graphql",
|
||||
headers: [],
|
||||
variables: '{\n "id": "1"\n}',
|
||||
query: "query Request { url }",
|
||||
auth: { authType: "none", authActive: true },
|
||||
},
|
||||
isDirty: true,
|
||||
optionTabPreference: "query",
|
||||
response: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const REST_TAB_STATE_MOCK: PersistableTabState<HoppRESTDocument> = {
|
||||
lastActiveTabID: "e6e8d800-caa8-44a2-a6a6-b4765a3167aa",
|
||||
orderedDocs: [
|
||||
{
|
||||
tabID: "e6e8d800-caa8-44a2-a6a6-b4765a3167aa",
|
||||
doc: {
|
||||
request: {
|
||||
v: "1",
|
||||
endpoint: "https://echo.hoppscotch.io",
|
||||
name: "Echo test",
|
||||
params: [],
|
||||
headers: [],
|
||||
method: "GET",
|
||||
auth: { authType: "none", authActive: true },
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
body: { contentType: null, body: null },
|
||||
},
|
||||
isDirty: false,
|
||||
saveContext: {
|
||||
originLocation: "user-collection",
|
||||
folderPath: "0",
|
||||
requestIndex: 0,
|
||||
},
|
||||
response: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
724
packages/hoppscotch-common/src/services/persistence/index.ts
Normal file
724
packages/hoppscotch-common/src/services/persistence/index.ts
Normal file
@@ -0,0 +1,724 @@
|
||||
/* eslint-disable no-restricted-globals, no-restricted-syntax */
|
||||
|
||||
import {
|
||||
Environment,
|
||||
translateToNewGQLCollection,
|
||||
translateToNewRESTCollection,
|
||||
} from "@hoppscotch/data"
|
||||
import { StorageLike, watchDebounced } from "@vueuse/core"
|
||||
import { Service } from "dioc"
|
||||
import { assign, clone, isEmpty } from "lodash-es"
|
||||
import { z } from "zod"
|
||||
|
||||
import { GQLTabService } from "~/services/tab/graphql"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
|
||||
import { useToast } from "~/composables/toast"
|
||||
import { MQTTRequest$, setMQTTRequest } from "../../newstore/MQTTSession"
|
||||
import { SSERequest$, setSSERequest } from "../../newstore/SSESession"
|
||||
import { SIORequest$, setSIORequest } from "../../newstore/SocketIOSession"
|
||||
import { WSRequest$, setWSRequest } from "../../newstore/WebSocketSession"
|
||||
import {
|
||||
graphqlCollectionStore,
|
||||
restCollectionStore,
|
||||
setGraphqlCollections,
|
||||
setRESTCollections,
|
||||
} from "../../newstore/collections"
|
||||
import {
|
||||
addGlobalEnvVariable,
|
||||
environments$,
|
||||
globalEnv$,
|
||||
replaceEnvironments,
|
||||
selectedEnvironmentIndex$,
|
||||
setGlobalEnvVariables,
|
||||
setSelectedEnvironmentIndex,
|
||||
} from "../../newstore/environments"
|
||||
import {
|
||||
graphqlHistoryStore,
|
||||
restHistoryStore,
|
||||
setGraphqlHistoryEntries,
|
||||
setRESTHistoryEntries,
|
||||
translateToNewGQLHistory,
|
||||
translateToNewRESTHistory,
|
||||
} from "../../newstore/history"
|
||||
import { bulkApplyLocalState, localStateStore } from "../../newstore/localstate"
|
||||
import {
|
||||
HoppAccentColor,
|
||||
HoppBgColor,
|
||||
applySetting,
|
||||
bulkApplySettings,
|
||||
getDefaultSettings,
|
||||
performSettingsDataMigrations,
|
||||
settingsStore,
|
||||
} from "../../newstore/settings"
|
||||
import {
|
||||
ENVIRONMENTS_SCHEMA,
|
||||
GLOBAL_ENV_SCHEMA,
|
||||
GQL_COLLECTION_SCHEMA,
|
||||
GQL_HISTORY_ENTRY_SCHEMA,
|
||||
GQL_TAB_STATE_SCHEMA,
|
||||
LOCAL_STATE_SCHEMA,
|
||||
MQTT_REQUEST_SCHEMA,
|
||||
NUXT_COLOR_MODE_SCHEMA,
|
||||
REST_COLLECTION_SCHEMA,
|
||||
REST_HISTORY_ENTRY_SCHEMA,
|
||||
REST_TAB_STATE_SCHEMA,
|
||||
SELECTED_ENV_INDEX_SCHEMA,
|
||||
SETTINGS_SCHEMA,
|
||||
SOCKET_IO_REQUEST_SCHEMA,
|
||||
SSE_REQUEST_SCHEMA,
|
||||
THEME_COLOR_SCHEMA,
|
||||
VUEX_SCHEMA,
|
||||
WEBSOCKET_REQUEST_SCHEMA,
|
||||
} from "./validation-schemas"
|
||||
|
||||
/**
|
||||
* This service compiles persistence logic across the codebase
|
||||
*/
|
||||
export class PersistenceService extends Service {
|
||||
public static readonly ID = "PERSISTENCE_SERVICE"
|
||||
|
||||
private readonly restTabService = this.bind(RESTTabService)
|
||||
private readonly gqlTabService = this.bind(GQLTabService)
|
||||
|
||||
public hoppLocalConfigStorage: StorageLike = localStorage
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
|
||||
private showErrorToast(localStorageKey: string) {
|
||||
const toast = useToast()
|
||||
toast.error(
|
||||
`There's a mismatch with the expected schema for the value corresponding to ${localStorageKey} read from localStorage, keeping a backup in ${localStorageKey}-backup`
|
||||
)
|
||||
}
|
||||
|
||||
private checkAndMigrateOldSettings() {
|
||||
if (window.localStorage.getItem("selectedEnvIndex")) {
|
||||
const index = window.localStorage.getItem("selectedEnvIndex")
|
||||
if (index) {
|
||||
if (index === "-1") {
|
||||
window.localStorage.setItem(
|
||||
"selectedEnvIndex",
|
||||
JSON.stringify({
|
||||
type: "NO_ENV_SELECTED",
|
||||
})
|
||||
)
|
||||
} else if (Number(index) >= 0) {
|
||||
window.localStorage.setItem(
|
||||
"selectedEnvIndex",
|
||||
JSON.stringify({
|
||||
type: "MY_ENV",
|
||||
index: parseInt(index),
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const vuexKey = "vuex"
|
||||
let vuexData = JSON.parse(window.localStorage.getItem(vuexKey) || "{}")
|
||||
|
||||
if (isEmpty(vuexData)) return
|
||||
|
||||
// Validate data read from localStorage
|
||||
const result = VUEX_SCHEMA.safeParse(vuexData)
|
||||
if (result.success) {
|
||||
vuexData = result.data
|
||||
} else {
|
||||
this.showErrorToast(vuexKey)
|
||||
window.localStorage.setItem(`${vuexKey}-backup`, JSON.stringify(vuexData))
|
||||
}
|
||||
|
||||
const { postwoman } = vuexData
|
||||
|
||||
if (!isEmpty(postwoman?.settings)) {
|
||||
const settingsData = assign(
|
||||
clone(getDefaultSettings()),
|
||||
postwoman.settings
|
||||
)
|
||||
|
||||
window.localStorage.setItem("settings", JSON.stringify(settingsData))
|
||||
|
||||
delete postwoman.settings
|
||||
window.localStorage.setItem(vuexKey, JSON.stringify(vuexData))
|
||||
}
|
||||
|
||||
if (postwoman?.collections) {
|
||||
window.localStorage.setItem(
|
||||
"collections",
|
||||
JSON.stringify(postwoman.collections)
|
||||
)
|
||||
|
||||
delete postwoman.collections
|
||||
window.localStorage.setItem(vuexKey, JSON.stringify(vuexData))
|
||||
}
|
||||
|
||||
if (postwoman?.collectionsGraphql) {
|
||||
window.localStorage.setItem(
|
||||
"collectionsGraphql",
|
||||
JSON.stringify(postwoman.collectionsGraphql)
|
||||
)
|
||||
|
||||
delete postwoman.collectionsGraphql
|
||||
window.localStorage.setItem(vuexKey, JSON.stringify(vuexData))
|
||||
}
|
||||
|
||||
if (postwoman?.environments) {
|
||||
window.localStorage.setItem(
|
||||
"environments",
|
||||
JSON.stringify(postwoman.environments)
|
||||
)
|
||||
|
||||
delete postwoman.environments
|
||||
window.localStorage.setItem(vuexKey, JSON.stringify(vuexData))
|
||||
}
|
||||
|
||||
const themeColorKey = "THEME_COLOR"
|
||||
let themeColorValue = window.localStorage.getItem(themeColorKey)
|
||||
|
||||
if (themeColorValue) {
|
||||
// Validate data read from localStorage
|
||||
const result = THEME_COLOR_SCHEMA.safeParse(themeColorValue)
|
||||
|
||||
if (result.success) {
|
||||
themeColorValue = result.data
|
||||
} else {
|
||||
this.showErrorToast(themeColorKey)
|
||||
window.localStorage.setItem(`${themeColorKey}-backup`, themeColorValue)
|
||||
}
|
||||
|
||||
applySetting(themeColorKey, themeColorValue as HoppAccentColor)
|
||||
window.localStorage.removeItem(themeColorKey)
|
||||
}
|
||||
|
||||
const nuxtColorModeKey = "nuxt-color-mode"
|
||||
let nuxtColorModeValue = window.localStorage.getItem(nuxtColorModeKey)
|
||||
|
||||
if (nuxtColorModeValue) {
|
||||
// Validate data read from localStorage
|
||||
const result = NUXT_COLOR_MODE_SCHEMA.safeParse(nuxtColorModeValue)
|
||||
|
||||
if (result.success) {
|
||||
nuxtColorModeValue = result.data
|
||||
} else {
|
||||
this.showErrorToast(nuxtColorModeKey)
|
||||
window.localStorage.setItem(
|
||||
`${nuxtColorModeKey}-backup`,
|
||||
nuxtColorModeValue
|
||||
)
|
||||
}
|
||||
|
||||
applySetting("BG_COLOR", nuxtColorModeValue as HoppBgColor)
|
||||
window.localStorage.removeItem(nuxtColorModeKey)
|
||||
}
|
||||
}
|
||||
|
||||
public setupLocalStatePersistence() {
|
||||
const localStateKey = "localState"
|
||||
let localStateData = JSON.parse(
|
||||
window.localStorage.getItem(localStateKey) ?? "{}"
|
||||
)
|
||||
|
||||
// Validate data read from localStorage
|
||||
const result = LOCAL_STATE_SCHEMA.safeParse(localStateData)
|
||||
|
||||
if (result.success) {
|
||||
localStateData = result.data
|
||||
} else {
|
||||
this.showErrorToast(localStateKey)
|
||||
window.localStorage.setItem(
|
||||
`${localStateKey}-backup`,
|
||||
JSON.stringify(localStateData)
|
||||
)
|
||||
}
|
||||
|
||||
if (localStateData) bulkApplyLocalState(localStateData)
|
||||
|
||||
localStateStore.subject$.subscribe((state) => {
|
||||
window.localStorage.setItem(localStateKey, JSON.stringify(state))
|
||||
})
|
||||
}
|
||||
|
||||
private setupSettingsPersistence() {
|
||||
const settingsKey = "settings"
|
||||
let settingsData = JSON.parse(
|
||||
window.localStorage.getItem(settingsKey) || "{}"
|
||||
)
|
||||
|
||||
// Validate data read from localStorage
|
||||
const result = SETTINGS_SCHEMA.safeParse(settingsData)
|
||||
if (result.success) {
|
||||
settingsData = result.data
|
||||
} else {
|
||||
this.showErrorToast(settingsKey)
|
||||
window.localStorage.setItem(
|
||||
`${settingsKey}-backup`,
|
||||
JSON.stringify(settingsData)
|
||||
)
|
||||
}
|
||||
|
||||
const updatedSettings = settingsData
|
||||
? performSettingsDataMigrations(settingsData)
|
||||
: settingsData
|
||||
|
||||
if (updatedSettings) {
|
||||
bulkApplySettings(updatedSettings)
|
||||
}
|
||||
|
||||
settingsStore.subject$.subscribe((settings) => {
|
||||
window.localStorage.setItem(settingsKey, JSON.stringify(settings))
|
||||
})
|
||||
}
|
||||
|
||||
private setupHistoryPersistence() {
|
||||
const restHistoryKey = "history"
|
||||
let restHistoryData = JSON.parse(
|
||||
window.localStorage.getItem(restHistoryKey) || "[]"
|
||||
)
|
||||
|
||||
const graphqlHistoryKey = "graphqlHistory"
|
||||
let graphqlHistoryData = JSON.parse(
|
||||
window.localStorage.getItem(graphqlHistoryKey) || "[]"
|
||||
)
|
||||
|
||||
// Validate data read from localStorage
|
||||
const restHistorySchemaParsedresult = z
|
||||
.array(REST_HISTORY_ENTRY_SCHEMA)
|
||||
.safeParse(restHistoryData)
|
||||
|
||||
if (restHistorySchemaParsedresult.success) {
|
||||
restHistoryData = restHistorySchemaParsedresult.data
|
||||
} else {
|
||||
this.showErrorToast(restHistoryKey)
|
||||
window.localStorage.setItem(
|
||||
`${restHistoryKey}-backup`,
|
||||
JSON.stringify(restHistoryData)
|
||||
)
|
||||
}
|
||||
|
||||
const gqlHistorySchemaParsedresult = z
|
||||
.array(GQL_HISTORY_ENTRY_SCHEMA)
|
||||
.safeParse(graphqlHistoryData)
|
||||
|
||||
if (gqlHistorySchemaParsedresult.success) {
|
||||
graphqlHistoryData = gqlHistorySchemaParsedresult.data
|
||||
} else {
|
||||
this.showErrorToast(graphqlHistoryKey)
|
||||
window.localStorage.setItem(
|
||||
`${graphqlHistoryKey}-backup`,
|
||||
JSON.stringify(graphqlHistoryData)
|
||||
)
|
||||
}
|
||||
|
||||
const translatedRestHistoryData = restHistoryData.map(
|
||||
translateToNewRESTHistory
|
||||
)
|
||||
const translatedGraphqlHistoryData = graphqlHistoryData.map(
|
||||
translateToNewGQLHistory
|
||||
)
|
||||
|
||||
setRESTHistoryEntries(translatedRestHistoryData)
|
||||
setGraphqlHistoryEntries(translatedGraphqlHistoryData)
|
||||
|
||||
restHistoryStore.subject$.subscribe(({ state }) => {
|
||||
window.localStorage.setItem(restHistoryKey, JSON.stringify(state))
|
||||
})
|
||||
|
||||
graphqlHistoryStore.subject$.subscribe(({ state }) => {
|
||||
window.localStorage.setItem(graphqlHistoryKey, JSON.stringify(state))
|
||||
})
|
||||
}
|
||||
|
||||
private setupCollectionsPersistence() {
|
||||
const restCollectionsKey = "collections"
|
||||
let restCollectionsData = JSON.parse(
|
||||
window.localStorage.getItem(restCollectionsKey) || "[]"
|
||||
)
|
||||
|
||||
const graphqlCollectionsKey = "collectionsGraphql"
|
||||
let graphqlCollectionsData = JSON.parse(
|
||||
window.localStorage.getItem(graphqlCollectionsKey) || "[]"
|
||||
)
|
||||
|
||||
// Validate data read from localStorage
|
||||
const restCollectionsSchemaParsedresult = z
|
||||
.array(REST_COLLECTION_SCHEMA)
|
||||
.safeParse(restCollectionsData)
|
||||
|
||||
if (restCollectionsSchemaParsedresult.success) {
|
||||
restCollectionsData = restCollectionsSchemaParsedresult.data
|
||||
} else {
|
||||
this.showErrorToast(restCollectionsKey)
|
||||
window.localStorage.setItem(
|
||||
`${restCollectionsKey}-backup`,
|
||||
JSON.stringify(restCollectionsData)
|
||||
)
|
||||
}
|
||||
|
||||
const gqlCollectionsSchemaParsedresult = z
|
||||
.array(GQL_COLLECTION_SCHEMA)
|
||||
.safeParse(graphqlCollectionsData)
|
||||
|
||||
if (gqlCollectionsSchemaParsedresult.success) {
|
||||
graphqlCollectionsData = gqlCollectionsSchemaParsedresult.data
|
||||
} else {
|
||||
this.showErrorToast(graphqlCollectionsKey)
|
||||
window.localStorage.setItem(
|
||||
`${graphqlCollectionsKey}-backup`,
|
||||
JSON.stringify(graphqlCollectionsData)
|
||||
)
|
||||
}
|
||||
|
||||
const translatedRestCollectionsData = restCollectionsData.map(
|
||||
translateToNewRESTCollection
|
||||
)
|
||||
const translatedGraphqlCollectionsData = graphqlCollectionsData.map(
|
||||
translateToNewGQLCollection
|
||||
)
|
||||
|
||||
setRESTCollections(translatedRestCollectionsData)
|
||||
setGraphqlCollections(translatedGraphqlCollectionsData)
|
||||
|
||||
restCollectionStore.subject$.subscribe(({ state }) => {
|
||||
window.localStorage.setItem(restCollectionsKey, JSON.stringify(state))
|
||||
})
|
||||
|
||||
graphqlCollectionStore.subject$.subscribe(({ state }) => {
|
||||
window.localStorage.setItem(graphqlCollectionsKey, JSON.stringify(state))
|
||||
})
|
||||
}
|
||||
|
||||
private setupEnvironmentsPersistence() {
|
||||
const environmentsKey = "environments"
|
||||
let environmentsData: Environment[] = JSON.parse(
|
||||
window.localStorage.getItem(environmentsKey) || "[]"
|
||||
)
|
||||
|
||||
// Validate data read from localStorage
|
||||
const result = ENVIRONMENTS_SCHEMA.safeParse(environmentsData)
|
||||
if (result.success) {
|
||||
environmentsData = result.data
|
||||
} else {
|
||||
this.showErrorToast(environmentsKey)
|
||||
window.localStorage.setItem(
|
||||
`${environmentsKey}-backup`,
|
||||
JSON.stringify(environmentsData)
|
||||
)
|
||||
}
|
||||
|
||||
// Check if a global env is defined and if so move that to globals
|
||||
const globalIndex = environmentsData.findIndex(
|
||||
(x) => x.name.toLowerCase() === "globals"
|
||||
)
|
||||
|
||||
if (globalIndex !== -1) {
|
||||
const globalEnv = environmentsData[globalIndex]
|
||||
globalEnv.variables.forEach((variable) => addGlobalEnvVariable(variable))
|
||||
|
||||
// Remove global from environments
|
||||
environmentsData.splice(globalIndex, 1)
|
||||
|
||||
// Just sync the changes manually
|
||||
window.localStorage.setItem(
|
||||
environmentsKey,
|
||||
JSON.stringify(environmentsData)
|
||||
)
|
||||
}
|
||||
|
||||
replaceEnvironments(environmentsData)
|
||||
|
||||
environments$.subscribe((envs) => {
|
||||
window.localStorage.setItem(environmentsKey, JSON.stringify(envs))
|
||||
})
|
||||
}
|
||||
|
||||
private setupSelectedEnvPersistence() {
|
||||
const selectedEnvIndexKey = "selectedEnvIndex"
|
||||
let selectedEnvIndexValue = JSON.parse(
|
||||
window.localStorage.getItem(selectedEnvIndexKey) ?? "null"
|
||||
)
|
||||
|
||||
// Validate data read from localStorage
|
||||
const result = SELECTED_ENV_INDEX_SCHEMA.safeParse(selectedEnvIndexValue)
|
||||
if (result.success) {
|
||||
selectedEnvIndexValue = result.data
|
||||
} else {
|
||||
this.showErrorToast(selectedEnvIndexKey)
|
||||
window.localStorage.setItem(
|
||||
`${selectedEnvIndexKey}-backup`,
|
||||
JSON.stringify(selectedEnvIndexValue)
|
||||
)
|
||||
}
|
||||
|
||||
// If there is a selected env index, set it to the store else set it to null
|
||||
if (selectedEnvIndexValue) {
|
||||
setSelectedEnvironmentIndex(selectedEnvIndexValue)
|
||||
} else {
|
||||
setSelectedEnvironmentIndex({
|
||||
type: "NO_ENV_SELECTED",
|
||||
})
|
||||
}
|
||||
|
||||
selectedEnvironmentIndex$.subscribe((envIndex) => {
|
||||
window.localStorage.setItem(selectedEnvIndexKey, JSON.stringify(envIndex))
|
||||
})
|
||||
}
|
||||
|
||||
private setupWebsocketPersistence() {
|
||||
const wsRequestKey = "WebsocketRequest"
|
||||
let wsRequestData = JSON.parse(
|
||||
window.localStorage.getItem(wsRequestKey) || "null"
|
||||
)
|
||||
|
||||
// Validate data read from localStorage
|
||||
const result = WEBSOCKET_REQUEST_SCHEMA.safeParse(wsRequestData)
|
||||
if (result.success) {
|
||||
wsRequestData = result.data
|
||||
} else {
|
||||
this.showErrorToast(wsRequestKey)
|
||||
window.localStorage.setItem(
|
||||
`${wsRequestKey}-backup`,
|
||||
JSON.stringify(wsRequestData)
|
||||
)
|
||||
}
|
||||
|
||||
setWSRequest(wsRequestData)
|
||||
|
||||
WSRequest$.subscribe((req) => {
|
||||
window.localStorage.setItem(wsRequestKey, JSON.stringify(req))
|
||||
})
|
||||
}
|
||||
|
||||
private setupSocketIOPersistence() {
|
||||
const sioRequestKey = "SocketIORequest"
|
||||
let sioRequestData = JSON.parse(
|
||||
window.localStorage.getItem(sioRequestKey) || "null"
|
||||
)
|
||||
|
||||
// Validate data read from localStorage
|
||||
const result = SOCKET_IO_REQUEST_SCHEMA.safeParse(sioRequestData)
|
||||
if (result.success) {
|
||||
sioRequestData = result.data
|
||||
} else {
|
||||
this.showErrorToast(sioRequestKey)
|
||||
window.localStorage.setItem(
|
||||
`${sioRequestKey}-backup`,
|
||||
JSON.stringify(sioRequestData)
|
||||
)
|
||||
}
|
||||
|
||||
setSIORequest(sioRequestData)
|
||||
|
||||
SIORequest$.subscribe((req) => {
|
||||
window.localStorage.setItem(sioRequestKey, JSON.stringify(req))
|
||||
})
|
||||
}
|
||||
|
||||
private setupSSEPersistence() {
|
||||
const sseRequestKey = "SSERequest"
|
||||
let sseRequestData = JSON.parse(
|
||||
window.localStorage.getItem(sseRequestKey) || "null"
|
||||
)
|
||||
|
||||
// Validate data read from localStorage
|
||||
const result = SSE_REQUEST_SCHEMA.safeParse(sseRequestData)
|
||||
if (result.success) {
|
||||
sseRequestData = result.data
|
||||
} else {
|
||||
this.showErrorToast(sseRequestKey)
|
||||
window.localStorage.setItem(
|
||||
`${sseRequestKey}-backup`,
|
||||
JSON.stringify(sseRequestData)
|
||||
)
|
||||
}
|
||||
|
||||
setSSERequest(sseRequestData)
|
||||
|
||||
SSERequest$.subscribe((req) => {
|
||||
window.localStorage.setItem(sseRequestKey, JSON.stringify(req))
|
||||
})
|
||||
}
|
||||
|
||||
private setupMQTTPersistence() {
|
||||
const mqttRequestKey = "MQTTRequest"
|
||||
let mqttRequestData = JSON.parse(
|
||||
window.localStorage.getItem(mqttRequestKey) || "null"
|
||||
)
|
||||
|
||||
// Validate data read from localStorage
|
||||
const result = MQTT_REQUEST_SCHEMA.safeParse(mqttRequestData)
|
||||
if (result.success) {
|
||||
mqttRequestData = result.data
|
||||
} else {
|
||||
this.showErrorToast(mqttRequestKey)
|
||||
window.localStorage.setItem(
|
||||
`${mqttRequestKey}-backup`,
|
||||
JSON.stringify(mqttRequestData)
|
||||
)
|
||||
}
|
||||
|
||||
setMQTTRequest(mqttRequestData)
|
||||
|
||||
MQTTRequest$.subscribe((req) => {
|
||||
window.localStorage.setItem(mqttRequestKey, JSON.stringify(req))
|
||||
})
|
||||
}
|
||||
|
||||
private setupGlobalEnvsPersistence() {
|
||||
const globalEnvKey = "globalEnv"
|
||||
let globalEnvData: Environment["variables"] = JSON.parse(
|
||||
window.localStorage.getItem(globalEnvKey) || "[]"
|
||||
)
|
||||
|
||||
// Validate data read from localStorage
|
||||
const result = GLOBAL_ENV_SCHEMA.safeParse(globalEnvData)
|
||||
if (result.success) {
|
||||
globalEnvData = result.data
|
||||
} else {
|
||||
this.showErrorToast(globalEnvKey)
|
||||
window.localStorage.setItem(
|
||||
`${globalEnvKey}-backup`,
|
||||
JSON.stringify(globalEnvData)
|
||||
)
|
||||
}
|
||||
|
||||
setGlobalEnvVariables(globalEnvData)
|
||||
|
||||
globalEnv$.subscribe((vars) => {
|
||||
window.localStorage.setItem(globalEnvKey, JSON.stringify(vars))
|
||||
})
|
||||
}
|
||||
|
||||
private setupGQLTabsPersistence() {
|
||||
const gqlTabStateKey = "gqlTabState"
|
||||
const gqlTabStateData = window.localStorage.getItem(gqlTabStateKey)
|
||||
|
||||
try {
|
||||
if (gqlTabStateData) {
|
||||
let parsedGqlTabStateData = JSON.parse(gqlTabStateData)
|
||||
|
||||
// Validate data read from localStorage
|
||||
const result = GQL_TAB_STATE_SCHEMA.safeParse(parsedGqlTabStateData)
|
||||
|
||||
if (result.success) {
|
||||
parsedGqlTabStateData = result.data
|
||||
} else {
|
||||
this.showErrorToast(gqlTabStateKey)
|
||||
window.localStorage.setItem(
|
||||
`${gqlTabStateKey}-backup`,
|
||||
JSON.stringify(parsedGqlTabStateData)
|
||||
)
|
||||
}
|
||||
|
||||
this.gqlTabService.loadTabsFromPersistedState(parsedGqlTabStateData)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(
|
||||
`Failed parsing persisted tab state, state:`,
|
||||
gqlTabStateData
|
||||
)
|
||||
}
|
||||
|
||||
watchDebounced(
|
||||
this.gqlTabService.persistableTabState,
|
||||
(newGqlTabStateData) => {
|
||||
window.localStorage.setItem(
|
||||
gqlTabStateKey,
|
||||
JSON.stringify(newGqlTabStateData)
|
||||
)
|
||||
},
|
||||
{ debounce: 500, deep: true }
|
||||
)
|
||||
}
|
||||
|
||||
private setupRESTTabsPersistence() {
|
||||
const restTabStateKey = "restTabState"
|
||||
const restTabStateData = window.localStorage.getItem(restTabStateKey)
|
||||
|
||||
try {
|
||||
if (restTabStateData) {
|
||||
let parsedGqlTabStateData = JSON.parse(restTabStateData)
|
||||
|
||||
// Validate data read from localStorage
|
||||
const result = REST_TAB_STATE_SCHEMA.safeParse(parsedGqlTabStateData)
|
||||
|
||||
if (result.success) {
|
||||
parsedGqlTabStateData = result.data
|
||||
} else {
|
||||
this.showErrorToast(restTabStateKey)
|
||||
window.localStorage.setItem(
|
||||
`${restTabStateKey}-backup`,
|
||||
JSON.stringify(parsedGqlTabStateData)
|
||||
)
|
||||
}
|
||||
|
||||
this.restTabService.loadTabsFromPersistedState(parsedGqlTabStateData)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(
|
||||
`Failed parsing persisted tab state, state:`,
|
||||
restTabStateData
|
||||
)
|
||||
}
|
||||
|
||||
watchDebounced(
|
||||
this.restTabService.persistableTabState,
|
||||
(newRestTabStateData) => {
|
||||
window.localStorage.setItem(
|
||||
restTabStateKey,
|
||||
JSON.stringify(newRestTabStateData)
|
||||
)
|
||||
},
|
||||
{ debounce: 500, deep: true }
|
||||
)
|
||||
}
|
||||
|
||||
public setupLocalPersistence() {
|
||||
this.checkAndMigrateOldSettings()
|
||||
|
||||
this.setupLocalStatePersistence()
|
||||
this.setupSettingsPersistence()
|
||||
this.setupRESTTabsPersistence()
|
||||
|
||||
this.setupGQLTabsPersistence()
|
||||
|
||||
this.setupHistoryPersistence()
|
||||
this.setupCollectionsPersistence()
|
||||
this.setupGlobalEnvsPersistence()
|
||||
this.setupEnvironmentsPersistence()
|
||||
this.setupSelectedEnvPersistence()
|
||||
this.setupWebsocketPersistence()
|
||||
this.setupSocketIOPersistence()
|
||||
this.setupSSEPersistence()
|
||||
this.setupMQTTPersistence()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a value from localStorage
|
||||
*
|
||||
* NOTE: Use localStorage to only store non-reactive simple data
|
||||
* For more complex data, use stores and connect it to `PersistenceService`
|
||||
*/
|
||||
public getLocalConfig(name: string) {
|
||||
return window.localStorage.getItem(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a value in localStorage
|
||||
*
|
||||
* NOTE: Use localStorage to only store non-reactive simple data
|
||||
* For more complex data, use stores and connect it to `PersistenceService`
|
||||
*/
|
||||
public setLocalConfig(key: string, value: string) {
|
||||
window.localStorage.setItem(key, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear config value in localStorage
|
||||
*/
|
||||
public removeLocalConfig(key: string) {
|
||||
window.localStorage.removeItem(key)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,470 @@
|
||||
import {
|
||||
Environment,
|
||||
GQLHeader,
|
||||
HoppGQLAuth,
|
||||
HoppGQLRequest,
|
||||
HoppRESTRequest,
|
||||
} from "@hoppscotch/data"
|
||||
import { entityReference } from "verzod"
|
||||
import { z } from "zod"
|
||||
import { HoppAccentColors, HoppBgColors } from "~/newstore/settings"
|
||||
|
||||
const ThemeColorSchema = z.enum([
|
||||
"green",
|
||||
"teal",
|
||||
"blue",
|
||||
"indigo",
|
||||
"purple",
|
||||
"yellow",
|
||||
"orange",
|
||||
"red",
|
||||
"pink",
|
||||
])
|
||||
|
||||
const BgColorSchema = z.enum(["system", "light", "dark", "black"])
|
||||
|
||||
const SettingsDefSchema = z.object({
|
||||
syncCollections: z.boolean(),
|
||||
syncHistory: z.boolean(),
|
||||
syncEnvironments: z.boolean(),
|
||||
PROXY_URL: z.string(),
|
||||
CURRENT_INTERCEPTOR_ID: z.string(),
|
||||
URL_EXCLUDES: z.object({
|
||||
auth: z.boolean(),
|
||||
httpUser: z.boolean(),
|
||||
httpPassword: z.boolean(),
|
||||
bearerToken: z.boolean(),
|
||||
oauth2Token: z.boolean(),
|
||||
}),
|
||||
THEME_COLOR: ThemeColorSchema,
|
||||
BG_COLOR: BgColorSchema,
|
||||
TELEMETRY_ENABLED: z.boolean(),
|
||||
EXPAND_NAVIGATION: z.boolean(),
|
||||
SIDEBAR: z.boolean(),
|
||||
SIDEBAR_ON_LEFT: z.boolean(),
|
||||
COLUMN_LAYOUT: z.boolean(),
|
||||
})
|
||||
|
||||
// Common properties shared across REST & GQL collections
|
||||
const HoppCollectionSchemaCommonProps = z
|
||||
.object({
|
||||
v: z.number(),
|
||||
name: z.string(),
|
||||
id: z.optional(z.string()),
|
||||
})
|
||||
.strict()
|
||||
|
||||
const HoppRESTRequestSchema = entityReference(HoppRESTRequest)
|
||||
|
||||
const HoppGQLRequestSchema = entityReference(HoppGQLRequest)
|
||||
|
||||
// @ts-expect-error recursive schema
|
||||
const HoppRESTCollectionSchema = HoppCollectionSchemaCommonProps.extend({
|
||||
folders: z.array(z.lazy(() => HoppRESTCollectionSchema)),
|
||||
requests: z.optional(z.array(HoppRESTRequestSchema)),
|
||||
}).strict()
|
||||
|
||||
// @ts-expect-error recursive schema
|
||||
const HoppGQLCollectionSchema = HoppCollectionSchemaCommonProps.extend({
|
||||
folders: z.array(z.lazy(() => HoppGQLCollectionSchema)),
|
||||
requests: z.optional(z.array(HoppGQLRequestSchema)),
|
||||
}).strict()
|
||||
|
||||
export const VUEX_SCHEMA = z.object({
|
||||
postwoman: z.optional(
|
||||
z.object({
|
||||
settings: z.optional(SettingsDefSchema),
|
||||
//! Versioned entities
|
||||
collections: z.optional(z.array(HoppRESTCollectionSchema)),
|
||||
collectionsGraphql: z.optional(z.array(HoppGQLCollectionSchema)),
|
||||
environments: z.optional(z.array(entityReference(Environment))),
|
||||
})
|
||||
),
|
||||
})
|
||||
|
||||
export const THEME_COLOR_SCHEMA = z.enum(HoppAccentColors)
|
||||
|
||||
export const NUXT_COLOR_MODE_SCHEMA = z.enum(HoppBgColors)
|
||||
|
||||
export const LOCAL_STATE_SCHEMA = z.union([
|
||||
z.object({}).strict(),
|
||||
z
|
||||
.object({
|
||||
REMEMBERED_TEAM_ID: z.optional(z.string()),
|
||||
})
|
||||
.strict(),
|
||||
])
|
||||
|
||||
export const SETTINGS_SCHEMA = z.union([
|
||||
z.object({}).strict(),
|
||||
SettingsDefSchema.extend({
|
||||
EXTENSIONS_ENABLED: z.optional(z.boolean()),
|
||||
PROXY_ENABLED: z.optional(z.boolean()),
|
||||
}),
|
||||
])
|
||||
|
||||
export const REST_HISTORY_ENTRY_SCHEMA = z
|
||||
.object({
|
||||
v: z.number(),
|
||||
//! Versioned entity
|
||||
request: HoppRESTRequestSchema,
|
||||
responseMeta: z
|
||||
.object({
|
||||
duration: z.nullable(z.number()),
|
||||
statusCode: z.nullable(z.number()),
|
||||
})
|
||||
.strict(),
|
||||
star: z.boolean(),
|
||||
id: z.optional(z.string()),
|
||||
updatedOn: z.optional(z.union([z.date(), z.string()])),
|
||||
})
|
||||
.strict()
|
||||
|
||||
export const GQL_HISTORY_ENTRY_SCHEMA = z
|
||||
.object({
|
||||
v: z.number(),
|
||||
//! Versioned entity
|
||||
request: HoppGQLRequestSchema,
|
||||
response: z.string(),
|
||||
star: z.boolean(),
|
||||
id: z.optional(z.string()),
|
||||
updatedOn: z.optional(z.union([z.date(), z.string()])),
|
||||
})
|
||||
.strict()
|
||||
|
||||
export const REST_COLLECTION_SCHEMA = HoppRESTCollectionSchema
|
||||
|
||||
export const GQL_COLLECTION_SCHEMA = HoppGQLCollectionSchema
|
||||
|
||||
export const ENVIRONMENTS_SCHEMA = z.array(entityReference(Environment))
|
||||
|
||||
export const SELECTED_ENV_INDEX_SCHEMA = z.nullable(
|
||||
z.discriminatedUnion("type", [
|
||||
z
|
||||
.object({
|
||||
type: z.literal("NO_ENV_SELECTED"),
|
||||
})
|
||||
.strict(),
|
||||
z
|
||||
.object({
|
||||
type: z.literal("MY_ENV"),
|
||||
index: z.number(),
|
||||
})
|
||||
.strict(),
|
||||
z.object({
|
||||
type: z.literal("TEAM_ENV"),
|
||||
teamID: z.string(),
|
||||
teamEnvID: z.string(),
|
||||
// ! Versioned entity
|
||||
environment: entityReference(Environment),
|
||||
}),
|
||||
])
|
||||
)
|
||||
|
||||
export const WEBSOCKET_REQUEST_SCHEMA = z.nullable(
|
||||
z
|
||||
.object({
|
||||
endpoint: z.string(),
|
||||
protocols: z.array(
|
||||
z
|
||||
.object({
|
||||
value: z.string(),
|
||||
active: z.boolean(),
|
||||
})
|
||||
.strict()
|
||||
),
|
||||
})
|
||||
.strict()
|
||||
)
|
||||
|
||||
export const SOCKET_IO_REQUEST_SCHEMA = z.nullable(
|
||||
z
|
||||
.object({
|
||||
endpoint: z.string(),
|
||||
path: z.string(),
|
||||
version: z.union([z.literal("v4"), z.literal("v3"), z.literal("v2")]),
|
||||
})
|
||||
.strict()
|
||||
)
|
||||
|
||||
export const SSE_REQUEST_SCHEMA = z.nullable(
|
||||
z
|
||||
.object({
|
||||
endpoint: z.string(),
|
||||
eventType: z.string(),
|
||||
})
|
||||
.strict()
|
||||
)
|
||||
|
||||
export const MQTT_REQUEST_SCHEMA = z.nullable(
|
||||
z
|
||||
.object({
|
||||
endpoint: z.string(),
|
||||
clientID: z.string(),
|
||||
})
|
||||
.strict()
|
||||
)
|
||||
|
||||
export const GLOBAL_ENV_SCHEMA = z.union([
|
||||
z.array(z.never()),
|
||||
z.array(
|
||||
z
|
||||
.object({
|
||||
key: z.string(),
|
||||
value: z.string(),
|
||||
})
|
||||
.strict()
|
||||
),
|
||||
])
|
||||
|
||||
const OperationTypeSchema = z.enum([
|
||||
"subscription",
|
||||
"query",
|
||||
"mutation",
|
||||
"teardown",
|
||||
])
|
||||
|
||||
const RunQueryOptionsSchema = z
|
||||
.object({
|
||||
name: z.optional(z.string()),
|
||||
url: z.string(),
|
||||
headers: z.array(GQLHeader),
|
||||
query: z.string(),
|
||||
variables: z.string(),
|
||||
auth: HoppGQLAuth,
|
||||
operationName: z.optional(z.string()),
|
||||
operationType: OperationTypeSchema,
|
||||
})
|
||||
.strict()
|
||||
|
||||
const HoppGQLSaveContextSchema = z.nullable(
|
||||
z.discriminatedUnion("originLocation", [
|
||||
z
|
||||
.object({
|
||||
originLocation: z.literal("user-collection"),
|
||||
folderPath: z.string(),
|
||||
requestIndex: z.number(),
|
||||
})
|
||||
.strict(),
|
||||
z
|
||||
.object({
|
||||
originLocation: z.literal("team-collection"),
|
||||
requestID: z.string(),
|
||||
teamID: z.optional(z.string()),
|
||||
collectionID: z.optional(z.string()),
|
||||
})
|
||||
.strict(),
|
||||
])
|
||||
)
|
||||
|
||||
const GQLResponseEventSchema = z.array(
|
||||
z
|
||||
.object({
|
||||
time: z.number(),
|
||||
operationName: z.optional(z.string()),
|
||||
operationType: OperationTypeSchema,
|
||||
data: z.string(),
|
||||
rawQuery: z.optional(RunQueryOptionsSchema),
|
||||
})
|
||||
.strict()
|
||||
)
|
||||
|
||||
const validGqlOperations = [
|
||||
"query",
|
||||
"headers",
|
||||
"variables",
|
||||
"authorization",
|
||||
] as const
|
||||
|
||||
export const GQL_TAB_STATE_SCHEMA = z
|
||||
.object({
|
||||
lastActiveTabID: z.string(),
|
||||
orderedDocs: z.array(
|
||||
z.object({
|
||||
tabID: z.string(),
|
||||
doc: z
|
||||
.object({
|
||||
// Versioned entity
|
||||
request: entityReference(HoppGQLRequest),
|
||||
isDirty: z.boolean(),
|
||||
saveContext: z.optional(HoppGQLSaveContextSchema),
|
||||
response: z.optional(z.nullable(GQLResponseEventSchema)),
|
||||
responseTabPreference: z.optional(z.string()),
|
||||
optionTabPreference: z.optional(z.enum(validGqlOperations)),
|
||||
})
|
||||
.strict(),
|
||||
})
|
||||
),
|
||||
})
|
||||
.strict()
|
||||
|
||||
const HoppTestExpectResultSchema = z
|
||||
.object({
|
||||
status: z.enum(["fail", "pass", "error"]),
|
||||
message: z.string(),
|
||||
})
|
||||
.strict()
|
||||
|
||||
// @ts-expect-error recursive schema
|
||||
const HoppTestDataSchema = z.lazy(() =>
|
||||
z
|
||||
.object({
|
||||
description: z.string(),
|
||||
expectResults: z.array(HoppTestExpectResultSchema),
|
||||
tests: z.array(HoppTestDataSchema),
|
||||
})
|
||||
.strict()
|
||||
)
|
||||
|
||||
const EnvironmentVariablesSchema = z
|
||||
.object({
|
||||
key: z.string(),
|
||||
value: z.string(),
|
||||
})
|
||||
.strict()
|
||||
|
||||
const HoppTestResultSchema = z
|
||||
.object({
|
||||
tests: z.array(HoppTestDataSchema),
|
||||
expectResults: z.array(HoppTestExpectResultSchema),
|
||||
description: z.string(),
|
||||
scriptError: z.boolean(),
|
||||
envDiff: z
|
||||
.object({
|
||||
global: z
|
||||
.object({
|
||||
additions: z.array(EnvironmentVariablesSchema),
|
||||
updations: z.array(
|
||||
EnvironmentVariablesSchema.extend({ previousValue: z.string() })
|
||||
),
|
||||
deletions: z.array(EnvironmentVariablesSchema),
|
||||
})
|
||||
.strict(),
|
||||
selected: z
|
||||
.object({
|
||||
additions: z.array(EnvironmentVariablesSchema),
|
||||
updations: z.array(
|
||||
EnvironmentVariablesSchema.extend({ previousValue: z.string() })
|
||||
),
|
||||
deletions: z.array(EnvironmentVariablesSchema),
|
||||
})
|
||||
.strict(),
|
||||
})
|
||||
.strict(),
|
||||
})
|
||||
.strict()
|
||||
|
||||
const HoppRESTResponseHeaderSchema = z
|
||||
.object({
|
||||
key: z.string(),
|
||||
value: z.string(),
|
||||
})
|
||||
.strict()
|
||||
|
||||
const HoppRESTResponseSchema = z.discriminatedUnion("type", [
|
||||
z
|
||||
.object({
|
||||
type: z.literal("loading"),
|
||||
// !Versioned entity
|
||||
req: HoppRESTRequestSchema,
|
||||
})
|
||||
.strict(),
|
||||
z
|
||||
.object({
|
||||
type: z.literal("fail"),
|
||||
headers: z.array(HoppRESTResponseHeaderSchema),
|
||||
body: z.instanceof(ArrayBuffer),
|
||||
statusCode: z.number(),
|
||||
meta: z
|
||||
.object({
|
||||
responseSize: z.number(),
|
||||
responseDuration: z.number(),
|
||||
})
|
||||
.strict(),
|
||||
// !Versioned entity
|
||||
req: HoppRESTRequestSchema,
|
||||
})
|
||||
.strict(),
|
||||
z
|
||||
.object({
|
||||
type: z.literal("network_fail"),
|
||||
error: z.unknown(),
|
||||
// !Versioned entity
|
||||
req: HoppRESTRequestSchema,
|
||||
})
|
||||
.strict(),
|
||||
z
|
||||
.object({
|
||||
type: z.literal("script_fail"),
|
||||
error: z.instanceof(Error),
|
||||
})
|
||||
.strict(),
|
||||
z
|
||||
.object({
|
||||
type: z.literal("success"),
|
||||
headers: z.array(HoppRESTResponseHeaderSchema),
|
||||
body: z.instanceof(ArrayBuffer),
|
||||
statusCode: z.number(),
|
||||
meta: z
|
||||
.object({
|
||||
responseSize: z.number(),
|
||||
responseDuration: z.number(),
|
||||
})
|
||||
.strict(),
|
||||
// !Versioned entity
|
||||
req: HoppRESTRequestSchema,
|
||||
})
|
||||
.strict(),
|
||||
])
|
||||
|
||||
const HoppRESTSaveContextSchema = z.nullable(
|
||||
z.discriminatedUnion("originLocation", [
|
||||
z
|
||||
.object({
|
||||
originLocation: z.literal("user-collection"),
|
||||
folderPath: z.string(),
|
||||
requestIndex: z.number(),
|
||||
})
|
||||
.strict(),
|
||||
z
|
||||
.object({
|
||||
originLocation: z.literal("team-collection"),
|
||||
requestID: z.string(),
|
||||
teamID: z.optional(z.string()),
|
||||
collectionID: z.optional(z.string()),
|
||||
})
|
||||
.strict(),
|
||||
])
|
||||
)
|
||||
|
||||
const validRestOperations = [
|
||||
"params",
|
||||
"bodyParams",
|
||||
"headers",
|
||||
"authorization",
|
||||
"preRequestScript",
|
||||
"tests",
|
||||
] as const
|
||||
|
||||
export const REST_TAB_STATE_SCHEMA = z
|
||||
.object({
|
||||
lastActiveTabID: z.string(),
|
||||
orderedDocs: z.array(
|
||||
z.object({
|
||||
tabID: z.string(),
|
||||
doc: z
|
||||
.object({
|
||||
// !Versioned entity
|
||||
request: entityReference(HoppRESTRequest),
|
||||
isDirty: z.boolean(),
|
||||
saveContext: z.optional(HoppRESTSaveContextSchema),
|
||||
response: z.optional(z.nullable(HoppRESTResponseSchema)),
|
||||
testResults: z.optional(z.nullable(HoppTestResultSchema)),
|
||||
responseTabPreference: z.optional(z.string()),
|
||||
optionTabPreference: z.optional(z.enum(validRestOperations)),
|
||||
})
|
||||
.strict(),
|
||||
})
|
||||
),
|
||||
})
|
||||
.strict()
|
||||
Reference in New Issue
Block a user