Open DolphaGo opened 2 years ago
import {Either, fail, success} from './either'
import params2query from './params-to-query'
import {NetworkError} from '../types/error'
import streamSaver from 'streamsaver'
export interface FetchRequestOptions {
prefix: string
headers: Record<string, string>
params: Record<string, string | number | boolean>
}
export default class FetchRequest {
private readonly defaultOptions: FetchRequestOptions = {
prefix: '',
headers: {},
params: {},
}
private readonly options: FetchRequestOptions
constructor (options: Partial<FetchRequestOptions> = {}) {
this.options = Object.assign({}, this.defaultOptions, options)
}
private readonly generateFinalUrl = (url: string, options: Partial<FetchRequestOptions> = {}): string => {
const prefix = options.prefix ?? this.options.prefix
const params = Object.assign({}, this.options.params, options.params ?? {})
let finalUrl = `${prefix}${url}`
if (Object.keys(params).length > 0) finalUrl += `?${params2query(params)}`
return finalUrl
}
private readonly generateFinalHeaders = (options: Partial<FetchRequestOptions> = {}): FetchRequestOptions['headers'] => {
return Object.assign({}, this.options.headers, options.headers ?? {})
}
private readonly handleResponse = <T>(response: Response): Promise<Either<NetworkError, T>> => {
if (response.ok) {
return response.json().then(json => success(json as T))
}
return Promise.resolve(fail(new NetworkError(response)))
}
private readonly handleCorrectResponse = <T>(response: Response): Promise<T> => {
if (response.ok) {
return response.json()
}
throw new NetworkError(response)
}
private runFetch ({ method, url, data, options }: {
method: 'GET' | 'DELETE' | 'POST' | 'PUT' | 'PATCH'
url: string
data?: unknown
options?: Partial<FetchRequestOptions>
}): Promise<Response> {
const finalUrl = this.generateFinalUrl(url, options)
const headers = this.generateFinalHeaders(options)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const fetchOptions: any = { method, headers, mode: 'cors' }
if (data !== undefined) fetchOptions.body = JSON.stringify(data)
return fetch(finalUrl, fetchOptions)
}
private runFileDownloadFetch (
method: 'GET' | 'POST',
url: string,
data?: unknown,
): Promise<Response> {
const finalUrl = this.generateFinalUrl(url)
const headers = { 'Content-Type': 'application/json; charset=UTF-8' }
const fetchOptions: any = { method, headers, mode: 'cors' }
if (data !== undefined) {
fetchOptions.body = JSON.stringify(data)
}
return fetch(finalUrl, fetchOptions)
}
private runSafeFetch (
method: 'GET' | 'DELETE',
url: string,
options?: Partial<FetchRequestOptions>,
): Promise<Response> {
return this.runFetch({ method, url, options })
}
private runUnsafeFetch (
method: 'POST' | 'PUT' | 'PATCH',
url: string,
data?: unknown,
options?: Partial<FetchRequestOptions>,
): Promise<Response> {
return this.runFetch({ method, url, options, data })
}
get<T = unknown> (url: string, options?: Partial<FetchRequestOptions>): Promise<T> {
return this.runSafeFetch('GET', url, options).then(r => this.handleCorrectResponse<T>(r))
}
checkableGet<T = unknown> (url: string, options?: Partial<FetchRequestOptions>): Promise<Either<NetworkError, T>> {
return this.runSafeFetch('GET', url, options).then(r => this.handleResponse<T>(r))
}
post<T = unknown> (url: string, data?: unknown, options?: Partial<FetchRequestOptions>): Promise<T> {
return this.runUnsafeFetch('POST', url, data, options).then(r => this.handleCorrectResponse<T>(r))
}
postFile<T = unknown> (url: string, fileName: string, data?: unknown): void {
const fileStream = streamSaver.createWriteStream(fileName)
this.runFileDownloadFetch('POST', url, data)
.then(res => {
const readableStream = res.body
// more optimized
if (window.WritableStream && readableStream?.pipeTo) {
return readableStream.pipeTo(fileStream)
.then(() => console.log('done writing'))
}
const writer = fileStream.getWriter()
const reader = res.body?.getReader()
if (!reader) {
throw new Error('stream reader initialize failed')
}
const pump = () => reader.read()
.then(res => res.done
? writer.close()
: writer.write(res.value).then(pump))
pump().finally(() => console.log('pump : done writing'))
})
.catch(r => alert('error'))
}
getFile<T = unknown> (url: string, fileName: string, data?: unknown): void {
const fileStream = streamSaver.createWriteStream(fileName)
this.runFileDownloadFetch('GET', url, data)
.then(res => {
const readableStream = res.body
// more optimized
if (window.WritableStream && readableStream?.pipeTo) {
return readableStream.pipeTo(fileStream)
.then(() => console.log('done writing'))
}
const writer = fileStream.getWriter()
const reader = res.body?.getReader()
if (!reader) {
throw new Error('stream reader initialize failed')
}
const pump = () => reader.read()
.then(res => res.done
? writer.close()
: writer.write(res.value).then(pump))
pump().finally(() => console.log('pump : done writing'))
})
.catch(r => alert('error'))
}
checkablePost<T = unknown> (url: string, data?: unknown, options?: Partial<FetchRequestOptions>): Promise<Either<NetworkError, T>> {
return this.runUnsafeFetch('POST', url, data, options).then(r => this.handleResponse<T>(r))
}
delete<T = unknown> (url: string, options?: Partial<FetchRequestOptions>): Promise<T> {
return this.runSafeFetch('DELETE', url, options).then(r => this.handleCorrectResponse<T>(r))
}
checkableDelete<T = unknown> (url: string, options?: Partial<FetchRequestOptions>): Promise<Either<NetworkError, T>> {
return this.runSafeFetch('DELETE', url, options).then(r => this.handleResponse<T>(r))
}
put<T = unknown> (url: string, data?: unknown, options?: Partial<FetchRequestOptions>): Promise<T> {
return this.runUnsafeFetch('PUT', url, data, options).then(r => this.handleCorrectResponse<T>(r))
}
checkablePut<T> (url: string, data?: unknown, options?: Partial<FetchRequestOptions>): Promise<Either<NetworkError, T>> {
return this.runUnsafeFetch('PUT', url, data, options).then(r => this.handleResponse<T>(r))
}
patch<T = unknown> (url: string, data?: unknown, options?: Partial<FetchRequestOptions>): Promise<T> {
return this.runUnsafeFetch('PATCH', url, data, options).then(r => this.handleCorrectResponse<T>(r))
}
checkablePatch<T> (url: string, data?: unknown, options?: Partial<FetchRequestOptions>): Promise<Either<NetworkError, T>> {
return this.runUnsafeFetch('PATCH', url, data, options).then(r => this.handleResponse<T>(r))
}
public setAuthorizationHeader (token: string): void {
if (token !== '') this.options.headers.Authorization = `Token ${token}`
}
public deleteAuthorizationHeader (): void {
delete this.options?.headers?.Authorization
}
}
Spring Server에서 OutputStream으로 데이터를 잘 넣고 있었으나, 받는 프론트 쪽에서 이슈가 있었다.
즉, request.getFile(url, filename) 으로 해결.