@cloudbase/node-sdk
Version:
tencent cloud base server sdk for node.js
407 lines (406 loc) • 15.8 kB
JavaScript
;
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;