mirror of
https://github.com/langgenius/dify.git
synced 2026-05-04 17:38:04 +08:00
feat: refactor http client
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import { API_PREFIX, IS_CE_EDITION, MARKETPLACE_API_PREFIX, PUBLIC_API_PREFIX } from '@/config'
|
||||
import { API_PREFIX, IS_CE_EDITION, PUBLIC_API_PREFIX } from '@/config'
|
||||
import { refreshAccessTokenOrRelogin } from './refresh-token'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import type { AnnotationReply, MessageEnd, MessageReplace, ThoughtItem } from '@/app/components/base/chat/chat/type'
|
||||
@ -17,27 +17,10 @@ import type {
|
||||
WorkflowStartedResponse,
|
||||
} from '@/types/workflow'
|
||||
import { removeAccessToken } from '@/app/components/share/utils'
|
||||
import type { FetchOptionType, ResponseError } from './fetch'
|
||||
import { ContentType, base, baseOptions, getPublicToken } from './fetch'
|
||||
const TIME_OUT = 100000
|
||||
|
||||
const ContentType = {
|
||||
json: 'application/json',
|
||||
stream: 'text/event-stream',
|
||||
audio: 'audio/mpeg',
|
||||
form: 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
download: 'application/octet-stream', // for download
|
||||
upload: 'multipart/form-data', // for upload
|
||||
}
|
||||
|
||||
const baseOptions = {
|
||||
method: 'GET',
|
||||
mode: 'cors',
|
||||
credentials: 'include', // always send cookies、HTTP Basic authentication.
|
||||
headers: new Headers({
|
||||
'Content-Type': ContentType.json,
|
||||
}),
|
||||
redirect: 'follow',
|
||||
}
|
||||
|
||||
export type IOnDataMoreInfo = {
|
||||
conversationId?: string
|
||||
taskId?: string
|
||||
@ -100,17 +83,6 @@ export type IOtherOptions = {
|
||||
onTextReplace?: IOnTextReplace
|
||||
}
|
||||
|
||||
type ResponseError = {
|
||||
code: string
|
||||
message: string
|
||||
status: number
|
||||
}
|
||||
|
||||
type FetchOptionType = Omit<RequestInit, 'body'> & {
|
||||
params?: Record<string, any>
|
||||
body?: BodyInit | Record<string, any> | null
|
||||
}
|
||||
|
||||
function unicodeToChar(text: string) {
|
||||
if (!text)
|
||||
return ''
|
||||
@ -277,153 +249,13 @@ const handleStream = (
|
||||
read()
|
||||
}
|
||||
|
||||
const baseFetch = <T>(
|
||||
url: string,
|
||||
fetchOptions: FetchOptionType,
|
||||
{
|
||||
isPublicAPI = false,
|
||||
isMarketplaceAPI = false,
|
||||
bodyStringify = true,
|
||||
needAllResponseContent,
|
||||
deleteContentType,
|
||||
getAbortController,
|
||||
silent,
|
||||
}: IOtherOptions,
|
||||
): Promise<T> => {
|
||||
const options: typeof baseOptions & FetchOptionType = Object.assign({}, baseOptions, fetchOptions)
|
||||
if (isMarketplaceAPI)
|
||||
options.credentials = 'omit'
|
||||
|
||||
if (getAbortController) {
|
||||
const abortController = new AbortController()
|
||||
getAbortController(abortController)
|
||||
options.signal = abortController.signal
|
||||
}
|
||||
|
||||
if (isPublicAPI) {
|
||||
const sharedToken = globalThis.location.pathname.split('/').slice(-1)[0]
|
||||
const accessToken = localStorage.getItem('token') || JSON.stringify({ [sharedToken]: '' })
|
||||
let accessTokenJson = { [sharedToken]: '' }
|
||||
try {
|
||||
accessTokenJson = JSON.parse(accessToken)
|
||||
}
|
||||
catch (e) {
|
||||
|
||||
}
|
||||
options.headers.set('Authorization', `Bearer ${accessTokenJson[sharedToken]}`)
|
||||
}
|
||||
else if (!isMarketplaceAPI) {
|
||||
const accessToken = localStorage.getItem('console_token') || ''
|
||||
options.headers.set('Authorization', `Bearer ${accessToken}`)
|
||||
}
|
||||
|
||||
if (deleteContentType) {
|
||||
options.headers.delete('Content-Type')
|
||||
}
|
||||
else {
|
||||
const contentType = options.headers.get('Content-Type')
|
||||
if (!contentType)
|
||||
options.headers.set('Content-Type', ContentType.json)
|
||||
}
|
||||
|
||||
const urlPrefix = (() => {
|
||||
if (isMarketplaceAPI)
|
||||
return MARKETPLACE_API_PREFIX
|
||||
if (isPublicAPI)
|
||||
return PUBLIC_API_PREFIX
|
||||
return API_PREFIX
|
||||
})()
|
||||
let urlWithPrefix = `${urlPrefix}${url.startsWith('/') ? url : `/${url}`}`
|
||||
|
||||
const { method, params, body } = options
|
||||
// handle query
|
||||
if (method === 'GET' && params) {
|
||||
const paramsArray: string[] = []
|
||||
Object.keys(params).forEach(key =>
|
||||
paramsArray.push(`${key}=${encodeURIComponent(params[key])}`),
|
||||
)
|
||||
if (urlWithPrefix.search(/\?/) === -1)
|
||||
urlWithPrefix += `?${paramsArray.join('&')}`
|
||||
|
||||
else
|
||||
urlWithPrefix += `&${paramsArray.join('&')}`
|
||||
|
||||
delete options.params
|
||||
}
|
||||
|
||||
if (body && bodyStringify)
|
||||
options.body = JSON.stringify(body)
|
||||
|
||||
// Handle timeout
|
||||
return Promise.race([
|
||||
new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
reject(new Error('request timeout'))
|
||||
}, TIME_OUT)
|
||||
}),
|
||||
new Promise((resolve, reject) => {
|
||||
globalThis.fetch(urlWithPrefix, options as RequestInit)
|
||||
.then((res) => {
|
||||
const resClone = res.clone()
|
||||
// Error handler
|
||||
if (!/^(2|3)\d{2}$/.test(String(res.status))) {
|
||||
const bodyJson = res.json()
|
||||
switch (res.status) {
|
||||
case 401:
|
||||
return Promise.reject(resClone)
|
||||
case 403:
|
||||
bodyJson.then((data: ResponseError) => {
|
||||
if (!silent)
|
||||
Toast.notify({ type: 'error', message: data.message })
|
||||
if (data.code === 'already_setup')
|
||||
globalThis.location.href = `${globalThis.location.origin}/signin`
|
||||
})
|
||||
break
|
||||
// fall through
|
||||
default:
|
||||
bodyJson.then((data: ResponseError) => {
|
||||
if (!silent)
|
||||
Toast.notify({ type: 'error', message: data.message })
|
||||
})
|
||||
}
|
||||
return Promise.reject(resClone)
|
||||
}
|
||||
|
||||
// handle delete api. Delete api not return content.
|
||||
if (res.status === 204) {
|
||||
resolve({ result: 'success' })
|
||||
return
|
||||
}
|
||||
|
||||
// return data
|
||||
if (options.headers.get('Content-type') === ContentType.download || options.headers.get('Content-type') === ContentType.audio)
|
||||
resolve(needAllResponseContent ? resClone : res.blob())
|
||||
|
||||
else resolve(needAllResponseContent ? resClone : res.json())
|
||||
})
|
||||
.catch((err) => {
|
||||
if (!silent)
|
||||
Toast.notify({ type: 'error', message: err })
|
||||
reject(err)
|
||||
})
|
||||
}),
|
||||
]) as Promise<T>
|
||||
}
|
||||
const baseFetch = base
|
||||
|
||||
export const upload = (options: any, isPublicAPI?: boolean, url?: string, searchParams?: string): Promise<any> => {
|
||||
const urlPrefix = isPublicAPI ? PUBLIC_API_PREFIX : API_PREFIX
|
||||
let token = ''
|
||||
if (isPublicAPI) {
|
||||
const sharedToken = globalThis.location.pathname.split('/').slice(-1)[0]
|
||||
const accessToken = localStorage.getItem('token') || JSON.stringify({ [sharedToken]: '' })
|
||||
let accessTokenJson = { [sharedToken]: '' }
|
||||
try {
|
||||
accessTokenJson = JSON.parse(accessToken)
|
||||
}
|
||||
catch (e) {
|
||||
|
||||
}
|
||||
token = accessTokenJson[sharedToken]
|
||||
token = getPublicToken()
|
||||
}
|
||||
else {
|
||||
const accessToken = localStorage.getItem('console_token') || ''
|
||||
@ -499,9 +331,9 @@ export const ssePost = (
|
||||
signal: abortController.signal,
|
||||
}, fetchOptions)
|
||||
|
||||
const contentType = options.headers.get('Content-Type')
|
||||
const contentType = (options.headers as Headers).get('Content-Type')
|
||||
if (!contentType)
|
||||
options.headers.set('Content-Type', ContentType.json)
|
||||
(options.headers as Headers).set('Content-Type', ContentType.json)
|
||||
|
||||
getAbortController?.(abortController)
|
||||
|
||||
@ -559,18 +391,17 @@ export const ssePost = (
|
||||
}
|
||||
|
||||
// base request
|
||||
export const request = <T>(url: string, options = {}, otherOptions?: IOtherOptions) => {
|
||||
export const request = <T>(url: string, options = {}, otherOptions: IOtherOptions = {}) => {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
const otherOptionsForBaseFetch = otherOptions || {}
|
||||
baseFetch<T>(url, options, otherOptionsForBaseFetch).then(resolve).catch((errResp) => {
|
||||
baseFetch<T>(url, options, otherOptions).then(resolve).catch((errResp) => {
|
||||
if (errResp?.status === 401) {
|
||||
return refreshAccessTokenOrRelogin(TIME_OUT).then(() => {
|
||||
baseFetch<T>(url, options, otherOptionsForBaseFetch).then(resolve).catch(reject)
|
||||
baseFetch<T>(url, options, otherOptions).then(resolve).catch(reject)
|
||||
}).catch(() => {
|
||||
const {
|
||||
isPublicAPI = false,
|
||||
silent,
|
||||
} = otherOptionsForBaseFetch
|
||||
} = otherOptions
|
||||
const bodyJson = errResp.json()
|
||||
if (isPublicAPI) {
|
||||
return bodyJson.then((data: ResponseError) => {
|
||||
|
||||
Reference in New Issue
Block a user