-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
close #400 --------- Co-authored-by: Divyansh Singh <40380293+brc-dd@users.noreply.github.com> Co-authored-by: Kia King Ishii <kia.king.08@gmail.com>
- Loading branch information
Showing
6 changed files
with
1,411 additions
and
789 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
# Http <Badge text="3.9.0" /> | ||
|
||
`Http` module provides a set of functions for making HTTP requests. | ||
|
||
## `Http` | ||
|
||
The `Http` class. It uses [ofetch](https://github.com/unjs/ofetch) under the hood so that it can be smoothly used together with Nuxt. | ||
|
||
This class deeply integrates with [Laravel Sanctum](https://laravel.com/docs/sanctum), which is the authentication system used by Laravel. When instantiating the class, it will automatically checks for cookies and set the `X-XSRF-TOKEN` header. When cookies are missing, it will automatically make a request to the Laravel Sanctum endpoint to obtain it. | ||
|
||
```ts | ||
import { Http } from '@globalbrain/sefirot/lib/http/Http' | ||
|
||
const http = new Http() | ||
|
||
const res = http.get('https://example.com') | ||
``` | ||
|
||
### `static xsrfUrl` | ||
|
||
Holds the static URL for the Laravel Sanctum endpoint. | ||
|
||
```ts | ||
class Http { | ||
// @default '/api/csrf-cookie' | ||
static xsrfUrl: string | ||
} | ||
``` | ||
|
||
### `get` | ||
|
||
Performs a `GET` request. | ||
|
||
```ts | ||
import { type FetchOptions } from 'ofetch' | ||
|
||
class Http { | ||
get<T = any>(url: string, options?: FetchOptions): Promise<T> | ||
} | ||
``` | ||
|
||
### `head` | ||
|
||
Performs a `HEAD` request. | ||
|
||
```ts | ||
import { type FetchOptions } from 'ofetch' | ||
|
||
class Http { | ||
head<T = any>(url: string, options?: FetchOptions): Promise<T> | ||
} | ||
``` | ||
|
||
### `post` | ||
|
||
Performs a `POST` request. | ||
|
||
```ts | ||
import { type FetchOptions } from 'ofetch' | ||
|
||
class Http { | ||
post<T = any>( | ||
url: string, | ||
body?: any, | ||
options?: FetchOptions | ||
): Promise<T> | ||
} | ||
``` | ||
### `put` | ||
|
||
Performs a `PUT` request. | ||
|
||
```ts | ||
import { type FetchOptions } from 'ofetch' | ||
|
||
class Http { | ||
put<T = any>( | ||
url: string, | ||
body?: any, | ||
options?: FetchOptions | ||
): Promise<T> | ||
} | ||
``` | ||
|
||
### `patch` | ||
|
||
Performs a `PATCH` request. | ||
|
||
```ts | ||
import { type FetchOptions } from 'ofetch' | ||
|
||
class Http { | ||
patch<T = any>( | ||
url: string, | ||
body?: any, | ||
options?: FetchOptions | ||
): Promise<T> | ||
} | ||
``` | ||
|
||
### `delete` | ||
|
||
Performs a `DELETE` request. | ||
|
||
```ts | ||
import { type FetchOptions } from 'ofetch' | ||
|
||
class Http { | ||
delete<T = any>(url: string, options?: FetchOptions): Promise<T> | ||
} | ||
``` | ||
|
||
### `upload` | ||
|
||
Performs a `POST` request with `multipart/form-data` content type. Useful for uploading files. It also handles nested body structures as well. | ||
|
||
```ts | ||
import { type FetchOptions } from 'ofetch' | ||
|
||
class Http { | ||
upload<T = any>( | ||
url: string, | ||
body?: any, | ||
options?: FetchOptions | ||
): Promise<T> | ||
} | ||
``` | ||
|
||
### `download` | ||
|
||
Download a file from the response. Use this method when you want browser to save a file to local disk. | ||
|
||
```ts | ||
import { type FetchOptions } from 'ofetch' | ||
|
||
class Http { | ||
download<T = any>(url: string, options?: FetchOptions): Promise<T> | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
import { parse as parseContentDisposition } from '@tinyhttp/content-disposition' | ||
import { parse as parseCookie } from '@tinyhttp/cookie' | ||
import FileSaver from 'file-saver' | ||
import { $fetch, type FetchOptions } from 'ofetch' | ||
import { stringify } from 'qs' | ||
|
||
export class Http { | ||
static xsrfUrl = '/api/csrf-cookie' | ||
|
||
private async ensureXsrfToken(): Promise<string | undefined> { | ||
let xsrfToken = parseCookie(document.cookie)['XSRF-TOKEN'] | ||
|
||
if (!xsrfToken) { | ||
await this.head(Http.xsrfUrl) | ||
xsrfToken = parseCookie(document.cookie)['XSRF-TOKEN'] | ||
} | ||
|
||
return xsrfToken | ||
} | ||
|
||
private async buildRequest( | ||
url: string, | ||
_options: FetchOptions = {} | ||
): Promise<[string, FetchOptions]> { | ||
const { method, params, query, ...options } = _options | ||
|
||
const xsrfToken | ||
= ['POST', 'PUT', 'PATCH', 'DELETE'].includes(method || '') && (await this.ensureXsrfToken()) | ||
|
||
const queryString = stringify( | ||
{ ...params, ...query }, | ||
{ arrayFormat: 'brackets', encodeValuesOnly: true } | ||
) | ||
|
||
return [ | ||
`${url}${queryString ? `?${queryString}` : ''}`, | ||
{ | ||
method, | ||
credentials: 'include', | ||
...options, | ||
headers: { | ||
Accept: 'application/json', | ||
...(xsrfToken && { 'X-XSRF-TOKEN': xsrfToken }), | ||
...options.headers | ||
} | ||
} | ||
] | ||
} | ||
|
||
private async performRequest<T>(url: string, options: FetchOptions = {}) { | ||
return $fetch<T, any>(...(await this.buildRequest(url, options))) | ||
} | ||
|
||
private async performRequestRaw<T>(url: string, options: FetchOptions = {}) { | ||
return $fetch.raw<T, any>(...(await this.buildRequest(url, options))) | ||
} | ||
|
||
private objectToFormData(obj: any, form?: FormData, namespace?: string) { | ||
const fd = form || new FormData() | ||
let formKey: string | ||
|
||
for (const property in obj) { | ||
if (Reflect.has(obj, property)) { | ||
if (namespace) { | ||
formKey = `${namespace}[${property}]` | ||
} else { | ||
formKey = property | ||
} | ||
|
||
if (obj[property] === undefined) { | ||
continue | ||
} | ||
|
||
if (typeof obj[property] === 'object' && !(obj[property] instanceof Blob)) { | ||
this.objectToFormData(obj[property], fd, property) | ||
} else { | ||
fd.append(formKey, obj[property]) | ||
} | ||
} | ||
} | ||
|
||
return fd | ||
} | ||
|
||
async get<T = any>(url: string, options?: FetchOptions): Promise<T> { | ||
return this.performRequest<T>(url, { method: 'GET', ...options }) | ||
} | ||
|
||
async head<T = any>(url: string, options?: FetchOptions): Promise<T> { | ||
return this.performRequest<T>(url, { method: 'HEAD', ...options }) | ||
} | ||
|
||
async post<T = any>(url: string, body?: any, options?: FetchOptions): Promise<T> { | ||
return this.performRequest<T>(url, { method: 'POST', body, ...options }) | ||
} | ||
|
||
async put<T = any>(url: string, body?: any, options?: FetchOptions): Promise<T> { | ||
return this.performRequest<T>(url, { method: 'PUT', body, ...options }) | ||
} | ||
|
||
async patch<T = any>(url: string, body?: any, options?: FetchOptions): Promise<T> { | ||
return this.performRequest<T>(url, { method: 'PATCH', body, ...options }) | ||
} | ||
|
||
async delete<T = any>(url: string, options?: FetchOptions): Promise<T> { | ||
return this.performRequest<T>(url, { method: 'DELETE', ...options }) | ||
} | ||
|
||
async upload<T = any>(url: string, body?: any, options?: FetchOptions): Promise<T> { | ||
const formData = this.objectToFormData(body) | ||
|
||
return this.performRequest<T>(url, { | ||
method: 'POST', | ||
body: formData, | ||
...options | ||
}) | ||
} | ||
|
||
async download(url: string, options?: FetchOptions): Promise<void> { | ||
const { _data: blob, headers } = await this.performRequestRaw<Blob>(url, { | ||
method: 'GET', | ||
responseType: 'blob', | ||
...options | ||
}) | ||
|
||
if (!blob) { | ||
throw new Error('No blob') | ||
} | ||
|
||
const { filename = 'download' } | ||
= parseContentDisposition(headers.get('Content-Disposition') || '')?.parameters || {} | ||
|
||
FileSaver.saveAs(blob, filename as string) | ||
} | ||
} | ||
|
||
export type { FetchOptions } |
Oops, something went wrong.