@cloudbase/node-sdk
Version:
tencent cloud base server sdk for node.js
357 lines (356 loc) • 15.8 kB
JavaScript
"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.request = exports.TcbApiHttpRequester = exports.prepareCredentials = exports.getCredentialsOnDemand = exports.getEnvIdFromContext = void 0;
const http_1 = __importDefault(require("http"));
/* eslint-disable-next-line */
const url_1 = require("url");
const signature_nodejs_1 = require("@cloudbase/signature-nodejs");
const code_1 = require("../const/code");
const symbol_1 = require("../const/symbol");
const tracing_1 = require("./tracing");
const utils = __importStar(require("./utils"));
const cloudbase_1 = require("../cloudbase");
const cloudplatform_1 = require("./cloudplatform");
const tcbapiendpoint_1 = require("./tcbapiendpoint");
const request_1 = require("./request");
const wxCloudToken_1 = require("./wxCloudToken");
const version_1 = require("./version");
const { E, second, processReturn } = utils;
function getEnvIdFromContext() {
const { TCB_ENV, SCF_NAMESPACE } = cloudbase_1.CloudBase.getCloudbaseContext();
return TCB_ENV || SCF_NAMESPACE || '';
}
exports.getEnvIdFromContext = getEnvIdFromContext;
function getCredentialsOnDemand(credentials) {
const { secretId, secretKey } = credentials;
let newCredentials = credentials;
// 原本这里只在SCF云函数环境下,运行支持任意环境通过环境变量传递密钥
if (!secretId || !secretKey) {
// 尝试从环境变量中读取
const { TENCENTCLOUD_SECRETID, TENCENTCLOUD_SECRETKEY, TENCENTCLOUD_SESSIONTOKEN } = cloudbase_1.CloudBase.getCloudbaseContext();
if (TENCENTCLOUD_SECRETID && TENCENTCLOUD_SECRETKEY) {
newCredentials = {
secretId: TENCENTCLOUD_SECRETID,
secretKey: TENCENTCLOUD_SECRETKEY,
sessionToken: TENCENTCLOUD_SESSIONTOKEN
};
}
// 注意:CBR 环境下,已经禁止该方式获取临时密钥,这里实际是不会成功的
// if (checkIsInCBR()) {
// const tmpSecret = await getTmpSecret()
// newCredentials = {
// secretId: tmpSecret.id,
// secretKey: tmpSecret.key,
// sessionToken: tmpSecret.token
// }
// return newCredentials
// }
// if (await checkIsInTencentCloud()) {
// const tmpSecret = await getTmpSecret()
// newCredentials = {
// secretId: tmpSecret.id,
// secretKey: tmpSecret.key,
// sessionToken: tmpSecret.token
// }
// return newCredentials
// }
}
return newCredentials;
}
exports.getCredentialsOnDemand = getCredentialsOnDemand;
async function prepareCredentials() {
const opts = this.opts;
// CrossAccountInfo: 跨账号调用
const getCrossAccountInfo = opts.getCrossAccountInfo || this.config.getCrossAccountInfo;
/* istanbul ignore if */
if (getCrossAccountInfo) {
const crossAccountInfo = await getCrossAccountInfo();
const { credential } = crossAccountInfo;
const { secretId, secretKey, token } = credential || {};
this.config = Object.assign(Object.assign({}, this.config), { secretId,
secretKey, sessionToken: token });
if (!this.config.secretId || !this.config.secretKey) {
throw E(Object.assign(Object.assign({}, code_1.ERROR.INVALID_PARAM), { message: 'missing secretId or secretKey of tencent cloud' }));
}
// 替换掉原函数,缓存数据,这里缓存是否起作用,取决于 this 实例是否复用
// 另一处获取 authorization 的代码可以服用吃这里的缓存
this.opts.getCrossAccountInfo = async () => await Promise.resolve(crossAccountInfo);
}
else {
const { secretId, secretKey, sessionToken } = this.config;
const credentials = getCredentialsOnDemand({ secretId, secretKey, sessionToken });
this.config = Object.assign(Object.assign({}, this.config), { secretId: credentials.secretId, secretKey: credentials.secretKey, sessionToken: credentials.sessionToken });
if (!this.config.secretId || !this.config.secretKey) {
throw E(Object.assign(Object.assign({}, code_1.ERROR.INVALID_PARAM), { message: 'missing secretId or secretKey of tencent cloud, please set secretId and secretKey in config' }));
}
}
}
exports.prepareCredentials = prepareCredentials;
class TcbApiHttpRequester {
constructor(args) {
var _a, _b;
this.defaultTimeout = 15000;
this.timestamp = new Date().valueOf();
/* eslint-disable no-undef */
this.slowWarnTimer = null;
/* eslint-enable no-undef */
this.hooks = {};
this.args = args;
this.config = args.config;
this.opts = args.opts || {};
this.tracingInfo = (0, tracing_1.generateTracingInfo)((_b = (_a = args.config) === null || _a === void 0 ? void 0 : _a.context) === null || _b === void 0 ? void 0 : _b.eventID);
}
async request() {
await this.prepareCredentials();
const params = await this.makeParams();
const opts = this.makeReqOpts(params);
const action = this.getAction();
const key = {
functions: 'function_name',
database: 'collectionName',
wx: 'apiName'
}[action.split('.')[0]];
const argopts = this.opts;
const config = this.config;
// 注意:必须初始化为 null
let retryOptions = null;
if (argopts.retryOptions) {
retryOptions = argopts.retryOptions;
}
else if (config.retries && typeof config.retries === 'number') {
retryOptions = { retries: config.retries };
}
return await (0, request_1.extraRequest)(opts, {
debug: config.debug,
op: `${action}:${this.args.params[key]}@${params.envName}`,
seqId: this.tracingInfo.seqId,
retryOptions,
timingsMeasurerOptions: config.timingsMeasurerOptions || {}
}).then((response) => {
this.slowWarnTimer && clearTimeout(this.slowWarnTimer);
const { body } = response;
if (response.statusCode === 200) {
let result;
try {
result = typeof body === 'string' ? JSON.parse(body) : body;
if (this.hooks && this.hooks.handleData) {
result = this.hooks.handleData(result, null, response, body);
}
}
catch (e) {
result = body;
}
return result;
}
else {
const e = E({
code: response.statusCode,
message: `${response.statusCode} ${http_1.default.STATUS_CODES[response.statusCode]} | [${opts.url}]`
});
throw e;
}
});
}
setHooks(hooks) {
Object.assign(this.hooks, hooks);
}
setSlowWarning(timeout) {
const action = this.getAction();
const { seqId } = this.tracingInfo;
this.slowWarnTimer = setTimeout(() => {
/* istanbul ignore next */
const msg = `[TCB][WARN] Your current request ${action
|| ''} is longer than 3s, it may be due to the network or your query performance | [${seqId}]`;
/* istanbul ignore next */
console.warn(msg);
}, timeout);
}
getAction() {
return this.args.params.action;
}
async makeParams() {
const { TCB_SESSIONTOKEN } = cloudbase_1.CloudBase.getCloudbaseContext();
const args = this.args;
const opts = this.opts;
const config = this.config;
const crossAuthorizationData = opts.getCrossAccountInfo && (await opts.getCrossAccountInfo()).authorization;
const { wxCloudApiToken, wxCloudbaseAccesstoken } = (0, wxCloudToken_1.getWxCloudToken)();
const params = Object.assign(Object.assign({}, args.params), { envName: config.envName || '', wxCloudApiToken,
wxCloudbaseAccesstoken, tcb_sessionToken: TCB_SESSIONTOKEN || '', sessionToken: config.sessionToken, crossAuthorizationToken: crossAuthorizationData
? Buffer.from(JSON.stringify(crossAuthorizationData)).toString('base64')
: '' });
if (!params.envName) {
if ((0, cloudplatform_1.checkIsInScf)()) {
params.envName = getEnvIdFromContext();
console.warn(`[TCB][WARN] 当前未指定env,将默认使用当前函数所在环境的环境:${params.envName}!`);
}
else {
console.warn('[TCB][WARN] 当前未指定env,将默认使用第一个创建的环境!');
}
}
// 取当前云函数环境时,替换为云函数下环境变量
if (params.envName === symbol_1.SYMBOL_CURRENT_ENV) {
params.envName = getEnvIdFromContext();
}
else if (params.envName === symbol_1.SYMBOL_DEFAULT_ENV) {
// 这里传空字符串没有可以跟不传的情况做一个区分
params.envName = '';
}
utils.filterUndefined(params);
return params;
}
makeReqOpts(params) {
var _a;
const config = this.config;
const args = this.args;
const url = (0, tcbapiendpoint_1.buildUrl)({
envId: params.envName || '',
region: this.config.region,
protocol: this.config.protocol || 'https',
serviceUrl: this.config.serviceUrl,
seqId: this.tracingInfo.seqId,
isInternal: this.args.isInternal
});
const method = this.args.method || 'get';
const timeout = ((_a = this.args.opts) === null || _a === void 0 ? void 0 : _a.timeout) || this.config.timeout || this.defaultTimeout;
const opts = {
url,
method,
timeout,
// 优先取config,其次取模块,最后取默认
headers: this.getHeaders(method, url, params),
proxy: config.proxy
};
if (typeof config.keepalive === 'undefined' && !(0, cloudplatform_1.checkIsInScf)()) {
// 非云函数环境下,默认开启 keepalive
opts.keepalive = true;
}
else {
/** eslint-disable-next-line */
opts.keepalive = typeof config.keepalive === 'boolean' && config.keepalive;
}
if (args.method === 'post') {
if (args.isFormData) {
opts.formData = params;
opts.encoding = null;
}
else {
opts.body = params;
opts.json = true;
}
}
else {
/* istanbul ignore next */
opts.qs = params;
}
return opts;
}
async prepareCredentials() {
prepareCredentials.bind(this)();
}
getHeaders(method, url, params) {
var _a;
const config = this.config;
const { context, secretId, secretKey } = config;
const args = this.args;
const { TCB_SOURCE } = cloudbase_1.CloudBase.getCloudbaseContext();
// Note: 云函数被调用时可能调用端未传递 SOURCE,TCB_SOURCE 可能为空
const SOURCE = `${((_a = context === null || context === void 0 ? void 0 : context.extendedContext) === null || _a === void 0 ? void 0 : _a.source) || TCB_SOURCE || ''},${args.opts.runEnvTag}`;
// 注意:因为 url.parse 和 url.URL 存在差异,因 url.parse 已被废弃,这里可能会需要改动。
// 因 @cloudbase/signature-nodejs sign 方法目前内部使用 url.parse 解析 url,
// 如果这里需要改动,需要注意与 @cloudbase/signature-nodejs 的兼容性
// 否则将导致签名存在问题
const parsedUrl = (0, url_1.parse)(url);
// const parsedUrl = new URL(url)
let requiredHeaders = {
'User-Agent': `tcb-node-sdk/${version_1.version}`,
'X-TCB-Source': SOURCE,
'X-Client-Timestamp': this.timestamp,
'X-SDK-Version': `tcb-node-sdk/${version_1.version}`,
Host: parsedUrl.host
};
if (config.version) {
requiredHeaders['X-SDK-Version'] = config.version;
}
if (this.tracingInfo.trace) {
requiredHeaders['X-TCB-Tracelog'] = this.tracingInfo.trace;
}
const region = this.config.region || process.env.TENCENTCLOUD_REGION || '';
if (region) {
requiredHeaders['X-TCB-Region'] = region;
}
requiredHeaders = Object.assign(Object.assign(Object.assign({}, config.headers), args.headers), requiredHeaders);
const { authorization, timestamp } = (0, signature_nodejs_1.sign)({
secretId,
secretKey,
method,
url,
params,
headers: requiredHeaders,
withSignedParams: true,
timestamp: second() - 1
});
/* eslint-disable @typescript-eslint/dot-notation */
requiredHeaders['Authorization'] = authorization;
requiredHeaders['X-Signature-Expires'] = 600;
requiredHeaders['X-Timestamp'] = timestamp;
return Object.assign({}, requiredHeaders);
}
}
exports.TcbApiHttpRequester = TcbApiHttpRequester;
const handleWxOpenApiData = (res, err, response, body) => {
// wx.openApi 调用时,需用content-type区分buffer or JSON
const { headers } = response;
let transformRes = res;
if (headers['content-type'] === 'application/json; charset=utf-8') {
transformRes = JSON.parse(transformRes.toString()); // JSON错误时buffer转JSON
}
return transformRes;
};
async function request(args) {
if (typeof args.isInternal === 'undefined') {
args.isInternal = await (0, cloudplatform_1.checkIsInternalAsync)();
}
args.opts = args.opts || {};
args.opts.runEnvTag = await (0, cloudplatform_1.getCurrRunEnvTag)();
const requester = new TcbApiHttpRequester(args);
const { action } = args.params;
if (action === 'wx.openApi' || action === 'wx.wxPayApi') {
requester.setHooks({ handleData: handleWxOpenApiData });
}
if (action.startsWith('database') && process.env.SILENCE !== 'true') {
requester.setSlowWarning(3000);
}
const result = await requester.request();
if (result === null || result === void 0 ? void 0 : result.code) {
return processReturn(result);
}
return result;
}
exports.request = request;