UNPKG

@cloudbase/node-sdk

Version:

tencent cloud base server sdk for node.js

357 lines (356 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.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;