har.ts 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. import * as RA from "fp-ts/ReadonlyArray"
  2. import * as S from "fp-ts/string"
  3. import { pipe, flow } from "fp-ts/function"
  4. import * as Har from "har-format"
  5. import { HoppRESTRequest } from "@hoppscotch/data"
  6. import { FieldEquals, objectFieldIncludes } from "../typeutils"
  7. // Hoppscotch support HAR Spec 1.2
  8. // For more info on the spec: http://www.softwareishard.com/blog/har-12-spec/
  9. const buildHarHeaders = (req: HoppRESTRequest): Har.Header[] => {
  10. return req.headers
  11. .filter((header) => header.active)
  12. .map((header) => ({
  13. name: header.key,
  14. value: header.value,
  15. }))
  16. }
  17. const buildHarQueryStrings = (req: HoppRESTRequest): Har.QueryString[] => {
  18. return req.params
  19. .filter((param) => param.active)
  20. .map((param) => ({
  21. name: param.key,
  22. value: param.value,
  23. }))
  24. }
  25. const buildHarPostParams = (
  26. req: HoppRESTRequest &
  27. FieldEquals<HoppRESTRequest, "method", ["POST", "PUT"]> & {
  28. body: {
  29. contentType: "application/x-www-form-urlencoded" | "multipart/form-data"
  30. }
  31. }
  32. ): Har.Param[] => {
  33. // URL Encoded strings have a string style of contents
  34. if (req.body.contentType === "application/x-www-form-urlencoded") {
  35. return pipe(
  36. req.body.body,
  37. S.split("\n"),
  38. RA.map(
  39. flow(
  40. // Define how each lines are parsed
  41. S.split(":"), // Split by ":"
  42. RA.map(S.trim), // Remove trailing spaces in key/value begins and ends
  43. ([key, value]) => ({
  44. // Convert into a proper key value definition
  45. name: key,
  46. value: value ?? "", // Value can be undefined (if no ":" is present)
  47. })
  48. )
  49. ),
  50. RA.toArray
  51. )
  52. } else {
  53. // FormData has its own format
  54. return req.body.body.flatMap((entry) => {
  55. if (entry.isFile) {
  56. // We support multiple files
  57. return entry.value.map(
  58. (file) =>
  59. <Har.Param>{
  60. name: entry.key,
  61. fileName: entry.key, // TODO: Blob doesn't contain file info, anyway to bring file name here ?
  62. contentType: file.type,
  63. }
  64. )
  65. } else {
  66. return {
  67. name: entry.key,
  68. value: entry.value,
  69. }
  70. }
  71. })
  72. }
  73. }
  74. const buildHarPostData = (req: HoppRESTRequest): Har.PostData | undefined => {
  75. if (!req.body.contentType) return undefined
  76. if (
  77. objectFieldIncludes(req.body, "contentType", [
  78. "application/x-www-form-urlencoded",
  79. "multipart/form-data",
  80. ] as const)
  81. ) {
  82. return {
  83. mimeType: req.body.contentType, // By default assume JSON ?
  84. params: buildHarPostParams(req as any),
  85. }
  86. }
  87. return {
  88. mimeType: req.body.contentType, // Let's assume by default content type is JSON
  89. text: req.body.body,
  90. }
  91. }
  92. export const buildHarRequest = (req: HoppRESTRequest): Har.Request => {
  93. return {
  94. bodySize: -1, // TODO: It would be cool if we can calculate the body size
  95. headersSize: -1, // TODO: It would be cool if we can calculate the header size
  96. httpVersion: "HTTP/1.1",
  97. cookies: [], // Hoppscotch does not have formal support for Cookies as of right now
  98. headers: buildHarHeaders(req),
  99. method: req.method,
  100. queryString: buildHarQueryStrings(req),
  101. url: req.endpoint,
  102. postData: buildHarPostData(req),
  103. }
  104. }