From 50744136d08f58590a6ae52537e56097d1542e25 Mon Sep 17 00:00:00 2001 From: Anwarul Islam Date: Sat, 30 Apr 2022 19:53:43 +0600 Subject: [PATCH] fix: same key params are not overwritten to the last defined (#2299) Co-authored-by: Andrew Bastin --- packages/hoppscotch-app/helpers/network.ts | 14 +++---- .../helpers/strategies/AxiosStrategy.ts | 37 ++++++++++++++++--- .../helpers/strategies/ExtensionStrategy.ts | 30 ++++++++++++++- 3 files changed, 66 insertions(+), 15 deletions(-) diff --git a/packages/hoppscotch-app/helpers/network.ts b/packages/hoppscotch-app/helpers/network.ts index 74fe856ac..8f3661235 100644 --- a/packages/hoppscotch-app/helpers/network.ts +++ b/packages/hoppscotch-app/helpers/network.ts @@ -132,13 +132,13 @@ export function createRESTNetworkRequestStream( ), // Assembling params object - TE.bind("params", ({ req }) => - TE.of( - req.effectiveFinalParams.reduce((acc, { key, value }) => { - return Object.assign(acc, { [key]: value }) - }, {}) - ) - ), + TE.bind("params", ({ req }) => { + const params = new URLSearchParams() + req.effectiveFinalParams.forEach((x) => { + params.append(x.key, x.value) + }) + return TE.of(params) + }), // Keeping the backup start time TE.bind("backupTimeStart", () => TE.of(Date.now())), diff --git a/packages/hoppscotch-app/helpers/strategies/AxiosStrategy.ts b/packages/hoppscotch-app/helpers/strategies/AxiosStrategy.ts index 5d21ab14e..b7c5451e6 100644 --- a/packages/hoppscotch-app/helpers/strategies/AxiosStrategy.ts +++ b/packages/hoppscotch-app/helpers/strategies/AxiosStrategy.ts @@ -2,6 +2,7 @@ import axios, { AxiosRequestConfig } from "axios" import { v4 } from "uuid" import { pipe } from "fp-ts/function" import * as TE from "fp-ts/TaskEither" +import cloneDeep from "lodash/cloneDeep" import { NetworkResponse, NetworkStrategy } from "../network" import { decodeB64StringToArrayBuffer } from "../utils/b64" import { settingsStore } from "~/newstore/settings" @@ -40,19 +41,43 @@ const getProxyPayload = ( return payload } +const preProcessRequest = (req: AxiosRequestConfig): AxiosRequestConfig => { + const reqClone = cloneDeep(req) + + // If the parameters are URLSearchParams, inject them to URL instead + // This prevents issues of marshalling the URLSearchParams to the proxy + if (reqClone.params instanceof URLSearchParams) { + try { + const url = new URL(reqClone.url ?? "") + + for (const [key, value] of reqClone.params.entries()) { + url.searchParams.append(key, value) + } + + reqClone.url = url.toString() + } catch (e) {} + + reqClone.params = {} + } + + return reqClone +} + const axiosWithProxy: NetworkStrategy = (req) => pipe( TE.Do, + TE.bind("processedReq", () => TE.of(preProcessRequest(req))), + // If the request has FormData, the proxy needs a key - TE.bind("multipartKey", () => - TE.of(req.data instanceof FormData ? v4() : null) + TE.bind("multipartKey", ({ processedReq }) => + TE.of(processedReq.data instanceof FormData ? v4() : null) ), // Build headers to send - TE.bind("headers", ({ multipartKey }) => + TE.bind("headers", ({ processedReq, multipartKey }) => TE.of( - req.data instanceof FormData + processedReq.data instanceof FormData ? { "multipart-part-key": `proxyRequestData-${multipartKey}`, } @@ -61,8 +86,8 @@ const axiosWithProxy: NetworkStrategy = (req) => ), // Create payload - TE.bind("payload", ({ multipartKey }) => - TE.of(getProxyPayload(req, multipartKey)) + TE.bind("payload", ({ processedReq, multipartKey }) => + TE.of(getProxyPayload(processedReq, multipartKey)) ), // Run the proxy request diff --git a/packages/hoppscotch-app/helpers/strategies/ExtensionStrategy.ts b/packages/hoppscotch-app/helpers/strategies/ExtensionStrategy.ts index f5af3e3db..013155f04 100644 --- a/packages/hoppscotch-app/helpers/strategies/ExtensionStrategy.ts +++ b/packages/hoppscotch-app/helpers/strategies/ExtensionStrategy.ts @@ -1,5 +1,7 @@ import * as TE from "fp-ts/TaskEither" import { pipe } from "fp-ts/function" +import { AxiosRequestConfig } from "axios" +import cloneDeep from "lodash/cloneDeep" import { NetworkResponse, NetworkStrategy } from "../network" import { browserIsChrome, browserIsFirefox } from "../utils/userAgent" @@ -21,19 +23,43 @@ export const cancelRunningExtensionRequest = () => { } } +const preProcessRequest = (req: AxiosRequestConfig): AxiosRequestConfig => { + const reqClone = cloneDeep(req) + + // If the parameters are URLSearchParams, inject them to URL instead + // This prevents marshalling issues with structured cloning of URLSearchParams + if (reqClone.params instanceof URLSearchParams) { + try { + const url = new URL(reqClone.url ?? "") + + for (const [key, value] of reqClone.params.entries()) { + url.searchParams.append(key, value) + } + + reqClone.url = url.toString() + } catch (e) {} + + reqClone.params = {} + } + + return reqClone +} + const extensionStrategy: NetworkStrategy = (req) => pipe( TE.Do, + TE.bind("processedReq", () => TE.of(preProcessRequest(req))), + // Storeing backup timing data in case the extension does not have that info TE.bind("backupTimeDataStart", () => TE.of(new Date().getTime())), // Run the request - TE.bind("response", () => + TE.bind("response", ({ processedReq }) => TE.tryCatch( () => window.__POSTWOMAN_EXTENSION_HOOK__.sendRequest({ - ...req, + ...processedReq, wantsBinary: true, }) as Promise, (err) => err as any