From 589a8b0eec9502d442a23df32a34b3b898c34daa Mon Sep 17 00:00:00 2001 From: Orion Kindel Date: Fri, 23 Feb 2024 19:54:08 -0600 Subject: [PATCH] feat: HTTP.Node.fetchProxy --- bun.lockb | Bin 53117 -> 55929 bytes package.json | 5 ++++- src/HTTP/Node.js | 19 +++++++++++++++++++ src/HTTP/Node.purs | 40 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 src/HTTP/Node.js create mode 100644 src/HTTP/Node.purs diff --git a/bun.lockb b/bun.lockb index c42d05ceb16556e06bd80e7e360bf13128ae05e8..ce824cd34cdad32c9271bfe13b4cca2696f5f3da 100755 GIT binary patch delta 7840 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 I6bzmJFJp&+jQ{`u diff --git a/package.json b/package.json index e446309..d3e26f4 100644 --- a/package.json +++ b/package.json @@ -14,5 +14,8 @@ "peerDependencies": { "typescript": "^5.0.0" }, - "dependencies": {} + "dependencies": {}, + "optionalDependencies": { + "fetch-socks": "^1.2.0" + } } diff --git a/src/HTTP/Node.js b/src/HTTP/Node.js new file mode 100644 index 0000000..41a216f --- /dev/null +++ b/src/HTTP/Node.js @@ -0,0 +1,19 @@ +import { fetch, ProxyAgent } from 'undici' +import { socksDispatcher } from 'fetch-socks' + +/** @type {(_: URL) => (_: URL) => (_: string) => (_: Record) => (_: null | string | FormData) => () => Promise} */ +export const fetchImpl = proxyURL => url => method => headers => body => () => { + const dispatcher = proxyURL.protocol.startsWith('https') + ? new ProxyAgent(proxyURL.host) + : proxyURL.protocol.startsWith('socks') + ? socksDispatcher({ + type: 5, + host: proxyURL.hostname, + port: parseInt(proxyURL.port, 10), + }) + : (() => { + throw new Error(`unsupported proxy scheme ${proxyURL.protocol}`) + })() + + return fetch(url, { dispatcher }) +} diff --git a/src/HTTP/Node.purs b/src/HTTP/Node.purs new file mode 100644 index 0000000..9149013 --- /dev/null +++ b/src/HTTP/Node.purs @@ -0,0 +1,40 @@ +module HTTP.Node (fetchProxy, module X) where + +import Prelude + +import Control.Promise (Promise) +import Control.Promise as Promise +import Data.Nullable (Nullable) +import Data.Nullable as Nullable +import Data.URL (URL) +import Effect (Effect) +import Effect.Aff.Class (class MonadAff, liftAff) +import Foreign.Object (Object) +import Foreign.Object as Object +import HTTP.Header (Headers(..)) +import HTTP.Header (headers) as X +import HTTP.Request (bodyToRaw) +import HTTP.Request (class Request, Method(..)) as X +import HTTP.Request as Req +import HTTP.Response (Response) + +foreign import fetchImpl :: URL -> URL -> String -> Object String -> Nullable Req.RawRequestBody -> Effect (Promise Response) + +fetchProxy :: forall m a. MonadAff m => Req.Request a => URL -> a -> m Response +fetchProxy pxy req = do + url <- Req.requestUrl req + method <- Req.requestMethod req + body <- Req.requestBody req + bodyRaw <- bodyToRaw body + Headers headers <- Req.requestHeaders req + + let + methodStr = case method of + Req.GET -> "GET" + Req.PUT -> "PUT" + Req.POST -> "POST" + Req.PATCH -> "PATCH" + Req.DELETE -> "DELETE" + headers' = Object.fromFoldableWithIndex headers + + liftAff $ Promise.toAffE $ fetchImpl pxy url methodStr headers' $ Nullable.toNullable bodyRaw