diff --git a/package.json b/package.json index ee48a5733f..06e656cbc1 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "prepare": "husky" }, "dependencies": { + "@aws-sdk/client-s3": "^3.840.0", "@cherrystudio/pdf-to-img-napi": "^0.0.1", "@libsql/client": "0.14.0", "@libsql/win32-x64-msvc": "^0.4.7", diff --git a/packages/shared/IpcChannel.ts b/packages/shared/IpcChannel.ts index 38c6c2b516..66475c50fa 100644 --- a/packages/shared/IpcChannel.ts +++ b/packages/shared/IpcChannel.ts @@ -165,6 +165,11 @@ export enum IpcChannel { Backup_CheckConnection = 'backup:checkConnection', Backup_CreateDirectory = 'backup:createDirectory', Backup_DeleteWebdavFile = 'backup:deleteWebdavFile', + Backup_BackupToS3 = 'backup:backupToS3', + Backup_RestoreFromS3 = 'backup:restoreFromS3', + Backup_ListS3Files = 'backup:listS3Files', + Backup_DeleteS3File = 'backup:deleteS3File', + Backup_CheckS3Connection = 'backup:checkS3Connection', // zip Zip_Compress = 'zip:compress', diff --git a/src/main/ipc.ts b/src/main/ipc.ts index f97cb60ed9..4a5433f67f 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -368,6 +368,11 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { ipcMain.handle(IpcChannel.Backup_CheckConnection, backupManager.checkConnection) ipcMain.handle(IpcChannel.Backup_CreateDirectory, backupManager.createDirectory) ipcMain.handle(IpcChannel.Backup_DeleteWebdavFile, backupManager.deleteWebdavFile) + ipcMain.handle(IpcChannel.Backup_BackupToS3, backupManager.backupToS3) + ipcMain.handle(IpcChannel.Backup_RestoreFromS3, backupManager.restoreFromS3) + ipcMain.handle(IpcChannel.Backup_ListS3Files, backupManager.listS3Files) + ipcMain.handle(IpcChannel.Backup_DeleteS3File, backupManager.deleteS3File) + ipcMain.handle(IpcChannel.Backup_CheckS3Connection, backupManager.checkS3Connection) // file ipcMain.handle(IpcChannel.File_Open, fileManager.open) diff --git a/src/main/services/BackupManager.ts b/src/main/services/BackupManager.ts index e994e90bed..576f004188 100644 --- a/src/main/services/BackupManager.ts +++ b/src/main/services/BackupManager.ts @@ -1,5 +1,6 @@ import { IpcChannel } from '@shared/IpcChannel' import { WebDavConfig } from '@types' +import { S3Config } from '@types' import archiver from 'archiver' import { exec } from 'child_process' import { app } from 'electron' @@ -10,6 +11,7 @@ import * as path from 'path' import { CreateDirectoryOptions, FileStat } from 'webdav' import { getDataPath } from '../utils' +import S3Storage from './S3Storage' import WebDav from './WebDav' import { windowService } from './WindowService' @@ -25,6 +27,11 @@ class BackupManager { this.restoreFromWebdav = this.restoreFromWebdav.bind(this) this.listWebdavFiles = this.listWebdavFiles.bind(this) this.deleteWebdavFile = this.deleteWebdavFile.bind(this) + this.backupToS3 = this.backupToS3.bind(this) + this.restoreFromS3 = this.restoreFromS3.bind(this) + this.listS3Files = this.listS3Files.bind(this) + this.deleteS3File = this.deleteS3File.bind(this) + this.checkS3Connection = this.checkS3Connection.bind(this) } private async setWritableRecursive(dirPath: string): Promise { @@ -85,7 +92,11 @@ class BackupManager { const onProgress = (processData: { stage: string; progress: number; total: number }) => { mainWindow?.webContents.send(IpcChannel.BackupProgress, processData) - Logger.log('[BackupManager] backup progress', processData) + // 只在关键阶段记录日志:开始、结束和主要阶段转换点 + const logStages = ['preparing', 'writing_data', 'preparing_compression', 'completed'] + if (logStages.includes(processData.stage) || processData.progress === 100) { + Logger.log('[BackupManager] backup progress', processData) + } } try { @@ -147,18 +158,23 @@ class BackupManager { let totalBytes = 0 let processedBytes = 0 - // 首先计算总文件数和总大小 + // 首先计算总文件数和总大小,但不记录详细日志 const calculateTotals = async (dirPath: string) => { - const items = await fs.readdir(dirPath, { withFileTypes: true }) - for (const item of items) { - const fullPath = path.join(dirPath, item.name) - if (item.isDirectory()) { - await calculateTotals(fullPath) - } else { - totalEntries++ - const stats = await fs.stat(fullPath) - totalBytes += stats.size + try { + const items = await fs.readdir(dirPath, { withFileTypes: true }) + for (const item of items) { + const fullPath = path.join(dirPath, item.name) + if (item.isDirectory()) { + await calculateTotals(fullPath) + } else { + totalEntries++ + const stats = await fs.stat(fullPath) + totalBytes += stats.size + } } + } catch (error) { + // 仅在出错时记录日志 + Logger.error('[BackupManager] Error calculating totals:', error) } } @@ -230,7 +246,11 @@ class BackupManager { const onProgress = (processData: { stage: string; progress: number; total: number }) => { mainWindow?.webContents.send(IpcChannel.RestoreProgress, processData) - Logger.log('[BackupManager] restore progress', processData) + // 只在关键阶段记录日志 + const logStages = ['preparing', 'extracting', 'extracted', 'reading_data', 'completed'] + if (logStages.includes(processData.stage) || processData.progress === 100) { + Logger.log('[BackupManager] restore progress', processData) + } } try { @@ -382,21 +402,54 @@ class BackupManager { destination: string, onProgress: (size: number) => void ): Promise { - const items = await fs.readdir(source, { withFileTypes: true }) + // 先统计总文件数 + let totalFiles = 0 + let processedFiles = 0 + let lastProgressReported = 0 - for (const item of items) { - const sourcePath = path.join(source, item.name) - const destPath = path.join(destination, item.name) + // 计算总文件数 + const countFiles = async (dir: string): Promise => { + let count = 0 + const items = await fs.readdir(dir, { withFileTypes: true }) + for (const item of items) { + if (item.isDirectory()) { + count += await countFiles(path.join(dir, item.name)) + } else { + count++ + } + } + return count + } - if (item.isDirectory()) { - await fs.ensureDir(destPath) - await this.copyDirWithProgress(sourcePath, destPath, onProgress) - } else { - const stats = await fs.stat(sourcePath) - await fs.copy(sourcePath, destPath) - onProgress(stats.size) + totalFiles = await countFiles(source) + + // 复制文件并更新进度 + const copyDir = async (src: string, dest: string): Promise => { + const items = await fs.readdir(src, { withFileTypes: true }) + + for (const item of items) { + const sourcePath = path.join(src, item.name) + const destPath = path.join(dest, item.name) + + if (item.isDirectory()) { + await fs.ensureDir(destPath) + await copyDir(sourcePath, destPath) + } else { + const stats = await fs.stat(sourcePath) + await fs.copy(sourcePath, destPath) + processedFiles++ + + // 只在进度变化超过5%时报告进度 + const currentProgress = Math.floor((processedFiles / totalFiles) * 100) + if (currentProgress - lastProgressReported >= 5 || processedFiles === totalFiles) { + lastProgressReported = currentProgress + onProgress(stats.size) + } + } } } + + await copyDir(source, destination) } async checkConnection(_: Electron.IpcMainInvokeEvent, webdavConfig: WebDavConfig) { @@ -423,6 +476,100 @@ class BackupManager { throw new Error(error.message || 'Failed to delete backup file') } } + + async backupToS3(_: Electron.IpcMainInvokeEvent, data: string, s3Config: S3Config) { + const os = require('os') + const deviceName = os.hostname ? os.hostname() : 'device' + const timestamp = new Date() + .toISOString() + .replace(/[-:T.Z]/g, '') + .slice(0, 14) + const filename = s3Config.fileName || `cherry-studio.backup.${deviceName}.${timestamp}.zip` + + Logger.log(`[BackupManager] Starting S3 backup to ${filename}`) + + const backupedFilePath = await this.backup(_, filename, data, undefined, s3Config.skipBackupFile) + const s3Client = new S3Storage(s3Config) + try { + const fileBuffer = await fs.promises.readFile(backupedFilePath) + const result = await s3Client.putFileContents(filename, fileBuffer) + await fs.remove(backupedFilePath) + + Logger.log(`[BackupManager] S3 backup completed successfully: ${filename}`) + return result + } catch (error) { + Logger.error(`[BackupManager] S3 backup failed:`, error) + await fs.remove(backupedFilePath) + throw error + } + } + + async restoreFromS3(_: Electron.IpcMainInvokeEvent, s3Config: S3Config) { + const filename = s3Config.fileName || 'cherry-studio.backup.zip' + + Logger.log(`[BackupManager] Starting restore from S3: ${filename}`) + + const s3Client = new S3Storage(s3Config) + try { + const retrievedFile = await s3Client.getFileContents(filename) + const backupedFilePath = path.join(this.backupDir, filename) + if (!fs.existsSync(this.backupDir)) { + fs.mkdirSync(this.backupDir, { recursive: true }) + } + await new Promise((resolve, reject) => { + const writeStream = fs.createWriteStream(backupedFilePath) + writeStream.write(retrievedFile as Buffer) + writeStream.end() + writeStream.on('finish', () => resolve()) + writeStream.on('error', (error) => reject(error)) + }) + + Logger.log(`[BackupManager] S3 restore file downloaded successfully: ${filename}`) + return await this.restore(_, backupedFilePath) + } catch (error: any) { + Logger.error('[BackupManager] Failed to restore from S3:', error) + throw new Error(error.message || 'Failed to restore backup file') + } + } + + listS3Files = async (_: Electron.IpcMainInvokeEvent, s3Config: S3Config) => { + try { + const s3Client = new S3Storage(s3Config) + + const objects = await s3Client.listFiles() + const files = objects + .filter((obj) => obj.key.endsWith('.zip')) + .map((obj) => { + const segments = obj.key.split('/') + const fileName = segments[segments.length - 1] + return { + fileName, + modifiedTime: obj.lastModified || '', + size: obj.size + } + }) + + return files.sort((a, b) => new Date(b.modifiedTime).getTime() - new Date(a.modifiedTime).getTime()) + } catch (error: any) { + Logger.error('Failed to list S3 files:', error) + throw new Error(error.message || 'Failed to list backup files') + } + } + + async deleteS3File(_: Electron.IpcMainInvokeEvent, fileName: string, s3Config: S3Config) { + try { + const s3Client = new S3Storage(s3Config) + return await s3Client.deleteFile(fileName) + } catch (error: any) { + Logger.error('Failed to delete S3 file:', error) + throw new Error(error.message || 'Failed to delete backup file') + } + } + + async checkS3Connection(_: Electron.IpcMainInvokeEvent, s3Config: S3Config) { + const s3Client = new S3Storage(s3Config) + return await s3Client.checkConnection() + } } export default BackupManager diff --git a/src/main/services/RemoteStorage.ts b/src/main/services/RemoteStorage.ts deleted file mode 100644 index b62489bbbe..0000000000 --- a/src/main/services/RemoteStorage.ts +++ /dev/null @@ -1,57 +0,0 @@ -// import Logger from 'electron-log' -// import { Operator } from 'opendal' - -// export default class RemoteStorage { -// public instance: Operator | undefined - -// /** -// * -// * @param scheme is the scheme for opendal services. Available value includes "azblob", "azdls", "cos", "gcs", "obs", "oss", "s3", "webdav", "webhdfs", "aliyun-drive", "alluxio", "azfile", "dropbox", "gdrive", "onedrive", "postgresql", "mysql", "redis", "swift", "mongodb", "alluxio", "b2", "seafile", "upyun", "koofr", "yandex-disk" -// * @param options is the options for given opendal services. Valid options depend on the scheme. Checkout https://docs.rs/opendal/latest/opendal/services/index.html for all valid options. -// * -// * For example, use minio as remote storage: -// * -// * ```typescript -// * const storage = new RemoteStorage('s3', { -// * endpoint: 'http://localhost:9000', -// * region: 'us-east-1', -// * bucket: 'testbucket', -// * access_key_id: 'user', -// * secret_access_key: 'password', -// * root: '/path/to/basepath', -// * }) -// * ``` -// */ -// constructor(scheme: string, options?: Record | undefined | null) { -// this.instance = new Operator(scheme, options) - -// this.putFileContents = this.putFileContents.bind(this) -// this.getFileContents = this.getFileContents.bind(this) -// } - -// public putFileContents = async (filename: string, data: string | Buffer) => { -// if (!this.instance) { -// return new Error('RemoteStorage client not initialized') -// } - -// try { -// return await this.instance.write(filename, data) -// } catch (error) { -// Logger.error('[RemoteStorage] Error putting file contents:', error) -// throw error -// } -// } - -// public getFileContents = async (filename: string) => { -// if (!this.instance) { -// throw new Error('RemoteStorage client not initialized') -// } - -// try { -// return await this.instance.read(filename) -// } catch (error) { -// Logger.error('[RemoteStorage] Error getting file contents:', error) -// throw error -// } -// } -// } diff --git a/src/main/services/S3Storage.ts b/src/main/services/S3Storage.ts new file mode 100644 index 0000000000..0b45bb0387 --- /dev/null +++ b/src/main/services/S3Storage.ts @@ -0,0 +1,183 @@ +import { + DeleteObjectCommand, + GetObjectCommand, + HeadBucketCommand, + ListObjectsV2Command, + PutObjectCommand, + S3Client +} from '@aws-sdk/client-s3' +import type { S3Config } from '@types' +import Logger from 'electron-log' +import * as net from 'net' +import { Readable } from 'stream' + +/** + * 将可读流转换为 Buffer + */ +function streamToBuffer(stream: Readable): Promise { + return new Promise((resolve, reject) => { + const chunks: Buffer[] = [] + stream.on('data', (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk))) + stream.on('error', reject) + stream.on('end', () => resolve(Buffer.concat(chunks))) + }) +} + +// 需要使用 Virtual Host-Style 的服务商域名后缀白名单 +const VIRTUAL_HOST_SUFFIXES = ['aliyuncs.com', 'myqcloud.com'] + +/** + * 使用 AWS SDK v3 的简单 S3 封装,兼容之前 RemoteStorage 的最常用接口。 + */ +export default class S3Storage { + private client: S3Client + private bucket: string + private root: string + + constructor(config: S3Config) { + const { endpoint, region, accessKeyId, secretAccessKey, bucket, root } = config + + const usePathStyle = (() => { + if (!endpoint) return false + + try { + const { hostname } = new URL(endpoint) + + if (hostname === 'localhost' || net.isIP(hostname) !== 0) { + return true + } + + const isInWhiteList = VIRTUAL_HOST_SUFFIXES.some((suffix) => hostname.endsWith(suffix)) + return !isInWhiteList + } catch (e) { + Logger.warn('[S3Storage] Failed to parse endpoint, fallback to Path-Style:', endpoint, e) + return true + } + })() + + this.client = new S3Client({ + region, + endpoint: endpoint || undefined, + credentials: { + accessKeyId: accessKeyId, + secretAccessKey: secretAccessKey + }, + forcePathStyle: usePathStyle + }) + + this.bucket = bucket + this.root = root?.replace(/^\/+/g, '').replace(/\/+$/g, '') || '' + + this.putFileContents = this.putFileContents.bind(this) + this.getFileContents = this.getFileContents.bind(this) + this.deleteFile = this.deleteFile.bind(this) + this.listFiles = this.listFiles.bind(this) + this.checkConnection = this.checkConnection.bind(this) + } + + /** + * 内部辅助方法,用来拼接带 root 的对象 key + */ + private buildKey(key: string): string { + if (!this.root) return key + return key.startsWith(`${this.root}/`) ? key : `${this.root}/${key}` + } + + async putFileContents(key: string, data: Buffer | string) { + try { + const contentType = key.endsWith('.zip') ? 'application/zip' : 'application/octet-stream' + + return await this.client.send( + new PutObjectCommand({ + Bucket: this.bucket, + Key: this.buildKey(key), + Body: data, + ContentType: contentType + }) + ) + } catch (error) { + Logger.error('[S3Storage] Error putting object:', error) + throw error + } + } + + async getFileContents(key: string): Promise { + try { + const res = await this.client.send(new GetObjectCommand({ Bucket: this.bucket, Key: this.buildKey(key) })) + if (!res.Body || !(res.Body instanceof Readable)) { + throw new Error('Empty body received from S3') + } + return await streamToBuffer(res.Body as Readable) + } catch (error) { + Logger.error('[S3Storage] Error getting object:', error) + throw error + } + } + + async deleteFile(key: string) { + try { + const keyWithRoot = this.buildKey(key) + const variations = new Set([keyWithRoot, key.replace(/^\//, '')]) + for (const k of variations) { + try { + await this.client.send(new DeleteObjectCommand({ Bucket: this.bucket, Key: k })) + } catch { + // 忽略删除失败 + } + } + } catch (error) { + Logger.error('[S3Storage] Error deleting object:', error) + throw error + } + } + + /** + * 列举指定前缀下的对象,默认列举全部。 + */ + async listFiles(prefix = ''): Promise> { + const files: Array<{ key: string; lastModified?: string; size: number }> = [] + let continuationToken: string | undefined + const fullPrefix = this.buildKey(prefix) + + try { + do { + const res = await this.client.send( + new ListObjectsV2Command({ + Bucket: this.bucket, + Prefix: fullPrefix === '' ? undefined : fullPrefix, + ContinuationToken: continuationToken + }) + ) + + res.Contents?.forEach((obj) => { + if (!obj.Key) return + files.push({ + key: obj.Key, + lastModified: obj.LastModified?.toISOString(), + size: obj.Size ?? 0 + }) + }) + + continuationToken = res.IsTruncated ? res.NextContinuationToken : undefined + } while (continuationToken) + + return files + } catch (error) { + Logger.error('[S3Storage] Error listing objects:', error) + throw error + } + } + + /** + * 尝试调用 HeadBucket 判断凭证/网络是否可用 + */ + async checkConnection() { + try { + await this.client.send(new HeadBucketCommand({ Bucket: this.bucket })) + return true + } catch (error) { + Logger.error('[S3Storage] Error checking connection:', error) + throw error + } + } +} diff --git a/src/preload/index.ts b/src/preload/index.ts index 533263512d..ea081645b2 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -10,6 +10,7 @@ import { KnowledgeItem, MCPServer, Provider, + S3Config, Shortcut, ThemeMode, WebDavConfig @@ -72,9 +73,9 @@ const api = { decompress: (text: Buffer) => ipcRenderer.invoke(IpcChannel.Zip_Decompress, text) }, backup: { - backup: (fileName: string, data: string, destinationPath?: string, skipBackupFile?: boolean) => - ipcRenderer.invoke(IpcChannel.Backup_Backup, fileName, data, destinationPath, skipBackupFile), - restore: (backupPath: string) => ipcRenderer.invoke(IpcChannel.Backup_Restore, backupPath), + backup: (filename: string, content: string, path: string, skipBackupFile: boolean) => + ipcRenderer.invoke(IpcChannel.Backup_Backup, filename, content, path, skipBackupFile), + restore: (path: string) => ipcRenderer.invoke(IpcChannel.Backup_Restore, path), backupToWebdav: (data: string, webdavConfig: WebDavConfig) => ipcRenderer.invoke(IpcChannel.Backup_BackupToWebdav, data, webdavConfig), restoreFromWebdav: (webdavConfig: WebDavConfig) => @@ -86,7 +87,16 @@ const api = { createDirectory: (webdavConfig: WebDavConfig, path: string, options?: CreateDirectoryOptions) => ipcRenderer.invoke(IpcChannel.Backup_CreateDirectory, webdavConfig, path, options), deleteWebdavFile: (fileName: string, webdavConfig: WebDavConfig) => - ipcRenderer.invoke(IpcChannel.Backup_DeleteWebdavFile, fileName, webdavConfig) + ipcRenderer.invoke(IpcChannel.Backup_DeleteWebdavFile, fileName, webdavConfig), + checkWebdavConnection: (webdavConfig: WebDavConfig) => + ipcRenderer.invoke(IpcChannel.Backup_CheckConnection, webdavConfig), + + backupToS3: (data: string, s3Config: S3Config) => ipcRenderer.invoke(IpcChannel.Backup_BackupToS3, data, s3Config), + restoreFromS3: (s3Config: S3Config) => ipcRenderer.invoke(IpcChannel.Backup_RestoreFromS3, s3Config), + listS3Files: (s3Config: S3Config) => ipcRenderer.invoke(IpcChannel.Backup_ListS3Files, s3Config), + deleteS3File: (fileName: string, s3Config: S3Config) => + ipcRenderer.invoke(IpcChannel.Backup_DeleteS3File, fileName, s3Config), + checkS3Connection: (s3Config: S3Config) => ipcRenderer.invoke(IpcChannel.Backup_CheckS3Connection, s3Config) }, file: { select: (options?: OpenDialogOptions) => ipcRenderer.invoke(IpcChannel.File_Select, options), diff --git a/src/renderer/src/components/S3BackupManager.tsx b/src/renderer/src/components/S3BackupManager.tsx new file mode 100644 index 0000000000..f644d2dce6 --- /dev/null +++ b/src/renderer/src/components/S3BackupManager.tsx @@ -0,0 +1,295 @@ +import { DeleteOutlined, ExclamationCircleOutlined, ReloadOutlined } from '@ant-design/icons' +import { restoreFromS3 } from '@renderer/services/BackupService' +import type { S3Config } from '@renderer/types' +import { formatFileSize } from '@renderer/utils' +import { Button, Modal, Table, Tooltip } from 'antd' +import dayjs from 'dayjs' +import { useCallback, useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' + +interface BackupFile { + fileName: string + modifiedTime: string + size: number +} + +interface S3BackupManagerProps { + visible: boolean + onClose: () => void + s3Config: Partial + restoreMethod?: (fileName: string) => Promise +} + +export function S3BackupManager({ visible, onClose, s3Config, restoreMethod }: S3BackupManagerProps) { + const [backupFiles, setBackupFiles] = useState([]) + const [loading, setLoading] = useState(false) + const [selectedRowKeys, setSelectedRowKeys] = useState([]) + const [deleting, setDeleting] = useState(false) + const [restoring, setRestoring] = useState(false) + const [pagination, setPagination] = useState({ + current: 1, + pageSize: 5, + total: 0 + }) + const { t } = useTranslation() + + const { endpoint, region, bucket, accessKeyId, secretAccessKey } = s3Config + + const fetchBackupFiles = useCallback(async () => { + if (!endpoint || !region || !bucket || !accessKeyId || !secretAccessKey) { + window.message.error(t('settings.data.s3.manager.config.incomplete')) + return + } + + setLoading(true) + try { + const files = await window.api.backup.listS3Files({ + ...s3Config, + endpoint, + region, + bucket, + accessKeyId, + secretAccessKey, + skipBackupFile: false, + autoSync: false, + syncInterval: 0, + maxBackups: 0 + }) + setBackupFiles(files) + setPagination((prev) => ({ + ...prev, + total: files.length + })) + } catch (error: any) { + window.message.error(t('settings.data.s3.manager.files.fetch.error', { message: error.message })) + } finally { + setLoading(false) + } + }, [endpoint, region, bucket, accessKeyId, secretAccessKey, t, s3Config]) + + useEffect(() => { + if (visible) { + fetchBackupFiles() + setSelectedRowKeys([]) + setPagination((prev) => ({ + ...prev, + current: 1 + })) + } + }, [visible, fetchBackupFiles]) + + const handleTableChange = (pagination: any) => { + setPagination(pagination) + } + + const handleDeleteSelected = async () => { + if (selectedRowKeys.length === 0) { + window.message.warning(t('settings.data.s3.manager.select.warning')) + return + } + + if (!endpoint || !region || !bucket || !accessKeyId || !secretAccessKey) { + window.message.error(t('settings.data.s3.manager.config.incomplete')) + return + } + + window.modal.confirm({ + title: t('settings.data.s3.manager.delete.confirm.title'), + icon: , + content: t('settings.data.s3.manager.delete.confirm.multiple', { count: selectedRowKeys.length }), + okText: t('settings.data.s3.manager.delete.confirm.title'), + cancelText: t('common.cancel'), + centered: true, + onOk: async () => { + setDeleting(true) + try { + // 依次删除选中的文件 + for (const key of selectedRowKeys) { + await window.api.backup.deleteS3File(key.toString(), { + ...s3Config, + endpoint, + region, + bucket, + accessKeyId, + secretAccessKey, + skipBackupFile: false, + autoSync: false, + syncInterval: 0, + maxBackups: 0 + }) + } + window.message.success( + t('settings.data.s3.manager.delete.success.multiple', { count: selectedRowKeys.length }) + ) + setSelectedRowKeys([]) + await fetchBackupFiles() + } catch (error: any) { + window.message.error(t('settings.data.s3.manager.delete.error', { message: error.message })) + } finally { + setDeleting(false) + } + } + }) + } + + const handleDeleteSingle = async (fileName: string) => { + if (!endpoint || !region || !bucket || !accessKeyId || !secretAccessKey) { + window.message.error(t('settings.data.s3.manager.config.incomplete')) + return + } + + window.modal.confirm({ + title: t('settings.data.s3.manager.delete.confirm.title'), + icon: , + content: t('settings.data.s3.manager.delete.confirm.single', { fileName }), + okText: t('settings.data.s3.manager.delete.confirm.title'), + cancelText: t('common.cancel'), + centered: true, + onOk: async () => { + setDeleting(true) + try { + await window.api.backup.deleteS3File(fileName, { + ...s3Config, + endpoint, + region, + bucket, + accessKeyId, + secretAccessKey, + skipBackupFile: false, + autoSync: false, + syncInterval: 0, + maxBackups: 0 + }) + window.message.success(t('settings.data.s3.manager.delete.success.single')) + await fetchBackupFiles() + } catch (error: any) { + window.message.error(t('settings.data.s3.manager.delete.error', { message: error.message })) + } finally { + setDeleting(false) + } + } + }) + } + + const handleRestore = async (fileName: string) => { + if (!endpoint || !region || !bucket || !accessKeyId || !secretAccessKey) { + window.message.error(t('settings.data.s3.manager.config.incomplete')) + return + } + + window.modal.confirm({ + title: t('settings.data.s3.restore.confirm.title'), + icon: , + content: t('settings.data.s3.restore.confirm.content'), + okText: t('settings.data.s3.restore.confirm.ok'), + cancelText: t('settings.data.s3.restore.confirm.cancel'), + centered: true, + onOk: async () => { + setRestoring(true) + try { + await (restoreMethod || restoreFromS3)(fileName) + window.message.success(t('settings.data.s3.restore.success')) + onClose() // 关闭模态框 + } catch (error: any) { + window.message.error(t('settings.data.s3.restore.error', { message: error.message })) + } finally { + setRestoring(false) + } + } + }) + } + + const columns = [ + { + title: t('settings.data.s3.manager.columns.fileName'), + dataIndex: 'fileName', + key: 'fileName', + ellipsis: { + showTitle: false + }, + render: (fileName: string) => ( + + {fileName} + + ) + }, + { + title: t('settings.data.s3.manager.columns.modifiedTime'), + dataIndex: 'modifiedTime', + key: 'modifiedTime', + width: 180, + render: (time: string) => dayjs(time).format('YYYY-MM-DD HH:mm:ss') + }, + { + title: t('settings.data.s3.manager.columns.size'), + dataIndex: 'size', + key: 'size', + width: 120, + render: (size: number) => formatFileSize(size) + }, + { + title: t('settings.data.s3.manager.columns.actions'), + key: 'action', + width: 160, + render: (_: any, record: BackupFile) => ( + <> + + + + ) + } + ] + + const rowSelection = { + selectedRowKeys, + onChange: (selectedRowKeys: React.Key[]) => { + setSelectedRowKeys(selectedRowKeys) + } + } + + return ( + } onClick={fetchBackupFiles} disabled={loading}> + {t('settings.data.s3.manager.refresh')} + , + , + + ]}> + + + ) +} diff --git a/src/renderer/src/components/S3Modals.tsx b/src/renderer/src/components/S3Modals.tsx new file mode 100644 index 0000000000..75c8b31b3a --- /dev/null +++ b/src/renderer/src/components/S3Modals.tsx @@ -0,0 +1,265 @@ +import { backupToS3 } from '@renderer/services/BackupService' +import { formatFileSize } from '@renderer/utils' +import { Input, Modal, Select, Spin } from 'antd' +import dayjs from 'dayjs' +import { useCallback, useState } from 'react' +import { useTranslation } from 'react-i18next' + +interface BackupFile { + fileName: string + modifiedTime: string + size: number +} + +export function useS3BackupModal() { + const [customFileName, setCustomFileName] = useState('') + const [isModalVisible, setIsModalVisible] = useState(false) + const [backuping, setBackuping] = useState(false) + + const handleBackup = async () => { + setBackuping(true) + try { + await backupToS3({ customFileName, showMessage: true }) + } finally { + setBackuping(false) + setIsModalVisible(false) + } + } + + const handleCancel = () => { + setIsModalVisible(false) + } + + const showBackupModal = useCallback(async () => { + // 获取默认文件名 + const deviceType = await window.api.system.getDeviceType() + const hostname = await window.api.system.getHostname() + const timestamp = dayjs().format('YYYYMMDDHHmmss') + const defaultFileName = `cherry-studio.${timestamp}.${hostname}.${deviceType}.zip` + setCustomFileName(defaultFileName) + setIsModalVisible(true) + }, []) + + return { + isModalVisible, + handleBackup, + handleCancel, + backuping, + customFileName, + setCustomFileName, + showBackupModal + } +} + +type S3BackupModalProps = { + isModalVisible: boolean + handleBackup: () => Promise + handleCancel: () => void + backuping: boolean + customFileName: string + setCustomFileName: (value: string) => void +} + +export function S3BackupModal({ + isModalVisible, + handleBackup, + handleCancel, + backuping, + customFileName, + setCustomFileName +}: S3BackupModalProps) { + const { t } = useTranslation() + + return ( + + setCustomFileName(e.target.value)} + placeholder={t('settings.data.s3.backup.modal.filename.placeholder')} + /> + + ) +} + +interface UseS3RestoreModalProps { + endpoint: string | undefined + region: string | undefined + bucket: string | undefined + accessKeyId: string | undefined + secretAccessKey: string | undefined + root?: string | undefined +} + +export function useS3RestoreModal({ + endpoint, + region, + bucket, + accessKeyId, + secretAccessKey, + root +}: UseS3RestoreModalProps) { + const [isRestoreModalVisible, setIsRestoreModalVisible] = useState(false) + const [restoring, setRestoring] = useState(false) + const [selectedFile, setSelectedFile] = useState(null) + const [loadingFiles, setLoadingFiles] = useState(false) + const [backupFiles, setBackupFiles] = useState([]) + const { t } = useTranslation() + + const showRestoreModal = useCallback(async () => { + if (!endpoint || !region || !bucket || !accessKeyId || !secretAccessKey) { + window.message.error({ content: t('settings.data.s3.manager.config.incomplete'), key: 's3-error' }) + return + } + + setIsRestoreModalVisible(true) + setLoadingFiles(true) + try { + const files = await window.api.backup.listS3Files({ + endpoint, + region, + bucket, + accessKeyId, + secretAccessKey, + root, + autoSync: false, + syncInterval: 0, + maxBackups: 0, + skipBackupFile: false + }) + setBackupFiles(files) + } catch (error: any) { + window.message.error({ + content: t('settings.data.s3.manager.files.fetch.error', { message: error.message }), + key: 'list-files-error' + }) + } finally { + setLoadingFiles(false) + } + }, [endpoint, region, bucket, accessKeyId, secretAccessKey, root, t]) + + const handleRestore = useCallback(async () => { + if (!selectedFile || !endpoint || !region || !bucket || !accessKeyId || !secretAccessKey) { + window.message.error({ + content: !selectedFile + ? t('settings.data.s3.restore.file.required') + : t('settings.data.s3.restore.config.incomplete'), + key: 'restore-error' + }) + return + } + + window.modal.confirm({ + title: t('settings.data.s3.restore.confirm.title'), + content: t('settings.data.s3.restore.confirm.content', { fileName: selectedFile }), + okText: t('settings.data.s3.restore.confirm.ok'), + cancelText: t('settings.data.s3.restore.confirm.cancel'), + centered: true, + onOk: async () => { + setRestoring(true) + try { + await window.api.backup.restoreFromS3({ + endpoint, + region, + bucket, + accessKeyId, + secretAccessKey, + root, + fileName: selectedFile, + autoSync: false, + syncInterval: 0, + maxBackups: 0, + skipBackupFile: false + }) + window.message.success({ content: t('message.restore.success'), key: 's3-restore' }) + setIsRestoreModalVisible(false) + } catch (error: any) { + window.message.error({ + content: t('settings.data.s3.restore.error', { message: error.message }), + key: 'restore-error' + }) + } finally { + setRestoring(false) + } + } + }) + }, [selectedFile, endpoint, region, bucket, accessKeyId, secretAccessKey, root, t]) + + const handleCancel = () => { + setIsRestoreModalVisible(false) + } + + return { + isRestoreModalVisible, + handleRestore, + handleCancel, + restoring, + selectedFile, + setSelectedFile, + loadingFiles, + backupFiles, + showRestoreModal + } +} + +type S3RestoreModalProps = ReturnType + +export function S3RestoreModal({ + isRestoreModalVisible, + handleRestore, + handleCancel, + restoring, + selectedFile, + setSelectedFile, + loadingFiles, + backupFiles +}: S3RestoreModalProps) { + const { t } = useTranslation() + + return ( + +
+ setEndpoint(e.target.value)} + style={{ width: 250 }} + type="url" + onBlur={() => dispatch(setS3Partial({ endpoint: endpoint || '' }))} + /> + + + + {t('settings.data.s3.region')} + setRegion(e.target.value)} + style={{ width: 250 }} + onBlur={() => dispatch(setS3Partial({ region: region || '' }))} + /> + + + + {t('settings.data.s3.bucket')} + setBucket(e.target.value)} + style={{ width: 250 }} + onBlur={() => dispatch(setS3Partial({ bucket: bucket || '' }))} + /> + + + + {t('settings.data.s3.accessKeyId')} + setAccessKeyId(e.target.value)} + style={{ width: 250 }} + onBlur={() => dispatch(setS3Partial({ accessKeyId: accessKeyId || '' }))} + /> + + + + {t('settings.data.s3.secretAccessKey')} + setSecretAccessKey(e.target.value)} + style={{ width: 250 }} + onBlur={() => dispatch(setS3Partial({ secretAccessKey: secretAccessKey || '' }))} + /> + + + + {t('settings.data.s3.root')} + setRoot(e.target.value)} + style={{ width: 250 }} + onBlur={() => dispatch(setS3Partial({ root: root || '' }))} + /> + + + + {t('settings.data.s3.backup.operation')} + + + + + + + + {t('settings.data.s3.autoSync')} + + + + + {t('settings.data.s3.maxBackups')} + + + + + {t('settings.data.s3.skipBackupFile')} + + + + {t('settings.data.s3.skipBackupFile.help')} + + {syncInterval > 0 && ( + <> + + + {t('settings.data.s3.syncStatus')} + {renderSyncStatus()} + + + )} + <> + + + + + + ) +} + +export default S3Settings diff --git a/src/renderer/src/services/BackupService.ts b/src/renderer/src/services/BackupService.ts index 3d78b2752a..00a09acf54 100644 --- a/src/renderer/src/services/BackupService.ts +++ b/src/renderer/src/services/BackupService.ts @@ -4,11 +4,63 @@ import { upgradeToV7 } from '@renderer/databases/upgrades' import i18n from '@renderer/i18n' import store from '@renderer/store' import { setWebDAVSyncState } from '@renderer/store/backup' +import { setS3SyncState } from '@renderer/store/backup' +import { S3Config, WebDavConfig } from '@renderer/types' import { uuid } from '@renderer/utils' import dayjs from 'dayjs' import { NotificationService } from './NotificationService' +// 重试删除S3文件的辅助函数 +async function deleteS3FileWithRetry(fileName: string, s3Config: S3Config, maxRetries = 3) { + let lastError: Error | null = null + + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + await window.api.backup.deleteS3File(fileName, s3Config) + Logger.log(`[Backup] Successfully deleted old backup file: ${fileName} (attempt ${attempt})`) + return true + } catch (error: any) { + lastError = error + Logger.warn(`[Backup] Delete attempt ${attempt}/${maxRetries} failed for ${fileName}:`, error.message) + + // 如果不是最后一次尝试,等待一段时间再重试 + if (attempt < maxRetries) { + const delay = attempt * 1000 + Math.random() * 1000 // 1-2秒的随机延迟 + await new Promise((resolve) => setTimeout(resolve, delay)) + } + } + } + + Logger.error(`[Backup] Failed to delete old backup file after ${maxRetries} attempts: ${fileName}`, lastError) + return false +} + +// 重试删除WebDAV文件的辅助函数 +async function deleteWebdavFileWithRetry(fileName: string, webdavConfig: WebDavConfig, maxRetries = 3) { + let lastError: Error | null = null + + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + await window.api.backup.deleteWebdavFile(fileName, webdavConfig) + Logger.log(`[Backup] Successfully deleted old backup file: ${fileName} (attempt ${attempt})`) + return true + } catch (error: any) { + lastError = error + Logger.warn(`[Backup] Delete attempt ${attempt}/${maxRetries} failed for ${fileName}:`, error.message) + + // 如果不是最后一次尝试,等待一段时间再重试 + if (attempt < maxRetries) { + const delay = attempt * 1000 + Math.random() * 1000 // 1-2秒的随机延迟 + await new Promise((resolve) => setTimeout(resolve, delay)) + } + } + } + + Logger.error(`[Backup] Failed to delete old backup file after ${maxRetries} attempts: ${fileName}`, lastError) + return false +} + export async function backup(skipBackupFile: boolean) { const filename = `cherry-studio.${dayjs().format('YYYYMMDDHHmm')}.zip` const fileContnet = await getBackupData() @@ -161,17 +213,21 @@ export async function backupToWebdav({ // 文件已按修改时间降序排序,所以最旧的文件在末尾 const filesToDelete = currentDeviceFiles.slice(webdavMaxBackups) - for (const file of filesToDelete) { - try { - await window.api.backup.deleteWebdavFile(file.fileName, { - webdavHost, - webdavUser, - webdavPass, - webdavPath - }) - Logger.log(`[Backup] Deleted old backup file: ${file.fileName}`) - } catch (error) { - Logger.error(`[Backup] Failed to delete old backup file: ${file.fileName}`, error) + Logger.log(`[Backup] Cleaning up ${filesToDelete.length} old backup files`) + + // 串行删除文件,避免并发请求导致的问题 + for (let i = 0; i < filesToDelete.length; i++) { + const file = filesToDelete[i] + await deleteWebdavFileWithRetry(file.fileName, { + webdavHost, + webdavUser, + webdavPass, + webdavPath + }) + + // 在删除操作之间添加短暂延迟,避免请求过于频繁 + if (i < filesToDelete.length - 1) { + await new Promise((resolve) => setTimeout(resolve, 500)) } } } @@ -242,6 +298,160 @@ export async function restoreFromWebdav(fileName?: string) { } } +export async function backupToS3({ + showMessage = false, + customFileName = '', + autoBackupProcess = false +}: { showMessage?: boolean; customFileName?: string; autoBackupProcess?: boolean } = {}) { + const notificationService = NotificationService.getInstance() + if (isManualBackupRunning) { + Logger.log('[Backup] Manual backup already in progress') + return + } + + if (autoBackupProcess) { + showMessage = false + } + + isManualBackupRunning = true + + store.dispatch(setS3SyncState({ syncing: true, lastSyncError: null })) + + const s3Config = store.getState().settings.s3 + let deviceType = 'unknown' + let hostname = 'unknown' + try { + deviceType = (await window.api.system.getDeviceType()) || 'unknown' + hostname = (await window.api.system.getHostname()) || 'unknown' + } catch (error) { + Logger.error('[Backup] Failed to get device type or hostname:', error) + } + const timestamp = dayjs().format('YYYYMMDDHHmmss') + const backupFileName = customFileName || `cherry-studio.${timestamp}.${hostname}.${deviceType}.zip` + const finalFileName = backupFileName.endsWith('.zip') ? backupFileName : `${backupFileName}.zip` + const backupData = await getBackupData() + + try { + const success = await window.api.backup.backupToS3(backupData, { + ...s3Config, + fileName: finalFileName + }) + + if (success) { + store.dispatch( + setS3SyncState({ + lastSyncError: null, + syncing: false, + lastSyncTime: Date.now() + }) + ) + notificationService.send({ + id: uuid(), + type: 'success', + title: i18n.t('common.success'), + message: i18n.t('message.backup.success'), + silent: false, + timestamp: Date.now(), + source: 'backup' + }) + showMessage && window.message.success({ content: i18n.t('message.backup.success'), key: 'backup' }) + + // 清理旧备份文件 + if (s3Config.maxBackups > 0) { + try { + // 获取所有备份文件 + const files = await window.api.backup.listS3Files(s3Config) + + // 筛选当前设备的备份文件 + const currentDeviceFiles = files.filter((file) => { + return file.fileName.includes(deviceType) && file.fileName.includes(hostname) + }) + + // 如果当前设备的备份文件数量超过最大保留数量,删除最旧的文件 + if (currentDeviceFiles.length > s3Config.maxBackups) { + const filesToDelete = currentDeviceFiles.slice(s3Config.maxBackups) + + Logger.log(`[Backup] Cleaning up ${filesToDelete.length} old backup files`) + + for (let i = 0; i < filesToDelete.length; i++) { + const file = filesToDelete[i] + await deleteS3FileWithRetry(file.fileName, s3Config) + + if (i < filesToDelete.length - 1) { + await new Promise((resolve) => setTimeout(resolve, 500)) + } + } + } + } catch (error) { + Logger.error('[Backup] Failed to clean up old backup files:', error) + } + } + } else { + if (autoBackupProcess) { + throw new Error(i18n.t('message.backup.failed')) + } + + store.dispatch(setS3SyncState({ lastSyncError: 'Backup failed' })) + showMessage && window.message.error({ content: i18n.t('message.backup.failed'), key: 'backup' }) + } + } catch (error: any) { + if (autoBackupProcess) { + throw error + } + notificationService.send({ + id: uuid(), + type: 'error', + title: i18n.t('message.backup.failed'), + message: error.message, + silent: false, + timestamp: Date.now(), + source: 'backup' + }) + store.dispatch(setS3SyncState({ lastSyncError: error.message })) + console.error('[Backup] backupToS3: Error uploading file to S3:', error) + showMessage && window.message.error({ content: i18n.t('message.backup.failed'), key: 'backup' }) + throw error + } finally { + if (!autoBackupProcess) { + store.dispatch( + setS3SyncState({ + lastSyncTime: Date.now(), + syncing: false + }) + ) + } + isManualBackupRunning = false + } +} + +// 从 S3 恢复 +export async function restoreFromS3(fileName?: string) { + const s3Config = store.getState().settings.s3 + + if (!fileName) { + const files = await window.api.backup.listS3Files(s3Config) + if (files.length > 0) { + fileName = files[0].fileName + } + } + + if (fileName) { + const restoreData = await window.api.backup.restoreFromS3({ + ...s3Config, + fileName + }) + const data = JSON.parse(restoreData) + await handleData(data) + store.dispatch( + setS3SyncState({ + lastSyncTime: Date.now(), + syncing: false, + lastSyncError: null + }) + ) + } +} + let autoSyncStarted = false let syncTimeout: NodeJS.Timeout | null = null let isAutoBackupRunning = false @@ -252,9 +462,18 @@ export function startAutoSync(immediate = false) { return } - const { webdavAutoSync, webdavHost } = store.getState().settings + const settings = store.getState().settings + const { webdavAutoSync, webdavHost } = settings + const s3Settings = settings.s3 - if (!webdavAutoSync || !webdavHost) { + const s3AutoSync = s3Settings?.autoSync + const s3Endpoint = s3Settings?.endpoint + + // 检查WebDAV或S3自动同步配置 + const hasWebdavConfig = webdavAutoSync && webdavHost + const hasS3Config = s3AutoSync && s3Endpoint + + if (!hasWebdavConfig && !hasS3Config) { Logger.log('[AutoSync] Invalid sync settings, auto sync disabled') return } @@ -277,22 +496,28 @@ export function startAutoSync(immediate = false) { syncTimeout = null } - const { webdavSyncInterval } = store.getState().settings - const { webdavSync } = store.getState().backup + const settings = store.getState().settings + const _webdavSyncInterval = settings.webdavSyncInterval + const _s3SyncInterval = settings.s3?.syncInterval + const { webdavSync, s3Sync } = store.getState().backup - if (webdavSyncInterval <= 0) { + // 使用当前激活的同步配置 + const syncInterval = hasWebdavConfig ? _webdavSyncInterval : _s3SyncInterval + const lastSyncTime = hasWebdavConfig ? webdavSync?.lastSyncTime : s3Sync?.lastSyncTime + + if (!syncInterval || syncInterval <= 0) { Logger.log('[AutoSync] Invalid sync interval, auto sync disabled') stopAutoSync() return } // 用户指定的自动备份时间间隔(毫秒) - const requiredInterval = webdavSyncInterval * 60 * 1000 + const requiredInterval = syncInterval * 60 * 1000 let timeUntilNextSync = 1000 //also immediate switch (type) { - case 'fromLastSyncTime': // 如果存在最后一次同步WebDAV的时间,以它为参考计算下一次同步的时间 - timeUntilNextSync = Math.max(1000, (webdavSync?.lastSyncTime || 0) + requiredInterval - Date.now()) + case 'fromLastSyncTime': // 如果存在最后一次同步的时间,以它为参考计算下一次同步的时间 + timeUntilNextSync = Math.max(1000, (lastSyncTime || 0) + requiredInterval - Date.now()) break case 'fromNow': timeUntilNextSync = requiredInterval @@ -301,8 +526,9 @@ export function startAutoSync(immediate = false) { syncTimeout = setTimeout(performAutoBackup, timeUntilNextSync) + const backupType = hasWebdavConfig ? 'WebDAV' : 'S3' Logger.log( - `[AutoSync] Next sync scheduled in ${Math.floor(timeUntilNextSync / 1000 / 60)} minutes ${Math.floor( + `[AutoSync] Next ${backupType} sync scheduled in ${Math.floor(timeUntilNextSync / 1000 / 60)} minutes ${Math.floor( (timeUntilNextSync / 1000) % 60 )} seconds` ) @@ -321,17 +547,28 @@ export function startAutoSync(immediate = false) { while (retryCount < maxRetries) { try { - Logger.log(`[AutoSync] Starting auto backup... (attempt ${retryCount + 1}/${maxRetries})`) + const backupType = hasWebdavConfig ? 'WebDAV' : 'S3' + Logger.log(`[AutoSync] Starting auto ${backupType} backup... (attempt ${retryCount + 1}/${maxRetries})`) - await backupToWebdav({ autoBackupProcess: true }) - - store.dispatch( - setWebDAVSyncState({ - lastSyncError: null, - lastSyncTime: Date.now(), - syncing: false - }) - ) + if (hasWebdavConfig) { + await backupToWebdav({ autoBackupProcess: true }) + store.dispatch( + setWebDAVSyncState({ + lastSyncError: null, + lastSyncTime: Date.now(), + syncing: false + }) + ) + } else if (hasS3Config) { + await backupToS3({ autoBackupProcess: true }) + store.dispatch( + setS3SyncState({ + lastSyncError: null, + lastSyncTime: Date.now(), + syncing: false + }) + ) + } isAutoBackupRunning = false scheduleNextBackup() @@ -340,20 +577,31 @@ export function startAutoSync(immediate = false) { } catch (error: any) { retryCount++ if (retryCount === maxRetries) { - Logger.error('[AutoSync] Auto backup failed after all retries:', error) + const backupType = hasWebdavConfig ? 'WebDAV' : 'S3' + Logger.error(`[AutoSync] Auto ${backupType} backup failed after all retries:`, error) - store.dispatch( - setWebDAVSyncState({ - lastSyncError: 'Auto backup failed', - lastSyncTime: Date.now(), - syncing: false - }) - ) + if (hasWebdavConfig) { + store.dispatch( + setWebDAVSyncState({ + lastSyncError: 'Auto backup failed', + lastSyncTime: Date.now(), + syncing: false + }) + ) + } else if (hasS3Config) { + store.dispatch( + setS3SyncState({ + lastSyncError: 'Auto backup failed', + lastSyncTime: Date.now(), + syncing: false + }) + ) + } //only show 1 time error modal, and autoback stopped until user click ok await window.modal.error({ title: i18n.t('message.backup.failed'), - content: `[WebDAV Auto Backup] ${new Date().toLocaleString()} ` + error.message + content: `[${backupType} Auto Backup] ${new Date().toLocaleString()} ` + error.message }) scheduleNextBackup('fromNow') diff --git a/src/renderer/src/services/NutstoreService.ts b/src/renderer/src/services/NutstoreService.ts index c52e6b8030..6eb727abc1 100644 --- a/src/renderer/src/services/NutstoreService.ts +++ b/src/renderer/src/services/NutstoreService.ts @@ -48,7 +48,7 @@ export async function checkConnection() { return false } - const isSuccess = await window.api.backup.checkConnection({ + const isSuccess = await window.api.backup.checkWebdavConnection({ ...config, webdavPath: '/' }) diff --git a/src/renderer/src/store/backup.ts b/src/renderer/src/store/backup.ts index a8b7d342c5..0418e5ab96 100644 --- a/src/renderer/src/store/backup.ts +++ b/src/renderer/src/store/backup.ts @@ -1,13 +1,14 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit' -export interface WebDAVSyncState { +export interface RemoteSyncState { lastSyncTime: number | null syncing: boolean lastSyncError: string | null } export interface BackupState { - webdavSync: WebDAVSyncState + webdavSync: RemoteSyncState + s3Sync: RemoteSyncState } const initialState: BackupState = { @@ -15,6 +16,11 @@ const initialState: BackupState = { lastSyncTime: null, syncing: false, lastSyncError: null + }, + s3Sync: { + lastSyncTime: null, + syncing: false, + lastSyncError: null } } @@ -22,11 +28,14 @@ const backupSlice = createSlice({ name: 'backup', initialState, reducers: { - setWebDAVSyncState: (state, action: PayloadAction>) => { + setWebDAVSyncState: (state, action: PayloadAction>) => { state.webdavSync = { ...state.webdavSync, ...action.payload } + }, + setS3SyncState: (state, action: PayloadAction>) => { + state.s3Sync = { ...state.s3Sync, ...action.payload } } } }) -export const { setWebDAVSyncState } = backupSlice.actions +export const { setWebDAVSyncState, setS3SyncState } = backupSlice.actions export default backupSlice.reducer diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts index 1e29aebd69..a305d03735 100644 --- a/src/renderer/src/store/migrate.ts +++ b/src/renderer/src/store/migrate.ts @@ -1726,6 +1726,16 @@ const migrateConfig = { } catch (error) { return state } + }, + '120': (state: RootState) => { + try { + if (!state.settings.s3) { + state.settings.s3 = settingsInitialState.s3 + } + return state + } catch (error) { + return state + } } } diff --git a/src/renderer/src/store/nutstore.ts b/src/renderer/src/store/nutstore.ts index 354a93bd39..cd4721b6df 100644 --- a/src/renderer/src/store/nutstore.ts +++ b/src/renderer/src/store/nutstore.ts @@ -1,8 +1,8 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit' -import { WebDAVSyncState } from './backup' +import { RemoteSyncState } from './backup' -export interface NutstoreSyncState extends WebDAVSyncState {} +export interface NutstoreSyncState extends RemoteSyncState {} export interface NutstoreState { nutstoreToken: string | null @@ -42,7 +42,7 @@ const nutstoreSlice = createSlice({ setNutstoreSyncInterval: (state, action: PayloadAction) => { state.nutstoreSyncInterval = action.payload }, - setNutstoreSyncState: (state, action: PayloadAction>) => { + setNutstoreSyncState: (state, action: PayloadAction>) => { state.nutstoreSyncState = { ...state.nutstoreSyncState, ...action.payload } }, setNutstoreSkipBackupFile: (state, action: PayloadAction) => { diff --git a/src/renderer/src/store/settings.ts b/src/renderer/src/store/settings.ts index 778837e388..771e7cf349 100644 --- a/src/renderer/src/store/settings.ts +++ b/src/renderer/src/store/settings.ts @@ -8,13 +8,14 @@ import { OpenAIServiceTier, OpenAISummaryText, PaintingProvider, + S3Config, ThemeMode, TranslateLanguageVarious } from '@renderer/types' import { uuid } from '@renderer/utils' import { UpgradeChannel } from '@shared/config/constant' -import { WebDAVSyncState } from './backup' +import { RemoteSyncState } from './backup' export type SendMessageShortcut = 'Enter' | 'Shift+Enter' | 'Ctrl+Enter' | 'Command+Enter' | 'Alt+Enter' @@ -30,7 +31,7 @@ export const DEFAULT_SIDEBAR_ICONS: SidebarIcon[] = [ 'files' ] -export interface NutstoreSyncRuntime extends WebDAVSyncState {} +export interface NutstoreSyncRuntime extends RemoteSyncState {} export type AssistantIconType = 'model' | 'emoji' | 'none' @@ -189,6 +190,7 @@ export interface SettingsState { knowledge: boolean } defaultPaintingProvider: PaintingProvider + s3: S3Config } export type MultiModelMessageStyle = 'horizontal' | 'vertical' | 'fold' | 'grid' @@ -336,7 +338,19 @@ export const initialState: SettingsState = { backup: false, knowledge: false }, - defaultPaintingProvider: 'aihubmix' + defaultPaintingProvider: 'aihubmix', + s3: { + endpoint: '', + region: '', + bucket: '', + accessKeyId: '', + secretAccessKey: '', + root: '', + autoSync: false, + syncInterval: 0, + maxBackups: 0, + skipBackupFile: false + } } const settingsSlice = createSlice({ @@ -703,6 +717,12 @@ const settingsSlice = createSlice({ }, setDefaultPaintingProvider: (state, action: PayloadAction) => { state.defaultPaintingProvider = action.payload + }, + setS3: (state, action: PayloadAction) => { + state.s3 = action.payload + }, + setS3Partial: (state, action: PayloadAction>) => { + state.s3 = { ...state.s3, ...action.payload } } } }) @@ -812,7 +832,9 @@ export const { setOpenAISummaryText, setOpenAIServiceTier, setNotificationSettings, - setDefaultPaintingProvider + setDefaultPaintingProvider, + setS3, + setS3Partial } = settingsSlice.actions export default settingsSlice.reducer diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index 084cc130e2..21eb4bbc99 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -749,4 +749,19 @@ export interface StoreSyncAction { export type OpenAISummaryText = 'auto' | 'concise' | 'detailed' | 'off' export type OpenAIServiceTier = 'auto' | 'default' | 'flex' + +export type S3Config = { + endpoint: string + region: string + bucket: string + accessKeyId: string + secretAccessKey: string + root?: string + fileName?: string + skipBackupFile: boolean + autoSync: boolean + syncInterval: number + maxBackups: number +} + export type { Message } from './newMessage' diff --git a/yarn.lock b/yarn.lock index 5496f84dee..7afa0defc2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -228,6 +228,650 @@ __metadata: languageName: node linkType: hard +"@aws-crypto/crc32@npm:5.2.0": + version: 5.2.0 + resolution: "@aws-crypto/crc32@npm:5.2.0" + dependencies: + "@aws-crypto/util": "npm:^5.2.0" + "@aws-sdk/types": "npm:^3.222.0" + tslib: "npm:^2.6.2" + checksum: 10c0/eab9581d3363af5ea498ae0e72de792f54d8890360e14a9d8261b7b5c55ebe080279fb2556e07994d785341cdaa99ab0b1ccf137832b53b5904cd6928f2b094b + languageName: node + linkType: hard + +"@aws-crypto/crc32c@npm:5.2.0": + version: 5.2.0 + resolution: "@aws-crypto/crc32c@npm:5.2.0" + dependencies: + "@aws-crypto/util": "npm:^5.2.0" + "@aws-sdk/types": "npm:^3.222.0" + tslib: "npm:^2.6.2" + checksum: 10c0/223efac396cdebaf5645568fa9a38cd0c322c960ae1f4276bedfe2e1031d0112e49d7d39225d386354680ecefae29f39af469a84b2ddfa77cb6692036188af77 + languageName: node + linkType: hard + +"@aws-crypto/sha1-browser@npm:5.2.0": + version: 5.2.0 + resolution: "@aws-crypto/sha1-browser@npm:5.2.0" + dependencies: + "@aws-crypto/supports-web-crypto": "npm:^5.2.0" + "@aws-crypto/util": "npm:^5.2.0" + "@aws-sdk/types": "npm:^3.222.0" + "@aws-sdk/util-locate-window": "npm:^3.0.0" + "@smithy/util-utf8": "npm:^2.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/51fed0bf078c10322d910af179871b7d299dde5b5897873ffbeeb036f427e5d11d23db9794439226544b73901920fd19f4d86bbc103ed73cc0cfdea47a83c6ac + languageName: node + linkType: hard + +"@aws-crypto/sha256-browser@npm:5.2.0": + version: 5.2.0 + resolution: "@aws-crypto/sha256-browser@npm:5.2.0" + dependencies: + "@aws-crypto/sha256-js": "npm:^5.2.0" + "@aws-crypto/supports-web-crypto": "npm:^5.2.0" + "@aws-crypto/util": "npm:^5.2.0" + "@aws-sdk/types": "npm:^3.222.0" + "@aws-sdk/util-locate-window": "npm:^3.0.0" + "@smithy/util-utf8": "npm:^2.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/05f6d256794df800fe9aef5f52f2ac7415f7f3117d461f85a6aecaa4e29e91527b6fd503681a17136fa89e9dd3d916e9c7e4cfb5eba222875cb6c077bdc1d00d + languageName: node + linkType: hard + +"@aws-crypto/sha256-js@npm:5.2.0, @aws-crypto/sha256-js@npm:^5.2.0": + version: 5.2.0 + resolution: "@aws-crypto/sha256-js@npm:5.2.0" + dependencies: + "@aws-crypto/util": "npm:^5.2.0" + "@aws-sdk/types": "npm:^3.222.0" + tslib: "npm:^2.6.2" + checksum: 10c0/6c48701f8336341bb104dfde3d0050c89c288051f6b5e9bdfeb8091cf3ffc86efcd5c9e6ff2a4a134406b019c07aca9db608128f8d9267c952578a3108db9fd1 + languageName: node + linkType: hard + +"@aws-crypto/supports-web-crypto@npm:^5.2.0": + version: 5.2.0 + resolution: "@aws-crypto/supports-web-crypto@npm:5.2.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10c0/4d2118e29d68ca3f5947f1e37ce1fbb3239a0c569cc938cdc8ab8390d595609b5caf51a07c9e0535105b17bf5c52ea256fed705a07e9681118120ab64ee73af2 + languageName: node + linkType: hard + +"@aws-crypto/util@npm:5.2.0, @aws-crypto/util@npm:^5.2.0": + version: 5.2.0 + resolution: "@aws-crypto/util@npm:5.2.0" + dependencies: + "@aws-sdk/types": "npm:^3.222.0" + "@smithy/util-utf8": "npm:^2.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/0362d4c197b1fd64b423966945130207d1fe23e1bb2878a18e361f7743c8d339dad3f8729895a29aa34fff6a86c65f281cf5167c4bf253f21627ae80b6dd2951 + languageName: node + linkType: hard + +"@aws-sdk/client-s3@npm:^3.840.0": + version: 3.840.0 + resolution: "@aws-sdk/client-s3@npm:3.840.0" + dependencies: + "@aws-crypto/sha1-browser": "npm:5.2.0" + "@aws-crypto/sha256-browser": "npm:5.2.0" + "@aws-crypto/sha256-js": "npm:5.2.0" + "@aws-sdk/core": "npm:3.840.0" + "@aws-sdk/credential-provider-node": "npm:3.840.0" + "@aws-sdk/middleware-bucket-endpoint": "npm:3.840.0" + "@aws-sdk/middleware-expect-continue": "npm:3.840.0" + "@aws-sdk/middleware-flexible-checksums": "npm:3.840.0" + "@aws-sdk/middleware-host-header": "npm:3.840.0" + "@aws-sdk/middleware-location-constraint": "npm:3.840.0" + "@aws-sdk/middleware-logger": "npm:3.840.0" + "@aws-sdk/middleware-recursion-detection": "npm:3.840.0" + "@aws-sdk/middleware-sdk-s3": "npm:3.840.0" + "@aws-sdk/middleware-ssec": "npm:3.840.0" + "@aws-sdk/middleware-user-agent": "npm:3.840.0" + "@aws-sdk/region-config-resolver": "npm:3.840.0" + "@aws-sdk/signature-v4-multi-region": "npm:3.840.0" + "@aws-sdk/types": "npm:3.840.0" + "@aws-sdk/util-endpoints": "npm:3.840.0" + "@aws-sdk/util-user-agent-browser": "npm:3.840.0" + "@aws-sdk/util-user-agent-node": "npm:3.840.0" + "@aws-sdk/xml-builder": "npm:3.821.0" + "@smithy/config-resolver": "npm:^4.1.4" + "@smithy/core": "npm:^3.6.0" + "@smithy/eventstream-serde-browser": "npm:^4.0.4" + "@smithy/eventstream-serde-config-resolver": "npm:^4.1.2" + "@smithy/eventstream-serde-node": "npm:^4.0.4" + "@smithy/fetch-http-handler": "npm:^5.0.4" + "@smithy/hash-blob-browser": "npm:^4.0.4" + "@smithy/hash-node": "npm:^4.0.4" + "@smithy/hash-stream-node": "npm:^4.0.4" + "@smithy/invalid-dependency": "npm:^4.0.4" + "@smithy/md5-js": "npm:^4.0.4" + "@smithy/middleware-content-length": "npm:^4.0.4" + "@smithy/middleware-endpoint": "npm:^4.1.13" + "@smithy/middleware-retry": "npm:^4.1.14" + "@smithy/middleware-serde": "npm:^4.0.8" + "@smithy/middleware-stack": "npm:^4.0.4" + "@smithy/node-config-provider": "npm:^4.1.3" + "@smithy/node-http-handler": "npm:^4.0.6" + "@smithy/protocol-http": "npm:^5.1.2" + "@smithy/smithy-client": "npm:^4.4.5" + "@smithy/types": "npm:^4.3.1" + "@smithy/url-parser": "npm:^4.0.4" + "@smithy/util-base64": "npm:^4.0.0" + "@smithy/util-body-length-browser": "npm:^4.0.0" + "@smithy/util-body-length-node": "npm:^4.0.0" + "@smithy/util-defaults-mode-browser": "npm:^4.0.21" + "@smithy/util-defaults-mode-node": "npm:^4.0.21" + "@smithy/util-endpoints": "npm:^3.0.6" + "@smithy/util-middleware": "npm:^4.0.4" + "@smithy/util-retry": "npm:^4.0.6" + "@smithy/util-stream": "npm:^4.2.2" + "@smithy/util-utf8": "npm:^4.0.0" + "@smithy/util-waiter": "npm:^4.0.6" + "@types/uuid": "npm:^9.0.1" + tslib: "npm:^2.6.2" + uuid: "npm:^9.0.1" + checksum: 10c0/c923c8a0b6743f81478758641190b7c1da8306e7f6bf81d7f9df722be183f7ad506ad47e1b9de0807961fffec6b36074385d4c611c0c2fb08c8e5b1d47948a48 + languageName: node + linkType: hard + +"@aws-sdk/client-sso@npm:3.840.0": + version: 3.840.0 + resolution: "@aws-sdk/client-sso@npm:3.840.0" + dependencies: + "@aws-crypto/sha256-browser": "npm:5.2.0" + "@aws-crypto/sha256-js": "npm:5.2.0" + "@aws-sdk/core": "npm:3.840.0" + "@aws-sdk/middleware-host-header": "npm:3.840.0" + "@aws-sdk/middleware-logger": "npm:3.840.0" + "@aws-sdk/middleware-recursion-detection": "npm:3.840.0" + "@aws-sdk/middleware-user-agent": "npm:3.840.0" + "@aws-sdk/region-config-resolver": "npm:3.840.0" + "@aws-sdk/types": "npm:3.840.0" + "@aws-sdk/util-endpoints": "npm:3.840.0" + "@aws-sdk/util-user-agent-browser": "npm:3.840.0" + "@aws-sdk/util-user-agent-node": "npm:3.840.0" + "@smithy/config-resolver": "npm:^4.1.4" + "@smithy/core": "npm:^3.6.0" + "@smithy/fetch-http-handler": "npm:^5.0.4" + "@smithy/hash-node": "npm:^4.0.4" + "@smithy/invalid-dependency": "npm:^4.0.4" + "@smithy/middleware-content-length": "npm:^4.0.4" + "@smithy/middleware-endpoint": "npm:^4.1.13" + "@smithy/middleware-retry": "npm:^4.1.14" + "@smithy/middleware-serde": "npm:^4.0.8" + "@smithy/middleware-stack": "npm:^4.0.4" + "@smithy/node-config-provider": "npm:^4.1.3" + "@smithy/node-http-handler": "npm:^4.0.6" + "@smithy/protocol-http": "npm:^5.1.2" + "@smithy/smithy-client": "npm:^4.4.5" + "@smithy/types": "npm:^4.3.1" + "@smithy/url-parser": "npm:^4.0.4" + "@smithy/util-base64": "npm:^4.0.0" + "@smithy/util-body-length-browser": "npm:^4.0.0" + "@smithy/util-body-length-node": "npm:^4.0.0" + "@smithy/util-defaults-mode-browser": "npm:^4.0.21" + "@smithy/util-defaults-mode-node": "npm:^4.0.21" + "@smithy/util-endpoints": "npm:^3.0.6" + "@smithy/util-middleware": "npm:^4.0.4" + "@smithy/util-retry": "npm:^4.0.6" + "@smithy/util-utf8": "npm:^4.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/6d83d3dfefaab731818eade68f08f563906e9bee37c0836da262f47b15be8b1885e813a67927dd2549b1a043dffb551a2ec39a963ef335b9df54e8b9faf534e5 + languageName: node + linkType: hard + +"@aws-sdk/core@npm:3.840.0": + version: 3.840.0 + resolution: "@aws-sdk/core@npm:3.840.0" + dependencies: + "@aws-sdk/types": "npm:3.840.0" + "@aws-sdk/xml-builder": "npm:3.821.0" + "@smithy/core": "npm:^3.6.0" + "@smithy/node-config-provider": "npm:^4.1.3" + "@smithy/property-provider": "npm:^4.0.4" + "@smithy/protocol-http": "npm:^5.1.2" + "@smithy/signature-v4": "npm:^5.1.2" + "@smithy/smithy-client": "npm:^4.4.5" + "@smithy/types": "npm:^4.3.1" + "@smithy/util-base64": "npm:^4.0.0" + "@smithy/util-body-length-browser": "npm:^4.0.0" + "@smithy/util-middleware": "npm:^4.0.4" + "@smithy/util-utf8": "npm:^4.0.0" + fast-xml-parser: "npm:4.4.1" + tslib: "npm:^2.6.2" + checksum: 10c0/6bd10d86a85c2f52d1a6ca3fe4e45fb8b8ba43abb0f52d2cd14b8d3fb9908f2e1ec0cd9dcf7980df847cfb3dbcd329679a6fe7d029fbc57840d716d1120bc445 + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-env@npm:3.840.0": + version: 3.840.0 + resolution: "@aws-sdk/credential-provider-env@npm:3.840.0" + dependencies: + "@aws-sdk/core": "npm:3.840.0" + "@aws-sdk/types": "npm:3.840.0" + "@smithy/property-provider": "npm:^4.0.4" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/ed12ee47f67980b2a434a168de12401312d995428f33e487ea64420a670fdfec59324318eb02e630ef779336723499ca13533cec2b64f1f9d9f48fe9c7e138ef + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-http@npm:3.840.0": + version: 3.840.0 + resolution: "@aws-sdk/credential-provider-http@npm:3.840.0" + dependencies: + "@aws-sdk/core": "npm:3.840.0" + "@aws-sdk/types": "npm:3.840.0" + "@smithy/fetch-http-handler": "npm:^5.0.4" + "@smithy/node-http-handler": "npm:^4.0.6" + "@smithy/property-provider": "npm:^4.0.4" + "@smithy/protocol-http": "npm:^5.1.2" + "@smithy/smithy-client": "npm:^4.4.5" + "@smithy/types": "npm:^4.3.1" + "@smithy/util-stream": "npm:^4.2.2" + tslib: "npm:^2.6.2" + checksum: 10c0/21892b9252b4f7692f9a3e9999a5991e476a8ef7541674c230e94d6a5a1fa7381e643e69d1f7e77dd3bbcee952fa9f4bf45793abf8e5a9c60c0ecb407f10ad4f + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-ini@npm:3.840.0": + version: 3.840.0 + resolution: "@aws-sdk/credential-provider-ini@npm:3.840.0" + dependencies: + "@aws-sdk/core": "npm:3.840.0" + "@aws-sdk/credential-provider-env": "npm:3.840.0" + "@aws-sdk/credential-provider-http": "npm:3.840.0" + "@aws-sdk/credential-provider-process": "npm:3.840.0" + "@aws-sdk/credential-provider-sso": "npm:3.840.0" + "@aws-sdk/credential-provider-web-identity": "npm:3.840.0" + "@aws-sdk/nested-clients": "npm:3.840.0" + "@aws-sdk/types": "npm:3.840.0" + "@smithy/credential-provider-imds": "npm:^4.0.6" + "@smithy/property-provider": "npm:^4.0.4" + "@smithy/shared-ini-file-loader": "npm:^4.0.4" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/963c9a675b327f70c7123c392ce0e96ee9e451e118b3af7ba1ea65921965718f96896c29992448c4d5f7739c499e66007aed03be28e094fab0728b8b2bb19731 + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-node@npm:3.840.0": + version: 3.840.0 + resolution: "@aws-sdk/credential-provider-node@npm:3.840.0" + dependencies: + "@aws-sdk/credential-provider-env": "npm:3.840.0" + "@aws-sdk/credential-provider-http": "npm:3.840.0" + "@aws-sdk/credential-provider-ini": "npm:3.840.0" + "@aws-sdk/credential-provider-process": "npm:3.840.0" + "@aws-sdk/credential-provider-sso": "npm:3.840.0" + "@aws-sdk/credential-provider-web-identity": "npm:3.840.0" + "@aws-sdk/types": "npm:3.840.0" + "@smithy/credential-provider-imds": "npm:^4.0.6" + "@smithy/property-provider": "npm:^4.0.4" + "@smithy/shared-ini-file-loader": "npm:^4.0.4" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/cef45e1d12aee1e05aae0498a03eafe6b0f18aa612cb7b49965dcb535bb7bc91339f33de299afb235d20e557a9a2ce16ab1ff2ddf9babec3860cc217437106b7 + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-process@npm:3.840.0": + version: 3.840.0 + resolution: "@aws-sdk/credential-provider-process@npm:3.840.0" + dependencies: + "@aws-sdk/core": "npm:3.840.0" + "@aws-sdk/types": "npm:3.840.0" + "@smithy/property-provider": "npm:^4.0.4" + "@smithy/shared-ini-file-loader": "npm:^4.0.4" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/c4278d64dd3a4c3072b30483fb723c6fabf811989f4f434f6573c729fed94e6851ff339275fe207e6aeab83a672d57dca70b1385c8c2dca731cae87fcec59319 + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-sso@npm:3.840.0": + version: 3.840.0 + resolution: "@aws-sdk/credential-provider-sso@npm:3.840.0" + dependencies: + "@aws-sdk/client-sso": "npm:3.840.0" + "@aws-sdk/core": "npm:3.840.0" + "@aws-sdk/token-providers": "npm:3.840.0" + "@aws-sdk/types": "npm:3.840.0" + "@smithy/property-provider": "npm:^4.0.4" + "@smithy/shared-ini-file-loader": "npm:^4.0.4" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/4b0398be1d148bcab6e228016fead4c14d0fa6c6d0a7bc59b1b3e937534070f9a99c2147a897a24e83de4601e406d47d8a1a5b19fa59a5d35beb2474b1b41087 + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-web-identity@npm:3.840.0": + version: 3.840.0 + resolution: "@aws-sdk/credential-provider-web-identity@npm:3.840.0" + dependencies: + "@aws-sdk/core": "npm:3.840.0" + "@aws-sdk/nested-clients": "npm:3.840.0" + "@aws-sdk/types": "npm:3.840.0" + "@smithy/property-provider": "npm:^4.0.4" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/a68d4b09d9c1869383372c105ed78c5b2c5442e783f8a2fa5f8ca3e9f84e4041d7eaf854a74f867b9f4bfa9f7288093b71e2789494e77ae04e8f77ef280ffdab + languageName: node + linkType: hard + +"@aws-sdk/middleware-bucket-endpoint@npm:3.840.0": + version: 3.840.0 + resolution: "@aws-sdk/middleware-bucket-endpoint@npm:3.840.0" + dependencies: + "@aws-sdk/types": "npm:3.840.0" + "@aws-sdk/util-arn-parser": "npm:3.804.0" + "@smithy/node-config-provider": "npm:^4.1.3" + "@smithy/protocol-http": "npm:^5.1.2" + "@smithy/types": "npm:^4.3.1" + "@smithy/util-config-provider": "npm:^4.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/371f6e30b16821e1a9c17efcbe6436616eb2bcbfe1757d5f70c56d5eca8452d8dddd42f26f53635b87f927b4da541dc36156e4d3529bb0eb0705969365dce8fc + languageName: node + linkType: hard + +"@aws-sdk/middleware-expect-continue@npm:3.840.0": + version: 3.840.0 + resolution: "@aws-sdk/middleware-expect-continue@npm:3.840.0" + dependencies: + "@aws-sdk/types": "npm:3.840.0" + "@smithy/protocol-http": "npm:^5.1.2" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/73099d06d044f5d82cf172398939c8776c966bf88466288270d80a4e93f451c9e620c92252b0b5c8086b22429f6a69137a21d81bbac66e573c36241859f0739b + languageName: node + linkType: hard + +"@aws-sdk/middleware-flexible-checksums@npm:3.840.0": + version: 3.840.0 + resolution: "@aws-sdk/middleware-flexible-checksums@npm:3.840.0" + dependencies: + "@aws-crypto/crc32": "npm:5.2.0" + "@aws-crypto/crc32c": "npm:5.2.0" + "@aws-crypto/util": "npm:5.2.0" + "@aws-sdk/core": "npm:3.840.0" + "@aws-sdk/types": "npm:3.840.0" + "@smithy/is-array-buffer": "npm:^4.0.0" + "@smithy/node-config-provider": "npm:^4.1.3" + "@smithy/protocol-http": "npm:^5.1.2" + "@smithy/types": "npm:^4.3.1" + "@smithy/util-middleware": "npm:^4.0.4" + "@smithy/util-stream": "npm:^4.2.2" + "@smithy/util-utf8": "npm:^4.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/55f31563a9811cc0b49c00d3c24e719416f51be31ac3d2af87425850d1c4ea2abb9a2dfc2f853ca6c3e10b837640e189c5cd37369476951dd0eab286e5abacbf + languageName: node + linkType: hard + +"@aws-sdk/middleware-host-header@npm:3.840.0": + version: 3.840.0 + resolution: "@aws-sdk/middleware-host-header@npm:3.840.0" + dependencies: + "@aws-sdk/types": "npm:3.840.0" + "@smithy/protocol-http": "npm:^5.1.2" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/aae5964c39118815293f3f1d42c6b5131ff44862d33af9c8d44eb98fb5b8db0e6191cceba59c487a2b89b70b2e7ad710b174a14506bc6d99d333af42fd6b3d07 + languageName: node + linkType: hard + +"@aws-sdk/middleware-location-constraint@npm:3.840.0": + version: 3.840.0 + resolution: "@aws-sdk/middleware-location-constraint@npm:3.840.0" + dependencies: + "@aws-sdk/types": "npm:3.840.0" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/4520274c5b350881df39e28b1732b482ee8023801e8cc6fe1da4b11856ea9660af5036dc6144cefce20338ed0cf5622cc03d10dddf67f95354447d3d0448d987 + languageName: node + linkType: hard + +"@aws-sdk/middleware-logger@npm:3.840.0": + version: 3.840.0 + resolution: "@aws-sdk/middleware-logger@npm:3.840.0" + dependencies: + "@aws-sdk/types": "npm:3.840.0" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/5cc4eec656ec9811b64e504a96812f05f1b57e3542ea1dae6710505f81f8dfb36119709538b736a55792f02565818ab71f803e91b00bc4f0652ab198fce153fd + languageName: node + linkType: hard + +"@aws-sdk/middleware-recursion-detection@npm:3.840.0": + version: 3.840.0 + resolution: "@aws-sdk/middleware-recursion-detection@npm:3.840.0" + dependencies: + "@aws-sdk/types": "npm:3.840.0" + "@smithy/protocol-http": "npm:^5.1.2" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/88b1dfbf487d86b2aa26761b08e3de2fd1edd8d09abffd88f5d31b77215fd0852c74deba38802a15cc7015a716d990c2925523af88577890311958f53ef739e7 + languageName: node + linkType: hard + +"@aws-sdk/middleware-sdk-s3@npm:3.840.0": + version: 3.840.0 + resolution: "@aws-sdk/middleware-sdk-s3@npm:3.840.0" + dependencies: + "@aws-sdk/core": "npm:3.840.0" + "@aws-sdk/types": "npm:3.840.0" + "@aws-sdk/util-arn-parser": "npm:3.804.0" + "@smithy/core": "npm:^3.6.0" + "@smithy/node-config-provider": "npm:^4.1.3" + "@smithy/protocol-http": "npm:^5.1.2" + "@smithy/signature-v4": "npm:^5.1.2" + "@smithy/smithy-client": "npm:^4.4.5" + "@smithy/types": "npm:^4.3.1" + "@smithy/util-config-provider": "npm:^4.0.0" + "@smithy/util-middleware": "npm:^4.0.4" + "@smithy/util-stream": "npm:^4.2.2" + "@smithy/util-utf8": "npm:^4.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/8ef8413028e710a5cee96af80b545d578c3c385dbcb87d2e2b61772b81813f700d7ca503305305af9819462c354e131e8aef692f58eeb08164279701ca1e67ef + languageName: node + linkType: hard + +"@aws-sdk/middleware-ssec@npm:3.840.0": + version: 3.840.0 + resolution: "@aws-sdk/middleware-ssec@npm:3.840.0" + dependencies: + "@aws-sdk/types": "npm:3.840.0" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/22cdded72582d15adb266e5f65b5756c129b7104535765ff5c67eedc24609bface9eebb1fa3b74ed41e7b8fade57940195810bbbe2e44b8283104849894ec658 + languageName: node + linkType: hard + +"@aws-sdk/middleware-user-agent@npm:3.840.0": + version: 3.840.0 + resolution: "@aws-sdk/middleware-user-agent@npm:3.840.0" + dependencies: + "@aws-sdk/core": "npm:3.840.0" + "@aws-sdk/types": "npm:3.840.0" + "@aws-sdk/util-endpoints": "npm:3.840.0" + "@smithy/core": "npm:^3.6.0" + "@smithy/protocol-http": "npm:^5.1.2" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/68822bc24d1311ba47a1e3b2ff194376f3923b39379aa29e6be658ee7e1b809bfea5ea07335c696ca581b42665f30899e25bbe8d9b3216003f602622b4326140 + languageName: node + linkType: hard + +"@aws-sdk/nested-clients@npm:3.840.0": + version: 3.840.0 + resolution: "@aws-sdk/nested-clients@npm:3.840.0" + dependencies: + "@aws-crypto/sha256-browser": "npm:5.2.0" + "@aws-crypto/sha256-js": "npm:5.2.0" + "@aws-sdk/core": "npm:3.840.0" + "@aws-sdk/middleware-host-header": "npm:3.840.0" + "@aws-sdk/middleware-logger": "npm:3.840.0" + "@aws-sdk/middleware-recursion-detection": "npm:3.840.0" + "@aws-sdk/middleware-user-agent": "npm:3.840.0" + "@aws-sdk/region-config-resolver": "npm:3.840.0" + "@aws-sdk/types": "npm:3.840.0" + "@aws-sdk/util-endpoints": "npm:3.840.0" + "@aws-sdk/util-user-agent-browser": "npm:3.840.0" + "@aws-sdk/util-user-agent-node": "npm:3.840.0" + "@smithy/config-resolver": "npm:^4.1.4" + "@smithy/core": "npm:^3.6.0" + "@smithy/fetch-http-handler": "npm:^5.0.4" + "@smithy/hash-node": "npm:^4.0.4" + "@smithy/invalid-dependency": "npm:^4.0.4" + "@smithy/middleware-content-length": "npm:^4.0.4" + "@smithy/middleware-endpoint": "npm:^4.1.13" + "@smithy/middleware-retry": "npm:^4.1.14" + "@smithy/middleware-serde": "npm:^4.0.8" + "@smithy/middleware-stack": "npm:^4.0.4" + "@smithy/node-config-provider": "npm:^4.1.3" + "@smithy/node-http-handler": "npm:^4.0.6" + "@smithy/protocol-http": "npm:^5.1.2" + "@smithy/smithy-client": "npm:^4.4.5" + "@smithy/types": "npm:^4.3.1" + "@smithy/url-parser": "npm:^4.0.4" + "@smithy/util-base64": "npm:^4.0.0" + "@smithy/util-body-length-browser": "npm:^4.0.0" + "@smithy/util-body-length-node": "npm:^4.0.0" + "@smithy/util-defaults-mode-browser": "npm:^4.0.21" + "@smithy/util-defaults-mode-node": "npm:^4.0.21" + "@smithy/util-endpoints": "npm:^3.0.6" + "@smithy/util-middleware": "npm:^4.0.4" + "@smithy/util-retry": "npm:^4.0.6" + "@smithy/util-utf8": "npm:^4.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/1b9ee866f37f433723e472ed194629155de2b1fb7d464bf772727c5140bcb6ad5fbc5d4ae911a19b319f55614239bb1935304fa3ec5a881038a577c32a96b238 + languageName: node + linkType: hard + +"@aws-sdk/region-config-resolver@npm:3.840.0": + version: 3.840.0 + resolution: "@aws-sdk/region-config-resolver@npm:3.840.0" + dependencies: + "@aws-sdk/types": "npm:3.840.0" + "@smithy/node-config-provider": "npm:^4.1.3" + "@smithy/types": "npm:^4.3.1" + "@smithy/util-config-provider": "npm:^4.0.0" + "@smithy/util-middleware": "npm:^4.0.4" + tslib: "npm:^2.6.2" + checksum: 10c0/27d72bb9657efd79637a4c4aa895004d29c66eefce083fa84050f092f68bcba8cb9bf0e4c16c11c132a5fa01f1841e878fa903bc837c4e1e6904d1b2d2c3dd37 + languageName: node + linkType: hard + +"@aws-sdk/signature-v4-multi-region@npm:3.840.0": + version: 3.840.0 + resolution: "@aws-sdk/signature-v4-multi-region@npm:3.840.0" + dependencies: + "@aws-sdk/middleware-sdk-s3": "npm:3.840.0" + "@aws-sdk/types": "npm:3.840.0" + "@smithy/protocol-http": "npm:^5.1.2" + "@smithy/signature-v4": "npm:^5.1.2" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/224e17e624925ba5972f698d92e92289912f9e1ca1fd0525bbc62e6965a9e0585abb309fdb6b7e304fddeb4301e5c832d4370b324c55cbfd42922e73c1abc70c + languageName: node + linkType: hard + +"@aws-sdk/token-providers@npm:3.840.0": + version: 3.840.0 + resolution: "@aws-sdk/token-providers@npm:3.840.0" + dependencies: + "@aws-sdk/core": "npm:3.840.0" + "@aws-sdk/nested-clients": "npm:3.840.0" + "@aws-sdk/types": "npm:3.840.0" + "@smithy/property-provider": "npm:^4.0.4" + "@smithy/shared-ini-file-loader": "npm:^4.0.4" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/a172666169fd8164ce48a3a0ea242405d8437119c8fbcf259223badf8ad04cf68a1ebba54c09c22cbee5c16775e885733788978aa99c9a27241036e967ea2fa5 + languageName: node + linkType: hard + +"@aws-sdk/types@npm:3.840.0, @aws-sdk/types@npm:^3.222.0": + version: 3.840.0 + resolution: "@aws-sdk/types@npm:3.840.0" + dependencies: + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/292d38f5087c3aa925addd890f8ae2bf650282c2cf4997d971a341dc0249dfca7ce02d69a4af09da2562b78a4232232d2a3b88105f34f66aee608d52aac238d1 + languageName: node + linkType: hard + +"@aws-sdk/util-arn-parser@npm:3.804.0": + version: 3.804.0 + resolution: "@aws-sdk/util-arn-parser@npm:3.804.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10c0/b6d4c883ec2949fa40552fe8573c9c32af07c92c1bd94a27d978aa14d37b005be95392069d6b882ba977484f4dd0371792296fb2516f5d7601be5102888ee9ee + languageName: node + linkType: hard + +"@aws-sdk/util-endpoints@npm:3.840.0": + version: 3.840.0 + resolution: "@aws-sdk/util-endpoints@npm:3.840.0" + dependencies: + "@aws-sdk/types": "npm:3.840.0" + "@smithy/types": "npm:^4.3.1" + "@smithy/util-endpoints": "npm:^3.0.6" + tslib: "npm:^2.6.2" + checksum: 10c0/822fe59c003b433c955756daf47736a17c42c25f449b9ca96c2c2bb79964866ee0a0a657824da6289588d689e76712a7058d70e42c3fad2b78bfb23f905643d9 + languageName: node + linkType: hard + +"@aws-sdk/util-locate-window@npm:^3.0.0": + version: 3.804.0 + resolution: "@aws-sdk/util-locate-window@npm:3.804.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10c0/a0ceaf6531f188751fea7e829b730650689fa2196e0b3f870dde3888bcb840fe0852e10488699d4d9683db0765cd7f7060ca8ac216348991996b6d794f9957ab + languageName: node + linkType: hard + +"@aws-sdk/util-user-agent-browser@npm:3.840.0": + version: 3.840.0 + resolution: "@aws-sdk/util-user-agent-browser@npm:3.840.0" + dependencies: + "@aws-sdk/types": "npm:3.840.0" + "@smithy/types": "npm:^4.3.1" + bowser: "npm:^2.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/873d5e3218958aa935127b05dad5a1d8cf26c9b7726584eb424a5958e7e205786dd99e4fa053b65f3b956261a7f8a3746e48e9b7dc47c3149792ff525da97631 + languageName: node + linkType: hard + +"@aws-sdk/util-user-agent-node@npm:3.840.0": + version: 3.840.0 + resolution: "@aws-sdk/util-user-agent-node@npm:3.840.0" + dependencies: + "@aws-sdk/middleware-user-agent": "npm:3.840.0" + "@aws-sdk/types": "npm:3.840.0" + "@smithy/node-config-provider": "npm:^4.1.3" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + peerDependencies: + aws-crt: ">=1.0.0" + peerDependenciesMeta: + aws-crt: + optional: true + checksum: 10c0/862fc435d8a25f3e299e5c92c5ba51ef287a75f18cb0a529797a42a72de1481e3c92458a5569eeeab09fddfb5a75db1c59aa766d95b0e832c32c6c1bd7745644 + languageName: node + linkType: hard + +"@aws-sdk/xml-builder@npm:3.821.0": + version: 3.821.0 + resolution: "@aws-sdk/xml-builder@npm:3.821.0" + dependencies: + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/316e0eb04bcec0bb0897f67718629deab29adb9664ce78743ad854df772472c02332ab12627d74b96ebe2205adc51b1cb7fb01fcb4251e80a7af405e56cfa135 + languageName: node + linkType: hard + "@babel/code-frame@npm:^7.10.4": version: 7.27.1 resolution: "@babel/code-frame@npm:7.27.1" @@ -3993,6 +4637,604 @@ __metadata: languageName: node linkType: hard +"@smithy/abort-controller@npm:^4.0.4": + version: 4.0.4 + resolution: "@smithy/abort-controller@npm:4.0.4" + dependencies: + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/eb172b002fb92406c69b83460f949ace73247e6abd85d0d3714de2765c5db7b98070b9abfb630e2c591dd7b2ff770cc24f7737c1c207581f716c402b16bf46f9 + languageName: node + linkType: hard + +"@smithy/chunked-blob-reader-native@npm:^4.0.0": + version: 4.0.0 + resolution: "@smithy/chunked-blob-reader-native@npm:4.0.0" + dependencies: + "@smithy/util-base64": "npm:^4.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/4387f4e8841f20c1c4e689078141de7e6f239e7883be3a02810a023aa30939b15576ee00227b991972d2c5a2f3b6152bcaeca0975c9fa8d3669354c647bd532a + languageName: node + linkType: hard + +"@smithy/chunked-blob-reader@npm:^5.0.0": + version: 5.0.0 + resolution: "@smithy/chunked-blob-reader@npm:5.0.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10c0/55ba0fe366ddaa3f93e1faf8a70df0b67efedbd0008922295efe215df09b68df0ba3043293e65b17e7d1be71448d074c2bfc54e5eb6bd18f59b425822c2b9e9a + languageName: node + linkType: hard + +"@smithy/config-resolver@npm:^4.1.4": + version: 4.1.4 + resolution: "@smithy/config-resolver@npm:4.1.4" + dependencies: + "@smithy/node-config-provider": "npm:^4.1.3" + "@smithy/types": "npm:^4.3.1" + "@smithy/util-config-provider": "npm:^4.0.0" + "@smithy/util-middleware": "npm:^4.0.4" + tslib: "npm:^2.6.2" + checksum: 10c0/41832a42f8da7143732c71098b410f4ddcb096066126f7e8f45bae8d9aeb95681bd0d0d54886f46244c945c63829ca5d23373d4de31a038487aa07159722ef4e + languageName: node + linkType: hard + +"@smithy/core@npm:^3.6.0": + version: 3.6.0 + resolution: "@smithy/core@npm:3.6.0" + dependencies: + "@smithy/middleware-serde": "npm:^4.0.8" + "@smithy/protocol-http": "npm:^5.1.2" + "@smithy/types": "npm:^4.3.1" + "@smithy/util-base64": "npm:^4.0.0" + "@smithy/util-body-length-browser": "npm:^4.0.0" + "@smithy/util-middleware": "npm:^4.0.4" + "@smithy/util-stream": "npm:^4.2.2" + "@smithy/util-utf8": "npm:^4.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/015874e1c44815b6e50594f2983a1a88e3c4f777760d062b2e31b402e8d145ce5c64b33065eaa97fd37867ef6c95493ddc62f3775cd7102e6fd41c9808be219a + languageName: node + linkType: hard + +"@smithy/credential-provider-imds@npm:^4.0.6": + version: 4.0.6 + resolution: "@smithy/credential-provider-imds@npm:4.0.6" + dependencies: + "@smithy/node-config-provider": "npm:^4.1.3" + "@smithy/property-provider": "npm:^4.0.4" + "@smithy/types": "npm:^4.3.1" + "@smithy/url-parser": "npm:^4.0.4" + tslib: "npm:^2.6.2" + checksum: 10c0/b1f3157d0a7b9f9155ac80aeac70d7db896d23d0322a6b38f0e848f1e53864ba1bca6d3dc5dd9af86446c371ebc5bffe01f0712ad562e7635e7d13e532622aa4 + languageName: node + linkType: hard + +"@smithy/eventstream-codec@npm:^4.0.4": + version: 4.0.4 + resolution: "@smithy/eventstream-codec@npm:4.0.4" + dependencies: + "@aws-crypto/crc32": "npm:5.2.0" + "@smithy/types": "npm:^4.3.1" + "@smithy/util-hex-encoding": "npm:^4.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/89b76826d4d3bf97317e3539ece105b9a03552144ad816a687b0b2cbca60e2b3513062c04b6cfacaffb270d616ffc8ac8bf549afc4aa676a6d7465df5a3215ba + languageName: node + linkType: hard + +"@smithy/eventstream-serde-browser@npm:^4.0.4": + version: 4.0.4 + resolution: "@smithy/eventstream-serde-browser@npm:4.0.4" + dependencies: + "@smithy/eventstream-serde-universal": "npm:^4.0.4" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/b2444538555c54ac96d4049b0be3a65d959914bcd5198a8059edc838c7ffac5a1db225290194f85ea8805c47c1edc95484dfeb415cb2004ec3e572880f4fc8c5 + languageName: node + linkType: hard + +"@smithy/eventstream-serde-config-resolver@npm:^4.1.2": + version: 4.1.2 + resolution: "@smithy/eventstream-serde-config-resolver@npm:4.1.2" + dependencies: + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/54184a29d1e42f1b972292efc3a5cbbe3ca237cd9ab76132bad40e8426fa62d0b7f6fdac01f23e3a9cac69919107ddfd9d2f2873f83ae1f65470d3052c67cefc + languageName: node + linkType: hard + +"@smithy/eventstream-serde-node@npm:^4.0.4": + version: 4.0.4 + resolution: "@smithy/eventstream-serde-node@npm:4.0.4" + dependencies: + "@smithy/eventstream-serde-universal": "npm:^4.0.4" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/e6d0765a73332c79b69531ed20c27e49475173da09ce21e4c011a64d8a61d7c5c328c9bc1cab991e145fc969b16071ffd6a33ab11291c0fa2a46e8dae28da23b + languageName: node + linkType: hard + +"@smithy/eventstream-serde-universal@npm:^4.0.4": + version: 4.0.4 + resolution: "@smithy/eventstream-serde-universal@npm:4.0.4" + dependencies: + "@smithy/eventstream-codec": "npm:^4.0.4" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/f0c18efa6cafa111ed20c8c53b4a7b6a0f8e25ccb0d2cafdf83282ebc6f96e47f26daf24b5b810ea83a02e03c994c35419d94fad76871f2cc6cb01d2aed277d9 + languageName: node + linkType: hard + +"@smithy/fetch-http-handler@npm:^5.0.4": + version: 5.0.4 + resolution: "@smithy/fetch-http-handler@npm:5.0.4" + dependencies: + "@smithy/protocol-http": "npm:^5.1.2" + "@smithy/querystring-builder": "npm:^4.0.4" + "@smithy/types": "npm:^4.3.1" + "@smithy/util-base64": "npm:^4.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/ce57acfcd40a6ff3965c5f14b432c5ab87f0b0766766960224d4af79af85e37d61da2db6dc5cfa16bf4b8f2d8966a2838d2ee6eef8d5cd5a837aacbc01517851 + languageName: node + linkType: hard + +"@smithy/hash-blob-browser@npm:^4.0.4": + version: 4.0.4 + resolution: "@smithy/hash-blob-browser@npm:4.0.4" + dependencies: + "@smithy/chunked-blob-reader": "npm:^5.0.0" + "@smithy/chunked-blob-reader-native": "npm:^4.0.0" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/f970058c2e04e86427e1474355199027fc84dc1d96d9a2278ed37904458d37b020472541390558bd3fb071bbd177b2850b18ceb1beb39d387fead06a2912f974 + languageName: node + linkType: hard + +"@smithy/hash-node@npm:^4.0.4": + version: 4.0.4 + resolution: "@smithy/hash-node@npm:4.0.4" + dependencies: + "@smithy/types": "npm:^4.3.1" + "@smithy/util-buffer-from": "npm:^4.0.0" + "@smithy/util-utf8": "npm:^4.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/07beb38643990f6c055457765d65af2aedd5944d819025df90d1f2f59596d1a1394cd8c9035ac6d343bc55e3afeb186b51b0ac91938024da8687120fc0b436dc + languageName: node + linkType: hard + +"@smithy/hash-stream-node@npm:^4.0.4": + version: 4.0.4 + resolution: "@smithy/hash-stream-node@npm:4.0.4" + dependencies: + "@smithy/types": "npm:^4.3.1" + "@smithy/util-utf8": "npm:^4.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/4899132433f520e45972bbacb6a999da8d7ccf4c813f2fb28b1af65eaf268ba549b2c37dd54a586cd7bcd82f6e4cec914651a6446b3fb3e1f226ca1864051535 + languageName: node + linkType: hard + +"@smithy/invalid-dependency@npm:^4.0.4": + version: 4.0.4 + resolution: "@smithy/invalid-dependency@npm:4.0.4" + dependencies: + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/5e5a6282c17a7310f8e866c7e34fa07479d42c650cf3c1875bdb0ec38d5280eeac82a269605a3521b8fa455b92673d8fd5e97eb997acf81a80da82d6f501d651 + languageName: node + linkType: hard + +"@smithy/is-array-buffer@npm:^2.2.0": + version: 2.2.0 + resolution: "@smithy/is-array-buffer@npm:2.2.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10c0/2f2523cd8cc4538131e408eb31664983fecb0c8724956788b015aaf3ab85a0c976b50f4f09b176f1ed7bbe79f3edf80743be7a80a11f22cd9ce1285d77161aaf + languageName: node + linkType: hard + +"@smithy/is-array-buffer@npm:^4.0.0": + version: 4.0.0 + resolution: "@smithy/is-array-buffer@npm:4.0.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10c0/ae393fbd5944d710443cd5dd225d1178ef7fb5d6259c14f3e1316ec75e401bda6cf86f7eb98bfd38e5ed76e664b810426a5756b916702cbd418f0933e15e7a3b + languageName: node + linkType: hard + +"@smithy/md5-js@npm:^4.0.4": + version: 4.0.4 + resolution: "@smithy/md5-js@npm:4.0.4" + dependencies: + "@smithy/types": "npm:^4.3.1" + "@smithy/util-utf8": "npm:^4.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/7c66405dca5d7df6694367dbb4a3d9f13fdfe2589abc81f85d5fb7bf876e1382578d9c477d2256d4b5bc59951c3c534e51eb65c53c2fb3251080f16d1d7ea82c + languageName: node + linkType: hard + +"@smithy/middleware-content-length@npm:^4.0.4": + version: 4.0.4 + resolution: "@smithy/middleware-content-length@npm:4.0.4" + dependencies: + "@smithy/protocol-http": "npm:^5.1.2" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/fde43ff13f0830c4608b83cf6e2bd3ae142aa6eb3df6f6c190c2564dd00c2c98f4f95da9146c69bc09115ad87ffc9dc24935d1a3d6d3b2383a9c8558d9177dd6 + languageName: node + linkType: hard + +"@smithy/middleware-endpoint@npm:^4.1.13": + version: 4.1.13 + resolution: "@smithy/middleware-endpoint@npm:4.1.13" + dependencies: + "@smithy/core": "npm:^3.6.0" + "@smithy/middleware-serde": "npm:^4.0.8" + "@smithy/node-config-provider": "npm:^4.1.3" + "@smithy/shared-ini-file-loader": "npm:^4.0.4" + "@smithy/types": "npm:^4.3.1" + "@smithy/url-parser": "npm:^4.0.4" + "@smithy/util-middleware": "npm:^4.0.4" + tslib: "npm:^2.6.2" + checksum: 10c0/a4f605ba95d59e5afbad326ed0a1417fb33cb1c6085a9c13f765520d3732e223ab501033457eab72ed223d41ce0a079d6895ebb3954935b2a6d25b223c4ef72c + languageName: node + linkType: hard + +"@smithy/middleware-retry@npm:^4.1.14": + version: 4.1.14 + resolution: "@smithy/middleware-retry@npm:4.1.14" + dependencies: + "@smithy/node-config-provider": "npm:^4.1.3" + "@smithy/protocol-http": "npm:^5.1.2" + "@smithy/service-error-classification": "npm:^4.0.6" + "@smithy/smithy-client": "npm:^4.4.5" + "@smithy/types": "npm:^4.3.1" + "@smithy/util-middleware": "npm:^4.0.4" + "@smithy/util-retry": "npm:^4.0.6" + tslib: "npm:^2.6.2" + uuid: "npm:^9.0.1" + checksum: 10c0/a720f366f3c8b5ea9d35bf38718d3885492fe896288623f9e5b3c293bfea14bc530b9107da100abdac3ff45bebbe1335f6da928c005fc78dbdefab2d65f1269d + languageName: node + linkType: hard + +"@smithy/middleware-serde@npm:^4.0.8": + version: 4.0.8 + resolution: "@smithy/middleware-serde@npm:4.0.8" + dependencies: + "@smithy/protocol-http": "npm:^5.1.2" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/11414e584780716b2b0487fe748da9927943d4d810b5b0161e73df6ab24a4d17f675773287f95868c57a71013385f7b027eb2afbab1eed3dbaafef754b482b27 + languageName: node + linkType: hard + +"@smithy/middleware-stack@npm:^4.0.4": + version: 4.0.4 + resolution: "@smithy/middleware-stack@npm:4.0.4" + dependencies: + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/b29b6430e31f11683f0ce0e06d21a4bfe6cb791ce1eb5686533559baa81698f617bfbfdac06f569e13f077ce177cb70e55f4db20701906b3e344d9294817f382 + languageName: node + linkType: hard + +"@smithy/node-config-provider@npm:^4.1.3": + version: 4.1.3 + resolution: "@smithy/node-config-provider@npm:4.1.3" + dependencies: + "@smithy/property-provider": "npm:^4.0.4" + "@smithy/shared-ini-file-loader": "npm:^4.0.4" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/bea20b3f92290fbefa32d30c4ac7632f94d4e89b5432dfe5a2d0c6261bfd90e882d62dd02e0a4e65f3bc89f815b19e44d7bb103a78b6c77941cc186450ad79f1 + languageName: node + linkType: hard + +"@smithy/node-http-handler@npm:^4.0.6": + version: 4.0.6 + resolution: "@smithy/node-http-handler@npm:4.0.6" + dependencies: + "@smithy/abort-controller": "npm:^4.0.4" + "@smithy/protocol-http": "npm:^5.1.2" + "@smithy/querystring-builder": "npm:^4.0.4" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/bde23701b6166b76958cbc194d551a139e3dcc1d05a6c7de3d5b14f54934ca5a49a28d13d8ec4b012716aae816cd0c8c4735c959d5ef697a7a1932fbcfc5d7f2 + languageName: node + linkType: hard + +"@smithy/property-provider@npm:^4.0.4": + version: 4.0.4 + resolution: "@smithy/property-provider@npm:4.0.4" + dependencies: + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/c370efbb43ab01fb6050fbf4c231bbe2fb7d660256adeee40c0c4c14b7af1b9b75c36f6924aeacdd2885fad1aaf0655047cafe5f0d22f5e371cbd25ff2f04b27 + languageName: node + linkType: hard + +"@smithy/protocol-http@npm:^5.1.2": + version: 5.1.2 + resolution: "@smithy/protocol-http@npm:5.1.2" + dependencies: + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/50fb026efa321e65a77f9747312eeb428ff2196095c15ed5937efe807a4734c47746759ccf2dbc84a45719effcbc81221662289be6d4d5ec122afb0e3cd66fd9 + languageName: node + linkType: hard + +"@smithy/querystring-builder@npm:^4.0.4": + version: 4.0.4 + resolution: "@smithy/querystring-builder@npm:4.0.4" + dependencies: + "@smithy/types": "npm:^4.3.1" + "@smithy/util-uri-escape": "npm:^4.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/30ec0301fbc2212101391841000a3117ab6c3ae2b6b2a1db230cc1dfcf97738f527b23f859f0a5e843f2a983793b58cdcd21a0ce11ef93fcdf5d8a1ee0d70fbc + languageName: node + linkType: hard + +"@smithy/querystring-parser@npm:^4.0.4": + version: 4.0.4 + resolution: "@smithy/querystring-parser@npm:4.0.4" + dependencies: + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/36bc93732a1628be5dd53748f6f36237bad26de2da810195213541dd35b20eee0b0264160a0de734b9333ca747e0229253d6729d1a8ddc26d176c0b1cce309e0 + languageName: node + linkType: hard + +"@smithy/service-error-classification@npm:^4.0.6": + version: 4.0.6 + resolution: "@smithy/service-error-classification@npm:4.0.6" + dependencies: + "@smithy/types": "npm:^4.3.1" + checksum: 10c0/b67f5ef633fa803f6b9f81f53dcc361253f33e01ffefbcb1beaf29c578834b1381e5f979e25b38985d351142e1ab4ee638cf132a2ba9f6f7a0a806a35da76d86 + languageName: node + linkType: hard + +"@smithy/shared-ini-file-loader@npm:^4.0.4": + version: 4.0.4 + resolution: "@smithy/shared-ini-file-loader@npm:4.0.4" + dependencies: + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/a3ecabadda13ff6fca99585e7e0086a04c4d2350b8c783b3a23493c2ae0a599f397d3cb80a7e171b7123889340995cada866d320726fa6a03f3063d60d5d0207 + languageName: node + linkType: hard + +"@smithy/signature-v4@npm:^5.1.2": + version: 5.1.2 + resolution: "@smithy/signature-v4@npm:5.1.2" + dependencies: + "@smithy/is-array-buffer": "npm:^4.0.0" + "@smithy/protocol-http": "npm:^5.1.2" + "@smithy/types": "npm:^4.3.1" + "@smithy/util-hex-encoding": "npm:^4.0.0" + "@smithy/util-middleware": "npm:^4.0.4" + "@smithy/util-uri-escape": "npm:^4.0.0" + "@smithy/util-utf8": "npm:^4.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/83d3870668a6c080c1d0cbecf2e7d1a86c0298cc3a3df9fba21bd942e2a9bcae81eb50960c66bba00c6f9820ef9e5ab3e5ddba67b2d7914a09a82c7887621c0c + languageName: node + linkType: hard + +"@smithy/smithy-client@npm:^4.4.5": + version: 4.4.5 + resolution: "@smithy/smithy-client@npm:4.4.5" + dependencies: + "@smithy/core": "npm:^3.6.0" + "@smithy/middleware-endpoint": "npm:^4.1.13" + "@smithy/middleware-stack": "npm:^4.0.4" + "@smithy/protocol-http": "npm:^5.1.2" + "@smithy/types": "npm:^4.3.1" + "@smithy/util-stream": "npm:^4.2.2" + tslib: "npm:^2.6.2" + checksum: 10c0/180115cf186a0984db9110b3763db2f84451b65c353ae8e908cc6b6941ad4ad13de690192e7ee50281c83694ab09a7f282bcf4c81a2d839497f515c951d86b38 + languageName: node + linkType: hard + +"@smithy/types@npm:^4.3.1": + version: 4.3.1 + resolution: "@smithy/types@npm:4.3.1" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10c0/8b350562b9ed4ff97465025b4ae77a34bb07b9d47fb6f9781755aac9401b0355a63c2fef307393e2dae3fa0277149dd7d83f5bc2a63d4ad3519ea32fd56b5cda + languageName: node + linkType: hard + +"@smithy/url-parser@npm:^4.0.4": + version: 4.0.4 + resolution: "@smithy/url-parser@npm:4.0.4" + dependencies: + "@smithy/querystring-parser": "npm:^4.0.4" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/5f4649d9ff618c683e339fa826b1d722419bf8e20d72726fc5fe3cd479ec8c161d4b09b6e24e49b0143a6fb4f9a950d35410db1834e143c28e377b9c529a3657 + languageName: node + linkType: hard + +"@smithy/util-base64@npm:^4.0.0": + version: 4.0.0 + resolution: "@smithy/util-base64@npm:4.0.0" + dependencies: + "@smithy/util-buffer-from": "npm:^4.0.0" + "@smithy/util-utf8": "npm:^4.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/ad18ec66cc357c189eef358d96876b114faf7086b13e47e009b265d0ff80cec046052500489c183957b3a036768409acdd1a373e01074cc002ca6983f780cffc + languageName: node + linkType: hard + +"@smithy/util-body-length-browser@npm:^4.0.0": + version: 4.0.0 + resolution: "@smithy/util-body-length-browser@npm:4.0.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10c0/574a10934024a86556e9dcde1a9776170284326c3dfcc034afa128cc5a33c1c8179fca9cfb622ef8be5f2004316cc3f427badccceb943e829105536ec26306d9 + languageName: node + linkType: hard + +"@smithy/util-body-length-node@npm:^4.0.0": + version: 4.0.0 + resolution: "@smithy/util-body-length-node@npm:4.0.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10c0/e91fd3816767606c5f786166ada26440457fceb60f96653b3d624dcf762a8c650e513c275ff3f647cb081c63c283cc178853a7ed9aa224abc8ece4eeeef7a1dd + languageName: node + linkType: hard + +"@smithy/util-buffer-from@npm:^2.2.0": + version: 2.2.0 + resolution: "@smithy/util-buffer-from@npm:2.2.0" + dependencies: + "@smithy/is-array-buffer": "npm:^2.2.0" + tslib: "npm:^2.6.2" + checksum: 10c0/223d6a508b52ff236eea01cddc062b7652d859dd01d457a4e50365af3de1e24a05f756e19433f6ccf1538544076b4215469e21a4ea83dc1d58d829725b0dbc5a + languageName: node + linkType: hard + +"@smithy/util-buffer-from@npm:^4.0.0": + version: 4.0.0 + resolution: "@smithy/util-buffer-from@npm:4.0.0" + dependencies: + "@smithy/is-array-buffer": "npm:^4.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/be7cd33b6cb91503982b297716251e67cdca02819a15797632091cadab2dc0b4a147fff0709a0aa9bbc0b82a2644a7ed7c8afdd2194d5093cee2e9605b3a9f6f + languageName: node + linkType: hard + +"@smithy/util-config-provider@npm:^4.0.0": + version: 4.0.0 + resolution: "@smithy/util-config-provider@npm:4.0.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10c0/cd9498d5f77a73aadd575084bcb22d2bb5945bac4605d605d36f2efe3f165f2b60f4dc88b7a62c2ed082ffa4b2c2f19621d0859f18399edbc2b5988d92e4649f + languageName: node + linkType: hard + +"@smithy/util-defaults-mode-browser@npm:^4.0.21": + version: 4.0.21 + resolution: "@smithy/util-defaults-mode-browser@npm:4.0.21" + dependencies: + "@smithy/property-provider": "npm:^4.0.4" + "@smithy/smithy-client": "npm:^4.4.5" + "@smithy/types": "npm:^4.3.1" + bowser: "npm:^2.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/401d5f83aa0c054755e18742a6f35de50268174d93ad05bd95123fe176870153da3bfc2344ebad23a2a159bd0668f2c0c758a94e3d5696dd59990d5e881c4c1b + languageName: node + linkType: hard + +"@smithy/util-defaults-mode-node@npm:^4.0.21": + version: 4.0.21 + resolution: "@smithy/util-defaults-mode-node@npm:4.0.21" + dependencies: + "@smithy/config-resolver": "npm:^4.1.4" + "@smithy/credential-provider-imds": "npm:^4.0.6" + "@smithy/node-config-provider": "npm:^4.1.3" + "@smithy/property-provider": "npm:^4.0.4" + "@smithy/smithy-client": "npm:^4.4.5" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/936399758fdecf68b14f7adfcb6a9dbc50b62edeabc6c146affe5f7dc40ccfc42df0c6af882748a8ccc32a54834bcf1d22fd42ec8589242dcabe5b983b67e40c + languageName: node + linkType: hard + +"@smithy/util-endpoints@npm:^3.0.6": + version: 3.0.6 + resolution: "@smithy/util-endpoints@npm:3.0.6" + dependencies: + "@smithy/node-config-provider": "npm:^4.1.3" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/d7d583c73a0c1ce38188569616cd4d7c95c36c0393516117043962b932f8c743e8cd672d2edd23ea8a9da0e30b84ee0f0ced0709cc8024b70ea8e5f17f505811 + languageName: node + linkType: hard + +"@smithy/util-hex-encoding@npm:^4.0.0": + version: 4.0.0 + resolution: "@smithy/util-hex-encoding@npm:4.0.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10c0/70dbb3aa1a79aff3329d07a66411ff26398df338bdd8a6d077b438231afe3dc86d9a7022204baddecd8bc633f059d5c841fa916d81dd7447ea79b64148f386d2 + languageName: node + linkType: hard + +"@smithy/util-middleware@npm:^4.0.4": + version: 4.0.4 + resolution: "@smithy/util-middleware@npm:4.0.4" + dependencies: + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/39530add63ec13dac555846c30e98128316136f7f57bfd8fe876a8c15a7677cb64d0a33fd1f08b671096d769ab3f025d4d8c785a9d7a7cdf42fd0188236b0f32 + languageName: node + linkType: hard + +"@smithy/util-retry@npm:^4.0.6": + version: 4.0.6 + resolution: "@smithy/util-retry@npm:4.0.6" + dependencies: + "@smithy/service-error-classification": "npm:^4.0.6" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/b1d3a5875769300bb74d63243868eba8a8f3567a9b22776cfb11700cfdd10bf10b2ed96bffdc9527d9130daf1be2482ea9217e1865a94efed01fe66e688768f4 + languageName: node + linkType: hard + +"@smithy/util-stream@npm:^4.2.2": + version: 4.2.2 + resolution: "@smithy/util-stream@npm:4.2.2" + dependencies: + "@smithy/fetch-http-handler": "npm:^5.0.4" + "@smithy/node-http-handler": "npm:^4.0.6" + "@smithy/types": "npm:^4.3.1" + "@smithy/util-base64": "npm:^4.0.0" + "@smithy/util-buffer-from": "npm:^4.0.0" + "@smithy/util-hex-encoding": "npm:^4.0.0" + "@smithy/util-utf8": "npm:^4.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/5e4ef783e41185d291a72e8503d02fd5a5f7bd23f3d30198f3d738c0f27dd6d7ea131fe6fbe36a6ac69b8bd4207f7dfc75a15329764e6aa52f62c45bc5442619 + languageName: node + linkType: hard + +"@smithy/util-uri-escape@npm:^4.0.0": + version: 4.0.0 + resolution: "@smithy/util-uri-escape@npm:4.0.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10c0/23984624060756adba8aa4ab1693fe6b387ee5064d8ec4dfd39bb5908c4ee8b9c3f2dc755da9b07505d8e3ce1338c1867abfa74158931e4728bf3cfcf2c05c3d + languageName: node + linkType: hard + +"@smithy/util-utf8@npm:^2.0.0": + version: 2.3.0 + resolution: "@smithy/util-utf8@npm:2.3.0" + dependencies: + "@smithy/util-buffer-from": "npm:^2.2.0" + tslib: "npm:^2.6.2" + checksum: 10c0/e18840c58cc507ca57fdd624302aefd13337ee982754c9aa688463ffcae598c08461e8620e9852a424d662ffa948fc64919e852508028d09e89ced459bd506ab + languageName: node + linkType: hard + +"@smithy/util-utf8@npm:^4.0.0": + version: 4.0.0 + resolution: "@smithy/util-utf8@npm:4.0.0" + dependencies: + "@smithy/util-buffer-from": "npm:^4.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/28a5a5372cbf0b3d2e32dd16f79b04c2aec6f704cf13789db922e9686fde38dde0171491cfa4c2c201595d54752a319faaeeed3c325329610887694431e28c98 + languageName: node + linkType: hard + +"@smithy/util-waiter@npm:^4.0.6": + version: 4.0.6 + resolution: "@smithy/util-waiter@npm:4.0.6" + dependencies: + "@smithy/abort-controller": "npm:^4.0.4" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/4027aed03515dfb627c09e0d490f001b912def1142865d0ec8de1fc0422e7c71e96df3efc7b92c7fdfff9030105b2b4213120506d064ad346cc79124708c1b17 + languageName: node + linkType: hard + "@strongtz/win32-arm64-msvc@npm:^0.4.7": version: 0.4.7 resolution: "@strongtz/win32-arm64-msvc@npm:0.4.7" @@ -4930,6 +6172,13 @@ __metadata: languageName: node linkType: hard +"@types/uuid@npm:^9.0.1": + version: 9.0.8 + resolution: "@types/uuid@npm:9.0.8" + checksum: 10c0/b411b93054cb1d4361919579ef3508a1f12bf15b5fdd97337d3d351bece6c921b52b6daeef89b62340fd73fd60da407878432a1af777f40648cbe53a01723489 + languageName: node + linkType: hard + "@types/verror@npm:^1.10.3": version: 1.10.11 resolution: "@types/verror@npm:1.10.11" @@ -5811,6 +7060,7 @@ __metadata: "@agentic/tavily": "npm:^7.3.3" "@ant-design/v5-patch-for-react-19": "npm:^1.0.3" "@anthropic-ai/sdk": "npm:^0.41.0" + "@aws-sdk/client-s3": "npm:^3.840.0" "@cherrystudio/embedjs": "npm:^0.1.31" "@cherrystudio/embedjs-libsql": "npm:^0.1.31" "@cherrystudio/embedjs-loader-csv": "npm:^0.1.31" @@ -6691,6 +7941,13 @@ __metadata: languageName: node linkType: hard +"bowser@npm:^2.11.0": + version: 2.11.0 + resolution: "bowser@npm:2.11.0" + checksum: 10c0/04efeecc7927a9ec33c667fa0965dea19f4ac60b3fea60793c2e6cf06c1dcd2f7ae1dbc656f450c5f50783b1c75cf9dc173ba6f3b7db2feee01f8c4b793e1bd3 + languageName: node + linkType: hard + "brace-expansion@npm:^1.1.7": version: 1.1.11 resolution: "brace-expansion@npm:1.1.11" @@ -9955,6 +11212,17 @@ __metadata: languageName: node linkType: hard +"fast-xml-parser@npm:4.4.1": + version: 4.4.1 + resolution: "fast-xml-parser@npm:4.4.1" + dependencies: + strnum: "npm:^1.0.5" + bin: + fxparser: src/cli/cli.js + checksum: 10c0/7f334841fe41bfb0bf5d920904ccad09cefc4b5e61eaf4c225bf1e1bb69ee77ef2147d8942f783ee8249e154d1ca8a858e10bda78a5d78b8bed3f48dcee9bf33 + languageName: node + linkType: hard + "fast-xml-parser@npm:^4.5.0, fast-xml-parser@npm:^4.5.1": version: 4.5.3 resolution: "fast-xml-parser@npm:4.5.3" @@ -17573,7 +18841,7 @@ __metadata: languageName: node linkType: hard -"strnum@npm:^1.1.1": +"strnum@npm:^1.0.5, strnum@npm:^1.1.1": version: 1.1.2 resolution: "strnum@npm:1.1.2" checksum: 10c0/a0fce2498fa3c64ce64a40dada41beb91cabe3caefa910e467dc0518ef2ebd7e4d10f8c2202a6104f1410254cae245066c0e94e2521fb4061a5cb41831952392 @@ -18143,7 +19411,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.1, tslib@npm:^2.1.0, tslib@npm:^2.4.0, tslib@npm:^2.8.1": +"tslib@npm:^2.0.1, tslib@npm:^2.1.0, tslib@npm:^2.4.0, tslib@npm:^2.6.2, tslib@npm:^2.8.1": version: 2.8.1 resolution: "tslib@npm:2.8.1" checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62