UNPKG

@cloudbase/node-sdk

Version:

tencent cloud base server sdk for node.js

407 lines (406 loc) 15.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.copyFile = exports.getFileAuthority = exports.getUploadMetadata = exports.downloadFile = exports.getFileInfo = exports.getTempFileURL = exports.deleteFile = exports.uploadFile = void 0; const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const stream_1 = require("stream"); const xml2js_1 = require("xml2js"); const tcbapicaller = __importStar(require("../utils/tcbapirequester")); const request_core_1 = require("../utils/request-core"); const utils_1 = require("../utils/utils"); const code_1 = require("../const/code"); const cloudbase_1 = require("../cloudbase"); async function parseXML(str) { return await new Promise((resolve, reject) => { (0, xml2js_1.parseString)(str, (err, result) => { if (err) { reject(err); } else { resolve(result); } }); }); } /** * 上传文件 * @param {string} cloudPath 上传后的文件路径 * @param {fs.ReadStream | Buffer} fileContent 上传文件的二进制流 */ async function uploadFile(cloudbase, { cloudPath, fileContent }, opts) { if (!(fileContent instanceof fs_1.default.ReadStream) && !(fileContent instanceof Buffer)) { throw (0, utils_1.E)(Object.assign(Object.assign({}, code_1.ERROR.INVALID_PARAM), { message: '[node-sdk] fileContent should be instance of fs.ReadStream or Buffer' })); } const { requestId, data: { url, token, authorization, fileId, cosFileId } } = await getUploadMetadata(cloudbase, { cloudPath }, opts); const headers = { Signature: authorization, 'x-cos-security-token': token, 'x-cos-meta-fileid': cosFileId, authorization, key: encodeURIComponent(cloudPath) }; const fileStream = stream_1.Readable.from(fileContent); let body = await new Promise((resolve, reject) => { const req = (0, request_core_1.request)({ method: 'put', url, headers, type: 'raw' }, (err, _, body) => { if (err) { reject(err); } else { resolve(body); } }); req.on('error', (err) => { reject(err); }); // automatically close, no need to call req.end fileStream.pipe(req); }); // 成功返回空字符串,失败返回如下格式 XML: // <?xml version='1.0' encoding='utf-8' ?> // <Error> // <Code>InvalidAccessKeyId</Code> // <Message>The Access Key Id you provided does not exist in our records</Message> // <Resource>/path/to/file/key.xyz</Resource> // <RequestId>NjQzZTMyYzBfODkxNGJlMDlfZjU4NF9hMjk4YTUy</RequestId> // <TraceId>OGVmYzZiMmQzYjA2OWNhODk0NTRkMTBiOWVmMDAxODc0OWRkZjk0ZDM1NmI1M2E2MTRlY2MzZDhmNmI5MWI1OTQyYWVlY2QwZTk2MDVmZDQ3MmI2Y2I4ZmI5ZmM4ODFjYmRkMmZmNzk1YjUxODZhZmZlNmNhYWUyZTQzYjdiZWY=</TraceId> // </Error> body = await parseXML(body); if (body === null || body === void 0 ? void 0 : body.Error) { const { Code: [code], Message: [message], RequestId: [cosRequestId], TraceId: [cosTraceId] } = body.Error; if (code === 'SignatureDoesNotMatch') { return (0, utils_1.processReturn)(Object.assign(Object.assign({}, code_1.ERROR.SYS_ERR), { message: `[${code}]: ${message}`, requestId: `${requestId}|${cosRequestId}|${cosTraceId}` })); } return (0, utils_1.processReturn)(Object.assign(Object.assign({}, code_1.ERROR.STORAGE_REQUEST_FAIL), { message: `[${code}]: ${message}`, requestId: `${requestId}|${cosRequestId}|${cosTraceId}` })); } return { fileID: fileId }; } exports.uploadFile = uploadFile; /** * 删除文件 * @param {Array.<string>} fileList 文件id数组 */ async function deleteFile(cloudbase, { fileList }, opts) { if (!fileList || !Array.isArray(fileList)) { return (0, utils_1.processReturn)(Object.assign(Object.assign({}, code_1.ERROR.INVALID_PARAM), { message: 'fileList必须是非空的数组' })); } for (const file of fileList) { if (!file || typeof file !== 'string') { return (0, utils_1.processReturn)(Object.assign(Object.assign({}, code_1.ERROR.INVALID_PARAM), { message: 'fileList的元素必须是非空的字符串' })); } } const params = { action: 'storage.batchDeleteFile', fileid_list: fileList }; return await tcbapicaller.request({ config: cloudbase.config, params, method: 'post', opts, headers: { 'content-type': 'application/json' } }).then(res => { if (res.code) { return res; } // throw E({ ...res }) // } else { return { fileList: res.data.delete_list, requestId: res.requestId }; // } }); } exports.deleteFile = deleteFile; /** * 获取文件下载链接 * @param {Array.<Object>} fileList */ async function getTempFileURL(cloudbase, { fileList }, opts) { if (!fileList || !Array.isArray(fileList)) { return (0, utils_1.processReturn)(Object.assign(Object.assign({}, code_1.ERROR.INVALID_PARAM), { message: 'fileList必须是非空的数组' })); } /* eslint-disable-next-line @typescript-eslint/naming-convention */ const file_list = []; for (const file of fileList) { if (typeof file === 'object') { if (!Object.prototype.hasOwnProperty.call(file, 'fileID') || !Object.prototype.hasOwnProperty.call(file, 'maxAge')) { return (0, utils_1.processReturn)(Object.assign(Object.assign({}, code_1.ERROR.INVALID_PARAM), { message: 'fileList 的元素如果是对象,必须是包含 fileID 和 maxAge 的对象' })); } file_list.push({ fileid: file.fileID, max_age: file.maxAge, url_type: file.urlType }); } else if (typeof file === 'string') { file_list.push({ fileid: file }); } else { return (0, utils_1.processReturn)(Object.assign(Object.assign({}, code_1.ERROR.INVALID_PARAM), { message: 'fileList的元素如果不是对象,则必须是字符串' })); } } const params = { action: 'storage.batchGetDownloadUrl', file_list }; return await tcbapicaller.request({ config: cloudbase.config, params, method: 'post', opts, headers: { 'content-type': 'application/json' } }).then(res => { if (res.code) { return res; } return { fileList: res.data.download_list, requestId: res.requestId }; }); } exports.getTempFileURL = getTempFileURL; async function getFileInfo(cloudbase, { fileList }, opts) { var _a; const fileInfo = await getTempFileURL(cloudbase, { fileList }, opts); if ((fileInfo === null || fileInfo === void 0 ? void 0 : fileInfo.fileList) && ((_a = fileInfo === null || fileInfo === void 0 ? void 0 : fileInfo.fileList) === null || _a === void 0 ? void 0 : _a.length) > 0) { const fileList = await Promise.all(fileInfo.fileList.map(async (item) => { if (item.code !== 'SUCCESS') { return { code: item.code, fileID: item.fileID, tempFileURL: item.tempFileURL }; } try { const res = await fetch(encodeURI(item.tempFileURL), { method: 'HEAD' }); const fileSize = parseInt(res.headers.get('content-length')) || 0; const contentType = res.headers.get('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 }; } exports.getFileInfo = getFileInfo; async function downloadFile(cloudbase, { fileID, urlType, tempFilePath }, opts) { const tmpUrlRes = await getTempFileURL(cloudbase, { fileList: [ { fileID, urlType, maxAge: 600 } ] }, opts); const res = tmpUrlRes.fileList[0]; if (res.code !== 'SUCCESS') { return (0, utils_1.processReturn)(Object.assign({}, res)); } // COS_URL 场景下,不需要再进行 Encode URL const tmpUrl = urlType === 'COS_URL' ? res.tempFileURL : encodeURI(res.tempFileURL); return await new Promise((resolve, reject) => { const reqOpts = { method: 'get', url: tmpUrl, type: tempFilePath ? 'stream' : 'raw' }; const req = (0, request_core_1.request)(reqOpts, (err, res, body) => { if (err) { reject(err); } else { if (tempFilePath) { res.pipe(fs_1.default.createWriteStream(tempFilePath, { autoClose: true })); } if (res.statusCode === 200) { resolve({ fileContent: tempFilePath ? undefined : body, message: '文件下载完成' }); } else { reject((0, utils_1.E)(Object.assign(Object.assign({}, code_1.ERROR.STORAGE_REQUEST_FAIL), { message: `下载文件失败: Status:${res.statusCode} Url:${tmpUrl}`, requestId: res.headers['x-cos-request-id'] }))); } } }); req.on('error', (err) => { if (tempFilePath) { fs_1.default.unlinkSync(tempFilePath); } reject(err); }); }); } exports.downloadFile = downloadFile; async function getUploadMetadata(cloudbase, { cloudPath }, opts) { const params = { action: 'storage.getUploadMetadata', path: cloudPath, method: 'put' // 使用 put 方式上传 }; const res = await tcbapicaller.request({ config: cloudbase.config, params, method: 'post', opts, headers: { 'content-type': 'application/json' } }); return res; } exports.getUploadMetadata = getUploadMetadata; async function getFileAuthority(cloudbase, { fileList }, opts) { const { LOGINTYPE } = cloudbase_1.CloudBase.getCloudbaseContext(); if (!Array.isArray(fileList)) { throw (0, utils_1.E)(Object.assign(Object.assign({}, code_1.ERROR.INVALID_PARAM), { message: '[node-sdk] getCosFileAuthority fileList must be a array' })); } if (fileList.some(file => { if (!(file === null || file === void 0 ? void 0 : file.path)) { return true; } if (!['READ', 'WRITE', 'READWRITE'].includes(file.type)) { return true; } return false; })) { throw (0, utils_1.E)(Object.assign(Object.assign({}, code_1.ERROR.INVALID_PARAM), { message: '[node-sdk] getCosFileAuthority fileList param error' })); } const userInfo = cloudbase.auth().getUserInfo(); const { openId, uid } = userInfo; if (!openId && !uid) { throw (0, utils_1.E)(Object.assign(Object.assign({}, code_1.ERROR.INVALID_PARAM), { message: '[node-sdk] admin do not need getCosFileAuthority.' })); } const params = { action: 'storage.getFileAuthority', openId, uid, loginType: LOGINTYPE, fileList }; const res = await tcbapicaller.request({ config: cloudbase.config, params, method: 'post', opts, headers: { 'content-type': 'application/json' } }); if (res.code) { /* istanbul ignore next */ throw (0, utils_1.E)(Object.assign(Object.assign({}, res), { message: '[node-sdk] getCosFileAuthority failed: ' + res.code })); } else { return res; } } exports.getFileAuthority = getFileAuthority; async function copyFile(cloudbase, { fileList }, opts) { // 参数校验 if (!fileList || !Array.isArray(fileList) || fileList.length === 0) { return (0, utils_1.processReturn)(Object.assign(Object.assign({}, code_1.ERROR.INVALID_PARAM), { message: 'fileList必须是非空的数组' })); } const list = []; for (const file of fileList) { const { srcPath, dstPath } = file; if (!srcPath || !dstPath || typeof srcPath !== 'string' || typeof dstPath !== 'string') { return (0, utils_1.processReturn)(Object.assign(Object.assign({}, code_1.ERROR.INVALID_PARAM), { message: 'srcPath和dstPath必须是非空的字符串' })); } if (srcPath === dstPath) { return (0, utils_1.processReturn)(Object.assign(Object.assign({}, code_1.ERROR.INVALID_PARAM), { message: 'srcPath和dstPath不能相同' })); } if (path_1.default.basename(srcPath) !== path_1.default.basename(dstPath)) { return (0, utils_1.processReturn)(Object.assign(Object.assign({}, code_1.ERROR.INVALID_PARAM), { message: 'srcPath和dstPath的文件名必须相同' })); } list.push({ src_path: srcPath, dst_path: dstPath, overwrite: file.overwrite, remove_original: file.removeOriginal }); } const params = { action: 'storage.batchCopyFile', file_list: list }; return await tcbapicaller.request({ config: cloudbase.config, params, method: 'post', opts, headers: { 'content-type': 'application/json' } }).then(res => { if (res.code) { return res; } return { fileList: res.data.copy_list, requestId: res.requestId }; }); } exports.copyFile = copyFile;