UNPKG

@antoinette-agency/sofetch

Version:

An opinionated Fetch wrapper for JSON APIs

4 lines 73.3 kB
{ "version": 3, "sources": ["src/index.ts", "src/cookieTypescriptUtils.ts", "src/UserAgents.ts", "src/soFetchConfig.ts", "src/soFetchPromise.ts", "src/sleep.ts", "src/getPayloadType.ts", "src/handleHttpErrors.ts", "src/transformRequest.ts", "src/handleBeforeFetchSend.ts", "src/soFetch.ts", "src/httpStatus.ts"], "sourcesContent": ["import soFetch, { SoFetchRequest } from \"./soFetch.ts\";\r\nimport {SoFetchPromise} from \"./soFetchPromise.ts\";\r\nimport {SoFetchConfig} from \"./soFetchConfig.ts\";\r\nimport {SoFetchLike} from \"./soFetchLike.ts\";\r\nimport { UploadPayload } from \"./uploadPayload.ts\";\r\nimport { ErrorHandlerDict } from \"./errorHandlerDict.ts\";\r\nimport { FileWithFieldName } from \"./fileWithFieldName.ts\";\r\nimport { AuthenticationType } from \"./authenticationType.ts\";\r\nimport { AuthTokenStorageType } from \"./authTokenStorageType.ts\";\r\nimport { HttpStatus } from \"./httpStatus.ts\";\r\nimport { Browser } from \"./browser.ts\";\r\nimport { OS } from \"./OS.ts\";\r\n\r\nexport default soFetch\r\n\r\nexport {SoFetchPromise, SoFetchConfig, HttpStatus}\r\n\r\nclass RequestMethod {\r\n}\r\n\r\nexport type {\r\n AuthenticationType,\r\n AuthTokenStorageType,\r\n Browser,\r\n ErrorHandlerDict,\r\n FileWithFieldName,\r\n OS,\r\n RequestMethod,\r\n SoFetchLike, \r\n SoFetchRequest, \r\n UploadPayload\r\n}\r\n\r\n// Expose to window in browser builds\r\nif (typeof window !== 'undefined') {\r\n (window as any).soFetch = soFetch;\r\n}", "export function getCookie(name: string, documentCookie?:string) {\r\n if (typeof(document) === \"undefined\" && !documentCookie) {\r\n return;\r\n }\r\n const value = documentCookie || document.cookie;\r\n const cookies = value.split(\"; \");\r\n const cookieEntries = cookies.map(c => {\r\n const parts = c.split(\"=\")\r\n return {\r\n key:parts[0],\r\n value:parts[1]\r\n }\r\n })\r\n const cookie = cookieEntries.find(x => x.key === name)\r\n return cookie?.value\r\n}\r\n", "import {Browser} from \"./browser.ts\";\r\nimport {OS} from \"./OS.ts\";\r\n\r\ninterface UserAgent {\r\n browser:Browser,\r\n os:OS,\r\n value: string\r\n}\r\nexport const UserAgents:UserAgent[] = [\r\n {\r\n browser:\"Chrome\",\r\n os:\"Windows\",\r\n value:\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36\"\r\n },\r\n {\r\n browser:\"Firefox\",\r\n os:\"Windows\",\r\n value:\"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0\"\r\n },\r\n {\r\n browser:\"Edge\",\r\n os:\"Windows\",\r\n value:\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0\"\r\n },\r\n {\r\n browser:\"Chrome\",\r\n os:\"macOS\",\r\n value:\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36\"\r\n },\r\n {\r\n browser:\"Firefox\",\r\n os:\"macOS\",\r\n value:\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:128.0) Gecko/20100101 Firefox/128.0\"\r\n },\r\n {\r\n browser:\"Safari\",\r\n os:\"macOS\",\r\n value:\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Safari/605.1.15\"\r\n },\r\n {\r\n browser:\"Chrome\",\r\n os:\"Linux\",\r\n value:\"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36\"\r\n },\r\n {\r\n browser:\"Firefox\",\r\n os:\"Linux\",\r\n value:\"Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0\"\r\n },\r\n]", "import {ErrorHandlerDict} from \"./errorHandlerDict.ts\";\r\nimport {SoFetchRequest} from \"./soFetch.ts\";\r\nimport {getCookie} from \"./cookieTypescriptUtils.ts\";\r\nimport {AuthTokenStorageType} from \"./authTokenStorageType.ts\";\r\nimport {AuthenticationType} from \"./authenticationType.ts\";\r\nimport {Browser} from \"./browser.ts\";\r\nimport {OS} from \"./OS.ts\";\r\nimport {UserAgents} from \"./UserAgents.ts\";\r\n\r\n/**\r\n * Configures all requests for a specific soFetch instance\r\n */\r\nexport class SoFetchConfig {\r\n private errorHandlers: ErrorHandlerDict = {}\r\n protected beforeSendHandlers: ((request: SoFetchRequest) => Promise<SoFetchRequest | void> | SoFetchRequest | void)[] = []\r\n protected beforeFetchSendHandlers: ((request: RequestInit) => Promise<RequestInit | void> | RequestInit | void)[] = []\r\n protected onRequestCompleteHandlers: ((response: Response, requestData: {\r\n duration: number,\r\n method: string\r\n }) => Promise<void> | void)[] = []\r\n\r\n /**\r\n * Specifies how (or if) soFetch should persist and authentication\r\n */\r\n public authTokenStorage: AuthTokenStorageType = null\r\n private inMemoryAuthToken: string = \"\"\r\n\r\n /**\r\n * Specifies how soFetch should send authentication credentials to the server\r\n */\r\n public authenticationType: AuthenticationType = null\r\n\r\n protected getAuthToken = async () => {\r\n switch (this.authTokenStorage) {\r\n case null:\r\n return \"\"\r\n case \"memory\":\r\n return this.inMemoryAuthToken\r\n case \"localStorage\":\r\n return localStorage?.getItem(this.authenticationKey) || \"\"\r\n case \"sessionStorage\":\r\n return sessionStorage?.getItem(this.authenticationKey) || \"\"\r\n case \"cookie\":\r\n return getCookie(this.authenticationKey) || \"\"\r\n default:\r\n return this.authTokenStorage();\r\n }\r\n }\r\n\r\n /**\r\n * Use this method to set an auth token after it's been received from a server, typically as\r\n * the response to a login request\r\n * @param authToken\r\n */\r\n public setAuthToken = (authToken: string) => {\r\n switch (this.authTokenStorage) {\r\n case \"memory\":\r\n this.inMemoryAuthToken = authToken\r\n break\r\n case \"localStorage\":\r\n localStorage.setItem(this.authenticationKey, authToken)\r\n break\r\n case \"sessionStorage\":\r\n sessionStorage.setItem(this.authenticationKey, authToken)\r\n break\r\n case \"cookie\":\r\n document.cookie = `${this.authenticationKey}=${authToken};`\r\n break\r\n /*If we're here then authTokenStorage is either null or a custom function so\r\n there's nothing to do*/\r\n default:\r\n break\r\n }\r\n }\r\n\r\n /**\r\n * The base URL for all HTTP requests in the instance. If absent this is assumed to be the current base url.\r\n * If running in Node relative requests without a baseUrl will throw an error.\r\n */\r\n baseUrl: string = \"\"\r\n\r\n /**\r\n * The key which is used if an authentication token is persisted via cookies, localStorage or sessionStorage\r\n */\r\n authenticationKey: string = \"SOFETCH_AUTHENTICATION\"\r\n\r\n /**\r\n * The key which is used if an authentication token is sent to the server via a custom header\r\n */\r\n authHeaderKey: string = \"\"\r\n\r\n /**\r\n * The key which is used if an authentication token is sent to the server via the query string\r\n */\r\n authQueryStringKey: string = \"\"\r\n userAgent: string = \"\"\r\n globalErrorHandler?: (e: any, res: (Response | undefined)) => void;\r\n\r\n /**\r\n * Adds a handler which will be executed on receipt from the server of the specified status code.\r\n * Multiple handlers will be executed in the order in which they are added. If a request has it's\r\n * own handler(s) for a given status code the corresponding handlers in the config will not be executed.\r\n * @param status An HTTP status code\r\n * @param handler A function which accepts a Fetch Response as an argument\r\n * @example\r\n *\r\n * soFetchConfig.catchHttp(404, (res:Response) => {\r\n * alert(\"This object can't be found\")\r\n * })\r\n *\r\n * @see For more examples see https://sofetch.antoinette.agency\r\n */\r\n catchHTTP(status: number, handler: (res: Response) => void) {\r\n if (!this.errorHandlers[status]) {\r\n this.errorHandlers[status] = []\r\n }\r\n this.errorHandlers[status].push(handler)\r\n }\r\n\r\n /**\r\n * Adds a global error handler which will be executed if any request fails for any reason\r\n * and no request-specific or status-specific handler exists\r\n * @handler A function which accept an error (which could be anything) and\r\n * a Fetch Response as an argument\r\n */\r\n catch(handler: (e: any, res: (Response | undefined)) => void) {\r\n this.globalErrorHandler = handler\r\n }\r\n\r\n\r\n /**\r\n * Adds a handler which will be executed before every request. beforeSend handlers on the config\r\n * will be executed before request-specific handlers\r\n * @param handler\r\n * @example\r\n *\r\n * soFetch.config.beforeSend((req:SoFetchRequest) => {\r\n * console.info(`Sending ${req.method} request to URL ${req.url}`\r\n * })\r\n *\r\n * @see For more examples see https://sofetch.antoinette.agency\r\n */\r\n beforeSend(handler: (request: SoFetchRequest) => Promise<SoFetchRequest | void> | SoFetchRequest | void) {\r\n this.beforeSendHandlers.push(handler)\r\n }\r\n\r\n /**\r\n * Adds a handler which will be executed before every request. beforeSend handlers on the config\r\n * will be executed before request-specific handlers\r\n * @param handler\r\n * @example\r\n *\r\n * soFetch.config.beforeSend((req:SoFetchRequest) => {\r\n * console.info(`Sending ${req.method} request to URL ${req.url}`\r\n * })\r\n *\r\n * @see For more examples see https://sofetch.antoinette.agency\r\n */\r\n beforeFetchSend(handler: (request: RequestInit) => Promise<RequestInit | void> | RequestInit | void) {\r\n this.beforeFetchSendHandlers.push(handler)\r\n }\r\n\r\n /**\r\n * Adds a handler which will be executed after every request. Handlers will fire regardless of whether\r\n * the response status code indicated an error\r\n * @param handler\r\n * @example\r\n *\r\n * soFetch.config.onRequestComplete((r: Response) => {\r\n * console.info(`Response received from ${r.url} with status ${r.status}`\r\n * })\r\n *\r\n * @see For more examples see https://sofetch.antoinette.agency\r\n */\r\n onRequestComplete(handler: (r: Response, metaData: { duration: number, method: string }) => void | Promise<void>) {\r\n this.onRequestCompleteHandlers.push(handler)\r\n }\r\n\r\n private setAuthTokenStorage = (authTokenStorage?: AuthTokenStorageType) => {\r\n if (authTokenStorage) {\r\n this.authTokenStorage = authTokenStorage\r\n return\r\n }\r\n //If null is explicitly passed rather than the argument being undefined:\r\n if (authTokenStorage === null) {\r\n this.authTokenStorage = null\r\n }\r\n //Default is memory:\r\n this.authTokenStorage = \"memory\"\r\n }\r\n\r\n /**\r\n * Tells soFetch to use [bearer authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Authentication#bearer) to send an authentication token to the server\r\n * @param authToken - optional. Use this if you have already obtained a token from a login process. Typically this would be left undefined for bearer authentication as the token is usually obtained from a login process.\r\n * @param authenticationKey - optional. Specify an authentication key if you don't want to use the default: 'SOFETCH_AUTHENTICATION'\r\n * @param authTokenStorage - optional, defaults to 'localStorage' on the browser and 'memory' in Node\r\n */\r\n useBearerAuthentication(props: {\r\n authenticationKey?: string,\r\n authTokenStorage?: AuthTokenStorageType,\r\n authToken?: string,\r\n } = {}) {\r\n let {authenticationKey, authTokenStorage, authToken} = props\r\n this.authenticationType = \"bearer\"\r\n if (authenticationKey) {\r\n this.authenticationKey = authenticationKey\r\n }\r\n if (authTokenStorage === undefined) {\r\n authTokenStorage = typeof (document) === \"undefined\" ? \"memory\" : \"localStorage\"\r\n }\r\n this.setAuthTokenStorage(authTokenStorage)\r\n if (authToken) {\r\n this.setAuthToken(authToken)\r\n }\r\n }\r\n\r\n /**\r\n * Tells soFetch to authenticate using cookies.\r\n * @param authToken - optional. Use this if you have already obtained a token. Typically this would be left undefined for bearer authentication as the token is usually obtained from a login process.\r\n * @param authenticationKey - optional. Specify an authentication key if you don't want to use the default: 'SOFETCH_AUTHENTICATION'\r\n */\r\n useCookieAuthentication(props?: {\r\n authenticationKey?: string,\r\n authToken?: string,\r\n } | undefined) {\r\n this.authenticationType = \"cookies\"\r\n let authenticationKey, authToken\r\n if (props) {\r\n authenticationKey = props.authenticationKey\r\n authToken = props.authToken\r\n }\r\n if (authenticationKey) {\r\n this.authenticationKey = authenticationKey\r\n }\r\n\r\n //If we're in Node we'll simulate cookies in memory.\r\n this.authTokenStorage = typeof (document) === \"undefined\" ? \"memory\" : \"cookie\"\r\n if (authToken) {\r\n this.setAuthToken(authToken)\r\n }\r\n }\r\n\r\n /**\r\n * Tells soFetch to send an authentication token to the server\r\n * @param headerKey - required. The key of the header with which to send the authentication token\r\n * @param authToken - optional. Use this if you have already obtained a token.\r\n * @param authenticationKey - optional. Specify an authentication key if you don't want to use the default: 'SOFETCH_AUTHENTICATION'\r\n * @param authTokenStorage - optional, defaults to 'localStorage' on the browser and 'memory' in Node\r\n */\r\n useHeaderAuthentication({headerKey, authToken, authenticationKey, authTokenStorage}: {\r\n headerKey: string,\r\n authenticationKey?: string,\r\n authToken?: string,\r\n authTokenStorage?: AuthTokenStorageType\r\n }) {\r\n this.authenticationType = \"header\"\r\n this.authHeaderKey = headerKey\r\n if (authenticationKey) {\r\n this.authenticationKey = authenticationKey\r\n }\r\n this.setAuthTokenStorage(authTokenStorage)\r\n if (authToken) {\r\n this.setAuthToken(authToken)\r\n }\r\n }\r\n\r\n /**\r\n * Tells soFetch to send append an authentication token to the request query string\r\n * @param queryStringKey - required. The key of the query string item with which to send the authentication token\r\n * @param authToken - optional. Use this if you have already obtained a token.\r\n * @param authenticationKey - optional. Specify an authentication key if you don't want to use the default: 'SOFETCH_AUTHENTICATION'\r\n * @param authTokenStorage - optional. Defaults to 'localStorage' on the browser and 'memory' in Node\r\n */\r\n useQueryStringAuthentication({queryStringKey, authToken, authenticationKey, authTokenStorage}: {\r\n queryStringKey: string,\r\n authenticationKey?: string,\r\n authToken?: string,\r\n authTokenStorage?: AuthTokenStorageType\r\n }) {\r\n this.authenticationType = \"queryString\"\r\n this.authQueryStringKey = queryStringKey\r\n if (authenticationKey) {\r\n this.authenticationKey = authenticationKey\r\n }\r\n this.setAuthTokenStorage(authTokenStorage)\r\n if (authToken) {\r\n this.setAuthToken(authToken)\r\n }\r\n }\r\n\r\n /**\r\n * Tells soFetch to use [basic authorization](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Authentication#basic) when communicating with the server\r\n * @param username - optional but required is password is used. Use this if you've already obtained a username and password.\r\n * @param password - optional but required is username is used. Use this if you've already obtained a username and password.\r\n * @param authenticationKey - optional. Specify an authentication key if you don't want to use the default: 'SOFETCH_AUTHENTICATION'\r\n * @param authTokenStorage - optional, defaults to 'localStorage' on the browser and 'memory' in Node\r\n */\r\n useBasicAuthentication({username, password, authenticationKey, authTokenStorage}: {\r\n username?: string,\r\n password?: string,\r\n authenticationKey?: string,\r\n authTokenStorage?: AuthTokenStorageType\r\n }) {\r\n this.authenticationType = \"basic\"\r\n if ((username && !password) || (password && !username)) {\r\n console.warn(\"Was expecting both username and password to be set for soFetch.config.useBasicAuthentication. Continuing but authentication may not behave as expected\")\r\n }\r\n if (authenticationKey) {\r\n this.authenticationKey = authenticationKey\r\n }\r\n this.setAuthTokenStorage(authTokenStorage)\r\n if (username && password) {\r\n this.setBasicAuthCredentials({username, password})\r\n }\r\n }\r\n\r\n private setBasicAuthCredentials({username, password}: { password: string; username: string }) {\r\n const token = btoa(`${username}:${password}`);\r\n this.setAuthToken(token)\r\n }\r\n\r\n /**\r\n * Tells SoFetch to use a specified user agent when making requests. You can either user a predefined user agent \r\n * by specifying an OS and browser or explicitly pass the user agent string you want to use.\r\n * @param browser - optional, required if OS is also passed. Ignored if userAgentString is passed.\r\n * @param OS - optional, required if browser is also passed. Ignored if userAgentString is passed.\r\n * @param userAgentString optional\r\n */\r\n\r\n setUserAgent({browser, OS, userAgentString}: { browser?: Browser, OS?: OS, userAgentString?: string }) {\r\n let finalUaString = \"\"\r\n if (userAgentString) {\r\n finalUaString = userAgentString\r\n } else {\r\n const entry = UserAgents.find(x => x.os === OS && x.browser == browser)\r\n if (!entry) {\r\n throw new Error(`No user agent defined for OS ${OS} and browser ${browser}`)\r\n }\r\n finalUaString = entry.value\r\n }\r\n this.userAgent = finalUaString\r\n }\r\n}\r\n", "import {ErrorHandlerDict} from \"./errorHandlerDict.ts\";\r\nimport {SoFetchRequest} from \"./soFetch.ts\";\r\nimport {HttpStatus} from \"./httpStatus.ts\";\r\n\r\n/**\r\n * An awaitable promise-like class that additionally allows event and error handlers to be attached to the HTTP request\r\n * @example\r\n * \r\n * const unicorn = await soFetch(\"https://unicorns.com/1234\")\r\n * .beforeSend(req:SoFetchRequest) => {\r\n * console.info(`Finding my unicorn at ${req.url}`)\r\n * })\r\n * .catchHttp(Status.NotFound404, (res:Response) => {\r\n * console.error(\"This unicorn can't be found\")\r\n * })\r\n */\r\nexport class SoFetchPromise<T> {\r\n private readonly inner: Promise<T>;\r\n errorHandlers:ErrorHandlerDict = {}\r\n beforeSendHandlers:((request:SoFetchRequest) => Promise<SoFetchRequest | void> | SoFetchRequest | void)[] = []\r\n beforeFetchSendHandlers:((init:RequestInit) => Promise<RequestInit | void> | RequestInit | void)[] = []\r\n onRequestCompleteHandlers: ((response: Response, requestData: { duration: number, method: string }) => void | Promise<void>)[] = []\r\n timeout: number = 30000\r\n then: Promise<T>[\"then\"];\r\n catch: Promise<T>[\"catch\"];\r\n finally: Promise<T>[\"finally\"];\r\n \r\n constructor(executor: (\r\n resolve: (value: T | PromiseLike<T>) => void,\r\n reject: (reason?: any) => void\r\n ) => void) {\r\n this.inner = new Promise(executor);\r\n // Bind promise methods once inner exists\r\n this.then = this.inner.then.bind(this.inner);\r\n this.catch = this.inner.catch.bind(this.inner);\r\n this.finally = this.inner.finally.bind(this.inner);\r\n }\r\n\r\n /**\r\n * Adds a handler which will be executed after this HTTP request is completed. Handlers will fire regardless of whether\r\n * the response status code indicated an error\r\n * @param handler\r\n * @example\r\n *\r\n * await soFetch(\"https://example.com/users\",{name:\"Sarah\", id:1234}).onRequestComplete((r: Response) => {\r\n * console.info(`Response received from ${r.url} with status ${r.status}`\r\n * })\r\n *\r\n * @see For more examples see https://sofetch.antoinette.agency\r\n */\r\n onRequestComplete(handler: (response: Response) => void | Promise<void>): SoFetchPromise<T> {\r\n this.onRequestCompleteHandlers.push(handler)\r\n return this\r\n }\r\n\r\n /**\r\n * Adds a handler which will be executed before this HTTP request is sent. BeforeSend handlers added here will\r\n * will be executed after those added on the config.\r\n * @param handler\r\n * @example\r\n *\r\n * await soFetch(\"https://example.com/users\",{name:\"Sarah\", id:1234}).beforeSend((req:SoFetchRequest) => {\r\n * console.info(`Sending ${req.method} request to URL ${req.url}`\r\n * })\r\n *\r\n * @see For more examples see https://sofetch.antoinette.agency\r\n */\r\n beforeSend(handler: (request: SoFetchRequest) => Promise<SoFetchRequest | void> | SoFetchRequest | void): SoFetchPromise<T> {\r\n this.beforeSendHandlers.push(handler)\r\n return this\r\n }\r\n\r\n /**\r\n * Adds a handler which allows developers to modify the low-level fetch RequestInit object before the HTTP\r\n * request is made. These handlers execute after beforeSend handlers. This is useful for one-off\r\n * occasions when you need to access some aspect of the low-level Fetch API. If you're using this a lot\r\n * it might make more sense for you to use the Fetch API directly.\r\n * @param handler\r\n * @example\r\n *\r\n * //An example of how you might send both files and data in a single request.\r\n * const postFilesAndDataResponse = await soFetch.put<PostFilesAndDataResponse>(\"https://example.com/files-and-data\").beforeFetchSend((init:RequestInit) => {\r\n * const formData = new FormData()\r\n * formData.append(\"company\", \"Antoinette\");\r\n * formData.append(\"file1\", myFile)\r\n * const headers = {...init.headers} as Record<string,string>\r\n * if (headers[\"content-type\"]) {\r\n * delete headers[\"content-type\"]\r\n * }\r\n * init.body = formData\r\n * init.headers = headers\r\n * return init\r\n * })\r\n *\r\n * @see For more examples see https://sofetch.antoinette.agency\r\n */\r\n beforeFetchSend(handler: (request: RequestInit) => Promise<RequestInit | void> | RequestInit | void): SoFetchPromise<T> {\r\n this.beforeFetchSendHandlers.push(handler)\r\n return this\r\n }\r\n\r\n /**\r\n * Adds a handler which will be executed on receipt from the server of the specified status code.\r\n * Multiple handlers will be executed in the order in which they are added. If you add an error handler\r\n * for a specific status code here any corresponding handlers in the config will not be executed.\r\n * @param status An HTTP status code\r\n * @param handler A function which accepts a Fetch Response as an argument\r\n * @example\r\n *\r\n * const unicorn = await soFetch(\"https://unicorns.com/1234\")\r\n * .catchHttp(404, (res:Response) => {\r\n * console.error(\"This unicorn can't be found\")\r\n * })\r\n *\r\n * @see For more examples see https://sofetch.antoinette.agency\r\n */\r\n catchHTTP(status: HttpStatus, handler: (response: Response) => void): SoFetchPromise<T> {\r\n if (!this.errorHandlers[status]) {\r\n this.errorHandlers[status] = []\r\n }\r\n this.errorHandlers[status].push(handler)\r\n return this\r\n }\r\n\r\n setTimeout(ms: number):SoFetchPromise<T> {\r\n this.timeout = ms\r\n return this\r\n }\r\n}\r\n", "export function sleep(ms: number) {\r\n return new Promise(resolve => setTimeout(resolve, ms));\r\n}", "import {UploadPayload} from \"./uploadPayload.ts\";\r\nimport {FileWithFieldName} from \"./fileWithFieldName.ts\";\r\n\r\nexport const normalisePayload = (payload:UploadPayload):{files?:FileWithFieldName[], jsonPayload?:object} => {\r\n const {isDefined, isArray, isFiles} = getPayloadType(payload)\r\n if (!isDefined) {\r\n return {}\r\n }\r\n if (!isFiles) {\r\n return {jsonPayload:payload}\r\n }\r\n const fileArray = (isArray ? payload : [payload]) as (File[] | FileWithFieldName[])\r\n const files = (isFileWithFieldName(fileArray[0]) ? fileArray : fileArray.map(((x,i) => ({file:x, fieldName:`file${i}`})))) as FileWithFieldName[]\r\n return {files}\r\n}\r\n\r\nexport const isFileWithFieldName = (v:object) => {\r\n return 'file' in v && v.file instanceof File\r\n}\r\n\r\nexport const getPayloadType = (payload: UploadPayload): { isDefined: boolean, isArray: boolean, isFiles: boolean } => {\r\n if (!payload) {\r\n return {isDefined: false, isArray: false, isFiles: false}\r\n }\r\n if (Array.isArray(payload)) {\r\n if (!payload.length) {\r\n //For the purposes of the HTTP request a payload which is an empty array is undefined\r\n //We won't be sending a body with the request.\r\n return {isDefined: false, isArray: true, isFiles: false}\r\n }\r\n if (payload[0] instanceof File) {\r\n return {isDefined: true, isArray: true, isFiles: true}\r\n }\r\n if (isFileWithFieldName(payload[0])) {\r\n return {isDefined: true, isArray: true, isFiles: true}\r\n }\r\n return {isDefined: true, isArray: true, isFiles: false}\r\n }\r\n if (payload instanceof File) {\r\n return {isDefined: true, isArray: false, isFiles: true}\r\n }\r\n if (isFileWithFieldName(payload)) {\r\n return {isDefined: true, isArray: false, isFiles: true}\r\n }\r\n return {isDefined: true, isArray: false, isFiles: false}\r\n}", "import {ErrorHandlerDict} from \"./errorHandlerDict.ts\";\r\n\r\nexport const handleHttpErrors = (response: Response, errorHandlers: ErrorHandlerDict) => {\r\n const status = response.status\r\n const handled = !!(errorHandlers[status] && errorHandlers[status].length)\r\n if (handled) {\r\n errorHandlers[status].forEach(h => h(response))\r\n }\r\n return handled\r\n}", "import {SoFetchRequest} from \"./soFetch.ts\";\r\n\r\nexport const transformRequest = async (request: SoFetchRequest, beforeSendHandlers: ((request: SoFetchRequest) => Promise<SoFetchRequest | void> | SoFetchRequest | void)[]) => {\r\n for(const h of beforeSendHandlers) {\r\n request = (await h(request)) || request\r\n }\r\n return request\r\n}", "export const handleBeforeFetchSend = async (init: RequestInit, handlers: ((init: RequestInit) => Promise<RequestInit | void> | RequestInit | void)[]) => {\r\n for(const h of handlers) {\r\n init = (await h(init)) || init\r\n }\r\n return init\r\n}", "import {SoFetchConfig} from \"./soFetchConfig.ts\";\r\nimport {SoFetchPromise} from \"./soFetchPromise.ts\";\r\nimport {sleep} from \"./sleep.ts\";\r\nimport {UploadPayload} from \"./uploadPayload.ts\";\r\nimport {normalisePayload} from \"./getPayloadType.ts\";\r\nimport {FileWithFieldName} from \"./fileWithFieldName.ts\";\r\nimport {SoFetchLike} from \"./soFetchLike.ts\";\r\nimport {handleHttpErrors} from \"./handleHttpErrors.ts\";\r\nimport {transformRequest} from \"./transformRequest.ts\";\r\nimport {handleBeforeFetchSend} from \"./handleBeforeFetchSend.ts\";\r\n\r\nimport {RequestMethod} from \"./requestMethod.ts\";\r\n\r\nasync function addAuthentication(request: SoFetchRequest, config: SoFetchConfig) {\r\n \r\n const token = config.authenticationType === null ? \"\" : await config[\"getAuthToken\"]()\r\n \r\n if (!token) {\r\n return request\r\n }\r\n \r\n switch(config.authenticationType) {\r\n case null:\r\n return request;\r\n case \"basic\":\r\n request.headers[\"Authorization\"] = `Basic ${token}`\r\n return request;\r\n case \"bearer\":\r\n request.headers[\"Authorization\"] = `Bearer ${token}`\r\n return request;\r\n case \"header\":\r\n request.headers[config.authHeaderKey] = token\r\n return request;\r\n case \"queryString\":\r\n const url = new URL(request.url)\r\n url.searchParams.append(config.authQueryStringKey, token)\r\n request.url = url.toString()\r\n return request;\r\n case \"cookies\":\r\n if (typeof document === \"undefined\") {\r\n request.headers['Cookie'] = `${config.authenticationKey}=${token}`\r\n }\r\n return request\r\n }\r\n}\r\n\r\n/** @import { UploadPayload } from \"./uploadPayload.ts\" */\r\n\r\nconst convertArgsToFetchInit = async <T>({url, method, body, config, promise, userAgent}: { \r\n url: string, \r\n method:string, \r\n body?:UploadPayload, \r\n config:SoFetchConfig, \r\n promise:SoFetchPromise<T>,\r\n userAgent:string\r\n}) => {\r\n const headers: Record<string,string> = {}\r\n if (userAgent) {\r\n headers[\"User-Agent\"] = userAgent\r\n }\r\n let request = {url, method, body, headers}\r\n request.url = !config.baseUrl || request.url.startsWith(\"http\") ? request.url : `${config.baseUrl}${request.url}`\r\n request = await addAuthentication(request, config)\r\n request = await transformRequest(request, promise.beforeSendHandlers)\r\n request = await transformRequest(request, config[\"beforeSendHandlers\"])\r\n const {files} = normalisePayload(request.body)\r\n const sendCookies = config.authenticationType === \"cookies\"\r\n let init = files ? makeFilesRequest(request, files, sendCookies) : makeJsonRequest(request, sendCookies)\r\n init = await handleBeforeFetchSend(init, promise.beforeFetchSendHandlers)\r\n init = await handleBeforeFetchSend(init, config[\"beforeFetchSendHandlers\"])\r\n return {init, finalUrl:request.url}\r\n}\r\n\r\nconst makeRequestWrapper = <TResponse>(config: SoFetchConfig, method:string, url:string, body?:UploadPayload) => {\r\n const promise = new SoFetchPromise<TResponse | undefined>((resolve, reject) => {\r\n (async () => {\r\n await sleep(0) //Allows the promise to be initialised\r\n const {finalUrl, init} = await convertArgsToFetchInit({url, method, body, config, promise, userAgent:config.userAgent})\r\n \r\n const startTime = new Date().getTime()\r\n const response = await Promise.race([\r\n fetch(finalUrl, init),\r\n new Promise<Response>((_, reject) =>\r\n setTimeout(() => {\r\n const error = new Error(`SoFetch timed out. Timeout for this request set to ${promise.timeout}ms.`)\r\n reject(error)\r\n }, promise.timeout)\r\n )\r\n ]);\r\n const duration = new Date().getTime() - startTime\r\n \r\n if (soFetch.verbose) {\r\n console.info(`SoFetch: ${method} ${response.status} ${finalUrl}`)\r\n }\r\n for(const h of promise[\"onRequestCompleteHandlers\"]) {\r\n await h(response, {duration, method:init.method || \"\"})\r\n }\r\n for(const h of config[\"onRequestCompleteHandlers\"]) {\r\n await h(response, {duration, method:init.method || \"\"})\r\n }\r\n if (!response.ok) {\r\n const requestHandled = handleHttpErrors(response, promise.errorHandlers)\r\n let configHandled = false\r\n if (!requestHandled) {\r\n configHandled = handleHttpErrors(response, config[\"errorHandlers\"])\r\n }\r\n if (config.globalErrorHandler) {\r\n // @ts-ignore\r\n const error = new Error(`Received response ${response.status} from URL ${response.url}`, {cause: response})\r\n config.globalErrorHandler(error, response)\r\n configHandled = true\r\n }\r\n if (!requestHandled && !configHandled) {\r\n // @ts-ignore\r\n throw new Error(`Received response ${response.status} from URL ${response.url}`, {cause: response})\r\n }\r\n }\r\n const returnObject = await handleResponse(response)\r\n resolve(returnObject)\r\n })().catch(e => {\r\n if (config.globalErrorHandler) {\r\n config.globalErrorHandler(e, undefined)\r\n resolve(undefined)\r\n } else {\r\n reject(e)\r\n }\r\n })\r\n })\r\n return promise\r\n}\r\n\r\nexport interface SoFetchRequest {\r\n url:string,\r\n method:string,\r\n body:object | undefined\r\n headers:Record<string,string>\r\n}\r\n\r\nconst makeJsonRequest = (request: SoFetchRequest, sendCookies: boolean):RequestInit => {\r\n const { method, body} = request\r\n if (body) {\r\n request.headers['Content-Type'] = 'application/json'\r\n }\r\n const init:RequestInit = {\r\n body: body ? JSON.stringify(body) : undefined,\r\n headers: request.headers,\r\n method,\r\n credentials:sendCookies ? \"include\" : undefined\r\n }\r\n return init\r\n}\r\n\r\nconst makeFilesRequest = (request: SoFetchRequest, files: FileWithFieldName[], sendCookies: boolean):RequestInit => {\r\n const {method, headers} = request\r\n const formData = new FormData()\r\n files.forEach(f => {\r\n formData.append(f.fieldName, f.file, f.file.name)\r\n })\r\n const init:RequestInit = {\r\n body: formData,\r\n headers,\r\n method,\r\n credentials:sendCookies ? \"include\" : undefined\r\n }\r\n return init\r\n}\r\n\r\nconst handleResponse = async (response:Response) => {\r\n\r\n if (response.status === 203) {\r\n return undefined\r\n }\r\n\r\n const responseBody = await response.text();\r\n if (!responseBody) {\r\n return undefined\r\n }\r\n let responseObject: any = responseBody\r\n try {\r\n responseObject = JSON.parse(responseBody);\r\n } catch {\r\n }\r\n\r\n return responseObject\r\n}\r\n\r\n/**\r\n * Makes an HTTP request to the specified URL.\r\n * @template TResponse The primitive or object type you're expecting from the server\r\n * @param {string} url An absolute or relative URL\r\n * @param {UploadPayload} [body] If absent soFetch will make a GET request. If present soFetch will make a POST request. To make PUT, PATCH, DELETE requests see soFetch.put, soFetch.patch, soFetch.delete\r\n * @returns An awaitable SoFetchPromise which resolves to type TResponse\r\n * @example\r\n * \r\n * const products = await soFetch<Product[]>(\"/api/products\")\r\n * \r\n * @see For more examples see https://sofetch.antoinette.agency\r\n */\r\nconst soFetch = (<TResponse>(url: string, body?: UploadPayload): SoFetchPromise<TResponse | undefined> => {\r\n return makeRequestWrapper<TResponse | undefined>(soFetch.config || new SoFetchConfig(), body ? \"POST\" : \"GET\", url, body)\r\n}) as SoFetchLike;\r\n\r\nsoFetch.verbose = false;\r\n\r\n\r\nsoFetch.config = new SoFetchConfig()\r\n\r\n/**\r\n * Makes a GET request to the specified URL\r\n * @template TResponse The primitive or object type you're expecting from the server\r\n * @param url An absolute or relative URL\r\n * @returns An awaitable SoFetchPromise which resolves to type TResponse\r\n * @example\r\n *\r\n * const products = await soFetch.get<Product[]>(\"/api/products\")\r\n *\r\n * @see For more examples see https://sofetch.antoinette.agency\r\n */\r\nsoFetch.get = (url: string) => {\r\n return makeRequestWrapper( soFetch.config,\"GET\", url)\r\n}\r\n\r\n/**\r\n * Makes a POST request to the specified URL\r\n * @template TResponse The primitive or object type you're expecting from the server\r\n * @param url An absolute or relative URL\r\n * @param {UploadPayload} [body] The body of the request\r\n * @returns An awaitable SoFetchPromise which resolves to type TResponse\r\n * @example\r\n *\r\n * const newUser = {\r\n * name:\"Regina George\",\r\n * email:\"regina@massive-deal.com\"\r\n * }\r\n * const successResponse = await soFetch.post<Success>(\"/api/users\", newUser)\r\n *\r\n * @see For more examples see https://sofetch.antoinette.agency\r\n */\r\nsoFetch.post = (url: string, body?: object) => {\r\n return makeRequestWrapper(soFetch.config,\"POST\", url, body)\r\n}\r\n\r\n/**\r\n * Makes a PUT request to the specified URL\r\n * @template TResponse The primitive or object type you're expecting from the server\r\n * @param url An absolute or relative URL\r\n * @param {UploadPayload} [body] The body of the request\r\n * @returns An awaitable SoFetchPromise which resolves to type TResponse\r\n * @example\r\n *\r\n * const upsertUser = {\r\n * name:\"Regina George\",\r\n * email:\"regina@massive-deal.com\"\r\n * }\r\n * const successResponse = await soFetch.put<Success>(\"/api/users/1234\", upsertUser)\r\n *\r\n * @see For more examples see https://sofetch.antoinette.agency\r\n */\r\nsoFetch.put = (url: string, body?: object) => {\r\n return makeRequestWrapper(soFetch.config,\"PUT\", url, body)\r\n}\r\n\r\n/**\r\n * Makes a PATCH request to the specified URL\r\n * @template TResponse The primitive or object type you're expecting from the server\r\n * @param url An absolute or relative URL\r\n * @param {UploadPayload} [body] The body of the request\r\n * @returns An awaitable SoFetchPromise which resolves to type TResponse\r\n * @example\r\n *\r\n * const updateUserEmail = {\r\n * email:\"regina@massive-deal.com\"\r\n * }\r\n * const successResponse = await soFetch.patch<Success>(\"/api/users/1234\", updateUserEmail)\r\n *\r\n * @see For more examples see https://sofetch.antoinette.agency\r\n */\r\nsoFetch.patch = (url: string, body?: object) => {\r\n return makeRequestWrapper(soFetch.config,\"PATCH\", url, body)\r\n}\r\n\r\n/**\r\n * Makes a request using supplied method to the specified URL\r\n * @template TResponse The primitive or object type you're expecting from the server\r\n * @param method One of GET, POST, PUT, PATCH, DELETE\r\n * @param url An absolute or relative URL\r\n * @param {UploadPayload} [body] The body of the request\r\n * @returns An awaitable SoFetchPromise which resolves to type TResponse\r\n * @example\r\n *\r\n * const updateUserEmail = {\r\n * email:\"regina@massive-deal.com\"\r\n * }\r\n * const successResponse = await soFetch.patch<Success>(\"/api/users/1234\", updateUserEmail)\r\n *\r\n * @see For more examples see https://sofetch.antoinette.agency\r\n */\r\nsoFetch.request = (method: RequestMethod, url: string, body?: object) => {\r\n return makeRequestWrapper(soFetch.config, method, url, body)\r\n}\r\n\r\n/**\r\n * Makes a DELETE request to the specified URL\r\n * @template TResponse The primitive or object type you're expecting from the server\r\n * @param url An absolute or relative URL\r\n * @returns An awaitable SoFetchPromise which resolves to type TResponse\r\n * @example\r\n *\r\n * await soFetch.delete(\"/api/users/1234\")\r\n *\r\n * @see For more examples see https://sofetch.antoinette.agency\r\n */\r\nsoFetch.delete = (url: string) => {\r\n return makeRequestWrapper(soFetch.config,\"DELETE\", url)\r\n}\r\n\r\nfunction generateNewAuthenticationKey(authenticationKey: string) {\r\n const regex = /^(.*?)([0-9]+)?$/;\r\n const match = authenticationKey.match(regex);\r\n const match1 = match ? match[1] : null\r\n const match2 = match ? match[2] : null\r\n if (!match1) {\r\n return authenticationKey\r\n }\r\n let next = match2 ? (parseInt(match2) + 1) : 1\r\n return `${match1}${next}`;\r\n}\r\n\r\n/**\r\n * Returns an independent instance of soFetch configured as per the original. The baseUrl and event handlers\r\n * will be copied over.\r\n * @see For examples see https://sofetch.antoinette.agency\r\n */\r\nsoFetch.instance = (configOrAuthKey?:SoFetchConfig | string) => {\r\n \r\n const configWasPassed = !!configOrAuthKey && typeof configOrAuthKey !== \"string\"\r\n const oldConfig = configWasPassed ? (configOrAuthKey as SoFetchConfig) : soFetch.config\r\n const newConfig = new SoFetchConfig()\r\n const newAuthKey = typeof configOrAuthKey == \"string\" ? (configOrAuthKey as string) : undefined\r\n if (!configWasPassed) {\r\n newConfig.baseUrl = oldConfig.baseUrl\r\n newConfig[\"beforeSendHandlers\"] = [...oldConfig[\"beforeSendHandlers\"]]\r\n newConfig[\"beforeFetchSendHandlers\"] = [...oldConfig[\"beforeFetchSendHandlers\"]]\r\n newConfig[\"onRequestCompleteHandlers\"] = [...oldConfig[\"onRequestCompleteHandlers\"]]\r\n newConfig.authTokenStorage = oldConfig.authTokenStorage\r\n newConfig[\"inMemoryAuthToken\"] = oldConfig[\"inMemoryAuthToken\"]\r\n newConfig.userAgent = oldConfig.userAgent\r\n }\r\n newConfig.authenticationKey = newAuthKey || generateNewAuthenticationKey(oldConfig.authenticationKey)\r\n \r\n const soFetchInstance = (<TResponse>(url: string, body?: UploadPayload): SoFetchPromise<TResponse | undefined> => {\r\n return makeRequestWrapper<TResponse | undefined>(newConfig,body ? \"POST\" : \"GET\", url, body)\r\n }) as SoFetchLike;\r\n soFetchInstance.get = (url: string, body?: UploadPayload) => {\r\n return makeRequestWrapper(newConfig, \"GET\", url, body)\r\n }\r\n soFetchInstance.post = (url: string, body?: UploadPayload) => {\r\n return makeRequestWrapper(newConfig,\"POST\", url, body)\r\n }\r\n soFetchInstance.put = (url: string, body?: UploadPayload) => {\r\n return makeRequestWrapper(newConfig,\"PUT\", url, body)\r\n }\r\n soFetchInstance.patch = (url: string, body?: UploadPayload) => {\r\n return makeRequestWrapper(newConfig,\"PATCH\", url, body)\r\n }\r\n soFetchInstance.delete = (url: string, body?: UploadPayload) => {\r\n return makeRequestWrapper(newConfig,\"DELETE\", url, body)\r\n }\r\n soFetchInstance.verbose = soFetch.verbose\r\n soFetchInstance.config = newConfig\r\n soFetchInstance.instance = (c?:SoFetchConfig) => {\r\n return soFetch.instance(c || newConfig)\r\n }\r\n return soFetchInstance\r\n}\r\n\r\nexport default soFetch;\r\n", "/**\r\n * Status codes issued by a server in response to a client's request. \r\n * All HTTP response status codes are separated into five classes or categories. The first digit of the status code defines the class of response, while the last two digits do not have any classifying or categorization role. There are five classes defined by the standard:\r\n *\r\n * - 1xx informational response \u2013 the request was received, continuing process\r\n * - 2xx successful \u2013 the request was successfully received, understood, and accepted\r\n * - 3xx redirection \u2013 further action needs to be taken in order to complete the request\r\n * - 4xx client error \u2013 the request contains bad syntax or cannot be fulfilled\r\n * - 5xx server error \u2013 the server failed to fulfil an apparently valid request\r\n *\r\n * @see https://en.wikipedia.org/wiki/List_of_HTTP_status_codes\r\n */\r\nexport const enum HttpStatus {\r\n /**\r\n * Interim response indicates that everything so far is OK and that the client should continue with the request or ignore it if it is already finished.\r\n * @see https://http.cat/status/100\r\n */\r\n Continue100 = 100,\r\n \r\n /**\r\n * Sent in response to an Upgrade request header by the client, and indicates the protocol the server is switching too.\r\n * @see https://http.cat/status/101\r\n */\r\n SwitchingProtocols101 = 101,\r\n\r\n /**\r\n * Indicates that the server has received and is processing the request, but no response is available yet.\r\n * @see https://http.cat/status/102\r\n */\r\n Processing102 = 102,\r\n\r\n /**\r\n * The request has succeeded. The meaning of a success varies depending on the HTTP method:\r\n * GET: The resource has been fetched and is transmitted in the message body.\r\n * HEAD: The entity headers are in the message body.\r\n * POST: The resource describing the result of the action is transmitted in the message body.\r\n * TRACE: The message body contains the request message as received by the server\r\n * @see https://http.cat/status/200\r\n */\r\n OK200 = 200,\r\n\r\n /**\r\n * Request has succeeded and a new resource has been created as a result of it. This is typically the response sent after a PUT request.\r\n * @see https://http.cat/status/201\r\n */\r\n Created201 = 201,\r\n\r\n /**\r\n * Request has been received but not yet acted upon. It is non-committal, meaning that there is no way in HTTP to later send an asynchronous response indicating the outcome of processing the request. It is intended for cases where another process or server handles the request, or for batch processing.\r\n * @see https://http.cat/status/202\r\n */\r\n Accepted202 = 202,\r\n\r\n /**\r\n * There is no content to send for this request, but the headers may be useful. The user-agent may update its cached headers for this resource with the new ones.\r\n * @see https://http.cat/status/204\r\n */\r\n NoContent204 = 204,\r\n\r\n /**\r\n * Response code is sent after accomplishing request to tell user agent reset document view which sent this request.\r\n * @see https://http.cat/status/205\r\n */\r\n ResetContent205 = 205,\r\n\r\n /**\r\n * Response code is used because of range header sent by the client to separate download into multiple streams.\r\n * @see https://http.cat/status/206\r\n */\r\n PartialContent206 = 206,\r\n\r\n /**\r\n * Request has more than one possible responses. User-agent or user should choose one of them. There is no standardized way to choose one of the responses.\r\n * @see https://http.cat/status/300\r\n */\r\n MultipleChoices300 = 300,\r\n\r\n /**\r\n * Means that URI of requested resource has been changed. Probably, new URI would be given in the response.\r\n * @see https://http.cat/status/301\r\n */\r\n MovedPermanently301 = 301,\r\n\r\n /**\r\n * Means that URI of requested resource has been changed temporarily. New changes in the URI might be made in the future. Therefore, this same URI should be used by the client in future requests.\r\n * @see https://en.wikipedia.org/wiki/HTTP_302\r\n */\r\n Found302 = 302,\r\n\r\n /**\r\n * Server sent this response to directing client to get requested resource to another URI with an GET request.\r\n * @see https://http.cat/status/303\r\n */\r\n SeeOther303 = 303,\r\n\r\n /**\r\n * Used for caching purposes. It is telling to client that response has not been modified. So, client can continue to use same cached version of response.\r\n * @see https://http.cat/status/304\r\n */\r\n NotModified304 = 304,\r\n\r\n /**\r\n * @deprecated\r\n * Was defined in a previous version of the HTTP specification to indicate that a requested response must be accessed by a proxy. It has been deprecated due to security concerns regarding in-band configuration of a proxy.\r\n * @see https://http.cat/status/305\r\n */\r\n UseProxy305 = 305,\r\n\r\n /**\r\n * Server sent this response to directing client to get requested resource to another URI with same method that used prior request. This has the same semantic than the 302 Found HTTP response code, with the exception that the user agent must not change the HTTP method used: if a POST was used in the first request, a POST must be used in the second request.\r\n * @see https://http.cat/status/307\r\n */\r\n TemporaryRedirect307 = 307