Merge remote-tracking branch 'origin/main' into feat/tax-text

This commit is contained in:
CodingOnStar 2025-10-10 09:59:57 +08:00
commit 97b5d4bba1
17 changed files with 105 additions and 49 deletions

View File

@ -362,11 +362,11 @@ class HttpConfig(BaseSettings):
) )
HTTP_REQUEST_MAX_READ_TIMEOUT: int = Field( HTTP_REQUEST_MAX_READ_TIMEOUT: int = Field(
ge=1, description="Maximum read timeout in seconds for HTTP requests", default=60 ge=1, description="Maximum read timeout in seconds for HTTP requests", default=600
) )
HTTP_REQUEST_MAX_WRITE_TIMEOUT: int = Field( HTTP_REQUEST_MAX_WRITE_TIMEOUT: int = Field(
ge=1, description="Maximum write timeout in seconds for HTTP requests", default=20 ge=1, description="Maximum write timeout in seconds for HTTP requests", default=600
) )
HTTP_REQUEST_NODE_MAX_BINARY_SIZE: PositiveInt = Field( HTTP_REQUEST_NODE_MAX_BINARY_SIZE: PositiveInt = Field(
@ -771,7 +771,7 @@ class MailConfig(BaseSettings):
MAIL_TEMPLATING_TIMEOUT: int = Field( MAIL_TEMPLATING_TIMEOUT: int = Field(
description=""" description="""
Timeout for email templating in seconds. Used to prevent infinite loops in malicious templates. Timeout for email templating in seconds. Used to prevent infinite loops in malicious templates.
Only available in sandbox mode.""", Only available in sandbox mode.""",
default=3, default=3,
) )

View File

@ -1,6 +1,6 @@
from collections.abc import Generator from collections.abc import Generator
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import TypeVar, Union, cast from typing import TypeVar, Union
from core.agent.entities import AgentInvokeMessage from core.agent.entities import AgentInvokeMessage
from core.tools.entities.tool_entities import ToolInvokeMessage from core.tools.entities.tool_entities import ToolInvokeMessage
@ -87,7 +87,8 @@ def merge_blob_chunks(
), ),
meta=resp.meta, meta=resp.meta,
) )
yield cast(MessageType, merged_message) assert isinstance(merged_message, (ToolInvokeMessage, AgentInvokeMessage))
yield merged_message # type: ignore
# Clean up the buffer # Clean up the buffer
del files[chunk_id] del files[chunk_id]
else: else:

View File

@ -2,7 +2,7 @@ import datetime
import logging import logging
import time import time
from collections.abc import Mapping from collections.abc import Mapping
from typing import Any, cast from typing import Any
from sqlalchemy import func, select from sqlalchemy import func, select
@ -62,7 +62,7 @@ class KnowledgeIndexNode(Node):
return self._node_data return self._node_data
def _run(self) -> NodeRunResult: # type: ignore def _run(self) -> NodeRunResult: # type: ignore
node_data = cast(KnowledgeIndexNodeData, self._node_data) node_data = self._node_data
variable_pool = self.graph_runtime_state.variable_pool variable_pool = self.graph_runtime_state.variable_pool
dataset_id = variable_pool.get(["sys", SystemVariableKey.DATASET_ID]) dataset_id = variable_pool.get(["sys", SystemVariableKey.DATASET_ID])
if not dataset_id: if not dataset_id:

View File

@ -25,7 +25,6 @@
"reportMissingParameterType": "hint", "reportMissingParameterType": "hint",
"reportMissingTypeArgument": "hint", "reportMissingTypeArgument": "hint",
"reportUnnecessaryComparison": "hint", "reportUnnecessaryComparison": "hint",
"reportUnnecessaryCast": "hint",
"reportUnnecessaryIsInstance": "hint", "reportUnnecessaryIsInstance": "hint",
"reportUntypedFunctionDecorator": "hint", "reportUntypedFunctionDecorator": "hint",

View File

@ -1,7 +1,7 @@
import hashlib import hashlib
import json import json
from datetime import datetime from datetime import datetime
from typing import Any, cast from typing import Any
from sqlalchemy import or_ from sqlalchemy import or_
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
@ -55,7 +55,7 @@ class MCPToolManageService:
cache=NoOpProviderCredentialCache(), cache=NoOpProviderCredentialCache(),
) )
return cast(dict[str, str], encrypter_instance.encrypt(headers)) return encrypter_instance.encrypt(headers)
@staticmethod @staticmethod
def get_mcp_provider_by_provider_id(provider_id: str, tenant_id: str) -> MCPToolProvider: def get_mcp_provider_by_provider_id(provider_id: str, tenant_id: str) -> MCPToolProvider:

View File

@ -15,13 +15,13 @@ def test_dify_config(monkeypatch: pytest.MonkeyPatch):
# Set environment variables using monkeypatch # Set environment variables using monkeypatch
monkeypatch.setenv("CONSOLE_API_URL", "https://example.com") monkeypatch.setenv("CONSOLE_API_URL", "https://example.com")
monkeypatch.setenv("CONSOLE_WEB_URL", "https://example.com") monkeypatch.setenv("CONSOLE_WEB_URL", "https://example.com")
monkeypatch.setenv("HTTP_REQUEST_MAX_WRITE_TIMEOUT", "30") monkeypatch.setenv("HTTP_REQUEST_MAX_WRITE_TIMEOUT", "30") # Custom value for testing
monkeypatch.setenv("DB_USERNAME", "postgres") monkeypatch.setenv("DB_USERNAME", "postgres")
monkeypatch.setenv("DB_PASSWORD", "postgres") monkeypatch.setenv("DB_PASSWORD", "postgres")
monkeypatch.setenv("DB_HOST", "localhost") monkeypatch.setenv("DB_HOST", "localhost")
monkeypatch.setenv("DB_PORT", "5432") monkeypatch.setenv("DB_PORT", "5432")
monkeypatch.setenv("DB_DATABASE", "dify") monkeypatch.setenv("DB_DATABASE", "dify")
monkeypatch.setenv("HTTP_REQUEST_MAX_READ_TIMEOUT", "600") monkeypatch.setenv("HTTP_REQUEST_MAX_READ_TIMEOUT", "300") # Custom value for testing
# load dotenv file with pydantic-settings # load dotenv file with pydantic-settings
config = DifyConfig() config = DifyConfig()
@ -35,16 +35,36 @@ def test_dify_config(monkeypatch: pytest.MonkeyPatch):
assert config.SENTRY_TRACES_SAMPLE_RATE == 1.0 assert config.SENTRY_TRACES_SAMPLE_RATE == 1.0
assert config.TEMPLATE_TRANSFORM_MAX_LENGTH == 400_000 assert config.TEMPLATE_TRANSFORM_MAX_LENGTH == 400_000
# annotated field with default value # annotated field with custom configured value
assert config.HTTP_REQUEST_MAX_READ_TIMEOUT == 600 assert config.HTTP_REQUEST_MAX_READ_TIMEOUT == 300
# annotated field with configured value # annotated field with custom configured value
assert config.HTTP_REQUEST_MAX_WRITE_TIMEOUT == 30 assert config.HTTP_REQUEST_MAX_WRITE_TIMEOUT == 30
# values from pyproject.toml # values from pyproject.toml
assert Version(config.project.version) >= Version("1.0.0") assert Version(config.project.version) >= Version("1.0.0")
def test_http_timeout_defaults(monkeypatch: pytest.MonkeyPatch):
"""Test that HTTP timeout defaults are correctly set"""
# clear system environment variables
os.environ.clear()
# Set minimal required env vars
monkeypatch.setenv("DB_USERNAME", "postgres")
monkeypatch.setenv("DB_PASSWORD", "postgres")
monkeypatch.setenv("DB_HOST", "localhost")
monkeypatch.setenv("DB_PORT", "5432")
monkeypatch.setenv("DB_DATABASE", "dify")
config = DifyConfig()
# Verify default timeout values
assert config.HTTP_REQUEST_MAX_CONNECT_TIMEOUT == 10
assert config.HTTP_REQUEST_MAX_READ_TIMEOUT == 600
assert config.HTTP_REQUEST_MAX_WRITE_TIMEOUT == 600
# NOTE: If there is a `.env` file in your Workspace, this test might not succeed as expected. # NOTE: If there is a `.env` file in your Workspace, this test might not succeed as expected.
# This is due to `pymilvus` loading all the variables from the `.env` file into `os.environ`. # This is due to `pymilvus` loading all the variables from the `.env` file into `os.environ`.
def test_flask_configs(monkeypatch: pytest.MonkeyPatch): def test_flask_configs(monkeypatch: pytest.MonkeyPatch):
@ -55,7 +75,6 @@ def test_flask_configs(monkeypatch: pytest.MonkeyPatch):
# Set environment variables using monkeypatch # Set environment variables using monkeypatch
monkeypatch.setenv("CONSOLE_API_URL", "https://example.com") monkeypatch.setenv("CONSOLE_API_URL", "https://example.com")
monkeypatch.setenv("CONSOLE_WEB_URL", "https://example.com") monkeypatch.setenv("CONSOLE_WEB_URL", "https://example.com")
monkeypatch.setenv("HTTP_REQUEST_MAX_WRITE_TIMEOUT", "30")
monkeypatch.setenv("DB_USERNAME", "postgres") monkeypatch.setenv("DB_USERNAME", "postgres")
monkeypatch.setenv("DB_PASSWORD", "postgres") monkeypatch.setenv("DB_PASSWORD", "postgres")
monkeypatch.setenv("DB_HOST", "localhost") monkeypatch.setenv("DB_HOST", "localhost")
@ -105,7 +124,6 @@ def test_inner_api_config_exist(monkeypatch: pytest.MonkeyPatch):
# Set environment variables using monkeypatch # Set environment variables using monkeypatch
monkeypatch.setenv("CONSOLE_API_URL", "https://example.com") monkeypatch.setenv("CONSOLE_API_URL", "https://example.com")
monkeypatch.setenv("CONSOLE_WEB_URL", "https://example.com") monkeypatch.setenv("CONSOLE_WEB_URL", "https://example.com")
monkeypatch.setenv("HTTP_REQUEST_MAX_WRITE_TIMEOUT", "30")
monkeypatch.setenv("DB_USERNAME", "postgres") monkeypatch.setenv("DB_USERNAME", "postgres")
monkeypatch.setenv("DB_PASSWORD", "postgres") monkeypatch.setenv("DB_PASSWORD", "postgres")
monkeypatch.setenv("DB_HOST", "localhost") monkeypatch.setenv("DB_HOST", "localhost")

View File

@ -930,6 +930,16 @@ WORKFLOW_LOG_CLEANUP_BATCH_SIZE=100
HTTP_REQUEST_NODE_MAX_BINARY_SIZE=10485760 HTTP_REQUEST_NODE_MAX_BINARY_SIZE=10485760
HTTP_REQUEST_NODE_MAX_TEXT_SIZE=1048576 HTTP_REQUEST_NODE_MAX_TEXT_SIZE=1048576
HTTP_REQUEST_NODE_SSL_VERIFY=True HTTP_REQUEST_NODE_SSL_VERIFY=True
# HTTP request node timeout configuration
# Maximum timeout values (in seconds) that users can set in HTTP request nodes
# - Connect timeout: Time to wait for establishing connection (default: 10s)
# - Read timeout: Time to wait for receiving response data (default: 600s, 10 minutes)
# - Write timeout: Time to wait for sending request data (default: 600s, 10 minutes)
HTTP_REQUEST_MAX_CONNECT_TIMEOUT=10
HTTP_REQUEST_MAX_READ_TIMEOUT=600
HTTP_REQUEST_MAX_WRITE_TIMEOUT=600
# Base64 encoded CA certificate data for custom certificate verification (PEM format, optional) # Base64 encoded CA certificate data for custom certificate verification (PEM format, optional)
# HTTP_REQUEST_NODE_SSL_CERT_DATA=LS0tLS1CRUdJTi... # HTTP_REQUEST_NODE_SSL_CERT_DATA=LS0tLS1CRUdJTi...
# Base64 encoded client certificate data for mutual TLS authentication (PEM format, optional) # Base64 encoded client certificate data for mutual TLS authentication (PEM format, optional)

View File

@ -418,6 +418,9 @@ x-shared-env: &shared-api-worker-env
HTTP_REQUEST_NODE_MAX_BINARY_SIZE: ${HTTP_REQUEST_NODE_MAX_BINARY_SIZE:-10485760} HTTP_REQUEST_NODE_MAX_BINARY_SIZE: ${HTTP_REQUEST_NODE_MAX_BINARY_SIZE:-10485760}
HTTP_REQUEST_NODE_MAX_TEXT_SIZE: ${HTTP_REQUEST_NODE_MAX_TEXT_SIZE:-1048576} HTTP_REQUEST_NODE_MAX_TEXT_SIZE: ${HTTP_REQUEST_NODE_MAX_TEXT_SIZE:-1048576}
HTTP_REQUEST_NODE_SSL_VERIFY: ${HTTP_REQUEST_NODE_SSL_VERIFY:-True} HTTP_REQUEST_NODE_SSL_VERIFY: ${HTTP_REQUEST_NODE_SSL_VERIFY:-True}
HTTP_REQUEST_MAX_CONNECT_TIMEOUT: ${HTTP_REQUEST_MAX_CONNECT_TIMEOUT:-10}
HTTP_REQUEST_MAX_READ_TIMEOUT: ${HTTP_REQUEST_MAX_READ_TIMEOUT:-600}
HTTP_REQUEST_MAX_WRITE_TIMEOUT: ${HTTP_REQUEST_MAX_WRITE_TIMEOUT:-600}
RESPECT_XFORWARD_HEADERS_ENABLED: ${RESPECT_XFORWARD_HEADERS_ENABLED:-false} RESPECT_XFORWARD_HEADERS_ENABLED: ${RESPECT_XFORWARD_HEADERS_ENABLED:-false}
SSRF_PROXY_HTTP_URL: ${SSRF_PROXY_HTTP_URL:-http://ssrf_proxy:3128} SSRF_PROXY_HTTP_URL: ${SSRF_PROXY_HTTP_URL:-http://ssrf_proxy:3128}
SSRF_PROXY_HTTPS_URL: ${SSRF_PROXY_HTTPS_URL:-http://ssrf_proxy:3128} SSRF_PROXY_HTTPS_URL: ${SSRF_PROXY_HTTPS_URL:-http://ssrf_proxy:3128}

View File

@ -276,7 +276,7 @@ function Form<
<div key={variable} className={cn(itemClassName, 'py-3')}> <div key={variable} className={cn(itemClassName, 'py-3')}>
<div className='system-sm-semibold flex items-center justify-between py-2 text-text-secondary'> <div className='system-sm-semibold flex items-center justify-between py-2 text-text-secondary'>
<div className='flex items-center space-x-2'> <div className='flex items-center space-x-2'>
<span className={cn(fieldLabelClassName, 'system-sm-regular flex items-center py-2 text-text-secondary')}>{label[language] || label.en_US}</span> <span className={cn(fieldLabelClassName, 'system-sm-semibold flex items-center py-2 text-text-secondary')}>{label[language] || label.en_US}</span>
{required && ( {required && (
<span className='ml-1 text-red-500'>*</span> <span className='ml-1 text-red-500'>*</span>
)} )}

View File

@ -78,15 +78,15 @@ const Result: FC<IResultProps> = ({
setRespondingFalse() setRespondingFalse()
}, [controlStopResponding]) }, [controlStopResponding])
const [completionRes, doSetCompletionRes] = useState<any>('') const [completionRes, doSetCompletionRes] = useState<string>('')
const completionResRef = useRef<any>() const completionResRef = useRef<string>('')
const setCompletionRes = (res: any) => { const setCompletionRes = (res: string) => {
completionResRef.current = res completionResRef.current = res
doSetCompletionRes(res) doSetCompletionRes(res)
} }
const getCompletionRes = () => completionResRef.current const getCompletionRes = () => completionResRef.current
const [workflowProcessData, doSetWorkflowProcessData] = useState<WorkflowProcess>() const [workflowProcessData, doSetWorkflowProcessData] = useState<WorkflowProcess>()
const workflowProcessDataRef = useRef<WorkflowProcess>() const workflowProcessDataRef = useRef<WorkflowProcess | undefined>(undefined)
const setWorkflowProcessData = (data: WorkflowProcess) => { const setWorkflowProcessData = (data: WorkflowProcess) => {
workflowProcessDataRef.current = data workflowProcessDataRef.current = data
doSetWorkflowProcessData(data) doSetWorkflowProcessData(data)

View File

@ -45,6 +45,7 @@ export const toolCredentialToFormSchemas = (parameters: ToolCredential[]) => {
return { return {
...parameter, ...parameter,
variable: parameter.name, variable: parameter.name,
type: toType(parameter.type),
label: parameter.label, label: parameter.label,
tooltip: parameter.help, tooltip: parameter.help,
show_on: [], show_on: [],

View File

@ -41,16 +41,16 @@ export const useWorkflowHistory = () => {
const { store: workflowHistoryStore } = useWorkflowHistoryStore() const { store: workflowHistoryStore } = useWorkflowHistoryStore()
const { t } = useTranslation() const { t } = useTranslation()
const [undoCallbacks, setUndoCallbacks] = useState<any[]>([]) const [undoCallbacks, setUndoCallbacks] = useState<(() => void)[]>([])
const [redoCallbacks, setRedoCallbacks] = useState<any[]>([]) const [redoCallbacks, setRedoCallbacks] = useState<(() => void)[]>([])
const onUndo = useCallback((callback: unknown) => { const onUndo = useCallback((callback: () => void) => {
setUndoCallbacks((prev: any) => [...prev, callback]) setUndoCallbacks(prev => [...prev, callback])
return () => setUndoCallbacks(prev => prev.filter(cb => cb !== callback)) return () => setUndoCallbacks(prev => prev.filter(cb => cb !== callback))
}, []) }, [])
const onRedo = useCallback((callback: unknown) => { const onRedo = useCallback((callback: () => void) => {
setRedoCallbacks((prev: any) => [...prev, callback]) setRedoCallbacks(prev => [...prev, callback])
return () => setRedoCallbacks(prev => prev.filter(cb => cb !== callback)) return () => setRedoCallbacks(prev => prev.filter(cb => cb !== callback))
}, []) }, [])

View File

@ -127,7 +127,7 @@ const VarReferencePicker: FC<Props> = ({
const reactflow = useReactFlow() const reactflow = useReactFlow()
const startNode = availableNodes.find((node: any) => { const startNode = availableNodes.find((node: Node) => {
return node.data.type === BlockEnum.Start return node.data.type === BlockEnum.Start
}) })

View File

@ -5,6 +5,8 @@ import { useTranslation } from 'react-i18next'
import type { Timeout as TimeoutPayloadType } from '../../types' import type { Timeout as TimeoutPayloadType } from '../../types'
import Input from '@/app/components/base/input' import Input from '@/app/components/base/input'
import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/collapse' import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/collapse'
import { useStore } from '@/app/components/workflow/store'
import { BlockEnum } from '@/app/components/workflow/types'
type Props = { type Props = {
readonly: boolean readonly: boolean
@ -61,6 +63,11 @@ const Timeout: FC<Props> = ({ readonly, payload, onChange }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { connect, read, write, max_connect_timeout, max_read_timeout, max_write_timeout } = payload ?? {} const { connect, read, write, max_connect_timeout, max_read_timeout, max_write_timeout } = payload ?? {}
// Get default config from store for max timeout values
const nodesDefaultConfigs = useStore(s => s.nodesDefaultConfigs)
const defaultConfig = nodesDefaultConfigs?.[BlockEnum.HttpRequest]
const defaultTimeout = defaultConfig?.timeout || {}
return ( return (
<FieldCollapse title={t(`${i18nPrefix}.timeout.title`)}> <FieldCollapse title={t(`${i18nPrefix}.timeout.title`)}>
<div className='mt-2 space-y-1'> <div className='mt-2 space-y-1'>
@ -73,7 +80,7 @@ const Timeout: FC<Props> = ({ readonly, payload, onChange }) => {
value={connect} value={connect}
onChange={v => onChange?.({ ...payload, connect: v })} onChange={v => onChange?.({ ...payload, connect: v })}
min={1} min={1}
max={max_connect_timeout || 300} max={max_connect_timeout || defaultTimeout.max_connect_timeout || 10}
/> />
<InputField <InputField
title={t('workflow.nodes.http.timeout.readLabel')!} title={t('workflow.nodes.http.timeout.readLabel')!}
@ -83,7 +90,7 @@ const Timeout: FC<Props> = ({ readonly, payload, onChange }) => {
value={read} value={read}
onChange={v => onChange?.({ ...payload, read: v })} onChange={v => onChange?.({ ...payload, read: v })}
min={1} min={1}
max={max_read_timeout || 600} max={max_read_timeout || defaultTimeout.max_read_timeout || 600}
/> />
<InputField <InputField
title={t('workflow.nodes.http.timeout.writeLabel')!} title={t('workflow.nodes.http.timeout.writeLabel')!}
@ -93,7 +100,7 @@ const Timeout: FC<Props> = ({ readonly, payload, onChange }) => {
value={write} value={write}
onChange={v => onChange?.({ ...payload, write: v })} onChange={v => onChange?.({ ...payload, write: v })}
min={1} min={1}
max={max_write_timeout || 600} max={max_write_timeout || defaultTimeout.max_write_timeout || 600}
/> />
</div> </div>
</div> </div>

View File

@ -120,7 +120,7 @@ const JsonSchemaConfig: FC<JsonSchemaConfigProps> = ({
setJson(JSON.stringify(schema, null, 2)) setJson(JSON.stringify(schema, null, 2))
}, [currentTab]) }, [currentTab])
const handleSubmit = useCallback((schema: any) => { const handleSubmit = useCallback((schema: Record<string, unknown>) => {
const jsonSchema = jsonToSchema(schema) as SchemaRoot const jsonSchema = jsonToSchema(schema) as SchemaRoot
if (currentTab === SchemaView.VisualEditor) if (currentTab === SchemaView.VisualEditor)
setJsonSchema(jsonSchema) setJsonSchema(jsonSchema)

View File

@ -152,14 +152,16 @@ const EditCard: FC<EditCardProps> = ({
}, [isAdvancedEditing, emitPropertyOptionsChange, currentFields]) }, [isAdvancedEditing, emitPropertyOptionsChange, currentFields])
const handleAdvancedOptionsChange = useCallback((options: AdvancedOptionsType) => { const handleAdvancedOptionsChange = useCallback((options: AdvancedOptionsType) => {
let enumValue: any = options.enum let enumValue: SchemaEnumType | undefined
if (enumValue === '') { if (options.enum === '') {
enumValue = undefined enumValue = undefined
} }
else { else {
enumValue = options.enum.replace(/\s/g, '').split(',') const stringArray = options.enum.replace(/\s/g, '').split(',')
if (currentFields.type === Type.number) if (currentFields.type === Type.number)
enumValue = (enumValue as SchemaEnumType).map(value => Number(value)).filter(num => !Number.isNaN(num)) enumValue = stringArray.map(value => Number(value)).filter(num => !Number.isNaN(num))
else
enumValue = stringArray
} }
setCurrentFields(prev => ({ ...prev, enum: enumValue })) setCurrentFields(prev => ({ ...prev, enum: enumValue }))
if (isAdvancedEditing) return if (isAdvancedEditing) return

View File

@ -180,7 +180,7 @@ const handleStream = (
let isFirstMessage = true let isFirstMessage = true
function read() { function read() {
let hasError = false let hasError = false
reader?.read().then((result: any) => { reader?.read().then((result: ReadableStreamReadResult<Uint8Array>) => {
if (result.done) { if (result.done) {
onCompleted?.() onCompleted?.()
return return
@ -322,7 +322,21 @@ const handleStream = (
const baseFetch = base const baseFetch = base
export const upload = async (options: any, isPublicAPI?: boolean, url?: string, searchParams?: string): Promise<any> => { type UploadOptions = {
xhr: XMLHttpRequest
method: string
url?: string
headers?: Record<string, string>
data: FormData
onprogress?: (this: XMLHttpRequest, ev: ProgressEvent<EventTarget>) => void
}
type UploadResponse = {
id: string
[key: string]: unknown
}
export const upload = async (options: UploadOptions, isPublicAPI?: boolean, url?: string, searchParams?: string): Promise<UploadResponse> => {
const urlPrefix = isPublicAPI ? PUBLIC_API_PREFIX : API_PREFIX const urlPrefix = isPublicAPI ? PUBLIC_API_PREFIX : API_PREFIX
const token = await getAccessToken(isPublicAPI) const token = await getAccessToken(isPublicAPI)
const defaultOptions = { const defaultOptions = {
@ -331,18 +345,18 @@ export const upload = async (options: any, isPublicAPI?: boolean, url?: string,
headers: { headers: {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
}, },
data: {},
} }
options = { const mergedOptions = {
...defaultOptions, ...defaultOptions,
...options, ...options,
headers: { ...defaultOptions.headers, ...options.headers }, url: options.url || defaultOptions.url,
headers: { ...defaultOptions.headers, ...options.headers } as Record<string, string>,
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const xhr = options.xhr const xhr = mergedOptions.xhr
xhr.open(options.method, options.url) xhr.open(mergedOptions.method, mergedOptions.url)
for (const key in options.headers) for (const key in mergedOptions.headers)
xhr.setRequestHeader(key, options.headers[key]) xhr.setRequestHeader(key, mergedOptions.headers[key])
xhr.withCredentials = true xhr.withCredentials = true
xhr.responseType = 'json' xhr.responseType = 'json'
@ -354,8 +368,9 @@ export const upload = async (options: any, isPublicAPI?: boolean, url?: string,
reject(xhr) reject(xhr)
} }
} }
xhr.upload.onprogress = options.onprogress if (mergedOptions.onprogress)
xhr.send(options.data) xhr.upload.onprogress = mergedOptions.onprogress
xhr.send(mergedOptions.data)
}) })
} }
@ -432,7 +447,7 @@ export const ssePost = async (
if (!/^[23]\d{2}$/.test(String(res.status))) { if (!/^[23]\d{2}$/.test(String(res.status))) {
if (res.status === 401) { if (res.status === 401) {
if (isPublicAPI) { if (isPublicAPI) {
res.json().then((data: any) => { res.json().then((data: { code?: string; message?: string }) => {
if (isPublicAPI) { if (isPublicAPI) {
if (data.code === 'web_app_access_denied') if (data.code === 'web_app_access_denied')
requiredWebSSOLogin(data.message, 403) requiredWebSSOLogin(data.message, 403)