Skip to content

Commit

Permalink
feat(http): support sending files along with json (#522)
Browse files Browse the repository at this point in the history
  • Loading branch information
brc-dd committed May 6, 2024
1 parent 7f2abd1 commit dd75beb
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 9 deletions.
11 changes: 11 additions & 0 deletions docs/network-requests/http.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ interface HttpOptions {
* @default undefined
*/
lang?: 'en' | 'ja'
/**
* If you call `http.post` with a file, it will be send as `multipart/form-data`.
* The rest of the body will be send as JSON string. This option allows you to
* specify the key for the JSON part. This key should match the key in backend
* middleware which parses the JSON part. Don't set this option to some common
* key to avoid conflicts with other parts of the body. (Sending JSON part as
* string is needed to preserve data types.)
*
* @default '__payload__'
*/
payloadKey?: string
}

interface HttpClient {
Expand Down
40 changes: 31 additions & 9 deletions lib/http/Http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ export interface HttpOptions {
xsrfUrl?: string | false
client?: HttpClient
lang?: Lang
payloadKey?: string
}

export class Http {
private static baseUrl: string | undefined = undefined
private static xsrfUrl: string | false = '/api/csrf-cookie'
private static client: HttpClient = ofetch
private static lang: Lang | undefined = undefined
private static payloadKey = '__payload__'

static config(options: HttpOptions) {
if (options.baseUrl) {
Expand All @@ -36,6 +38,9 @@ export class Http {
if (options.lang) {
Http.lang = options.lang
}
if (options.payloadKey) {
Http.payloadKey = options.payloadKey
}
}

private async ensureXsrfToken(): Promise<string | undefined> {
Expand Down Expand Up @@ -101,6 +106,26 @@ export class Http {
}

async post<T = any>(url: string, body?: any, options?: FetchOptions): Promise<T> {
if (body && !(body instanceof FormData)) {
let hasFile = false

const payload = JSON.stringify(body, (_, value) => {
if (value instanceof Blob) {
hasFile = true
return undefined
}
return value
})

if (hasFile) {
const formData = this.objectToFormData(body, undefined, undefined, true)
formData.append(Http.payloadKey, payload)
body = formData
} else {
body = payload
}
}

return this.performRequest<T>(url, { method: 'POST', body, ...options })
}

Expand All @@ -117,13 +142,7 @@ export class Http {
}

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
})
return this.post<T>(url, this.objectToFormData(body), options)
}

async download(url: string, options?: FetchOptions): Promise<void> {
Expand All @@ -143,7 +162,7 @@ export class Http {
FileSaver.saveAs(blob, filename as string)
}

private objectToFormData(obj: any, form?: FormData, namespace?: string) {
private objectToFormData(obj: any, form?: FormData, namespace?: string, onlyFiles = false) {
const fd = form || new FormData()
let formKey: string

Expand All @@ -163,9 +182,12 @@ export class Http {
&& !(obj[property] instanceof Blob)
&& obj[property] !== null
) {
this.objectToFormData(obj[property], fd, property)
this.objectToFormData(obj[property], fd, property, onlyFiles)
} else {
const value = obj[property] === null ? '' : obj[property]
if (onlyFiles && !(value instanceof Blob)) {
return
}
fd.append(formKey, value)
}
})
Expand Down

0 comments on commit dd75beb

Please sign in to comment.