@cloudbase/storage
Version:
cloudbase js sdk storage module
687 lines (623 loc) • 20.1 kB
text/typescript
import { constants, utils, helpers } from '@cloudbase/utilities'
import { ICloudbase } from '@cloudbase/types'
import { ICloudbaseComponent } from '@cloudbase/types/component'
import {
ICloudbaseFileMetaDataRes,
ICloudbaseFileInfo,
ICloudbaseUploadFileParams,
ICloudbaseUploadFileResult,
ICloudbaseGetUploadMetadataParams,
ICloudbaseDeleteFileParams,
ICloudbaseDeleteFileResult,
ICloudbaseGetTempFileURLResult,
ICloudbaseGetTempFileURLParams,
ICloudbaseDownloadFileResult,
ICloudbaseDownloadFileParams,
ICloudbaseCopyFileParams,
ICloudbaseCopyFileResult,
} from '@cloudbase/types/storage'
declare const cloudbase: ICloudbase
enum EUploadMethod {
put = 'put',
post = 'post',
}
const { getSdkName, ERRORS, COMMUNITY_SITE_URL } = constants
const { isArray, isString, isPalinObject, execCallback } = utils
const { catchErrorsDecorator } = helpers
const COMPONENT_NAME = 'storage'
function basename(path) {
const lastSlashIndex = path.lastIndexOf('/')
if (lastSlashIndex === -1) return path
return path.slice(lastSlashIndex + 1)
}
const storageGateWay = {
getUploadInfo: async (
request,
params: { path: string; headers?: Record<string, string> },
customReqOpts: ICloudbaseUploadFileParams['customReqOpts'],
) => {
let res = await request.gateWay(
{
path: 'storages',
name: 'get-objects-upload-info',
data: [
{
objectId: params.path,
...(params.headers ? { signedHeader: params.headers } : {}),
},
],
},
customReqOpts,
)
const data = res.data?.[0] || {}
res = {
...res,
data: {
...(data.code ? { ...data } : {}),
authorization: data.authorization,
token: data.token,
url: data.uploadUrl,
fileId: data.cloudObjectId,
cosFileId: data.cloudObjectMeta,
download_url: data.downloadUrl,
},
}
return res
},
getDownLoadInfo: async (
request,
params: { convertedFileList: Array<{ fileid: string }> },
customReqOpts: ICloudbaseUploadFileParams['customReqOpts'],
) => {
let res = await request.gateWay(
{
path: 'storages',
name: 'get-objects-download-info',
data: params.convertedFileList.map((v: any) => ({ cloudObjectId: v.fileid })),
},
customReqOpts,
)
res = {
...res,
data: {
download_list: res.data?.map(v => ({
code: v.code || 'SUCCESS',
message: v.message,
fileid: v.cloudObjectId,
download_url: v.downloadUrl,
fileID: v.cloudObjectId,
tempFileURL: v.downloadUrl,
})),
},
}
return res
},
delete: async (
request,
params: { fileList: Array<string> },
customReqOpts: ICloudbaseUploadFileParams['customReqOpts'],
) => {
let res = await request.gateWay(
{
path: 'storages',
name: 'delete-objects',
data: params.fileList.map(v => ({ cloudObjectId: v })),
},
customReqOpts,
)
res = {
...res,
data: {
delete_list: res.data?.map(v => ({
code: v.code || 'SUCCESS',
fileID: v.cloudObjectId,
message: v.message,
})),
},
}
return res
},
copyFile: async (
request,
params: {
convertedFileList: Array<{ src_path: string; dst_path: string; overwrite: boolean; remove_original: boolean }>
},
customReqOpts: ICloudbaseUploadFileParams['customReqOpts'],
) => {
let res = await request.gateWay(
{
path: 'storages',
name: 'copy-objects',
data: params.convertedFileList.map((v: any) => ({
srcPath: v.src_path,
dstPath: v.dst_path,
overwrite: v.overwrite,
removeOriginal: v.remove_original,
})),
},
customReqOpts,
)
res = {
...res,
data: {
copy_list: res.data?.map(v => ({
code: v.code || 'SUCCESS',
fileID: v.cloudObjectId,
message: v.message,
})),
},
}
return res
},
}
class CloudbaseStorage {
public isGateWay() {
// @ts-ignore
const { config } = this
const endPointMode = config.endPointMode || 'CLOUD_API'
return endPointMode === 'GATEWAY'
}
@catchErrorsDecorator({
customInfo: {
className: 'Cloudbase',
methodName: 'uploadFile',
},
title: '上传文件失败',
messages: [
'请确认以下各项:',
' 1 - 调用 uploadFile() 的语法或参数是否正确',
' 2 - 当前域名是否在安全域名列表中:https://console.cloud.tencent.com/tcb/env/safety',
' 3 - 云存储安全规则是否限制了当前登录状态访问',
`如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
],
})
public async uploadFile(
params: ICloudbaseUploadFileParams,
callback?: Function,
): Promise<ICloudbaseUploadFileResult> {
const { cloudPath, filePath, onUploadProgress, method = 'put', headers = {}, fileContent } = params
if (!isString(cloudPath) || (!filePath && !fileContent)) {
throw new Error(JSON.stringify({
code: ERRORS.INVALID_PARAMS,
msg: `[${COMPONENT_NAME}.uploadFile] invalid params`,
}),)
}
const uploadMethod = { put: EUploadMethod.put, post: EUploadMethod.post }[method.toLocaleLowerCase()] || EUploadMethod.put
const action = 'storage.getUploadMetadata'
// @ts-ignore
const { request } = this
const metaDataParam: {
path: string
method: EUploadMethod
headers?: Record<any, any>
} = {
path: cloudPath,
method: uploadMethod,
}
if (uploadMethod === EUploadMethod.put) {
metaDataParam.headers = headers
}
let metaData: ICloudbaseFileMetaDataRes = {} as any
if (this.isGateWay()) {
metaData = await storageGateWay.getUploadInfo(request, metaDataParam, params.customReqOpts)
} else {
metaData = await request.send(action, metaDataParam, params.customReqOpts)
}
const {
data: { url, authorization, token, fileId, cosFileId, download_url: downloadUrl },
requestId,
} = metaData
if (metaData.data?.code) {
return execCallback(
callback,
new Error(`[${getSdkName()}][${ERRORS.OPERATION_FAIL}][${COMPONENT_NAME}]:${metaData.data}`),
)
}
const commonParams = {
url,
file: filePath,
name: cloudPath,
onUploadProgress,
fileContent,
fileId,
requestId,
}
const putParams = {
...commonParams,
method: EUploadMethod.put,
headers: {
...headers,
authorization,
'x-cos-meta-fileid': cosFileId,
'x-cos-security-token': token,
},
}
const postParams = {
...commonParams,
method: EUploadMethod.post,
data: {
key: cloudPath,
signature: authorization,
'x-cos-meta-fileid': cosFileId,
success_action_status: '201',
'x-cos-security-token': token,
},
}
const uploadConfig = {
[EUploadMethod.put]: {
params: putParams,
isSuccess: (code: number) => code >= 200 && code < 300,
},
[EUploadMethod.post]: {
params: postParams,
isSuccess: (code: number) => code === 201,
},
}
const res = await request.upload(uploadConfig[uploadMethod].params)
if (uploadConfig[uploadMethod].isSuccess(res.statusCode)) {
return execCallback(callback, null, {
fileID: fileId,
download_url: downloadUrl,
requestId,
})
}
return execCallback(
callback,
new Error(`[${getSdkName()}][${ERRORS.OPERATION_FAIL}][${COMPONENT_NAME}]:${res.data}`),
)
}
@catchErrorsDecorator({
customInfo: {
className: 'Cloudbase',
methodName: 'getUploadMetadata',
},
title: '获取上传元信息失败',
messages: [
'请确认以下各项:',
' 1 - 调用 getUploadMetadata() 的语法或参数是否正确',
' 2 - 当前域名是否在安全域名列表中:https://console.cloud.tencent.com/tcb/env/safety',
' 3 - 云存储安全规则是否限制了当前登录状态访问',
`如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
],
})
public async getUploadMetadata(params: ICloudbaseGetUploadMetadataParams, callback?: Function): Promise<any> {
const { cloudPath } = params
if (!isString(cloudPath)) {
throw new Error(JSON.stringify({
code: ERRORS.INVALID_PARAMS,
msg: `[${COMPONENT_NAME}.getUploadMetadata] invalid cloudPath`,
}),)
}
// @ts-ignore
const { request } = this
const action = 'storage.getUploadMetadata'
try {
let metaData: any = {}
if (this.isGateWay()) {
metaData = await storageGateWay.getUploadInfo(request, { path: cloudPath }, params.customReqOpts)
} else {
metaData = await request.send(
action,
{
path: cloudPath,
},
params.customReqOpts,
)
}
return execCallback(callback, null, metaData)
} catch (err) {
return execCallback(callback, err)
}
}
@catchErrorsDecorator({
customInfo: {
className: 'Cloudbase',
methodName: 'deleteFile',
},
title: '删除文件失败',
messages: [
'请确认以下各项:',
' 1 - 调用 deleteFile() 的语法或参数是否正确',
' 2 - 当前域名是否在安全域名列表中:https://console.cloud.tencent.com/tcb/env/safety',
' 3 - 云存储安全规则是否限制了当前登录状态访问',
`如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
],
})
public async deleteFile(
params: ICloudbaseDeleteFileParams,
callback?: Function,
): Promise<ICloudbaseDeleteFileResult> {
const { fileList } = params
if (!fileList || !isArray(fileList) || fileList.length === 0) {
throw new Error(JSON.stringify({
code: ERRORS.INVALID_PARAMS,
msg: `[${COMPONENT_NAME}.deleteFile] fileList must not be empty`,
}),)
}
for (const fileId of fileList) {
if (!fileId || !isString(fileId)) {
throw new Error(JSON.stringify({
code: ERRORS.INVALID_PARAMS,
msg: `[${COMPONENT_NAME}.deleteFile] fileID must be string`,
}),)
}
}
const action = 'storage.batchDeleteFile'
// @ts-ignore
const { request } = this
let res: any = {}
if (this.isGateWay()) {
res = await storageGateWay.delete(request, { fileList }, params.customReqOpts)
} else {
res = await request.send(
action,
{
fileid_list: fileList,
},
params.customReqOpts,
)
}
if (res.code) {
return execCallback(callback, null, res)
}
const data = {
fileList: res.data.delete_list,
requestId: res.requestId,
}
return execCallback(callback, null, data)
}
@catchErrorsDecorator({
customInfo: {
className: 'Cloudbase',
methodName: 'getTempFileURL',
},
title: '获取文件下载链接',
messages: [
'请确认以下各项:',
' 1 - 调用 getTempFileURL() 的语法或参数是否正确',
' 2 - 当前域名是否在安全域名列表中:https://console.cloud.tencent.com/tcb/env/safety',
' 3 - 云存储安全规则是否限制了当前登录状态访问',
`如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
],
})
public async getTempFileURL(
params: ICloudbaseGetTempFileURLParams,
callback?: Function,
): Promise<ICloudbaseGetTempFileURLResult> {
const { fileList } = params
if (!fileList || !isArray(fileList) || fileList.length === 0) {
throw new Error(JSON.stringify({
code: ERRORS.INVALID_PARAMS,
msg: `[${COMPONENT_NAME}.getTempFileURL] fileList must not be empty`,
}),)
}
const convertedFileList = []
for (const file of fileList) {
if (isPalinObject(file)) {
if (
!Object.prototype.hasOwnProperty.call(file, 'fileID')
|| !Object.prototype.hasOwnProperty.call(file, 'maxAge')
) {
throw new Error(JSON.stringify({
code: ERRORS.INVALID_PARAMS,
msg: `[${COMPONENT_NAME}.getTempFileURL] file info must include fileID and maxAge`,
}),)
}
convertedFileList.push({
fileid: (file as ICloudbaseFileInfo).fileID,
max_age: (file as ICloudbaseFileInfo).maxAge,
})
} else if (isString(file)) {
convertedFileList.push({
fileid: file,
})
} else {
throw new Error(JSON.stringify({
code: ERRORS.INVALID_PARAMS,
msg: `[${COMPONENT_NAME}.getTempFileURL] invalid fileList`,
}),)
}
}
const action = 'storage.batchGetDownloadUrl'
// @ts-ignore
const { request } = this
let res: any = {}
if (this.isGateWay()) {
res = await storageGateWay.getDownLoadInfo(request, { convertedFileList }, params.customReqOpts)
} else {
res = await request.send(action, { file_list: convertedFileList }, params.customReqOpts)
}
if (res.code) {
return execCallback(callback, null, res)
}
return execCallback(callback, null, {
fileList: res.data.download_list,
requestId: res.requestId,
})
}
@catchErrorsDecorator({
customInfo: {
className: 'Cloudbase',
methodName: 'downloadFile',
},
title: '下载文件失败',
messages: [
'请确认以下各项:',
' 1 - 调用 downloadFile() 的语法或参数是否正确',
' 2 - 当前域名是否在安全域名列表中:https://console.cloud.tencent.com/tcb/env/safety',
' 3 - 云存储安全规则是否限制了当前登录状态访问',
`如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
],
})
public async downloadFile(
params: ICloudbaseDownloadFileParams,
callback?: Function,
): Promise<ICloudbaseDownloadFileResult> {
const { fileID } = params
if (!isString(fileID)) {
throw new Error(JSON.stringify({
code: ERRORS.INVALID_PARAMS,
msg: `[${COMPONENT_NAME}.getTempFileURL] fileID must be string`,
}),)
}
const tmpUrlRes = await this.getTempFileURL.call(this, {
fileList: [
{
fileID,
maxAge: 600,
},
],
customReqOpts: params.customReqOpts,
})
const res = tmpUrlRes.fileList[0]
if (res.code !== 'SUCCESS') {
return execCallback(callback, res)
}
// @ts-ignore
const { request } = this
const tmpUrl = encodeURI(res.download_url)
const result = await request.download({ url: tmpUrl, tempFilePath: params.tempFilePath })
return execCallback(callback, null, result)
}
@catchErrorsDecorator({
customInfo: {
className: 'Cloudbase',
methodName: 'copyFile',
},
title: '批量复制文件',
messages: [
'请确认以下各项:',
' 1 - 调用 copyFile() 的语法或参数是否正确',
' 2 - 当前域名是否在安全域名列表中:https://console.cloud.tencent.com/tcb/env/safety',
' 3 - 云存储安全规则是否限制了当前登录状态访问',
`如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
],
})
public async copyFile(params: ICloudbaseCopyFileParams, callback?: Function): Promise<ICloudbaseCopyFileResult> {
const { fileList } = params
if (!fileList || !isArray(fileList) || fileList.length === 0) {
throw new Error(JSON.stringify({
code: ERRORS.INVALID_PARAMS,
msg: `[${COMPONENT_NAME}.copyFile] fileList must not be empty`,
}),)
}
const convertedFileList = []
for (const file of fileList) {
const { srcPath, dstPath } = file
if (!srcPath || !dstPath || typeof srcPath !== 'string' || typeof dstPath !== 'string') {
throw new Error(JSON.stringify({
code: ERRORS.INVALID_PARAMS,
msg: `[${COMPONENT_NAME}.copyFile] srcPath and dstPath may not be empty`,
}),)
}
if (srcPath === dstPath) {
throw new Error(JSON.stringify({
code: ERRORS.INVALID_PARAMS,
msg: `[${COMPONENT_NAME}.copyFile] srcPath and dstPath can not be the same`,
}),)
}
if (basename(srcPath) !== basename(dstPath)) {
throw new Error(JSON.stringify({
code: ERRORS.INVALID_PARAMS,
msg: `[${COMPONENT_NAME}.copyFile] srcPath and dstPath file name must be the same`,
}),)
}
convertedFileList.push({
src_path: srcPath,
dst_path: dstPath,
overwrite: file.overwrite,
remove_original: file.removeOriginal,
})
}
const action = 'storage.batchCopyFile'
// @ts-ignore
const { request } = this
let res: any = {}
if (this.isGateWay()) {
res = await storageGateWay.copyFile(request, { convertedFileList }, params.customReqOpts)
} else {
res = await request.send(action, { file_list: convertedFileList }, params.customReqOpts)
}
if (res.code) {
return execCallback(callback, null, res)
}
return execCallback(callback, null, {
fileList: res.data.copy_list,
requestId: res.requestId,
})
}
public async getFileInfo(params: ICloudbaseGetTempFileURLParams): Promise<ICloudbaseGetTempFileURLResult> {
const fileInfo = await this.getTempFileURL(params)
if (fileInfo?.fileList && fileInfo?.fileList?.length > 0) {
const fileList = await Promise.all(fileInfo.fileList.map(async (item: ICloudbaseGetTempFileURLResult['fileList'][0]) => {
if (item.code !== 'SUCCESS') {
return {
code: item.code,
fileID: item.fileID,
tempFileURL: item.tempFileURL,
}
}
try {
// @ts-ignore
const { request } = this
const res = await request.fetch({ url: item.tempFileURL, method: 'HEAD' })
// eslint-disable-next-line radix
const fileSize = parseInt(res.headers['content-length']) || 0
const contentType = res.headers['content-type'] || ''
const fileInfo = {
code: item.code,
fileID: item.fileID,
tempFileURL: item.tempFileURL,
cloudId: item.fileID,
fileName: item.fileID.split('/').pop(),
contentType,
mime: contentType.split(';')[0].trim(),
size: fileSize,
}
return fileInfo
} catch (e) {
return {
code: 'FETCH_FILE_INFO_ERROR',
fileID: item.fileID,
tempFileURL: item.tempFileURL,
}
}
}),)
return {
fileList,
requestId: fileInfo.requestId,
}
}
return {
fileList: [],
requestId: fileInfo.requestId,
}
}
}
const cloudbaseStorage = new CloudbaseStorage()
const component: ICloudbaseComponent = {
name: COMPONENT_NAME,
entity: {
uploadFile: cloudbaseStorage.uploadFile,
deleteFile: cloudbaseStorage.deleteFile,
getTempFileURL: cloudbaseStorage.getTempFileURL,
downloadFile: cloudbaseStorage.downloadFile,
getUploadMetadata: cloudbaseStorage.getUploadMetadata,
copyFile: cloudbaseStorage.copyFile,
getFileInfo: cloudbaseStorage.getFileInfo,
isGateWay: cloudbaseStorage.isGateWay,
},
}
try {
cloudbase.registerComponent(component)
} catch (e) {}
export function registerStorage(app: Pick<ICloudbase, 'registerComponent'>) {
try {
app.registerComponent(component)
} catch (e) {
console.warn(e)
}
}
try {
(window as any).registerStorage = registerStorage
} catch (e) {}