diff --git a/bun.lockb b/bun.lockb index c42d05c..ce824cd 100755 Binary files a/bun.lockb and b/bun.lockb differ 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