mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-27 21:01:32 +08:00
* refactor: replace axios and node fetch with electron's net module for network requests in preprocess providers - Updated Doc2xPreprocessProvider and MineruPreprocessProvider to use net.fetch instead of axios for making HTTP requests. - Improved error handling for network responses across various methods. - Removed unnecessary AxiosRequestConfig and related code to streamline the implementation. * lint * refactor(Doc2xPreprocessProvider): enhance file validation and upload process - Added file size validation to prevent loading files larger than 300MB into memory. - Implemented file size check before reading the PDF to ensure efficient memory usage. - Updated the file upload method to use a stream, setting the 'Content-Length' header for better handling of large files. * refactor(brave-search): update net.fetch calls to use url.toString() - Modified all instances of net.fetch to use url.toString() for better URL handling. - Ensured consistency in how URLs are passed to the fetch method across various functions. * refactor(MCPService): improve URL handling in net.fetch calls - Updated net.fetch to use url.toString() for better type handling of URLs. - Ensured consistent URL processing across the MCPService class. * feat(ProxyManager): integrate axios with fetch proxy support - Added axios as a dependency to enable fetch proxy usage. - Implemented logic to set axios's adapter to 'fetch' for proxy handling. - Preserved original axios adapter for restoration when disabling the proxy.
140 lines
3.6 KiB
TypeScript
140 lines
3.6 KiB
TypeScript
import path from 'node:path'
|
|
|
|
import { loggerService } from '@logger'
|
|
import { NUTSTORE_HOST } from '@shared/config/nutstore'
|
|
import { net } from 'electron'
|
|
import { XMLParser } from 'fast-xml-parser'
|
|
import { isNil, partial } from 'lodash'
|
|
import { type FileStat } from 'webdav'
|
|
|
|
import { createOAuthUrl, decryptSecret } from '../integration/nutstore/sso/lib/index.mjs'
|
|
|
|
const logger = loggerService.withContext('NutstoreService')
|
|
|
|
interface OAuthResponse {
|
|
username: string
|
|
userid: string
|
|
access_token: string
|
|
}
|
|
|
|
interface WebDAVResponse {
|
|
multistatus: {
|
|
response: Array<{
|
|
href: string
|
|
propstat: {
|
|
prop: {
|
|
displayname: string
|
|
resourcetype: { collection?: any }
|
|
getlastmodified?: string
|
|
getcontentlength?: string
|
|
getcontenttype?: string
|
|
}
|
|
status: string
|
|
}
|
|
}>
|
|
}
|
|
}
|
|
|
|
export async function getNutstoreSSOUrl() {
|
|
return await createOAuthUrl({
|
|
app: 'cherrystudio'
|
|
})
|
|
}
|
|
|
|
export async function decryptToken(token: string) {
|
|
try {
|
|
const decrypted = await decryptSecret({
|
|
app: 'cherrystudio',
|
|
s: token
|
|
})
|
|
return JSON.parse(decrypted) as OAuthResponse
|
|
} catch (error) {
|
|
logger.error('Failed to decrypt token:', error as Error)
|
|
return null
|
|
}
|
|
}
|
|
|
|
export async function getDirectoryContents(token: string, target: string): Promise<FileStat[]> {
|
|
const contents: FileStat[] = []
|
|
if (!target.startsWith('/')) {
|
|
target = '/' + target
|
|
}
|
|
|
|
let currentUrl = `${NUTSTORE_HOST}${target}`
|
|
|
|
while (true) {
|
|
const response = await net.fetch(currentUrl, {
|
|
method: 'PROPFIND',
|
|
headers: {
|
|
Authorization: `Basic ${token}`,
|
|
'Content-Type': 'application/xml',
|
|
Depth: '1'
|
|
},
|
|
body: `<?xml version="1.0" encoding="utf-8"?>
|
|
<propfind xmlns="DAV:">
|
|
<prop>
|
|
<displayname/>
|
|
<resourcetype/>
|
|
<getlastmodified/>
|
|
<getcontentlength/>
|
|
<getcontenttype/>
|
|
</prop>
|
|
</propfind>`
|
|
})
|
|
|
|
const text = await response.text()
|
|
|
|
const result = parseXml<WebDAVResponse>(text)
|
|
const items = Array.isArray(result.multistatus.response)
|
|
? result.multistatus.response
|
|
: [result.multistatus.response]
|
|
|
|
// 跳过第一个条目(当前目录)
|
|
contents.push(...items.slice(1).map(partial(convertToFileStat, '/dav')))
|
|
|
|
const linkHeader = response.headers['link'] || response.headers['Link']
|
|
if (!linkHeader) {
|
|
break
|
|
}
|
|
|
|
const nextLink = extractNextLink(linkHeader)
|
|
if (!nextLink) {
|
|
break
|
|
}
|
|
|
|
currentUrl = decodeURI(nextLink)
|
|
}
|
|
|
|
return contents
|
|
}
|
|
|
|
function extractNextLink(linkHeader: string): string | null {
|
|
const matches = linkHeader.match(/<([^>]+)>;\s*rel="next"/)
|
|
return matches ? matches[1] : null
|
|
}
|
|
|
|
function convertToFileStat(serverBase: string, item: WebDAVResponse['multistatus']['response'][number]): FileStat {
|
|
const props = item.propstat.prop
|
|
const isDir = !isNil(props.resourcetype?.collection)
|
|
const href = decodeURIComponent(item.href)
|
|
const filename = serverBase === '/' ? href : path.posix.join('/', href.replace(serverBase, ''))
|
|
|
|
return {
|
|
filename: filename.endsWith('/') ? filename.slice(0, -1) : filename,
|
|
basename: path.basename(filename),
|
|
lastmod: props.getlastmodified || '',
|
|
size: props.getcontentlength ? parseInt(props.getcontentlength, 10) : 0,
|
|
type: isDir ? 'directory' : 'file',
|
|
etag: null,
|
|
mime: props.getcontenttype
|
|
}
|
|
}
|
|
|
|
function parseXml<T>(xml: string) {
|
|
const parser = new XMLParser({
|
|
attributeNamePrefix: '',
|
|
removeNSPrefix: true
|
|
})
|
|
return parser.parse(xml) as T
|
|
}
|