diff --git a/packages/hoppscotch-app/components/http/Headers.vue b/packages/hoppscotch-app/components/http/Headers.vue
index e25933640..651118c93 100644
--- a/packages/hoppscotch-app/components/http/Headers.vue
+++ b/packages/hoppscotch-app/components/http/Headers.vue
@@ -137,6 +137,47 @@
/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/hoppscotch-app/components/http/RequestOptions.vue b/packages/hoppscotch-app/components/http/RequestOptions.vue
index 486509733..3dec44d16 100644
--- a/packages/hoppscotch-app/components/http/RequestOptions.vue
+++ b/packages/hoppscotch-app/components/http/RequestOptions.vue
@@ -18,7 +18,7 @@
:label="`${$t('tab.headers')}`"
:info="`${newActiveHeadersCount$}`"
>
-
+
diff --git a/packages/hoppscotch-app/components/smart/EnvInput.vue b/packages/hoppscotch-app/components/smart/EnvInput.vue
index 196f0fafa..761cc4564 100644
--- a/packages/hoppscotch-app/components/smart/EnvInput.vue
+++ b/packages/hoppscotch-app/components/smart/EnvInput.vue
@@ -47,6 +47,7 @@ const props = withDefaults(
styles: string
envs: { key: string; value: string; source: string }[] | null
focus: boolean
+ readonly: boolean
}>(),
{
value: "",
@@ -54,6 +55,7 @@ const props = withDefaults(
styles: "",
envs: null,
focus: false,
+ readonly: false,
}
)
@@ -123,7 +125,23 @@ const envTooltipPlugin = new HoppReactiveEnvPlugin(envVars, view)
const initView = (el: any) => {
const extensions: Extension = [
EditorView.contentAttributes.of({ "aria-label": props.placeholder }),
+ EditorView.updateListener.of((update) => {
+ if (props.readonly) {
+ update.view.contentDOM.inputMode = "none"
+ }
+ }),
+ EditorState.changeFilter.of(() => !props.readonly),
inputTheme,
+ props.readonly
+ ? EditorView.theme({
+ ".cm-content": {
+ caretColor: "var(--secondary-dark-color) !important",
+ color: "var(--secondary-dark-color) !important",
+ backgroundColor: "var(--divider-color) !important",
+ opacity: 0.25,
+ },
+ })
+ : EditorView.theme({}),
tooltips({
position: "absolute",
}),
@@ -141,6 +159,8 @@ const initView = (el: any) => {
ViewPlugin.fromClass(
class {
update(update: ViewUpdate) {
+ if (props.readonly) return
+
if (update.docChanged) {
const prevValue = clone(cachedValue.value)
diff --git a/packages/hoppscotch-app/helpers/utils/EffectiveURL.ts b/packages/hoppscotch-app/helpers/utils/EffectiveURL.ts
index bcecc176c..fae74280e 100644
--- a/packages/hoppscotch-app/helpers/utils/EffectiveURL.ts
+++ b/packages/hoppscotch-app/helpers/utils/EffectiveURL.ts
@@ -11,6 +11,8 @@ import {
parseBodyEnvVariables,
parseRawKeyValueEntries,
Environment,
+ HoppRESTHeader,
+ HoppRESTParam,
} from "@hoppscotch/data"
import { arrayFlatMap, arraySort } from "../functional/array"
import { toFormData } from "../functional/formData"
@@ -29,6 +31,146 @@ export interface EffectiveHoppRESTRequest extends HoppRESTRequest {
effectiveFinalBody: FormData | string | null
}
+/**
+ * Get headers that can be generated by authorization config of the request
+ * @param req Request to check
+ * @param envVars Currently active environment variables
+ * @returns The list of headers
+ */
+const getComputedAuthHeaders = (
+ req: HoppRESTRequest,
+ envVars: Environment["variables"]
+) => {
+ // If Authorization header is also being user-defined, that takes priority
+ if (req.headers.find((h) => h.key.toLowerCase() === "authorization"))
+ return []
+
+ if (!req.auth.authActive) return []
+
+ const headers: HoppRESTHeader[] = []
+
+ // TODO: Support a better b64 implementation than btoa ?
+ if (req.auth.authType === "basic") {
+ const username = parseTemplateString(req.auth.username, envVars)
+ const password = parseTemplateString(req.auth.password, envVars)
+
+ headers.push({
+ active: true,
+ key: "Authorization",
+ value: `Basic ${btoa(`${username}:${password}`)}`,
+ })
+ } else if (
+ req.auth.authType === "bearer" ||
+ req.auth.authType === "oauth-2"
+ ) {
+ headers.push({
+ active: true,
+ key: "Authorization",
+ value: `Bearer ${parseTemplateString(req.auth.token, envVars)}`,
+ })
+ } else if (req.auth.authType === "api-key") {
+ const { key, value, addTo } = req.auth
+
+ if (addTo === "Headers") {
+ headers.push({
+ active: true,
+ key: parseTemplateString(key, envVars),
+ value: parseTemplateString(value, envVars),
+ })
+ }
+ }
+
+ return headers
+}
+
+/**
+ * Get headers that can be generated by body config of the request
+ * @param req Request to check
+ * @returns The list of headers
+ */
+export const getComputedBodyHeaders = (
+ req: HoppRESTRequest
+): HoppRESTHeader[] => {
+ // If a content-type is already defined, that will override this
+ if (
+ req.headers.find(
+ (req) => req.active && req.key.toLowerCase() === "content-type"
+ )
+ )
+ return []
+
+ // Body should have a non-null content-type
+ if (req.body.contentType === null) return []
+
+ return [
+ {
+ active: true,
+ key: "content-type",
+ value: req.body.contentType,
+ },
+ ]
+}
+
+export type ComputedHeader = {
+ source: "auth" | "body"
+ header: HoppRESTHeader
+}
+
+/**
+ * Returns a list of headers that will be added during execution of the request
+ * For e.g, Authorization headers maybe added if an Auth Mode is defined on REST
+ * @param req The request to check
+ * @param envVars The environment variables active
+ * @returns The headers that are generated along with the source of that header
+ */
+export const getComputedHeaders = (
+ req: HoppRESTRequest,
+ envVars: Environment["variables"]
+): ComputedHeader[] => [
+ ...getComputedAuthHeaders(req, envVars).map((header) => ({
+ source: "auth" as const,
+ header,
+ })),
+ ...getComputedBodyHeaders(req).map((header) => ({
+ source: "body" as const,
+ header,
+ })),
+]
+
+export type ComputedParam = {
+ source: "auth"
+ param: HoppRESTParam
+}
+
+/**
+ * Returns a list of params that will be added during execution of the request
+ * For e.g, Authorization params (like API-key) maybe added if an Auth Mode is defined on REST
+ * @param req The request to check
+ * @param envVars The environment variables active
+ * @returns The params that are generated along with the source of that header
+ */
+export const getComputedParams = (
+ req: HoppRESTRequest,
+ envVars: Environment["variables"]
+): ComputedParam[] => {
+ // When this gets complex, its best to split this function off (like with getComputedHeaders)
+ // API-key auth can be added to query params
+ if (!req.auth.authActive) return []
+ if (req.auth.authType !== "api-key") return []
+ if (req.auth.addTo !== "Query params") return []
+
+ return [
+ {
+ source: "auth",
+ param: {
+ active: true,
+ key: parseTemplateString(req.auth.key, envVars),
+ value: parseTemplateString(req.auth.value, envVars),
+ },
+ },
+ ]
+}
+
// Resolves environment variables in the body
export const resolvesEnvsInBody = (
body: HoppRESTReqBody,
@@ -135,83 +277,29 @@ export function getEffectiveRESTRequest(
): EffectiveHoppRESTRequest {
const envVariables = [...environment.variables, ...getGlobalVariables()]
- const effectiveFinalHeaders = request.headers
- .filter(
- (x) =>
- x.key !== "" && // Remove empty keys
- x.active // Only active
- )
- .map((x) => ({
- // Parse out environment template strings
+ const effectiveFinalHeaders = pipe(
+ getComputedHeaders(request, envVariables).map((h) => h.header),
+ A.concat(request.headers),
+ A.filter((x) => x.active && x.key !== ""),
+ A.map((x) => ({
active: true,
key: parseTemplateString(x.key, envVariables),
value: parseTemplateString(x.value, envVariables),
}))
+ )
- const effectiveFinalParams = request.params
- .filter(
- (x) =>
- x.key !== "" && // Remove empty keys
- x.active // Only active
- )
- .map((x) => ({
+ const effectiveFinalParams = pipe(
+ getComputedParams(request, envVariables).map((p) => p.param),
+ A.concat(request.params),
+ A.filter((x) => x.active && x.key !== ""),
+ A.map((x) => ({
active: true,
key: parseTemplateString(x.key, envVariables),
value: parseTemplateString(x.value, envVariables),
}))
-
- // Authentication
- if (request.auth.authActive) {
- // TODO: Support a better b64 implementation than btoa ?
- if (request.auth.authType === "basic") {
- const username = parseTemplateString(request.auth.username, envVariables)
- const password = parseTemplateString(request.auth.password, envVariables)
-
- effectiveFinalHeaders.push({
- active: true,
- key: "Authorization",
- value: `Basic ${btoa(`${username}:${password}`)}`,
- })
- } else if (
- request.auth.authType === "bearer" ||
- request.auth.authType === "oauth-2"
- ) {
- effectiveFinalHeaders.push({
- active: true,
- key: "Authorization",
- value: `Bearer ${parseTemplateString(
- request.auth.token,
- envVariables
- )}`,
- })
- } else if (request.auth.authType === "api-key") {
- const { key, value, addTo } = request.auth
- if (addTo === "Headers") {
- effectiveFinalHeaders.push({
- active: true,
- key: parseTemplateString(key, envVariables),
- value: parseTemplateString(value, envVariables),
- })
- } else if (addTo === "Query params") {
- effectiveFinalParams.push({
- active: true,
- key: parseTemplateString(key, envVariables),
- value: parseTemplateString(value, envVariables),
- })
- }
- }
- }
+ )
const effectiveFinalBody = getFinalBodyFromRequest(request, envVariables)
- const contentTypeInHeader = effectiveFinalHeaders.find(
- (x) => x.key.toLowerCase() === "content-type"
- )
- if (request.body.contentType && !contentTypeInHeader?.value)
- effectiveFinalHeaders.push({
- active: true,
- key: "content-type",
- value: request.body.contentType,
- })
return {
...request,