url.ts 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. import parser from "yargs-parser"
  2. import { pipe } from "fp-ts/function"
  3. import * as O from "fp-ts/Option"
  4. import * as A from "fp-ts/Array"
  5. import { getDefaultRESTRequest } from "~/newstore/RESTSession"
  6. import { stringArrayJoin } from "~/helpers/functional/array"
  7. const defaultRESTReq = getDefaultRESTRequest()
  8. const getProtocolFromURL = (url: string) =>
  9. pipe(
  10. // get the base URL
  11. /^([^\s:@]+:[^\s:@]+@)?([^:/\s]+)([:]*)/.exec(url),
  12. O.fromNullable,
  13. O.filter((burl) => burl.length > 1),
  14. O.map((burl) => burl[2]),
  15. // set protocol to http for local URLs
  16. O.map((burl) =>
  17. burl === "localhost" ||
  18. burl === "2130706433" ||
  19. /127(\.0){0,2}\.1/.test(burl) ||
  20. /0177(\.0){0,2}\.1/.test(burl) ||
  21. /0x7f(\.0){0,2}\.1/.test(burl) ||
  22. /192\.168(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){2}/.test(burl) ||
  23. /10(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}/.test(burl)
  24. ? "http://" + url
  25. : "https://" + url
  26. )
  27. )
  28. /**
  29. * Checks if the URL is valid using the URL constructor
  30. * @param urlString URL string (with protocol)
  31. * @returns boolean whether the URL is valid using the inbuilt URL class
  32. */
  33. const isURLValid = (urlString: string) =>
  34. pipe(
  35. O.tryCatch(() => new URL(urlString)),
  36. O.isSome
  37. )
  38. /**
  39. * Checks and returns URL object for the valid URL
  40. * @param urlText Raw URL string provided by argument parser
  41. * @returns Option of URL object
  42. */
  43. const parseURL = (urlText: string | number) =>
  44. pipe(
  45. urlText,
  46. O.fromNullable,
  47. // preprocess url string
  48. O.map((u) => u.toString().replaceAll(/[^a-zA-Z0-9_\-./?&=:@%+#,;\s]/g, "")),
  49. O.filter((u) => u.length > 0),
  50. O.chain((u) =>
  51. pipe(
  52. u,
  53. // check if protocol is available
  54. O.fromPredicate(
  55. (url: string) => /^[^:\s]+(?=:\/\/)/.exec(url) !== null
  56. ),
  57. O.alt(() => getProtocolFromURL(u))
  58. )
  59. ),
  60. O.filter(isURLValid),
  61. O.map((u) => new URL(u))
  62. )
  63. /**
  64. * Processes URL string and returns the URL object
  65. * @param parsedArguments Parsed Arguments object
  66. * @returns URL object
  67. */
  68. export function getURLObject(parsedArguments: parser.Arguments) {
  69. return pipe(
  70. // contains raw url strings
  71. parsedArguments._.slice(1),
  72. A.findFirstMap(parseURL),
  73. // no url found
  74. O.getOrElse(() => new URL(defaultRESTReq.endpoint))
  75. )
  76. }
  77. /**
  78. * Joins dangling params to origin
  79. * @param urlObject URL object containing origin and pathname
  80. * @param danglingParams Keys of params with empty values
  81. * @returns origin string concatenated with dangling paramas
  82. */
  83. export function concatParams(urlObject: URL, danglingParams: string[]) {
  84. return pipe(
  85. O.Do,
  86. O.bind("originString", () =>
  87. pipe(
  88. urlObject.origin,
  89. O.fromPredicate((h) => h !== "")
  90. )
  91. ),
  92. O.map(({ originString }) =>
  93. pipe(
  94. danglingParams,
  95. O.fromPredicate((dp) => dp.length > 0),
  96. O.map(stringArrayJoin("&")),
  97. O.map((h) => originString + (urlObject.pathname || "") + "?" + h),
  98. O.getOrElse(() => originString + (urlObject.pathname || ""))
  99. )
  100. ),
  101. O.getOrElse(() => defaultRESTReq.endpoint)
  102. )
  103. }