fix: inspections bugs (#3277)
* fix: environment add bug in inspection * chore: add 127.0.0.1 in url inspection * chore: update browserextension inspection help url * fix: team env not showing bug in selector * chore: rework inspector systems to be reactive * chore: handling tab changes gracefully * refactor: move out url interceptor from the platform * chore: add view function in inspector service to get views into the list * fix: interceptors not kicking in on initial load * fix: don't show no internet connection error unless browser deems so * chore: fix tests --------- Co-authored-by: Andrew Bastin <andrewbastin.k@gmail.com>
This commit is contained in:
@@ -21,7 +21,12 @@
|
|||||||
<label for="value" class="font-semibold min-w-10">{{
|
<label for="value" class="font-semibold min-w-10">{{
|
||||||
t("environment.value")
|
t("environment.value")
|
||||||
}}</label>
|
}}</label>
|
||||||
<input type="text" :value="value" class="input" />
|
<input
|
||||||
|
v-model="editingValue"
|
||||||
|
type="text"
|
||||||
|
class="input"
|
||||||
|
:placeholder="t('environment.value')"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-8 ml-2">
|
<div class="flex items-center space-x-8 ml-2">
|
||||||
<label for="scope" class="font-semibold min-w-10">
|
<label for="scope" class="font-semibold min-w-10">
|
||||||
@@ -105,9 +110,12 @@ watch(
|
|||||||
scope.value = {
|
scope.value = {
|
||||||
type: "global",
|
type: "global",
|
||||||
}
|
}
|
||||||
editingName.value = ""
|
|
||||||
replaceWithVariable.value = false
|
replaceWithVariable.value = false
|
||||||
|
editingName.value = ""
|
||||||
|
editingValue.value = ""
|
||||||
}
|
}
|
||||||
|
editingName.value = props.name
|
||||||
|
editingValue.value = props.value
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -132,6 +140,7 @@ const scope = ref<Scope>({
|
|||||||
const replaceWithVariable = ref(false)
|
const replaceWithVariable = ref(false)
|
||||||
|
|
||||||
const editingName = ref(props.name)
|
const editingName = ref(props.name)
|
||||||
|
const editingValue = ref(props.value)
|
||||||
|
|
||||||
const addEnvironment = async () => {
|
const addEnvironment = async () => {
|
||||||
if (!editingName.value) {
|
if (!editingName.value) {
|
||||||
@@ -141,13 +150,13 @@ const addEnvironment = async () => {
|
|||||||
if (scope.value.type === "global") {
|
if (scope.value.type === "global") {
|
||||||
addGlobalEnvVariable({
|
addGlobalEnvVariable({
|
||||||
key: editingName.value,
|
key: editingName.value,
|
||||||
value: props.value,
|
value: editingValue.value,
|
||||||
})
|
})
|
||||||
toast.success(`${t("environment.updated")}`)
|
toast.success(`${t("environment.updated")}`)
|
||||||
} else if (scope.value.type === "my-environment") {
|
} else if (scope.value.type === "my-environment") {
|
||||||
addEnvironmentVariable(scope.value.index, {
|
addEnvironmentVariable(scope.value.index, {
|
||||||
key: editingName.value,
|
key: editingName.value,
|
||||||
value: props.value,
|
value: editingValue.value,
|
||||||
})
|
})
|
||||||
toast.success(`${t("environment.updated")}`)
|
toast.success(`${t("environment.updated")}`)
|
||||||
} else {
|
} else {
|
||||||
@@ -155,7 +164,7 @@ const addEnvironment = async () => {
|
|||||||
...scope.value.environment.environment.variables,
|
...scope.value.environment.environment.variables,
|
||||||
{
|
{
|
||||||
key: editingName.value,
|
key: editingName.value,
|
||||||
value: props.value,
|
value: editingValue.value,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
await pipe(
|
await pipe(
|
||||||
@@ -182,7 +191,7 @@ const addEnvironment = async () => {
|
|||||||
//replace the currenttab endpoint containing the value in the text with variablename
|
//replace the currenttab endpoint containing the value in the text with variablename
|
||||||
currentActiveTab.value.document.request.endpoint =
|
currentActiveTab.value.document.request.endpoint =
|
||||||
currentActiveTab.value.document.request.endpoint.replace(
|
currentActiveTab.value.document.request.endpoint.replace(
|
||||||
props.value,
|
editingValue.value,
|
||||||
variableName
|
variableName
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
@keyup.escape="hide()"
|
@keyup.escape="hide()"
|
||||||
>
|
>
|
||||||
<HoppSmartItem
|
<HoppSmartItem
|
||||||
|
v-if="!isScopeSelector"
|
||||||
:label="`${t('environment.no_environment')}`"
|
:label="`${t('environment.no_environment')}`"
|
||||||
:info-icon="
|
:info-icon="
|
||||||
selectedEnvironmentIndex.type === 'NO_ENV_SELECTED'
|
selectedEnvironmentIndex.type === 'NO_ENV_SELECTED'
|
||||||
@@ -48,6 +49,21 @@
|
|||||||
}
|
}
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
<HoppSmartItem
|
||||||
|
v-else-if="isScopeSelector && modelValue"
|
||||||
|
:label="t('environment.global')"
|
||||||
|
:icon="IconGlobe"
|
||||||
|
:info-icon="modelValue.type === 'global' ? IconCheck : undefined"
|
||||||
|
:active-info-icon="modelValue.type === 'global'"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
$emit('update:modelValue', {
|
||||||
|
type: 'global',
|
||||||
|
})
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
<HoppSmartTabs
|
<HoppSmartTabs
|
||||||
v-model="selectedEnvTab"
|
v-model="selectedEnvTab"
|
||||||
:styles="`sticky overflow-x-auto my-2 border border-divider rounded flex-shrink-0 z-0 top-0 bg-primary ${
|
:styles="`sticky overflow-x-auto my-2 border border-divider rounded flex-shrink-0 z-0 top-0 bg-primary ${
|
||||||
@@ -66,14 +82,14 @@
|
|||||||
:key="`gen-${index}`"
|
:key="`gen-${index}`"
|
||||||
:icon="IconLayers"
|
:icon="IconLayers"
|
||||||
:label="gen.name"
|
:label="gen.name"
|
||||||
:info-icon="index === selectedEnv.index ? IconCheck : undefined"
|
:info-icon="isEnvActive(index) ? IconCheck : undefined"
|
||||||
:active-info-icon="index === selectedEnv.index"
|
:active-info-icon="isEnvActive(index)"
|
||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
selectedEnvironmentIndex = {
|
handleEnvironmentChange(index, {
|
||||||
type: 'MY_ENV',
|
type: 'my-environment',
|
||||||
index: index,
|
environment: gen,
|
||||||
}
|
})
|
||||||
hide()
|
hide()
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
@@ -113,18 +129,14 @@
|
|||||||
:key="`gen-team-${index}`"
|
:key="`gen-team-${index}`"
|
||||||
:icon="IconLayers"
|
:icon="IconLayers"
|
||||||
:label="gen.environment.name"
|
:label="gen.environment.name"
|
||||||
:info-icon="
|
:info-icon="isEnvActive(gen.id) ? IconCheck : undefined"
|
||||||
gen.id === selectedEnv.teamEnvID ? IconCheck : undefined
|
:active-info-icon="isEnvActive(gen.id)"
|
||||||
"
|
|
||||||
:active-info-icon="gen.id === selectedEnv.teamEnvID"
|
|
||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
selectedEnvironmentIndex = {
|
handleEnvironmentChange(index, {
|
||||||
type: 'TEAM_ENV',
|
type: 'team-environment',
|
||||||
teamEnvID: gen.id,
|
environment: gen,
|
||||||
teamID: gen.teamID,
|
})
|
||||||
environment: gen.environment,
|
|
||||||
}
|
|
||||||
hide()
|
hide()
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
@@ -285,6 +297,7 @@ import IconCheck from "~icons/lucide/check"
|
|||||||
import IconLayers from "~icons/lucide/layers"
|
import IconLayers from "~icons/lucide/layers"
|
||||||
import IconEye from "~icons/lucide/eye"
|
import IconEye from "~icons/lucide/eye"
|
||||||
import IconEdit from "~icons/lucide/edit"
|
import IconEdit from "~icons/lucide/edit"
|
||||||
|
import IconGlobe from "~icons/lucide/globe"
|
||||||
import { TippyComponent } from "vue-tippy"
|
import { TippyComponent } from "vue-tippy"
|
||||||
import { useI18n } from "~/composables/i18n"
|
import { useI18n } from "~/composables/i18n"
|
||||||
import { GQLError } from "~/helpers/backend/GQLClient"
|
import { GQLError } from "~/helpers/backend/GQLClient"
|
||||||
@@ -295,11 +308,39 @@ import {
|
|||||||
selectedEnvironmentIndex$,
|
selectedEnvironmentIndex$,
|
||||||
setSelectedEnvironmentIndex,
|
setSelectedEnvironmentIndex,
|
||||||
} from "~/newstore/environments"
|
} from "~/newstore/environments"
|
||||||
import { workspaceStatus$ } from "~/newstore/workspace"
|
import { changeWorkspace, workspaceStatus$ } from "~/newstore/workspace"
|
||||||
import TeamEnvironmentAdapter from "~/helpers/teams/TeamEnvironmentAdapter"
|
import TeamEnvironmentAdapter from "~/helpers/teams/TeamEnvironmentAdapter"
|
||||||
import { useColorMode } from "@composables/theming"
|
import { useColorMode } from "@composables/theming"
|
||||||
import { breakpointsTailwind, useBreakpoints } from "@vueuse/core"
|
import { breakpointsTailwind, useBreakpoints } from "@vueuse/core"
|
||||||
import { invokeAction } from "~/helpers/actions"
|
import { invokeAction } from "~/helpers/actions"
|
||||||
|
import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment"
|
||||||
|
import { Environment } from "@hoppscotch/data"
|
||||||
|
import { onMounted } from "vue"
|
||||||
|
import { onLoggedIn } from "~/composables/auth"
|
||||||
|
import TeamListAdapter from "~/helpers/teams/TeamListAdapter"
|
||||||
|
import { useLocalState } from "~/newstore/localstate"
|
||||||
|
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
||||||
|
|
||||||
|
type Scope =
|
||||||
|
| {
|
||||||
|
type: "global"
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "my-environment"
|
||||||
|
environment: Environment
|
||||||
|
index: number
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "team-environment"
|
||||||
|
environment: TeamEnvironment
|
||||||
|
}
|
||||||
|
const props = defineProps<{
|
||||||
|
isScopeSelector?: boolean
|
||||||
|
modelValue?: Scope
|
||||||
|
}>()
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: "update:modelValue", data: Scope): void
|
||||||
|
}>()
|
||||||
|
|
||||||
const breakpoints = useBreakpoints(breakpointsTailwind)
|
const breakpoints = useBreakpoints(breakpointsTailwind)
|
||||||
const mdAndLarger = breakpoints.greater("md")
|
const mdAndLarger = breakpoints.greater("md")
|
||||||
@@ -314,6 +355,38 @@ const myEnvironments = useReadonlyStream(environments$, [])
|
|||||||
|
|
||||||
const workspace = useReadonlyStream(workspaceStatus$, { type: "personal" })
|
const workspace = useReadonlyStream(workspaceStatus$, { type: "personal" })
|
||||||
|
|
||||||
|
// TeamList-Adapter
|
||||||
|
const teamListAdapter = new TeamListAdapter(true)
|
||||||
|
const myTeams = useReadonlyStream(teamListAdapter.teamList$, null)
|
||||||
|
const teamListFetched = ref(false)
|
||||||
|
const REMEMBERED_TEAM_ID = useLocalState("REMEMBERED_TEAM_ID")
|
||||||
|
|
||||||
|
onLoggedIn(() => {
|
||||||
|
!teamListAdapter.isInitialized && teamListAdapter.initialize()
|
||||||
|
})
|
||||||
|
|
||||||
|
const switchToTeamWorkspace = (team: GetMyTeamsQuery["myTeams"][number]) => {
|
||||||
|
REMEMBERED_TEAM_ID.value = team.id
|
||||||
|
changeWorkspace({
|
||||||
|
teamID: team.id,
|
||||||
|
teamName: team.name,
|
||||||
|
type: "team",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
watch(
|
||||||
|
() => myTeams.value,
|
||||||
|
(newTeams) => {
|
||||||
|
if (newTeams && !teamListFetched.value) {
|
||||||
|
teamListFetched.value = true
|
||||||
|
if (REMEMBERED_TEAM_ID.value) {
|
||||||
|
const team = newTeams.find((t) => t.id === REMEMBERED_TEAM_ID.value)
|
||||||
|
if (team) switchToTeamWorkspace(team)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// TeamEnv List Adapter
|
||||||
const teamEnvListAdapter = new TeamEnvironmentAdapter(undefined)
|
const teamEnvListAdapter = new TeamEnvironmentAdapter(undefined)
|
||||||
const teamListLoading = useReadonlyStream(teamEnvListAdapter.loading$, false)
|
const teamListLoading = useReadonlyStream(teamEnvListAdapter.loading$, false)
|
||||||
const teamAdapterError = useReadonlyStream(teamEnvListAdapter.error$, null)
|
const teamAdapterError = useReadonlyStream(teamEnvListAdapter.error$, null)
|
||||||
@@ -322,6 +395,70 @@ const teamEnvironmentList = useReadonlyStream(
|
|||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const handleEnvironmentChange = (
|
||||||
|
index: number,
|
||||||
|
env?:
|
||||||
|
| {
|
||||||
|
type: "my-environment"
|
||||||
|
environment: Environment
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "team-environment"
|
||||||
|
environment: TeamEnvironment
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
if (props.isScopeSelector && env) {
|
||||||
|
if (env.type === "my-environment") {
|
||||||
|
emit("update:modelValue", {
|
||||||
|
type: "my-environment",
|
||||||
|
environment: env.environment,
|
||||||
|
index,
|
||||||
|
})
|
||||||
|
} else if (env.type === "team-environment") {
|
||||||
|
emit("update:modelValue", {
|
||||||
|
type: "team-environment",
|
||||||
|
environment: env.environment,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (env && env.type === "my-environment") {
|
||||||
|
selectedEnvironmentIndex.value = {
|
||||||
|
type: "MY_ENV",
|
||||||
|
index,
|
||||||
|
}
|
||||||
|
} else if (env && env.type === "team-environment") {
|
||||||
|
selectedEnvironmentIndex.value = {
|
||||||
|
type: "TEAM_ENV",
|
||||||
|
teamEnvID: env.environment.id,
|
||||||
|
teamID: env.environment.teamID,
|
||||||
|
environment: env.environment.environment,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const isEnvActive = (id: string | number) => {
|
||||||
|
if (props.isScopeSelector) {
|
||||||
|
if (props.modelValue?.type === "my-environment") {
|
||||||
|
return props.modelValue.index === id
|
||||||
|
} else if (props.modelValue?.type === "team-environment") {
|
||||||
|
return (
|
||||||
|
props.modelValue?.type === "team-environment" &&
|
||||||
|
props.modelValue.environment &&
|
||||||
|
props.modelValue.environment.id === id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (selectedEnvironmentIndex.value.type === "MY_ENV") {
|
||||||
|
return selectedEnv.value.index === id
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
selectedEnvironmentIndex.value.type === "TEAM_ENV" &&
|
||||||
|
selectedEnv.value.teamEnvID === id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const selectedEnvironmentIndex = useStream(
|
const selectedEnvironmentIndex = useStream(
|
||||||
selectedEnvironmentIndex$,
|
selectedEnvironmentIndex$,
|
||||||
{ type: "NO_ENV_SELECTED" },
|
{ type: "NO_ENV_SELECTED" },
|
||||||
@@ -349,34 +486,90 @@ watch(
|
|||||||
)
|
)
|
||||||
|
|
||||||
const selectedEnv = computed(() => {
|
const selectedEnv = computed(() => {
|
||||||
if (selectedEnvironmentIndex.value.type === "MY_ENV") {
|
if (props.isScopeSelector) {
|
||||||
const environment =
|
if (props.modelValue?.type === "my-environment") {
|
||||||
myEnvironments.value[selectedEnvironmentIndex.value.index]
|
return {
|
||||||
return {
|
type: "MY_ENV",
|
||||||
type: "MY_ENV",
|
index: props.modelValue.index,
|
||||||
index: selectedEnvironmentIndex.value.index,
|
name: props.modelValue.environment?.name,
|
||||||
name: environment.name,
|
}
|
||||||
variables: environment.variables,
|
} else if (props.modelValue?.type === "team-environment") {
|
||||||
}
|
|
||||||
} else if (selectedEnvironmentIndex.value.type === "TEAM_ENV") {
|
|
||||||
const teamEnv = teamEnvironmentList.value.find(
|
|
||||||
(env) =>
|
|
||||||
env.id ===
|
|
||||||
(selectedEnvironmentIndex.value.type === "TEAM_ENV" &&
|
|
||||||
selectedEnvironmentIndex.value.teamEnvID)
|
|
||||||
)
|
|
||||||
if (teamEnv) {
|
|
||||||
return {
|
return {
|
||||||
type: "TEAM_ENV",
|
type: "TEAM_ENV",
|
||||||
name: teamEnv.environment.name,
|
name: props.modelValue.environment.environment.name,
|
||||||
teamEnvID: selectedEnvironmentIndex.value.teamEnvID,
|
teamEnvID: props.modelValue.environment.id,
|
||||||
variables: teamEnv.environment.variables,
|
}
|
||||||
|
} else {
|
||||||
|
return { type: "global", name: "Global" }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (selectedEnvironmentIndex.value.type === "MY_ENV") {
|
||||||
|
const environment =
|
||||||
|
myEnvironments.value[selectedEnvironmentIndex.value.index]
|
||||||
|
return {
|
||||||
|
type: "MY_ENV",
|
||||||
|
index: selectedEnvironmentIndex.value.index,
|
||||||
|
name: environment.name,
|
||||||
|
variables: environment.variables,
|
||||||
|
}
|
||||||
|
} else if (selectedEnvironmentIndex.value.type === "TEAM_ENV") {
|
||||||
|
const teamEnv = teamEnvironmentList.value.find(
|
||||||
|
(env) =>
|
||||||
|
env.id ===
|
||||||
|
(selectedEnvironmentIndex.value.type === "TEAM_ENV" &&
|
||||||
|
selectedEnvironmentIndex.value.teamEnvID)
|
||||||
|
)
|
||||||
|
if (teamEnv) {
|
||||||
|
return {
|
||||||
|
type: "TEAM_ENV",
|
||||||
|
name: teamEnv.environment.name,
|
||||||
|
teamEnvID: selectedEnvironmentIndex.value.teamEnvID,
|
||||||
|
variables: teamEnv.environment.variables,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return { type: "NO_ENV_SELECTED" }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return { type: "NO_ENV_SELECTED" }
|
return { type: "NO_ENV_SELECTED" }
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
return { type: "NO_ENV_SELECTED" }
|
})
|
||||||
|
|
||||||
|
// Set the selected environment as initial scope value
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.isScopeSelector) {
|
||||||
|
if (
|
||||||
|
selectedEnvironmentIndex.value.type === "MY_ENV" &&
|
||||||
|
selectedEnvironmentIndex.value.index !== undefined
|
||||||
|
) {
|
||||||
|
emit("update:modelValue", {
|
||||||
|
type: "my-environment",
|
||||||
|
environment: myEnvironments.value[selectedEnvironmentIndex.value.index],
|
||||||
|
index: selectedEnvironmentIndex.value.index,
|
||||||
|
})
|
||||||
|
} else if (
|
||||||
|
selectedEnvironmentIndex.value.type === "TEAM_ENV" &&
|
||||||
|
selectedEnvironmentIndex.value.teamEnvID &&
|
||||||
|
teamEnvironmentList.value &&
|
||||||
|
teamEnvironmentList.value.length > 0
|
||||||
|
) {
|
||||||
|
const teamEnv = teamEnvironmentList.value.find(
|
||||||
|
(env) =>
|
||||||
|
env.id ===
|
||||||
|
(selectedEnvironmentIndex.value.type === "TEAM_ENV" &&
|
||||||
|
selectedEnvironmentIndex.value.teamEnvID)
|
||||||
|
)
|
||||||
|
if (teamEnv) {
|
||||||
|
emit("update:modelValue", {
|
||||||
|
type: "team-environment",
|
||||||
|
environment: teamEnv,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emit("update:modelValue", {
|
||||||
|
type: "global",
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -300,7 +300,7 @@ watch(
|
|||||||
|
|
||||||
defineActionHandler("modals.environment.add", ({ envName, variableName }) => {
|
defineActionHandler("modals.environment.add", ({ envName, variableName }) => {
|
||||||
editingVariableName.value = envName
|
editingVariableName.value = envName
|
||||||
editingVariableValue.value = variableName
|
if (variableName) editingVariableValue.value = variableName
|
||||||
displayModalNew(true)
|
displayModalNew(true)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -508,30 +508,17 @@ const changeTab = (tab: ComputedHeader["source"]) => {
|
|||||||
|
|
||||||
const inspectionService = useService(InspectionService)
|
const inspectionService = useService(InspectionService)
|
||||||
|
|
||||||
const allTabResults = inspectionService.tabs
|
const headerKeyResults = inspectionService.getResultViewFor(
|
||||||
|
currentTabID.value,
|
||||||
|
(result) =>
|
||||||
|
result.locations.type === "header" && result.locations.position === "key"
|
||||||
|
)
|
||||||
|
|
||||||
const headerKeyResults = computed(() => {
|
const headerValueResults = inspectionService.getResultViewFor(
|
||||||
return (
|
currentTabID.value,
|
||||||
allTabResults.value
|
(result) =>
|
||||||
.get(currentTabID.value)
|
result.locations.type === "header" && result.locations.position === "value"
|
||||||
.filter(
|
)
|
||||||
(result) =>
|
|
||||||
result.locations.type === "header" &&
|
|
||||||
result.locations.position === "key"
|
|
||||||
) ?? []
|
|
||||||
)
|
|
||||||
})
|
|
||||||
const headerValueResults = computed(() => {
|
|
||||||
return (
|
|
||||||
allTabResults.value
|
|
||||||
.get(currentTabID.value)
|
|
||||||
.filter(
|
|
||||||
(result) =>
|
|
||||||
result.locations.type === "header" &&
|
|
||||||
result.locations.position === "value"
|
|
||||||
) ?? []
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const getInspectorResult = (results: InspectorResult[], index: number) => {
|
const getInspectorResult = (results: InspectorResult[], index: number) => {
|
||||||
return results.filter((result) => {
|
return results.filter((result) => {
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ import IconCheckCircle from "~icons/lucide/check-circle"
|
|||||||
import IconCircle from "~icons/lucide/circle"
|
import IconCircle from "~icons/lucide/circle"
|
||||||
import IconTrash from "~icons/lucide/trash"
|
import IconTrash from "~icons/lucide/trash"
|
||||||
import IconWrapText from "~icons/lucide/wrap-text"
|
import IconWrapText from "~icons/lucide/wrap-text"
|
||||||
import { computed, reactive, ref, watch } from "vue"
|
import { reactive, ref, watch } from "vue"
|
||||||
import { flow, pipe } from "fp-ts/function"
|
import { flow, pipe } from "fp-ts/function"
|
||||||
import * as O from "fp-ts/Option"
|
import * as O from "fp-ts/Option"
|
||||||
import * as A from "fp-ts/Array"
|
import * as A from "fp-ts/Array"
|
||||||
@@ -409,30 +409,18 @@ const clearContent = () => {
|
|||||||
|
|
||||||
const inspectionService = useService(InspectionService)
|
const inspectionService = useService(InspectionService)
|
||||||
|
|
||||||
const allTabResults = inspectionService.tabs
|
const parameterKeyResults = inspectionService.getResultViewFor(
|
||||||
|
currentTabID.value,
|
||||||
|
(result) =>
|
||||||
|
result.locations.type === "parameter" && result.locations.position === "key"
|
||||||
|
)
|
||||||
|
|
||||||
const parameterKeyResults = computed(() => {
|
const parameterValueResults = inspectionService.getResultViewFor(
|
||||||
return (
|
currentTabID.value,
|
||||||
allTabResults.value
|
(result) =>
|
||||||
.get(currentTabID.value)
|
result.locations.type === "parameter" &&
|
||||||
.filter(
|
result.locations.position === "value"
|
||||||
(result) =>
|
)
|
||||||
result.locations.type === "parameter" &&
|
|
||||||
result.locations.position === "key"
|
|
||||||
) ?? []
|
|
||||||
)
|
|
||||||
})
|
|
||||||
const parameterValueResults = computed(() => {
|
|
||||||
return (
|
|
||||||
allTabResults.value
|
|
||||||
.get(currentTabID.value)
|
|
||||||
.filter(
|
|
||||||
(result) =>
|
|
||||||
result.locations.type === "parameter" &&
|
|
||||||
result.locations.position === "value"
|
|
||||||
) ?? []
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const getInspectorResult = (results: InspectorResult[], index: number) => {
|
const getInspectorResult = (results: InspectorResult[], index: number) => {
|
||||||
return results.filter((result) => {
|
return results.filter((result) => {
|
||||||
|
|||||||
@@ -642,9 +642,5 @@ const COLUMN_LAYOUT = useSetting("COLUMN_LAYOUT")
|
|||||||
|
|
||||||
const inspectionService = useService(InspectionService)
|
const inspectionService = useService(InspectionService)
|
||||||
|
|
||||||
const allTabResults = inspectionService.tabs
|
const tabResults = inspectionService.getResultViewFor(currentTabID.value)
|
||||||
|
|
||||||
const tabResults = computed(() => {
|
|
||||||
return allTabResults.value.get(currentTabID.value) ?? []
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -145,13 +145,8 @@ const statusCategory = computed(() => {
|
|||||||
|
|
||||||
const inspectionService = useService(InspectionService)
|
const inspectionService = useService(InspectionService)
|
||||||
|
|
||||||
const allTabResults = inspectionService.tabs
|
const tabResults = inspectionService.getResultViewFor(
|
||||||
|
currentTabID.value,
|
||||||
const tabResults = computed(() => {
|
(result) => result.locations.type === "response"
|
||||||
return (
|
)
|
||||||
allTabResults.value
|
|
||||||
.get(currentTabID.value)
|
|
||||||
?.filter((result) => result.locations.type === "response") ?? []
|
|
||||||
)
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { HoppRESTResponse } from "../types/HoppRESTResponse"
|
|||||||
import { getDefaultRESTRequest } from "./default"
|
import { getDefaultRESTRequest } from "./default"
|
||||||
import { HoppTestResult } from "../types/HoppTestResult"
|
import { HoppTestResult } from "../types/HoppTestResult"
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
|
import { nextTick } from "vue"
|
||||||
|
|
||||||
export type HoppRESTTab = {
|
export type HoppRESTTab = {
|
||||||
id: string
|
id: string
|
||||||
@@ -178,7 +179,9 @@ export function closeTab(tabID: string) {
|
|||||||
|
|
||||||
tabOrdering.value.splice(tabOrdering.value.indexOf(tabID), 1)
|
tabOrdering.value.splice(tabOrdering.value.indexOf(tabID), 1)
|
||||||
|
|
||||||
tabMap.delete(tabID)
|
nextTick(() => {
|
||||||
|
tabMap.delete(tabID)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function closeOtherTabs(tabID: string) {
|
export function closeOtherTabs(tabID: string) {
|
||||||
|
|||||||
@@ -94,7 +94,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, onMounted, onBeforeUnmount, onBeforeMount, watch } from "vue"
|
import { ref, onMounted, onBeforeUnmount, onBeforeMount } from "vue"
|
||||||
import { safelyExtractRESTRequest } from "@hoppscotch/data"
|
import { safelyExtractRESTRequest } from "@hoppscotch/data"
|
||||||
import { translateExtURLParams } from "~/helpers/RESTExtURLParams"
|
import { translateExtURLParams } from "~/helpers/RESTExtURLParams"
|
||||||
import { useRoute } from "vue-router"
|
import { useRoute } from "vue-router"
|
||||||
@@ -140,7 +140,6 @@ import { useService } from "dioc/vue"
|
|||||||
import { InspectionService } from "~/services/inspection"
|
import { InspectionService } from "~/services/inspection"
|
||||||
import { HeaderInspectorService } from "~/services/inspection/inspectors/header.inspector"
|
import { HeaderInspectorService } from "~/services/inspection/inspectors/header.inspector"
|
||||||
import { EnvironmentInspectorService } from "~/services/inspection/inspectors/environment.inspector"
|
import { EnvironmentInspectorService } from "~/services/inspection/inspectors/environment.inspector"
|
||||||
import { URLInspectorService } from "~/services/inspection/inspectors/url.inspector"
|
|
||||||
import { ResponseInspectorService } from "~/services/inspection/inspectors/response.inspector"
|
import { ResponseInspectorService } from "~/services/inspection/inspectors/response.inspector"
|
||||||
|
|
||||||
const savingRequest = ref(false)
|
const savingRequest = ref(false)
|
||||||
@@ -215,6 +214,8 @@ const sortTabs = (e: { oldIndex: number; newIndex: number }) => {
|
|||||||
updateTabOrdering(e.oldIndex, e.newIndex)
|
updateTabOrdering(e.oldIndex, e.newIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const inspectionService = useService(InspectionService)
|
||||||
|
|
||||||
const removeTab = (tabID: string) => {
|
const removeTab = (tabID: string) => {
|
||||||
const tabState = getTabRef(tabID).value
|
const tabState = getTabRef(tabID).value
|
||||||
|
|
||||||
@@ -465,17 +466,10 @@ defineActionHandler("request.duplicate-tab", ({ tabID }) => {
|
|||||||
duplicateTab(tabID)
|
duplicateTab(tabID)
|
||||||
})
|
})
|
||||||
|
|
||||||
const inspectionService = useService(InspectionService)
|
|
||||||
useService(HeaderInspectorService)
|
useService(HeaderInspectorService)
|
||||||
useService(EnvironmentInspectorService)
|
useService(EnvironmentInspectorService)
|
||||||
useService(URLInspectorService)
|
|
||||||
useService(ResponseInspectorService)
|
useService(ResponseInspectorService)
|
||||||
|
for (const inspectorDef of platform.additionalInspectors ?? []) {
|
||||||
watch(
|
useService(inspectorDef.service)
|
||||||
() => currentTabID.value,
|
}
|
||||||
() => {
|
|
||||||
inspectionService.initializeTabInspectors()
|
|
||||||
},
|
|
||||||
{ immediate: true }
|
|
||||||
)
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { TabStatePlatformDef } from "./tab"
|
|||||||
import { AnalyticsPlatformDef } from "./analytics"
|
import { AnalyticsPlatformDef } from "./analytics"
|
||||||
import { InterceptorsPlatformDef } from "./interceptors"
|
import { InterceptorsPlatformDef } from "./interceptors"
|
||||||
import { HoppModule } from "~/modules"
|
import { HoppModule } from "~/modules"
|
||||||
|
import { InspectorsPlatformDef } from "./inspectors"
|
||||||
|
|
||||||
export type PlatformDef = {
|
export type PlatformDef = {
|
||||||
ui?: UIPlatformDef
|
ui?: UIPlatformDef
|
||||||
@@ -22,6 +23,7 @@ export type PlatformDef = {
|
|||||||
tabState: TabStatePlatformDef
|
tabState: TabStatePlatformDef
|
||||||
}
|
}
|
||||||
interceptors: InterceptorsPlatformDef
|
interceptors: InterceptorsPlatformDef
|
||||||
|
additionalInspectors?: InspectorsPlatformDef
|
||||||
platformFeatureFlags: {
|
platformFeatureFlags: {
|
||||||
exportAsGIST: boolean
|
exportAsGIST: boolean
|
||||||
}
|
}
|
||||||
|
|||||||
16
packages/hoppscotch-common/src/platform/inspectors.ts
Normal file
16
packages/hoppscotch-common/src/platform/inspectors.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { Service } from "dioc"
|
||||||
|
import { Inspector } from "~/services/inspection"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines an added interceptor by the platform
|
||||||
|
*/
|
||||||
|
export type PlatformInspectorsDef = {
|
||||||
|
// We are keeping this as the only mode for now
|
||||||
|
// So that if we choose to add other modes, we can do without breaking
|
||||||
|
type: "service"
|
||||||
|
service: typeof Service<unknown> & { ID: string } & {
|
||||||
|
new (): Service & Inspector
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type InspectorsPlatformDef = PlatformInspectorsDef[]
|
||||||
@@ -1,15 +1,17 @@
|
|||||||
import { TestContainer } from "dioc/testing"
|
import { TestContainer } from "dioc/testing"
|
||||||
import { describe, expect, it, vi } from "vitest"
|
import { describe, expect, it, vi } from "vitest"
|
||||||
import { URLInspectorService } from "../url.inspector"
|
import { ExtensionInspectorService } from "../extension.inspector"
|
||||||
import { InspectionService } from "../../index"
|
import { InspectionService } from "~/services/inspection"
|
||||||
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
||||||
|
import { ref } from "vue"
|
||||||
|
import { ExtensionInterceptorService } from "~/platform/std/interceptors/extension"
|
||||||
|
|
||||||
vi.mock("~/modules/i18n", () => ({
|
vi.mock("~/modules/i18n", () => ({
|
||||||
__esModule: true,
|
__esModule: true,
|
||||||
getI18n: () => (x: string) => x,
|
getI18n: () => (x: string) => x,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
describe("URLInspectorService", () => {
|
describe("ExtensionInspectorService", () => {
|
||||||
it("registers with the inspection service upon initialization", () => {
|
it("registers with the inspection service upon initialization", () => {
|
||||||
const container = new TestContainer()
|
const container = new TestContainer()
|
||||||
|
|
||||||
@@ -19,7 +21,7 @@ describe("URLInspectorService", () => {
|
|||||||
registerInspector: registerInspectorFn,
|
registerInspector: registerInspectorFn,
|
||||||
})
|
})
|
||||||
|
|
||||||
const urlInspector = container.bind(URLInspectorService)
|
const urlInspector = container.bind(ExtensionInspectorService)
|
||||||
|
|
||||||
expect(registerInspectorFn).toHaveBeenCalledOnce()
|
expect(registerInspectorFn).toHaveBeenCalledOnce()
|
||||||
expect(registerInspectorFn).toHaveBeenCalledWith(urlInspector)
|
expect(registerInspectorFn).toHaveBeenCalledWith(urlInspector)
|
||||||
@@ -28,55 +30,57 @@ describe("URLInspectorService", () => {
|
|||||||
describe("getInspectorFor", () => {
|
describe("getInspectorFor", () => {
|
||||||
it("should return an inspector result when localhost is in URL and extension is not available", () => {
|
it("should return an inspector result when localhost is in URL and extension is not available", () => {
|
||||||
const container = new TestContainer()
|
const container = new TestContainer()
|
||||||
const urlInspector = container.bind(URLInspectorService)
|
const urlInspector = container.bind(ExtensionInspectorService)
|
||||||
|
|
||||||
const req = {
|
const req = ref({
|
||||||
...getDefaultRESTRequest(),
|
...getDefaultRESTRequest(),
|
||||||
endpoint: "http://localhost:8000/api/data",
|
endpoint: "http://localhost:8000/api/data",
|
||||||
}
|
})
|
||||||
|
|
||||||
const result = urlInspector.getInspectorFor(req)
|
const result = urlInspector.getInspections(req)
|
||||||
|
|
||||||
expect(result).toContainEqual(
|
expect(result.value).toContainEqual(
|
||||||
expect.objectContaining({ id: "url", isApplicable: true })
|
expect.objectContaining({ id: "url", isApplicable: true })
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should not return an inspector result when localhost is not in URL", () => {
|
it("should not return an inspector result when localhost is not in URL", () => {
|
||||||
const container = new TestContainer()
|
const container = new TestContainer()
|
||||||
const urlInspector = container.bind(URLInspectorService)
|
|
||||||
|
|
||||||
const req = {
|
container.bindMock(ExtensionInterceptorService, {
|
||||||
|
extensionStatus: ref("unknown-origin" as const),
|
||||||
|
})
|
||||||
|
|
||||||
|
const urlInspector = container.bind(ExtensionInspectorService)
|
||||||
|
|
||||||
|
const req = ref({
|
||||||
...getDefaultRESTRequest(),
|
...getDefaultRESTRequest(),
|
||||||
endpoint: "http://example.com/api/data",
|
endpoint: "http://example.com/api/data",
|
||||||
}
|
})
|
||||||
|
|
||||||
const result = urlInspector.getInspectorFor(req)
|
const result = urlInspector.getInspections(req)
|
||||||
|
|
||||||
expect(result).toHaveLength(0)
|
expect(result.value).toHaveLength(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should add the correct text to the results when extension is not installed", () => {
|
it("should add the correct text to the results when extension is not installed", () => {
|
||||||
vi.mock("~/newstore/HoppExtension", async () => {
|
|
||||||
const { BehaviorSubject }: any = await vi.importActual("rxjs")
|
|
||||||
|
|
||||||
return {
|
|
||||||
__esModule: true,
|
|
||||||
extensionStatus$: new BehaviorSubject("waiting"),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const container = new TestContainer()
|
const container = new TestContainer()
|
||||||
const urlInspector = container.bind(URLInspectorService)
|
|
||||||
|
|
||||||
const req = {
|
container.bindMock(ExtensionInterceptorService, {
|
||||||
|
extensionStatus: ref("waiting" as const),
|
||||||
|
})
|
||||||
|
|
||||||
|
const urlInspector = container.bind(ExtensionInspectorService)
|
||||||
|
|
||||||
|
const req = ref({
|
||||||
...getDefaultRESTRequest(),
|
...getDefaultRESTRequest(),
|
||||||
endpoint: "http://localhost:8000/api/data",
|
endpoint: "http://localhost:8000/api/data",
|
||||||
}
|
})
|
||||||
|
|
||||||
const result = urlInspector.getInspectorFor(req)
|
const result = urlInspector.getInspections(req)
|
||||||
|
|
||||||
expect(result).toHaveLength(1)
|
expect(result.value).toHaveLength(1)
|
||||||
expect(result[0]).toMatchObject({
|
expect(result.value[0]).toMatchObject({
|
||||||
text: { type: "text", text: "inspections.url.extension_not_installed" },
|
text: { type: "text", text: "inspections.url.extension_not_installed" },
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
import { Service } from "dioc"
|
||||||
|
import {
|
||||||
|
InspectionService,
|
||||||
|
Inspector,
|
||||||
|
InspectorResult,
|
||||||
|
} from "~/services/inspection"
|
||||||
|
import { getI18n } from "~/modules/i18n"
|
||||||
|
import { HoppRESTRequest } from "@hoppscotch/data"
|
||||||
|
import { computed, markRaw } from "vue"
|
||||||
|
import IconAlertTriangle from "~icons/lucide/alert-triangle"
|
||||||
|
import { Ref } from "vue"
|
||||||
|
import { InterceptorService } from "~/services/interceptor.service"
|
||||||
|
import { ExtensionInterceptorService } from "~/platform/std/interceptors/extension"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This inspector is responsible for inspecting the URL of a request.
|
||||||
|
* It checks if the URL contains localhost and if the extension is installed.
|
||||||
|
* It also provides an action to enable the extension.
|
||||||
|
*
|
||||||
|
* NOTE: Initializing this service registers it as a inspector with the Inspection Service.
|
||||||
|
*/
|
||||||
|
export class ExtensionInspectorService extends Service implements Inspector {
|
||||||
|
public static readonly ID = "EXTENSION_INSPECTOR_SERVICE"
|
||||||
|
|
||||||
|
private t = getI18n()
|
||||||
|
|
||||||
|
public readonly inspectorID = "extension"
|
||||||
|
|
||||||
|
private readonly interceptorService = this.bind(InterceptorService)
|
||||||
|
private readonly extensionService = this.bind(ExtensionInterceptorService)
|
||||||
|
|
||||||
|
private readonly inspection = this.bind(InspectionService)
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
|
||||||
|
this.inspection.registerInspector(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
getInspections(req: Readonly<Ref<HoppRESTRequest>>) {
|
||||||
|
const currentExtensionStatus = this.extensionService.extensionStatus
|
||||||
|
|
||||||
|
const isExtensionInstalled = computed(
|
||||||
|
() => currentExtensionStatus.value === "available"
|
||||||
|
)
|
||||||
|
|
||||||
|
const EXTENSIONS_ENABLED = computed(
|
||||||
|
() => this.interceptorService.currentInterceptorID.value === "extension"
|
||||||
|
)
|
||||||
|
|
||||||
|
return computed(() => {
|
||||||
|
const results: InspectorResult[] = []
|
||||||
|
|
||||||
|
const url = req.value.endpoint
|
||||||
|
const localHostURLs = ["localhost", "127.0.0.1"]
|
||||||
|
|
||||||
|
const isContainLocalhost = localHostURLs.some((host) =>
|
||||||
|
url.includes(host)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
isContainLocalhost &&
|
||||||
|
(!EXTENSIONS_ENABLED.value || !isExtensionInstalled.value)
|
||||||
|
) {
|
||||||
|
let text
|
||||||
|
|
||||||
|
if (!isExtensionInstalled.value) {
|
||||||
|
if (currentExtensionStatus.value === "unknown-origin") {
|
||||||
|
text = this.t("inspections.url.extension_unknown_origin")
|
||||||
|
} else {
|
||||||
|
text = this.t("inspections.url.extension_not_installed")
|
||||||
|
}
|
||||||
|
} else if (!EXTENSIONS_ENABLED.value) {
|
||||||
|
text = this.t("inspections.url.extention_not_enabled")
|
||||||
|
} else {
|
||||||
|
text = this.t("inspections.url.localhost")
|
||||||
|
}
|
||||||
|
|
||||||
|
results.push({
|
||||||
|
id: "url",
|
||||||
|
icon: markRaw(IconAlertTriangle),
|
||||||
|
text: {
|
||||||
|
type: "text",
|
||||||
|
text: text,
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
text: this.t("inspections.url.extention_enable_action"),
|
||||||
|
apply: () => {
|
||||||
|
this.interceptorService.currentInterceptorID.value = "extension"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
severity: 2,
|
||||||
|
isApplicable: true,
|
||||||
|
locations: {
|
||||||
|
type: "url",
|
||||||
|
},
|
||||||
|
doc: {
|
||||||
|
text: this.t("action.learn_more"),
|
||||||
|
link: "https://docs.hoppscotch.io/documentation/features/interceptor#browser-extension",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return results
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -61,7 +61,7 @@ describe("EnvironmentMenuService", () => {
|
|||||||
expect(actionsMock.invokeAction).toHaveBeenCalledWith(
|
expect(actionsMock.invokeAction).toHaveBeenCalledWith(
|
||||||
"modals.environment.add",
|
"modals.environment.add",
|
||||||
{
|
{
|
||||||
envName: "test",
|
envName: "",
|
||||||
variableName: test,
|
variableName: test,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export class EnvironmentMenuService extends Service implements ContextMenu {
|
|||||||
icon: markRaw(IconPlusCircle),
|
icon: markRaw(IconPlusCircle),
|
||||||
action: () => {
|
action: () => {
|
||||||
invokeAction("modals.environment.add", {
|
invokeAction("modals.environment.add", {
|
||||||
envName: "test",
|
envName: "",
|
||||||
variableName: text,
|
variableName: text,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { HoppRESTRequest } from "@hoppscotch/data"
|
import { HoppRESTRequest } from "@hoppscotch/data"
|
||||||
|
import { refDebounced } from "@vueuse/core"
|
||||||
import { Service } from "dioc"
|
import { Service } from "dioc"
|
||||||
|
import { computed, markRaw, reactive } from "vue"
|
||||||
import { Component, Ref, ref, watch } from "vue"
|
import { Component, Ref, ref, watch } from "vue"
|
||||||
import { currentActiveTab, currentTabID } from "~/helpers/rest/tab"
|
import { currentActiveTab } from "~/helpers/rest/tab"
|
||||||
import { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
|
import { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -80,15 +82,16 @@ export interface Inspector {
|
|||||||
*/
|
*/
|
||||||
inspectorID: string
|
inspectorID: string
|
||||||
/**
|
/**
|
||||||
* Returns the inspector results for the request
|
* Returns the inspector results for the request.
|
||||||
* @param req The request to inspect
|
* NOTE: The refs passed down are readonly and are debounced to avoid performance issues
|
||||||
* @param res The response to inspect
|
* @param req The ref to the request to inspect
|
||||||
* @returns The inspector results
|
* @param res The ref to the response to inspect
|
||||||
|
* @returns The ref to the inspector results
|
||||||
*/
|
*/
|
||||||
getInspectorFor: (
|
getInspections: (
|
||||||
req: HoppRESTRequest,
|
req: Readonly<Ref<HoppRESTRequest>>,
|
||||||
res?: HoppRESTResponse
|
res: Readonly<Ref<HoppRESTResponse | null | undefined>>
|
||||||
) => InspectorResult[]
|
) => Ref<InspectorResult[]>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -98,38 +101,73 @@ export interface Inspector {
|
|||||||
export class InspectionService extends Service {
|
export class InspectionService extends Service {
|
||||||
public static readonly ID = "INSPECTION_SERVICE"
|
public static readonly ID = "INSPECTION_SERVICE"
|
||||||
|
|
||||||
private inspectors: Map<string, Inspector> = new Map()
|
private inspectors: Map<string, Inspector> = reactive(new Map())
|
||||||
|
|
||||||
public tabs: Ref<Map<string, InspectorResult[]>> = ref(new Map())
|
private tabs: Ref<Map<string, InspectorResult[]>> = ref(new Map())
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
|
||||||
|
this.initializeListeners()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a inspector with the inspection service
|
* Registers a inspector with the inspection service
|
||||||
* @param inspector The inspector instance to register
|
* @param inspector The inspector instance to register
|
||||||
*/
|
*/
|
||||||
public registerInspector(inspector: Inspector) {
|
public registerInspector(inspector: Inspector) {
|
||||||
this.inspectors.set(inspector.inspectorID, inspector)
|
// markRaw is required here so that the inspector is not made reactive
|
||||||
|
this.inspectors.set(inspector.inspectorID, markRaw(inspector))
|
||||||
}
|
}
|
||||||
|
|
||||||
public initializeTabInspectors() {
|
private initializeListeners() {
|
||||||
watch(
|
watch(
|
||||||
currentActiveTab.value,
|
() => [this.inspectors.entries(), currentActiveTab.value.id],
|
||||||
(tab) => {
|
() => {
|
||||||
if (!tab) return
|
const reqRef = computed(() => currentActiveTab.value.document.request)
|
||||||
const req = currentActiveTab.value.document.request
|
const resRef = computed(() => currentActiveTab.value.response)
|
||||||
const res = currentActiveTab.value.response
|
|
||||||
const inspectors = Array.from(this.inspectors.values()).map((x) =>
|
const debouncedReq = refDebounced(reqRef, 1000, { maxWait: 2000 })
|
||||||
x.getInspectorFor(req, res)
|
const debouncedRes = refDebounced(resRef, 1000, { maxWait: 2000 })
|
||||||
|
|
||||||
|
const inspectorRefs = Array.from(this.inspectors.values()).map((x) =>
|
||||||
|
x.getInspections(debouncedReq, debouncedRes)
|
||||||
)
|
)
|
||||||
this.tabs.value.set(
|
|
||||||
currentTabID.value,
|
const activeInspections = computed(() =>
|
||||||
inspectors.flatMap((x) => x)
|
inspectorRefs.flatMap((x) => x!.value)
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => [...inspectorRefs.flatMap((x) => x!.value)],
|
||||||
|
() => {
|
||||||
|
this.tabs.value.set(
|
||||||
|
currentActiveTab.value.id,
|
||||||
|
activeInspections.value
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{ immediate: true, deep: true }
|
{ immediate: true, flush: "pre" }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public deleteTabInspectorResult(tabID: string) {
|
public deleteTabInspectorResult(tabID: string) {
|
||||||
|
// TODO: Move Tabs into a service and implement this with an event instead
|
||||||
this.tabs.value.delete(tabID)
|
this.tabs.value.delete(tabID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a reactive view into the inspector results for a specific tab
|
||||||
|
* @param tabID The ID of the tab to get the results for
|
||||||
|
* @param filter The filter to apply to the results.
|
||||||
|
* @returns The ref into the inspector results, if the tab doesn't exist, a ref into an empty array is returned
|
||||||
|
*/
|
||||||
|
public getResultViewFor(
|
||||||
|
tabID: string,
|
||||||
|
filter: (x: InspectorResult) => boolean = () => true
|
||||||
|
) {
|
||||||
|
return computed(() => this.tabs.value.get(tabID)?.filter(filter) ?? [])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,16 +3,23 @@ import { describe, expect, it, vi } from "vitest"
|
|||||||
import { EnvironmentInspectorService } from "../environment.inspector"
|
import { EnvironmentInspectorService } from "../environment.inspector"
|
||||||
import { InspectionService } from "../../index"
|
import { InspectionService } from "../../index"
|
||||||
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
||||||
|
import { ref } from "vue"
|
||||||
|
|
||||||
vi.mock("~/modules/i18n", () => ({
|
vi.mock("~/modules/i18n", () => ({
|
||||||
__esModule: true,
|
__esModule: true,
|
||||||
getI18n: () => (x: string) => x,
|
getI18n: () => (x: string) => x,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock("~/newstore/environments", () => ({
|
vi.mock("~/newstore/environments", async () => {
|
||||||
__esModule: true,
|
const { BehaviorSubject }: any = await vi.importActual("rxjs")
|
||||||
getAggregateEnvs: () => [{ key: "EXISTING_ENV_VAR", value: "test_value" }],
|
|
||||||
}))
|
return {
|
||||||
|
__esModule: true,
|
||||||
|
aggregateEnvs$: new BehaviorSubject([
|
||||||
|
{ key: "EXISTING_ENV_VAR", value: "test_value" },
|
||||||
|
]),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
describe("EnvironmentInspectorService", () => {
|
describe("EnvironmentInspectorService", () => {
|
||||||
it("registers with the inspection service upon initialization", () => {
|
it("registers with the inspection service upon initialization", () => {
|
||||||
@@ -35,14 +42,14 @@ describe("EnvironmentInspectorService", () => {
|
|||||||
const container = new TestContainer()
|
const container = new TestContainer()
|
||||||
const envInspector = container.bind(EnvironmentInspectorService)
|
const envInspector = container.bind(EnvironmentInspectorService)
|
||||||
|
|
||||||
const req = {
|
const req = ref({
|
||||||
...getDefaultRESTRequest(),
|
...getDefaultRESTRequest(),
|
||||||
endpoint: "<<UNDEFINED_ENV_VAR>>",
|
endpoint: "<<UNDEFINED_ENV_VAR>>",
|
||||||
}
|
})
|
||||||
|
|
||||||
const result = envInspector.getInspectorFor(req)
|
const result = envInspector.getInspections(req)
|
||||||
|
|
||||||
expect(result).toContainEqual(
|
expect(result.value).toContainEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: "environment",
|
id: "environment",
|
||||||
isApplicable: true,
|
isApplicable: true,
|
||||||
@@ -58,31 +65,31 @@ describe("EnvironmentInspectorService", () => {
|
|||||||
const container = new TestContainer()
|
const container = new TestContainer()
|
||||||
const envInspector = container.bind(EnvironmentInspectorService)
|
const envInspector = container.bind(EnvironmentInspectorService)
|
||||||
|
|
||||||
const req = {
|
const req = ref({
|
||||||
...getDefaultRESTRequest(),
|
...getDefaultRESTRequest(),
|
||||||
endpoint: "<<EXISTING_ENV_VAR>>",
|
endpoint: "<<EXISTING_ENV_VAR>>",
|
||||||
}
|
})
|
||||||
|
|
||||||
const result = envInspector.getInspectorFor(req)
|
const result = envInspector.getInspections(req)
|
||||||
|
|
||||||
expect(result).toHaveLength(0)
|
expect(result.value).toHaveLength(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should return an inspector result when the headers contain undefined environment variables", () => {
|
it("should return an inspector result when the headers contain undefined environment variables", () => {
|
||||||
const container = new TestContainer()
|
const container = new TestContainer()
|
||||||
const envInspector = container.bind(EnvironmentInspectorService)
|
const envInspector = container.bind(EnvironmentInspectorService)
|
||||||
|
|
||||||
const req = {
|
const req = ref({
|
||||||
...getDefaultRESTRequest(),
|
...getDefaultRESTRequest(),
|
||||||
endpoint: "http://example.com/api/data",
|
endpoint: "http://example.com/api/data",
|
||||||
headers: [
|
headers: [
|
||||||
{ key: "<<UNDEFINED_ENV_VAR>>", value: "some-value", active: true },
|
{ key: "<<UNDEFINED_ENV_VAR>>", value: "some-value", active: true },
|
||||||
],
|
],
|
||||||
}
|
})
|
||||||
|
|
||||||
const result = envInspector.getInspectorFor(req)
|
const result = envInspector.getInspections(req)
|
||||||
|
|
||||||
expect(result).toContainEqual(
|
expect(result.value).toContainEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: "environment",
|
id: "environment",
|
||||||
isApplicable: true,
|
isApplicable: true,
|
||||||
@@ -98,34 +105,34 @@ describe("EnvironmentInspectorService", () => {
|
|||||||
const container = new TestContainer()
|
const container = new TestContainer()
|
||||||
const envInspector = container.bind(EnvironmentInspectorService)
|
const envInspector = container.bind(EnvironmentInspectorService)
|
||||||
|
|
||||||
const req = {
|
const req = ref({
|
||||||
...getDefaultRESTRequest(),
|
...getDefaultRESTRequest(),
|
||||||
endpoint: "http://example.com/api/data",
|
endpoint: "http://example.com/api/data",
|
||||||
headers: [
|
headers: [
|
||||||
{ key: "<<EXISTING_ENV_VAR>>", value: "some-value", active: true },
|
{ key: "<<EXISTING_ENV_VAR>>", value: "some-value", active: true },
|
||||||
],
|
],
|
||||||
}
|
})
|
||||||
|
|
||||||
const result = envInspector.getInspectorFor(req)
|
const result = envInspector.getInspections(req)
|
||||||
|
|
||||||
expect(result).toHaveLength(0)
|
expect(result.value).toHaveLength(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should return an inspector result when the params contain undefined environment variables", () => {
|
it("should return an inspector result when the params contain undefined environment variables", () => {
|
||||||
const container = new TestContainer()
|
const container = new TestContainer()
|
||||||
const envInspector = container.bind(EnvironmentInspectorService)
|
const envInspector = container.bind(EnvironmentInspectorService)
|
||||||
|
|
||||||
const req = {
|
const req = ref({
|
||||||
...getDefaultRESTRequest(),
|
...getDefaultRESTRequest(),
|
||||||
endpoint: "http://example.com/api/data",
|
endpoint: "http://example.com/api/data",
|
||||||
params: [
|
params: [
|
||||||
{ key: "<<UNDEFINED_ENV_VAR>>", value: "some-value", active: true },
|
{ key: "<<UNDEFINED_ENV_VAR>>", value: "some-value", active: true },
|
||||||
],
|
],
|
||||||
}
|
})
|
||||||
|
|
||||||
const result = envInspector.getInspectorFor(req)
|
const result = envInspector.getInspections(req)
|
||||||
|
|
||||||
expect(result).toContainEqual(
|
expect(result.value).toContainEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: "environment",
|
id: "environment",
|
||||||
isApplicable: true,
|
isApplicable: true,
|
||||||
@@ -141,18 +148,18 @@ describe("EnvironmentInspectorService", () => {
|
|||||||
const container = new TestContainer()
|
const container = new TestContainer()
|
||||||
const envInspector = container.bind(EnvironmentInspectorService)
|
const envInspector = container.bind(EnvironmentInspectorService)
|
||||||
|
|
||||||
const req = {
|
const req = ref({
|
||||||
...getDefaultRESTRequest(),
|
...getDefaultRESTRequest(),
|
||||||
endpoint: "http://example.com/api/data",
|
endpoint: "http://example.com/api/data",
|
||||||
headers: [],
|
headers: [],
|
||||||
params: [
|
params: [
|
||||||
{ key: "<<EXISTING_ENV_VAR>>", value: "some-value", active: true },
|
{ key: "<<EXISTING_ENV_VAR>>", value: "some-value", active: true },
|
||||||
],
|
],
|
||||||
}
|
})
|
||||||
|
|
||||||
const result = envInspector.getInspectorFor(req)
|
const result = envInspector.getInspections(req)
|
||||||
|
|
||||||
expect(result).toHaveLength(0)
|
expect(result.value).toHaveLength(0)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { describe, expect, it, vi } from "vitest"
|
|||||||
import { HeaderInspectorService } from "../header.inspector"
|
import { HeaderInspectorService } from "../header.inspector"
|
||||||
import { InspectionService } from "../../index"
|
import { InspectionService } from "../../index"
|
||||||
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
||||||
|
import { ref } from "vue"
|
||||||
|
|
||||||
vi.mock("~/modules/i18n", () => ({
|
vi.mock("~/modules/i18n", () => ({
|
||||||
__esModule: true,
|
__esModule: true,
|
||||||
@@ -30,15 +31,15 @@ describe("HeaderInspectorService", () => {
|
|||||||
const container = new TestContainer()
|
const container = new TestContainer()
|
||||||
const headerInspector = container.bind(HeaderInspectorService)
|
const headerInspector = container.bind(HeaderInspectorService)
|
||||||
|
|
||||||
const req = {
|
const req = ref({
|
||||||
...getDefaultRESTRequest(),
|
...getDefaultRESTRequest(),
|
||||||
endpoint: "http://example.com/api/data",
|
endpoint: "http://example.com/api/data",
|
||||||
headers: [{ key: "Cookie", value: "some-cookie", active: true }],
|
headers: [{ key: "Cookie", value: "some-cookie", active: true }],
|
||||||
}
|
})
|
||||||
|
|
||||||
const result = headerInspector.getInspectorFor(req)
|
const result = headerInspector.getInspections(req)
|
||||||
|
|
||||||
expect(result).toContainEqual(
|
expect(result.value).toContainEqual(
|
||||||
expect.objectContaining({ id: "header", isApplicable: true })
|
expect.objectContaining({ id: "header", isApplicable: true })
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -47,15 +48,15 @@ describe("HeaderInspectorService", () => {
|
|||||||
const container = new TestContainer()
|
const container = new TestContainer()
|
||||||
const headerInspector = container.bind(HeaderInspectorService)
|
const headerInspector = container.bind(HeaderInspectorService)
|
||||||
|
|
||||||
const req = {
|
const req = ref({
|
||||||
...getDefaultRESTRequest(),
|
...getDefaultRESTRequest(),
|
||||||
endpoint: "http://example.com/api/data",
|
endpoint: "http://example.com/api/data",
|
||||||
headers: [{ key: "Authorization", value: "Bearer abcd", active: true }],
|
headers: [{ key: "Authorization", value: "Bearer abcd", active: true }],
|
||||||
}
|
})
|
||||||
|
|
||||||
const result = headerInspector.getInspectorFor(req)
|
const result = headerInspector.getInspections(req)
|
||||||
|
|
||||||
expect(result).toHaveLength(0)
|
expect(result.value).toHaveLength(0)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,245 @@
|
|||||||
|
import { TestContainer } from "dioc/testing"
|
||||||
|
import { describe, expect, it, beforeEach, afterEach, vi } from "vitest"
|
||||||
|
import { ResponseInspectorService } from "../response.inspector"
|
||||||
|
import { InspectionService } from "../../index"
|
||||||
|
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
||||||
|
import { ref } from "vue"
|
||||||
|
import { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
|
||||||
|
|
||||||
|
vi.mock("~/modules/i18n", () => ({
|
||||||
|
__esModule: true,
|
||||||
|
getI18n: () => (x: string) => x,
|
||||||
|
}))
|
||||||
|
|
||||||
|
describe("ResponseInspectorService", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.stubGlobal("navigator", {
|
||||||
|
onLine: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.unstubAllGlobals()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("registers with the inspection service upon initialization", () => {
|
||||||
|
const container = new TestContainer()
|
||||||
|
|
||||||
|
const registerInspectorFn = vi.fn()
|
||||||
|
|
||||||
|
container.bindMock(InspectionService, {
|
||||||
|
registerInspector: registerInspectorFn,
|
||||||
|
})
|
||||||
|
|
||||||
|
const responseInspector = container.bind(ResponseInspectorService)
|
||||||
|
|
||||||
|
expect(registerInspectorFn).toHaveBeenCalledOnce()
|
||||||
|
expect(registerInspectorFn).toHaveBeenCalledWith(responseInspector)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("getInspectorFor", () => {
|
||||||
|
it("should return an empty array when response is undefined", () => {
|
||||||
|
const container = new TestContainer()
|
||||||
|
const responseInspector = container.bind(ResponseInspectorService)
|
||||||
|
|
||||||
|
const req = ref({
|
||||||
|
...getDefaultRESTRequest(),
|
||||||
|
endpoint: "http://example.com/api/data",
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = responseInspector.getInspections(req, ref(undefined))
|
||||||
|
|
||||||
|
expect(result.value).toHaveLength(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return an inspector result when response type is not success or status code is not 200 and if the network is not available", () => {
|
||||||
|
const container = new TestContainer()
|
||||||
|
const responseInspector = container.bind(ResponseInspectorService)
|
||||||
|
|
||||||
|
const req = ref({
|
||||||
|
...getDefaultRESTRequest(),
|
||||||
|
endpoint: "http://example.com/api/data",
|
||||||
|
})
|
||||||
|
const res = ref<HoppRESTResponse>({
|
||||||
|
type: "network_fail",
|
||||||
|
error: new Error("test"),
|
||||||
|
req: req.value,
|
||||||
|
})
|
||||||
|
|
||||||
|
vi.stubGlobal("navigator", {
|
||||||
|
onLine: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = responseInspector.getInspections(req, res)
|
||||||
|
|
||||||
|
expect(result.value).toContainEqual(
|
||||||
|
expect.objectContaining({ id: "url", isApplicable: true })
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return no inspector result when response type is not success or status code is not 200 and if the network is not available", () => {
|
||||||
|
const container = new TestContainer()
|
||||||
|
const responseInspector = container.bind(ResponseInspectorService)
|
||||||
|
|
||||||
|
const req = ref({
|
||||||
|
...getDefaultRESTRequest(),
|
||||||
|
endpoint: "http://example.com/api/data",
|
||||||
|
})
|
||||||
|
const res = ref<HoppRESTResponse>({
|
||||||
|
type: "network_fail",
|
||||||
|
error: new Error("test"),
|
||||||
|
req: req.value,
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = responseInspector.getInspections(req, res)
|
||||||
|
|
||||||
|
expect(result.value).toHaveLength(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should handle network_fail responses and return nothing when no network is present", () => {
|
||||||
|
const container = new TestContainer()
|
||||||
|
const responseInspector = container.bind(ResponseInspectorService)
|
||||||
|
|
||||||
|
const req = ref({
|
||||||
|
...getDefaultRESTRequest(),
|
||||||
|
endpoint: "http://example.com/api/data",
|
||||||
|
})
|
||||||
|
const res = ref<HoppRESTResponse>({
|
||||||
|
type: "network_fail",
|
||||||
|
error: new Error("test"),
|
||||||
|
req: req.value,
|
||||||
|
})
|
||||||
|
|
||||||
|
vi.stubGlobal("navigator", {
|
||||||
|
onLine: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = responseInspector.getInspections(req, res)
|
||||||
|
|
||||||
|
expect(result.value).toContainEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
text: { type: "text", text: "inspections.response.network_error" },
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should handle network_fail responses and return nothing when network is present", () => {
|
||||||
|
const container = new TestContainer()
|
||||||
|
const responseInspector = container.bind(ResponseInspectorService)
|
||||||
|
|
||||||
|
const req = ref({
|
||||||
|
...getDefaultRESTRequest(),
|
||||||
|
endpoint: "http://example.com/api/data",
|
||||||
|
})
|
||||||
|
const res = ref<HoppRESTResponse>({
|
||||||
|
type: "network_fail",
|
||||||
|
error: new Error("test"),
|
||||||
|
req: req.value,
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = responseInspector.getInspections(req, res)
|
||||||
|
|
||||||
|
expect(result.value).toHaveLength(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should handle fail responses", () => {
|
||||||
|
const container = new TestContainer()
|
||||||
|
const responseInspector = container.bind(ResponseInspectorService)
|
||||||
|
|
||||||
|
const req = ref({
|
||||||
|
...getDefaultRESTRequest(),
|
||||||
|
endpoint: "http://example.com/api/data",
|
||||||
|
})
|
||||||
|
const res = ref<HoppRESTResponse>({
|
||||||
|
type: "fail",
|
||||||
|
statusCode: 500,
|
||||||
|
body: Uint8Array.from([]),
|
||||||
|
headers: [],
|
||||||
|
meta: { responseDuration: 0, responseSize: 0 },
|
||||||
|
req: req.value,
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = responseInspector.getInspections(req, res)
|
||||||
|
|
||||||
|
expect(result.value).toContainEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
text: { type: "text", text: "inspections.response.default_error" },
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should handle 404 responses", () => {
|
||||||
|
const container = new TestContainer()
|
||||||
|
const responseInspector = container.bind(ResponseInspectorService)
|
||||||
|
|
||||||
|
const req = ref({
|
||||||
|
...getDefaultRESTRequest(),
|
||||||
|
endpoint: "http://example.com/api/data",
|
||||||
|
})
|
||||||
|
const res = ref<HoppRESTResponse>({
|
||||||
|
type: "success",
|
||||||
|
statusCode: 404,
|
||||||
|
body: Uint8Array.from([]),
|
||||||
|
headers: [],
|
||||||
|
meta: { responseDuration: 0, responseSize: 0 },
|
||||||
|
req: req.value,
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = responseInspector.getInspections(req, res)
|
||||||
|
|
||||||
|
expect(result.value).toContainEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
text: { type: "text", text: "inspections.response.404_error" },
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should handle 401 responses", () => {
|
||||||
|
const container = new TestContainer()
|
||||||
|
const responseInspector = container.bind(ResponseInspectorService)
|
||||||
|
|
||||||
|
const req = ref({
|
||||||
|
...getDefaultRESTRequest(),
|
||||||
|
endpoint: "http://example.com/api/data",
|
||||||
|
})
|
||||||
|
const res = ref<HoppRESTResponse>({
|
||||||
|
type: "success",
|
||||||
|
statusCode: 401,
|
||||||
|
body: Uint8Array.from([]),
|
||||||
|
headers: [],
|
||||||
|
meta: { responseDuration: 0, responseSize: 0 },
|
||||||
|
req: req.value,
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = responseInspector.getInspections(req, res)
|
||||||
|
|
||||||
|
expect(result.value).toContainEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
text: { type: "text", text: "inspections.response.401_error" },
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should handle successful responses", () => {
|
||||||
|
const container = new TestContainer()
|
||||||
|
const responseInspector = container.bind(ResponseInspectorService)
|
||||||
|
|
||||||
|
const req = ref({
|
||||||
|
...getDefaultRESTRequest(),
|
||||||
|
endpoint: "http://example.com/api/data",
|
||||||
|
})
|
||||||
|
const res = ref<HoppRESTResponse>({
|
||||||
|
type: "success",
|
||||||
|
statusCode: 200,
|
||||||
|
body: Uint8Array.from([]),
|
||||||
|
headers: [],
|
||||||
|
meta: { responseDuration: 0, responseSize: 0 },
|
||||||
|
req: req.value,
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = responseInspector.getInspections(req, res)
|
||||||
|
|
||||||
|
expect(result.value).toHaveLength(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
import { TestContainer } from "dioc/testing"
|
|
||||||
import { describe, expect, it, vi } from "vitest"
|
|
||||||
import { ResponseInspectorService } from "../response.inspector"
|
|
||||||
import { InspectionService } from "../../index"
|
|
||||||
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
|
||||||
|
|
||||||
vi.mock("~/modules/i18n", () => ({
|
|
||||||
__esModule: true,
|
|
||||||
getI18n: () => (x: string) => x,
|
|
||||||
}))
|
|
||||||
|
|
||||||
describe("ResponseInspectorService", () => {
|
|
||||||
it("registers with the inspection service upon initialization", () => {
|
|
||||||
const container = new TestContainer()
|
|
||||||
|
|
||||||
const registerInspectorFn = vi.fn()
|
|
||||||
|
|
||||||
container.bindMock(InspectionService, {
|
|
||||||
registerInspector: registerInspectorFn,
|
|
||||||
})
|
|
||||||
|
|
||||||
const responseInspector = container.bind(ResponseInspectorService)
|
|
||||||
|
|
||||||
expect(registerInspectorFn).toHaveBeenCalledOnce()
|
|
||||||
expect(registerInspectorFn).toHaveBeenCalledWith(responseInspector)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("getInspectorFor", () => {
|
|
||||||
it("should return an empty array when response is undefined", () => {
|
|
||||||
const container = new TestContainer()
|
|
||||||
const responseInspector = container.bind(ResponseInspectorService)
|
|
||||||
|
|
||||||
const req = {
|
|
||||||
...getDefaultRESTRequest(),
|
|
||||||
endpoint: "http://example.com/api/data",
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = responseInspector.getInspectorFor(req, undefined)
|
|
||||||
|
|
||||||
expect(result).toHaveLength(0)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should return an inspector result when response type is not success or status code is not 200", () => {
|
|
||||||
const container = new TestContainer()
|
|
||||||
const responseInspector = container.bind(ResponseInspectorService)
|
|
||||||
|
|
||||||
const req = {
|
|
||||||
...getDefaultRESTRequest(),
|
|
||||||
endpoint: "http://example.com/api/data",
|
|
||||||
}
|
|
||||||
const res = { type: "network_fail", statusCode: 400 }
|
|
||||||
|
|
||||||
const result = responseInspector.getInspectorFor(req, res)
|
|
||||||
|
|
||||||
expect(result).toContainEqual(
|
|
||||||
expect.objectContaining({ id: "url", isApplicable: true })
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should handle network_fail responses", () => {
|
|
||||||
const container = new TestContainer()
|
|
||||||
const responseInspector = container.bind(ResponseInspectorService)
|
|
||||||
|
|
||||||
const req = {
|
|
||||||
...getDefaultRESTRequest(),
|
|
||||||
endpoint: "http://example.com/api/data",
|
|
||||||
}
|
|
||||||
const res = { type: "network_fail", statusCode: 500 }
|
|
||||||
|
|
||||||
const result = responseInspector.getInspectorFor(req, res)
|
|
||||||
|
|
||||||
expect(result).toContainEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
text: { type: "text", text: "inspections.response.network_error" },
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should handle fail responses", () => {
|
|
||||||
const container = new TestContainer()
|
|
||||||
const responseInspector = container.bind(ResponseInspectorService)
|
|
||||||
|
|
||||||
const req = {
|
|
||||||
...getDefaultRESTRequest(),
|
|
||||||
endpoint: "http://example.com/api/data",
|
|
||||||
}
|
|
||||||
const res = { type: "fail", statusCode: 500 }
|
|
||||||
|
|
||||||
const result = responseInspector.getInspectorFor(req, res)
|
|
||||||
|
|
||||||
expect(result).toContainEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
text: { type: "text", text: "inspections.response.default_error" },
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should handle 404 responses", () => {
|
|
||||||
const container = new TestContainer()
|
|
||||||
const responseInspector = container.bind(ResponseInspectorService)
|
|
||||||
|
|
||||||
const req = {
|
|
||||||
...getDefaultRESTRequest(),
|
|
||||||
endpoint: "http://example.com/api/data",
|
|
||||||
}
|
|
||||||
const res = { type: "success", statusCode: 404 }
|
|
||||||
|
|
||||||
const result = responseInspector.getInspectorFor(req, res)
|
|
||||||
|
|
||||||
expect(result).toContainEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
text: { type: "text", text: "inspections.response.404_error" },
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should handle 401 responses", () => {
|
|
||||||
const container = new TestContainer()
|
|
||||||
const responseInspector = container.bind(ResponseInspectorService)
|
|
||||||
|
|
||||||
const req = {
|
|
||||||
...getDefaultRESTRequest(),
|
|
||||||
endpoint: "http://example.com/api/data",
|
|
||||||
}
|
|
||||||
const res = { type: "success", statusCode: 401 }
|
|
||||||
|
|
||||||
const result = responseInspector.getInspectorFor(req, res)
|
|
||||||
|
|
||||||
expect(result).toContainEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
text: { type: "text", text: "inspections.response.401_error" },
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should handle successful responses", () => {
|
|
||||||
const container = new TestContainer()
|
|
||||||
const responseInspector = container.bind(ResponseInspectorService)
|
|
||||||
|
|
||||||
const req = {
|
|
||||||
...getDefaultRESTRequest(),
|
|
||||||
endpoint: "http://example.com/api/data",
|
|
||||||
}
|
|
||||||
const res = { type: "success", statusCode: 200 }
|
|
||||||
|
|
||||||
const result = responseInspector.getInspectorFor(req, res)
|
|
||||||
|
|
||||||
expect(result).toHaveLength(0)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -6,11 +6,13 @@ import {
|
|||||||
InspectorResult,
|
InspectorResult,
|
||||||
} from ".."
|
} from ".."
|
||||||
import { Service } from "dioc"
|
import { Service } from "dioc"
|
||||||
import { Ref, markRaw, ref } from "vue"
|
import { Ref, markRaw } from "vue"
|
||||||
import IconPlusCircle from "~icons/lucide/plus-circle"
|
import IconPlusCircle from "~icons/lucide/plus-circle"
|
||||||
import { HoppRESTRequest } from "@hoppscotch/data"
|
import { HoppRESTRequest } from "@hoppscotch/data"
|
||||||
import { getAggregateEnvs } from "~/newstore/environments"
|
import { aggregateEnvs$ } from "~/newstore/environments"
|
||||||
import { invokeAction } from "~/helpers/actions"
|
import { invokeAction } from "~/helpers/actions"
|
||||||
|
import { computed } from "vue"
|
||||||
|
import { useStreamStatic } from "~/composables/stream"
|
||||||
|
|
||||||
const HOPP_ENVIRONMENT_REGEX = /(<<[a-zA-Z0-9-_]+>>)/g
|
const HOPP_ENVIRONMENT_REGEX = /(<<[a-zA-Z0-9-_]+>>)/g
|
||||||
|
|
||||||
@@ -34,6 +36,10 @@ export class EnvironmentInspectorService extends Service implements Inspector {
|
|||||||
|
|
||||||
private readonly inspection = this.bind(InspectionService)
|
private readonly inspection = this.bind(InspectionService)
|
||||||
|
|
||||||
|
private aggregateEnvs = useStreamStatic(aggregateEnvs$, [], () => {
|
||||||
|
/* noop */
|
||||||
|
})[0]
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
@@ -49,11 +55,11 @@ export class EnvironmentInspectorService extends Service implements Inspector {
|
|||||||
*/
|
*/
|
||||||
private validateEnvironmentVariables = (
|
private validateEnvironmentVariables = (
|
||||||
target: any[],
|
target: any[],
|
||||||
results: Ref<InspectorResult[]>,
|
|
||||||
locations: InspectorLocation
|
locations: InspectorLocation
|
||||||
) => {
|
) => {
|
||||||
const env = getAggregateEnvs()
|
const newErrors: InspectorResult[] = []
|
||||||
const envKeys = env.map((e) => e.key)
|
|
||||||
|
const envKeys = this.aggregateEnvs.value.map((e) => e.key)
|
||||||
|
|
||||||
target.forEach((element, index) => {
|
target.forEach((element, index) => {
|
||||||
if (isENVInString(element)) {
|
if (isENVInString(element)) {
|
||||||
@@ -83,7 +89,7 @@ export class EnvironmentInspectorService extends Service implements Inspector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!envKeys.includes(formattedExEnv)) {
|
if (!envKeys.includes(formattedExEnv)) {
|
||||||
results.value.push({
|
newErrors.push({
|
||||||
id: "environment",
|
id: "environment",
|
||||||
text: {
|
text: {
|
||||||
type: "text",
|
type: "text",
|
||||||
@@ -96,8 +102,8 @@ export class EnvironmentInspectorService extends Service implements Inspector {
|
|||||||
text: this.t("inspections.environment.add_environment"),
|
text: this.t("inspections.environment.add_environment"),
|
||||||
apply: () => {
|
apply: () => {
|
||||||
invokeAction("modals.environment.add", {
|
invokeAction("modals.environment.add", {
|
||||||
envName: "test",
|
envName: formattedExEnv,
|
||||||
variableName: formattedExEnv,
|
variableName: "",
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -114,54 +120,61 @@ export class EnvironmentInspectorService extends Service implements Inspector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return newErrors
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
getInspections(req: Readonly<Ref<HoppRESTRequest>>) {
|
||||||
* Returns the inspector results for the request
|
return computed(() => {
|
||||||
* It checks if any env is used in the request ie, url, headers, params
|
const results: InspectorResult[] = []
|
||||||
* and checks if the env is defined in the environment using the validateEnvironmentVariables function
|
|
||||||
* @param req The request to inspect
|
|
||||||
* @returns The inspector results
|
|
||||||
*/
|
|
||||||
getInspectorFor(req: HoppRESTRequest): InspectorResult[] {
|
|
||||||
const results = ref<InspectorResult[]>([])
|
|
||||||
|
|
||||||
const headers = req.headers
|
const headers = req.value.headers
|
||||||
|
|
||||||
const params = req.params
|
const params = req.value.params
|
||||||
|
|
||||||
this.validateEnvironmentVariables([req.endpoint], results, {
|
results.push(
|
||||||
type: "url",
|
...this.validateEnvironmentVariables([req.value.endpoint], {
|
||||||
|
type: "url",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const headerKeys = Object.values(headers).map((header) => header.key)
|
||||||
|
|
||||||
|
results.push(
|
||||||
|
...this.validateEnvironmentVariables(headerKeys, {
|
||||||
|
type: "header",
|
||||||
|
position: "key",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const headerValues = Object.values(headers).map((header) => header.value)
|
||||||
|
|
||||||
|
results.push(
|
||||||
|
...this.validateEnvironmentVariables(headerValues, {
|
||||||
|
type: "header",
|
||||||
|
position: "value",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const paramsKeys = Object.values(params).map((param) => param.key)
|
||||||
|
|
||||||
|
results.push(
|
||||||
|
...this.validateEnvironmentVariables(paramsKeys, {
|
||||||
|
type: "parameter",
|
||||||
|
position: "key",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const paramsValues = Object.values(params).map((param) => param.value)
|
||||||
|
|
||||||
|
results.push(
|
||||||
|
...this.validateEnvironmentVariables(paramsValues, {
|
||||||
|
type: "parameter",
|
||||||
|
position: "value",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
return results
|
||||||
})
|
})
|
||||||
|
|
||||||
const headerKeys = Object.values(headers).map((header) => header.key)
|
|
||||||
|
|
||||||
this.validateEnvironmentVariables(headerKeys, results, {
|
|
||||||
type: "header",
|
|
||||||
position: "key",
|
|
||||||
})
|
|
||||||
|
|
||||||
const headerValues = Object.values(headers).map((header) => header.value)
|
|
||||||
|
|
||||||
this.validateEnvironmentVariables(headerValues, results, {
|
|
||||||
type: "header",
|
|
||||||
position: "value",
|
|
||||||
})
|
|
||||||
|
|
||||||
const paramsKeys = Object.values(params).map((param) => param.key)
|
|
||||||
|
|
||||||
this.validateEnvironmentVariables(paramsKeys, results, {
|
|
||||||
type: "parameter",
|
|
||||||
position: "key",
|
|
||||||
})
|
|
||||||
|
|
||||||
const paramsValues = Object.values(params).map((param) => param.value)
|
|
||||||
|
|
||||||
this.validateEnvironmentVariables(paramsValues, results, {
|
|
||||||
type: "parameter",
|
|
||||||
position: "value",
|
|
||||||
})
|
|
||||||
|
|
||||||
return results.value
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Service } from "dioc"
|
|||||||
import { InspectionService, Inspector, InspectorResult } from ".."
|
import { InspectionService, Inspector, InspectorResult } from ".."
|
||||||
import { getI18n } from "~/modules/i18n"
|
import { getI18n } from "~/modules/i18n"
|
||||||
import { HoppRESTRequest } from "@hoppscotch/data"
|
import { HoppRESTRequest } from "@hoppscotch/data"
|
||||||
import { markRaw, ref } from "vue"
|
import { Ref, computed, markRaw } from "vue"
|
||||||
import IconAlertTriangle from "~icons/lucide/alert-triangle"
|
import IconAlertTriangle from "~icons/lucide/alert-triangle"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -26,53 +26,50 @@ export class HeaderInspectorService extends Service implements Inspector {
|
|||||||
this.inspection.registerInspector(this)
|
this.inspection.registerInspector(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private cookiesCheck(headerKey: string) {
|
||||||
* Checks if the header contains cookies
|
const cookieKeywords = ["Cookie", "Set-Cookie", "Cookie2", "Set-Cookie2"]
|
||||||
* @param req The request to inspect
|
|
||||||
* @returns The inspector results
|
|
||||||
*/
|
|
||||||
getInspectorFor(req: HoppRESTRequest): InspectorResult[] {
|
|
||||||
const results = ref<InspectorResult[]>([])
|
|
||||||
|
|
||||||
const cookiesCheck = (headerKey: string) => {
|
return cookieKeywords.includes(headerKey)
|
||||||
const cookieKeywords = ["Cookie", "Set-Cookie", "Cookie2", "Set-Cookie2"]
|
}
|
||||||
|
|
||||||
return cookieKeywords.includes(headerKey)
|
getInspections(req: Readonly<Ref<HoppRESTRequest>>) {
|
||||||
}
|
return computed(() => {
|
||||||
|
const results: InspectorResult[] = []
|
||||||
|
|
||||||
const headers = req.headers
|
const headers = req.value.headers
|
||||||
|
|
||||||
const headerKeys = Object.values(headers).map((header) => header.key)
|
const headerKeys = Object.values(headers).map((header) => header.key)
|
||||||
|
|
||||||
const isContainCookies = headerKeys.includes("Cookie")
|
const isContainCookies = headerKeys.includes("Cookie")
|
||||||
|
|
||||||
if (isContainCookies) {
|
if (isContainCookies) {
|
||||||
headerKeys.forEach((headerKey, index) => {
|
headerKeys.forEach((headerKey, index) => {
|
||||||
if (cookiesCheck(headerKey)) {
|
if (this.cookiesCheck(headerKey)) {
|
||||||
results.value.push({
|
results.push({
|
||||||
id: "header",
|
id: "header",
|
||||||
icon: markRaw(IconAlertTriangle),
|
icon: markRaw(IconAlertTriangle),
|
||||||
text: {
|
text: {
|
||||||
type: "text",
|
type: "text",
|
||||||
text: this.t("inspections.header.cookie"),
|
text: this.t("inspections.header.cookie"),
|
||||||
},
|
},
|
||||||
severity: 2,
|
severity: 2,
|
||||||
isApplicable: true,
|
isApplicable: true,
|
||||||
locations: {
|
locations: {
|
||||||
type: "header",
|
type: "header",
|
||||||
position: "key",
|
position: "key",
|
||||||
key: headerKey,
|
key: headerKey,
|
||||||
index: index,
|
index: index,
|
||||||
},
|
},
|
||||||
doc: {
|
doc: {
|
||||||
text: this.t("action.learn_more"),
|
text: this.t("action.learn_more"),
|
||||||
link: "https://docs.hoppscotch.io/",
|
link: "https://docs.hoppscotch.io/",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return results.value
|
return results
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ import { Service } from "dioc"
|
|||||||
import { InspectionService, Inspector, InspectorResult } from ".."
|
import { InspectionService, Inspector, InspectorResult } from ".."
|
||||||
import { getI18n } from "~/modules/i18n"
|
import { getI18n } from "~/modules/i18n"
|
||||||
import { HoppRESTRequest } from "@hoppscotch/data"
|
import { HoppRESTRequest } from "@hoppscotch/data"
|
||||||
import { markRaw, ref } from "vue"
|
import { markRaw } from "vue"
|
||||||
import IconAlertTriangle from "~icons/lucide/alert-triangle"
|
import IconAlertTriangle from "~icons/lucide/alert-triangle"
|
||||||
import { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
|
import { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
|
||||||
|
import { Ref } from "vue"
|
||||||
|
import { computed } from "vue"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This inspector is responsible for inspecting the response of a request.
|
* This inspector is responsible for inspecting the response of a request.
|
||||||
@@ -27,47 +29,50 @@ export class ResponseInspectorService extends Service implements Inspector {
|
|||||||
this.inspection.registerInspector(this)
|
this.inspection.registerInspector(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
getInspectorFor(
|
getInspections(
|
||||||
req: HoppRESTRequest,
|
_req: Readonly<Ref<HoppRESTRequest>>,
|
||||||
res: HoppRESTResponse | undefined
|
res: Readonly<Ref<HoppRESTResponse | null | undefined>>
|
||||||
): InspectorResult[] {
|
) {
|
||||||
const results = ref<InspectorResult[]>([])
|
return computed(() => {
|
||||||
if (!res) return results.value
|
const results: InspectorResult[] = []
|
||||||
|
if (!res.value) return results
|
||||||
|
|
||||||
const hasErrors = res && (res.type !== "success" || res.statusCode !== 200)
|
const hasErrors =
|
||||||
|
res && (res.value.type !== "success" || res.value.statusCode !== 200)
|
||||||
|
|
||||||
let text
|
let text: string | undefined = undefined
|
||||||
|
|
||||||
if (res.type === "network_fail") {
|
if (res.value.type === "network_fail" && !navigator.onLine) {
|
||||||
text = this.t("inspections.response.network_error")
|
text = this.t("inspections.response.network_error")
|
||||||
} else if (res.type === "fail") {
|
} else if (res.value.type === "fail") {
|
||||||
text = this.t("inspections.response.default_error")
|
text = this.t("inspections.response.default_error")
|
||||||
} else if (res.type === "success" && res.statusCode === 404) {
|
} else if (res.value.type === "success" && res.value.statusCode === 404) {
|
||||||
text = this.t("inspections.response.404_error")
|
text = this.t("inspections.response.404_error")
|
||||||
} else if (res.type === "success" && res.statusCode === 401) {
|
} else if (res.value.type === "success" && res.value.statusCode === 401) {
|
||||||
text = this.t("inspections.response.401_error")
|
text = this.t("inspections.response.401_error")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasErrors && text) {
|
if (hasErrors && text) {
|
||||||
results.value.push({
|
results.push({
|
||||||
id: "url",
|
id: "url",
|
||||||
icon: markRaw(IconAlertTriangle),
|
icon: markRaw(IconAlertTriangle),
|
||||||
text: {
|
text: {
|
||||||
type: "text",
|
type: "text",
|
||||||
text: text,
|
text: text,
|
||||||
},
|
},
|
||||||
severity: 2,
|
severity: 2,
|
||||||
isApplicable: true,
|
isApplicable: true,
|
||||||
locations: {
|
locations: {
|
||||||
type: "response",
|
type: "response",
|
||||||
},
|
},
|
||||||
doc: {
|
doc: {
|
||||||
text: this.t("action.learn_more"),
|
text: this.t("action.learn_more"),
|
||||||
link: "https://docs.hoppscotch.io/",
|
link: "https://docs.hoppscotch.io/",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return results.value
|
return results
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
import { Service } from "dioc"
|
|
||||||
import { InspectionService, Inspector, InspectorResult } from ".."
|
|
||||||
import { getI18n } from "~/modules/i18n"
|
|
||||||
import { HoppRESTRequest } from "@hoppscotch/data"
|
|
||||||
import { computed, markRaw, ref } from "vue"
|
|
||||||
import IconAlertTriangle from "~icons/lucide/alert-triangle"
|
|
||||||
import { useReadonlyStream } from "~/composables/stream"
|
|
||||||
import { extensionStatus$ } from "~/newstore/HoppExtension"
|
|
||||||
import { useSetting } from "~/composables/settings"
|
|
||||||
import { applySetting, toggleSetting } from "~/newstore/settings"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This inspector is responsible for inspecting the URL of a request.
|
|
||||||
* It checks if the URL contains localhost and if the extension is installed.
|
|
||||||
* It also provides an action to enable the extension.
|
|
||||||
*
|
|
||||||
* NOTE: Initializing this service registers it as a inspector with the Inspection Service.
|
|
||||||
*/
|
|
||||||
export class URLInspectorService extends Service implements Inspector {
|
|
||||||
public static readonly ID = "URL_INSPECTOR_SERVICE"
|
|
||||||
|
|
||||||
private t = getI18n()
|
|
||||||
|
|
||||||
public readonly inspectorID = "url"
|
|
||||||
|
|
||||||
private readonly inspection = this.bind(InspectionService)
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super()
|
|
||||||
|
|
||||||
this.inspection.registerInspector(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
getInspectorFor(req: HoppRESTRequest): InspectorResult[] {
|
|
||||||
const PROXY_ENABLED = useSetting("PROXY_ENABLED")
|
|
||||||
|
|
||||||
const currentExtensionStatus = useReadonlyStream(extensionStatus$, null)
|
|
||||||
|
|
||||||
const isExtensionInstalled = computed(() => {
|
|
||||||
return currentExtensionStatus.value === "available"
|
|
||||||
})
|
|
||||||
const EXTENSIONS_ENABLED = useSetting("EXTENSIONS_ENABLED")
|
|
||||||
|
|
||||||
const results = ref<InspectorResult[]>([])
|
|
||||||
|
|
||||||
const url = req.endpoint
|
|
||||||
|
|
||||||
const isContainLocalhost = url.includes("localhost")
|
|
||||||
|
|
||||||
if (
|
|
||||||
isContainLocalhost &&
|
|
||||||
(!EXTENSIONS_ENABLED.value || !isExtensionInstalled.value)
|
|
||||||
) {
|
|
||||||
let text
|
|
||||||
|
|
||||||
if (!isExtensionInstalled.value) {
|
|
||||||
if (currentExtensionStatus.value === "unknown-origin") {
|
|
||||||
text = this.t("inspections.url.extension_unknown_origin")
|
|
||||||
} else {
|
|
||||||
text = this.t("inspections.url.extension_not_installed")
|
|
||||||
}
|
|
||||||
} else if (!EXTENSIONS_ENABLED.value) {
|
|
||||||
text = this.t("inspections.url.extention_not_enabled")
|
|
||||||
} else {
|
|
||||||
text = this.t("inspections.url.localhost")
|
|
||||||
}
|
|
||||||
|
|
||||||
results.value.push({
|
|
||||||
id: "url",
|
|
||||||
icon: markRaw(IconAlertTriangle),
|
|
||||||
text: {
|
|
||||||
type: "text",
|
|
||||||
text: text,
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
text: this.t("inspections.url.extention_enable_action"),
|
|
||||||
apply: () => {
|
|
||||||
applySetting("EXTENSIONS_ENABLED", true)
|
|
||||||
if (PROXY_ENABLED.value) toggleSetting("PROXY_ENABLED")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
severity: 2,
|
|
||||||
isApplicable: true,
|
|
||||||
locations: {
|
|
||||||
type: "url",
|
|
||||||
},
|
|
||||||
doc: {
|
|
||||||
text: this.t("action.learn_more"),
|
|
||||||
link: "https://docs.hoppscotch.io/",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return results.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,6 +7,7 @@ import { def as historyDef } from "./platform/history/history.platform"
|
|||||||
import { def as tabStateDef } from "./platform/tabState/tabState.platform"
|
import { def as tabStateDef } from "./platform/tabState/tabState.platform"
|
||||||
import { browserInterceptor } from "@hoppscotch/common/platform/std/interceptors/browser"
|
import { browserInterceptor } from "@hoppscotch/common/platform/std/interceptors/browser"
|
||||||
import { proxyInterceptor } from "@hoppscotch/common/platform/std/interceptors/proxy"
|
import { proxyInterceptor } from "@hoppscotch/common/platform/std/interceptors/proxy"
|
||||||
|
import { ExtensionInspectorService } from "@hoppscotch/common/platform/std/inspections/extension.inspector"
|
||||||
import { ExtensionInterceptorService } from "@hoppscotch/common/platform/std/interceptors/extension"
|
import { ExtensionInterceptorService } from "@hoppscotch/common/platform/std/interceptors/extension"
|
||||||
import { stdFooterItems } from "@hoppscotch/common/platform/std/ui/footerItem"
|
import { stdFooterItems } from "@hoppscotch/common/platform/std/ui/footerItem"
|
||||||
import { stdSupportOptionItems } from "@hoppscotch/common/platform/std/ui/supportOptionsItem"
|
import { stdSupportOptionItems } from "@hoppscotch/common/platform/std/ui/supportOptionsItem"
|
||||||
@@ -32,6 +33,9 @@ createHoppApp("#app", {
|
|||||||
{ type: "service", service: ExtensionInterceptorService },
|
{ type: "service", service: ExtensionInterceptorService },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
additionalInspectors: [
|
||||||
|
{ type: "service", service: ExtensionInspectorService },
|
||||||
|
],
|
||||||
platformFeatureFlags: {
|
platformFeatureFlags: {
|
||||||
exportAsGIST: false,
|
exportAsGIST: false,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user