UNPKG

@cloudbase/node-sdk

Version:

tencent cloud base server sdk for node.js

389 lines (325 loc) 12.8 kB
import axios from 'axios' import wxCloudClient, { IMySqlClient, IMySqlOptions, OrmClient, OrmRawQueryClient } from '@cloudbase/wx-cloud-client-sdk' import { ICloudBaseConfig, ISCFContext, IContextParam, ICompleteCloudbaseContext, ICustomReqOpts, ICallFunctionOptions, ICallContainerOptions, IUploadFileOptions, IUploadFileResult, IDownloadFileOptions, IDownloadFileResult, ICopyFileOptions, ICopyFileResult, IDeleteFileOptions, IDeleteFileResult, IGetFileUrlOptions, IGetFileUrlResult, IGetFileInfoOptions, IGetFileInfoResult, IGetUploadMetadataOptions, IGetUploadMetadataResult, IGetFileAuthorityOptions, IGetFileAuthorityResult, ICallWxOpenApiOptions, ICallWxOpenApiResult, IReportData, Extension, ICallApisOptions, ICloudBaseDBConfig } from '../types' import { auth } from './auth' import { callApis, callFunction } from './functions' import { callContainer } from './cloudrun' import { newDb } from './database' import { uploadFile, deleteFile, getTempFileURL, getFileInfo, downloadFile, getUploadMetadata, getFileAuthority, copyFile } from './storage' import { callWxOpenApi, callCompatibleWxOpenApi, callWxPayApi, wxCallContainerApi } from './wx' import { analytics } from './analytics' import { createAI } from './ai' import { AI } from '@cloudbase/ai' import { Logger, logger } from './logger' import { ERROR } from './const/code' import * as utils from './utils/utils' import { preflightRuntimeCloudPlatform } from './utils/cloudplatform' import { parseContext, getCloudbaseContext } from './utils/tcbcontext' import { sendNotification, ITemplateNotifyReq } from './notification' import * as openapicommonrequester from './utils/tcbopenapicommonrequester' import { IFetchOptions } from '@cloudbase/adapter-interface' import { buildCommonOpenApiUrlWithPath } from './utils/tcbopenapiendpoint' import { SYMBOL_CURRENT_ENV } from './const/symbol' import { IncomingHttpHeaders } from 'http' import { normalizeConfig } from './utils/utils' export class CloudBase { public static scfContext: ISCFContext public static parseContext(context: IContextParam): ISCFContext { const parseResult = parseContext(context) CloudBase.scfContext = parseResult return parseResult } public static getCloudbaseContext(context?: IContextParam): ICompleteCloudbaseContext { return getCloudbaseContext(context) } public config: ICloudBaseConfig private clsLogger: Logger private extensionMap: Map<string, Extension> private aiInstance: AI public models: OrmClient & OrmRawQueryClient public mysql: IMySqlClient public rdb: IMySqlClient public constructor(config?: ICloudBaseConfig) { this.init(config) } public init(config: ICloudBaseConfig = {}): void { // 预检运行环境,调用与否并不影响后续逻辑 // 注意:该函数为异步函数,这里并不等待检查结果 /* eslint-disable-next-line */ preflightRuntimeCloudPlatform() // 所有的鉴权,参数塑形都在 normalizeConfig 中处理 // 后续其他模块获取 config 都通过 CloudBase 实例的 config 获取 // 禁止在业务模块中直接修改 config 配置 this.config = normalizeConfig(config) this.extensionMap = new Map() // NOTE:try-catch 为防止 init 报错 try { // 初始化数据模型等 SDK 方法 const envId = this.config.envName === SYMBOL_CURRENT_ENV ? openapicommonrequester.getEnvIdFromContext() : (this.config.envName as string) const httpClient = wxCloudClient.generateHTTPClient(this.callFunction.bind(this), async (options: IFetchOptions) => { const result = await openapicommonrequester.request({ config: this.config, data: safeParseJSON(options.body), method: options.method?.toUpperCase(), url: options.url, headers: { 'Content-Type': 'application/json', ...headersInitToRecord(options.headers) }, /** * 既然 openapicommonrequester.request 的参数里的 token 获取也是通过 openapicommonrequester.request 方法去获取的 * 为什么不把这里的 token 去掉,全部放在 openapicommonrequester.request 中去统一处理 token 获取的逻辑 */ token: (await this.auth().getClientCredential()).access_token }) return result.body }, buildCommonOpenApiUrlWithPath({ serviceUrl: this.config.serviceUrl, envId, path: '/v1/model', region: this.config.region }), { sqlBaseUrl: buildCommonOpenApiUrlWithPath({ serviceUrl: this.config.serviceUrl, envId, path: '/v1/sql', region: this.config.region }) }) this.models = httpClient } catch (e) { // ignore } try { const getEntity = (options: IMySqlOptions) => { const envId = this.config.envName === SYMBOL_CURRENT_ENV ? openapicommonrequester.getEnvIdFromContext() : (this.config.envName as string) const { instance = 'default', database = envId } = options || {} const mysqlClient = wxCloudClient.generateMySQLClient(this, { mysqlBaseUrl: buildCommonOpenApiUrlWithPath({ serviceUrl: this.config.serviceUrl, envId, path: '/v1/rdb/rest', region: this.config.region }), fetch: async (url: RequestInfo | URL, options: RequestInit) => { let headers = {} if (options.headers instanceof Headers) { options.headers.forEach((value, key) => { headers[key] = value }) } else { headers = options.headers || {} } const result = await openapicommonrequester.request({ config: this.config, data: safeParseJSON(options.body), method: options.method?.toUpperCase(), url: url instanceof URL ? url.href : String(url), headers: { 'Content-Type': 'application/json', ...headersInitToRecord({ 'X-Db-Instance': instance, 'Accept-Profile': database, 'Content-Profile': database, ...headers }) }, token: (await this.auth().getClientCredential()).access_token }) const data = result.body const res = { ok: result?.statusCode >= 200 && result?.statusCode < 300, status: result?.statusCode || 200, statusText: (result as unknown as { statusMessage: string })?.statusMessage || 'OK', json: async () => await Promise.resolve(data || {}), text: async () => await Promise.resolve(typeof data === 'string' ? data : JSON.stringify(data || {})), headers: new Headers(incomingHttpHeadersToHeadersInit(result?.headers || {})) } return res as Response } }) return mysqlClient } this.mysql = (options: IMySqlOptions) => { return getEntity(options)(options) } this.rdb = (options: IMySqlOptions) => { return getEntity(options)(options) } } catch (e) { // ignore } } public logger(): Logger { if (!this.clsLogger) { this.clsLogger = logger() } return this.clsLogger } public auth() { return auth(this) } public database(dbConfig: ICloudBaseDBConfig = {}) { return newDb(this, dbConfig) } public ai(): AI { if (!this.aiInstance) { this.aiInstance = createAI(this) } return this.aiInstance } public async callFunction<ParaT, ResultT>(callFunctionOptions: ICallFunctionOptions<ParaT>, opts?: ICustomReqOpts) { return await callFunction<ParaT, ResultT>(this, callFunctionOptions, opts) } public async callContainer<ParaT, ResultT>(callContainerOptions: ICallContainerOptions<ParaT>, opts?: ICustomReqOpts) { return await callContainer<ParaT, ResultT>(this, callContainerOptions, opts) } public async callApis<ParaT>(callApiOptions: ICallApisOptions<ParaT>, opts?: ICustomReqOpts) { return await callApis<ParaT>(this, callApiOptions, opts) } public async callWxOpenApi(wxOpenApiOptions: ICallWxOpenApiOptions, opts?: ICustomReqOpts): Promise<ICallWxOpenApiResult> { return await callWxOpenApi(this, wxOpenApiOptions, opts) } public async callWxPayApi(wxOpenApiOptions: ICallWxOpenApiOptions, opts?: ICustomReqOpts): Promise<any> { return await callWxPayApi(this, wxOpenApiOptions, opts) } public async wxCallContainerApi(wxOpenApiOptions: ICallWxOpenApiOptions, opts?: ICustomReqOpts): Promise<any> { return await wxCallContainerApi(this, wxOpenApiOptions, opts) } public async callCompatibleWxOpenApi(wxOpenApiOptions: ICallWxOpenApiOptions, opts?: ICustomReqOpts): Promise<any> { return await callCompatibleWxOpenApi(this, wxOpenApiOptions, opts) } public async uploadFile({ cloudPath, fileContent }: IUploadFileOptions, opts?: ICustomReqOpts): Promise<IUploadFileResult> { return await uploadFile(this, { cloudPath, fileContent }, opts) } public async downloadFile({ fileID, urlType, tempFilePath }: IDownloadFileOptions, opts?: ICustomReqOpts): Promise<IDownloadFileResult> { return await downloadFile(this, { fileID, urlType, tempFilePath }, opts) } /** * 复制文件 * * @param fileList 复制列表 * @param fileList.srcPath 源文件路径 * @param fileList.dstPath 目标文件路径 * @param fileList.overwrite 当目标文件已经存在时,是否允许覆盖已有文件,默认 true * @param fileList.removeOriginal 复制文件后是否删除源文件,默认不删除 * @param opts */ public async copyFile({ fileList }: ICopyFileOptions, opts?: ICustomReqOpts): Promise<ICopyFileResult> { return await copyFile(this, { fileList }, opts) } public async deleteFile({ fileList }: IDeleteFileOptions, opts?: ICustomReqOpts): Promise<IDeleteFileResult> { return await deleteFile(this, { fileList }, opts) } public async getTempFileURL({ fileList }: IGetFileUrlOptions, opts?: ICustomReqOpts): Promise<IGetFileUrlResult> { return await getTempFileURL(this, { fileList }, opts) } public async getFileInfo({ fileList }: IGetFileInfoOptions, opts?: ICustomReqOpts): Promise<IGetFileInfoResult> { return await getFileInfo(this, { fileList }, opts) } public async getUploadMetadata({ cloudPath }: IGetUploadMetadataOptions, opts?: ICustomReqOpts): Promise<IGetUploadMetadataResult> { return await getUploadMetadata(this, { cloudPath }, opts) } public async getFileAuthority({ fileList }: IGetFileAuthorityOptions, opts?: ICustomReqOpts): Promise<IGetFileAuthorityResult> { return await getFileAuthority(this, { fileList }, opts) } /** * @deprecated */ public async analytics(reportData: IReportData): Promise<void> { await analytics(this, reportData) } public registerExtension(ext: Extension): void { this.extensionMap.set(ext.name, ext) } public async invokeExtension<OptsT>(name: string, opts: OptsT) { const ext = this.extensionMap.get(name) if (!ext) { throw Error(`Please register '${name}' extension before invoke.`) } return ext.invoke(opts, this) } // SDK推送消息(对外API:sendTemplateNotification) public async sendTemplateNotification(params: ITemplateNotifyReq, opts?: ICustomReqOpts) { return await sendNotification(this, params, opts) } /** * shim for tcb extension ci */ public get requestClient() { return { get: axios, post: axios, put: axios, delete: axios } } } function headersInitToRecord(headers: HeadersInit): Record<string, string> { if (!headers) { return {} } const ret: Record<string, string> = {} if (Array.isArray(headers)) { headers.forEach(([key, value]) => { ret[key] = value }) } else if (typeof headers.forEach === 'function') { headers.forEach(([key, value]) => { ret[key] = value }) } else { Object.keys(headers).forEach((key) => { ret[key] = headers[key] }) } return ret } function safeParseJSON(x: unknown) { try { return JSON.parse(x as string) } catch (e) { return x } } function incomingHttpHeadersToHeadersInit(headers: IncomingHttpHeaders): HeadersInit { const result: Array<[string, string]> = [] for (const [key, value] of Object.entries(headers)) { if (value === undefined) { continue } if (Array.isArray(value)) { for (const v of value) { result.push([key, v]) } } else { result.push([key, value]) } } return result }