diff --git a/packages/hoppscotch-app/helpers/new-codegen/har.ts b/packages/hoppscotch-app/helpers/new-codegen/har.ts new file mode 100644 index 000000000..d8e1bdfcc --- /dev/null +++ b/packages/hoppscotch-app/helpers/new-codegen/har.ts @@ -0,0 +1,107 @@ +import * as Har from "har-format" +import { HoppRESTRequest } from "@hoppscotch/data" +import { FieldEquals, objectFieldIncludes } from "../typeutils" + +// We support HAR Spec 1.2 +// For more info on the spec: http://www.softwareishard.com/blog/har-12-spec/ + +const buildHarHeaders = (req: HoppRESTRequest): Har.Header[] => { + return req.headers + .filter((header) => header.active) + .map((header) => ({ + name: header.key, + value: header.value, + })) +} + +const buildHarQueryStrings = (req: HoppRESTRequest): Har.QueryString[] => { + return req.params + .filter((param) => param.active) + .map((param) => ({ + name: param.key, + value: param.value, + })) +} + +const buildHarPostParams = ( + req: HoppRESTRequest & + FieldEquals & { + body: { + contentType: "application/x-www-form-urlencoded" | "multipart/form-data" + } + } +): Har.Param[] => { + // URL Encoded strings have a string style of contents + if (req.body.contentType === "application/x-www-form-urlencoded") { + return req.body.body + .split("&") // Split by separators + .map((keyValue) => { + const [key, value] = keyValue.split("=") + + return { + name: key, + value, + } + }) + } else { + // FormData has its own format + return req.body.body.flatMap((entry) => { + if (entry.isFile) { + // We support multiple files + return entry.value.map( + (file) => + { + name: entry.key, + fileName: entry.key, // TODO: Blob doesn't contain file info, anyway to bring file name here ? + contentType: file.type, + } + ) + } else { + return { + name: entry.key, + value: entry.value, + } + } + }) + } +} + +const buildHarPostData = (req: HoppRESTRequest): Har.PostData | undefined => { + if (!req.body.contentType) return undefined + + if (!objectFieldIncludes(req, "method", ["POST", "PUT"] as const)) + return undefined + + if ( + objectFieldIncludes(req.body, "contentType", [ + "application/x-www-form-urlencoded", + "multipart/form-data", + ] as const) + ) { + return { + mimeType: req.body.contentType, // By default assume JSON ? + params: buildHarPostParams(req as any), + } + } else { + if (!req.body.contentType) return undefined + + return { + mimeType: req.body.contentType, // Let's assume by default content type is JSON + text: req.body.body, + } + } +} + +export const buildHarRequest = (req: HoppRESTRequest): Har.Request => { + return { + bodySize: -1, // TODO: It would be cool if we can calculate the body size + headersSize: -1, // TODO: It would be cool if we can calculate the header size + httpVersion: "HTTP/1.1", + cookies: [], // Hoppscotch does not have formal support for Cookies as of right now + headers: buildHarHeaders(req), + method: req.method, + queryString: buildHarQueryStrings(req), + url: req.endpoint, + postData: buildHarPostData(req), + } +} diff --git a/packages/hoppscotch-app/helpers/typeutils.ts b/packages/hoppscotch-app/helpers/typeutils.ts new file mode 100644 index 000000000..654932956 --- /dev/null +++ b/packages/hoppscotch-app/helpers/typeutils.ts @@ -0,0 +1,17 @@ +export type FieldEquals = { + // eslint-disable-next-line + [_x in K]: Vals[number] +} + +export const objectFieldIncludes = < + T, + K extends keyof T, + V extends readonly T[K][] +>( + obj: T, + field: K, + values: V + // eslint-disable-next-line +): obj is T & { [_x in K]: V[number] } => { + return values.includes(obj[field]) +} diff --git a/packages/hoppscotch-app/package.json b/packages/hoppscotch-app/package.json index 83576cf89..e5f426318 100644 --- a/packages/hoppscotch-app/package.json +++ b/packages/hoppscotch-app/package.json @@ -81,6 +81,7 @@ "graphql-language-service-interface": "^2.9.1", "graphql-language-service-parser": "^1.10.4", "graphql-tag": "^2.12.6", + "httpsnippet": "^2.0.0", "io-ts": "^2.2.16", "json-loader": "^0.5.7", "lodash": "^4.17.21", @@ -135,6 +136,8 @@ "@types/codemirror": "^5.60.5", "@types/cookie": "^0.4.1", "@types/esprima": "^4.0.3", + "@types/har-format": "^1.2.8", + "@types/httpsnippet": "^1.23.1", "@types/lodash": "^4.14.177", "@types/splitpanes": "^2.2.1", "@types/uuid": "^8.3.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a50d179e9..508d68204 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -100,6 +100,8 @@ importers: '@types/codemirror': ^5.60.5 '@types/cookie': ^0.4.1 '@types/esprima': ^4.0.3 + '@types/har-format': ^1.2.8 + '@types/httpsnippet': ^1.23.1 '@types/lodash': ^4.14.177 '@types/splitpanes': ^2.2.1 '@types/uuid': ^8.3.3 @@ -129,6 +131,7 @@ importers: graphql-language-service-interface: ^2.9.1 graphql-language-service-parser: ^1.10.4 graphql-tag: ^2.12.6 + httpsnippet: ^2.0.0 io-ts: ^2.2.16 jest: ^27.4.5 jest-serializer-vue: ^2.0.2 @@ -219,6 +222,7 @@ importers: graphql-language-service-interface: 2.9.1_graphql@15.7.2+typescript@4.5.4 graphql-language-service-parser: 1.10.4_graphql@15.7.2+typescript@4.5.4 graphql-tag: 2.12.6_graphql@15.7.2 + httpsnippet: 2.0.0 io-ts: 2.2.16_fp-ts@2.11.5 json-loader: 0.5.7 lodash: 4.17.21 @@ -272,6 +276,8 @@ importers: '@types/codemirror': 5.60.5 '@types/cookie': 0.4.1 '@types/esprima': 4.0.3 + '@types/har-format': 1.2.8 + '@types/httpsnippet': 1.23.1 '@types/lodash': 4.14.178 '@types/splitpanes': 2.2.1 '@types/uuid': 8.3.3 @@ -4635,6 +4641,10 @@ packages: '@types/node': 17.0.5 dev: true + /@types/har-format/1.2.8: + resolution: {integrity: sha512-OP6L9VuZNdskgNN3zFQQ54ceYD8OLq5IbqO4VK91ORLfOm7WdT/CiT/pHEBSQEqCInJ2y3O6iCm/zGtPElpgJQ==} + dev: true + /@types/html-minifier-terser/5.1.2: resolution: {integrity: sha512-h4lTMgMJctJybDp8CQrxTUiiYmedihHWkjnF/8Pxseu2S6Nlfcy8kwboQ8yejh456rP2yWoEVm1sS/FVsfM48w==} dev: false @@ -4657,6 +4667,12 @@ packages: '@types/node': 16.11.12 dev: false + /@types/httpsnippet/1.23.1: + resolution: {integrity: sha512-i8PkOoRuOBunHpIs07aB55eqqXlFxZD8Q37UemJ2nCFK+x1dagJtrQzEvsbseefqHmW6Z9mJl834jY+ktm3FLA==} + dependencies: + '@types/har-format': 1.2.8 + dev: true + /@types/istanbul-lib-coverage/2.0.3: resolution: {integrity: sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==} dev: true @@ -5634,7 +5650,7 @@ packages: resolution: {integrity: sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==} engines: {node: '>= 0.6'} dependencies: - mime-types: 2.1.33 + mime-types: 2.1.34 negotiator: 0.6.2 dev: false @@ -5978,7 +5994,6 @@ packages: /asynckit/0.4.0: resolution: {integrity: sha1-x57Zf380y48robyXkLzDZkdLS3k=} - dev: true /at-least-node/1.0.0: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} @@ -7194,7 +7209,6 @@ packages: engines: {node: '>= 0.8'} dependencies: delayed-stream: 1.0.0 - dev: true /commander/2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -7254,7 +7268,7 @@ packages: resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} engines: {node: '>= 0.6'} dependencies: - mime-db: 1.50.0 + mime-db: 1.51.0 dev: false /compression/1.7.4: @@ -8057,7 +8071,6 @@ packages: /delayed-stream/1.0.0: resolution: {integrity: sha1-3zrhmayt+31ECqrgsp4icrJOxhk=} engines: {node: '>=0.4.0'} - dev: true /delegates/1.0.0: resolution: {integrity: sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=} @@ -9121,6 +9134,18 @@ packages: resolution: {integrity: sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=} engines: {node: '>= 0.6'} + /event-stream/3.3.4: + resolution: {integrity: sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=} + dependencies: + duplexer: 0.1.2 + from: 0.1.7 + map-stream: 0.1.0 + pause-stream: 0.0.11 + split: 0.3.3 + stream-combiner: 0.0.4 + through: 2.3.8 + dev: false + /event-target-shim/5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} @@ -9600,6 +9625,15 @@ packages: /form-data-encoder/1.7.1: resolution: {integrity: sha512-EFRDrsMm/kyqbTQocNvRXMLjc7Es2Vk+IQFx/YW7hkUH1eBl4J1fqiP34l74Yt0pFLCNpc06fkbVk00008mzjg==} + /form-data/3.0.0: + resolution: {integrity: sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.34 + dev: false + /form-data/3.0.1: resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==} engines: {node: '>= 6'} @@ -9639,6 +9673,10 @@ packages: resolution: {integrity: sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=} engines: {node: '>= 0.6'} + /from/0.1.7: + resolution: {integrity: sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=} + dev: false + /from2/2.3.0: resolution: {integrity: sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=} dependencies: @@ -9686,6 +9724,12 @@ packages: /fs-monkey/1.0.3: resolution: {integrity: sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==} + /fs-readfile-promise/2.0.1: + resolution: {integrity: sha1-gAI4I5gfn//+AWCei+Zo9prknnA=} + dependencies: + graceful-fs: 4.2.8 + dev: false + /fs-write-stream-atomic/1.0.10: resolution: {integrity: sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=} dependencies: @@ -9695,6 +9739,16 @@ packages: readable-stream: 2.3.7 dev: false + /fs-writefile-promise/1.0.3: + resolution: {integrity: sha1-4C+bWP/CVe2CKtx6ARFPRF1I0GM=} + engines: {node: '>=0.10'} + dependencies: + mkdirp-promise: 1.1.0 + pinkie-promise: 1.0.0 + transitivePeerDependencies: + - mkdirp + dev: false + /fs.realpath/1.0.0: resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=} @@ -9757,6 +9811,10 @@ packages: has: 1.0.3 has-symbols: 1.0.2 + /get-own-enumerable-property-symbols/3.0.2: + resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==} + dev: false + /get-package-type/0.1.0: resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} engines: {node: '>=8.0.0'} @@ -10136,6 +10194,20 @@ packages: resolution: {integrity: sha512-7+G0/2/COR8pwteYFqHIVYfQpuEiO2HXwJrhCBJVgrNrl9O5eaUoJVDGXUJX+0RpGncNVTuestexjk1afj01wQ==} dev: false + /har-schema/2.0.0: + resolution: {integrity: sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=} + engines: {node: '>=4'} + dev: false + + /har-validator/5.1.5: + resolution: {integrity: sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==} + engines: {node: '>=6'} + deprecated: this library is no longer supported + dependencies: + ajv: 6.12.6 + har-schema: 2.0.0 + dev: false + /hard-rejection/2.1.0: resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} engines: {node: '>=6'} @@ -10515,6 +10587,24 @@ packages: transitivePeerDependencies: - supports-color + /httpsnippet/2.0.0: + resolution: {integrity: sha512-Hb2ttfB5OhasYxwChZ8QKpYX3v4plNvwMaMulUIC7M3RHRDf1Op6EMp47LfaU2sgQgfvo5spWK4xRAirMEisrg==} + engines: {node: '>=10'} + hasBin: true + dependencies: + chalk: 1.1.3 + commander: 2.20.3 + debug: 2.6.9 + event-stream: 3.3.4 + form-data: 3.0.0 + fs-readfile-promise: 2.0.1 + fs-writefile-promise: 1.0.3 + har-validator: 5.1.5 + stringify-object: 3.3.0 + transitivePeerDependencies: + - mkdirp + dev: false + /human-signals/2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -10986,6 +11076,11 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + /is-obj/1.0.1: + resolution: {integrity: sha1-PkcprB9f3gJc19g6iW2rn09n2w8=} + engines: {node: '>=0.10.0'} + dev: false + /is-obj/2.0.0: resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} engines: {node: '>=8'} @@ -11037,6 +11132,11 @@ packages: call-bind: 1.0.2 has-tostringtag: 1.0.0 + /is-regexp/1.0.0: + resolution: {integrity: sha1-/S2INUXEa6xaYz57mgnof6LLUGk=} + engines: {node: '>=0.10.0'} + dev: false + /is-regexp/2.1.0: resolution: {integrity: sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA==} engines: {node: '>=6'} @@ -12541,6 +12641,10 @@ packages: engines: {node: '>=8'} dev: true + /map-stream/0.1.0: + resolution: {integrity: sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=} + dev: false + /map-visit/1.0.0: resolution: {integrity: sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=} engines: {node: '>=0.10.0'} @@ -12729,7 +12833,6 @@ packages: /mime-db/1.51.0: resolution: {integrity: sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==} engines: {node: '>= 0.6'} - dev: true /mime-types/2.1.33: resolution: {integrity: sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g==} @@ -12742,7 +12845,6 @@ packages: engines: {node: '>= 0.6'} dependencies: mime-db: 1.51.0 - dev: true /mime/1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} @@ -12872,6 +12974,14 @@ packages: for-in: 1.0.2 is-extendable: 1.0.1 + /mkdirp-promise/1.1.0: + resolution: {integrity: sha1-LISJPtZ24NmPsY+5piEv0bK5qBk=} + engines: {node: '>=4'} + deprecated: This package is broken and no longer maintained. 'mkdirp' itself supports promises now, please switch to that. + peerDependencies: + mkdirp: '>=0.5.0' + dev: false + /mkdirp/0.5.5: resolution: {integrity: sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==} hasBin: true @@ -13131,7 +13241,7 @@ packages: dependencies: destroy: 1.0.4 etag: 1.8.1 - mime-types: 2.1.33 + mime-types: 2.1.34 on-finished: 2.3.0 vary: 1.1.2 dev: false @@ -13783,6 +13893,12 @@ packages: resolution: {integrity: sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==} dev: true + /pause-stream/0.0.11: + resolution: {integrity: sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=} + dependencies: + through: 2.3.8 + dev: false + /pbkdf2/3.1.2: resolution: {integrity: sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==} engines: {node: '>=0.12'} @@ -13828,6 +13944,18 @@ packages: engines: {node: '>=10'} dev: false + /pinkie-promise/1.0.0: + resolution: {integrity: sha1-0dpn9UglY7t89X8oauKCLs+/NnA=} + engines: {node: '>=0.10.0'} + dependencies: + pinkie: 1.0.0 + dev: false + + /pinkie/1.0.0: + resolution: {integrity: sha1-Wkfyi6EBXQIBvae/DzWOR77Ix+Q=} + engines: {node: '>=0.10.0'} + dev: false + /pirates/4.0.4: resolution: {integrity: sha512-ZIrVPH+A52Dw84R0L3/VS9Op04PuQ2SEoJL6bkshmiTic/HldyW9Tf7oH5mhJZBK7NmDx27vSMrYEXPXclpDKw==} engines: {node: '>= 6'} @@ -15333,7 +15461,7 @@ packages: rollup: 2.62.0 typescript: 4.5.4 optionalDependencies: - '@babel/code-frame': 7.16.0 + '@babel/code-frame': 7.16.7 dev: true /rollup-plugin-ts/2.0.4_rollup@2.62.0+typescript@4.5.4: @@ -16048,6 +16176,12 @@ packages: dependencies: extend-shallow: 3.0.2 + /split/0.3.3: + resolution: {integrity: sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=} + dependencies: + through: 2.3.8 + dev: false + /split2/3.2.2: resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} dependencies: @@ -16126,6 +16260,12 @@ packages: readable-stream: 2.3.7 dev: false + /stream-combiner/0.0.4: + resolution: {integrity: sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=} + dependencies: + duplexer: 0.1.2 + dev: false + /stream-each/1.2.3: resolution: {integrity: sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==} dependencies: @@ -16236,6 +16376,15 @@ packages: dependencies: safe-buffer: 5.2.1 + /stringify-object/3.3.0: + resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==} + engines: {node: '>=4'} + dependencies: + get-own-enumerable-property-symbols: 3.0.2 + is-obj: 1.0.1 + is-regexp: 1.0.0 + dev: false + /strip-ansi/3.0.1: resolution: {integrity: sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=} engines: {node: '>=0.10.0'} @@ -17885,7 +18034,7 @@ packages: colorette: 1.4.0 mem: 8.1.1 memfs: 3.3.0 - mime-types: 2.1.33 + mime-types: 2.1.34 range-parser: 1.2.1 schema-utils: 3.1.1 webpack: 4.46.0