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 } 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 { 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' 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 public models: OrmClient & OrmRawQueryClient public mysql: IMySqlClient public constructor(config?: ICloudBaseConfig) { this.init(config) } public init(config: ICloudBaseConfig = {}): void { // 预检运行环境,调用与否并不影响后续逻辑 // 注意:该函数为异步函数,这里并不等待检查结果 /* eslint-disable-next-line */ preflightRuntimeCloudPlatform() const { debug, secretId, secretKey, sessionToken, env, timeout, headers = {}, ...restConfig } = config if (('secretId' in config && !('secretKey' in config)) || (!('secretId' in config) && 'secretKey' in config)) { throw utils.E({ ...ERROR.INVALID_PARAM, message: 'secretId and secretKey must be a pair' }) } const newConfig: ICloudBaseConfig = { ...restConfig, debug: !!debug, secretId, secretKey, sessionToken, env, envName: env, headers: { ...headers }, timeout: timeout || 15000 } if (config.context?.extendedContext) { const extendedContext = config.context.extendedContext if (!newConfig.env) { newConfig.env = extendedContext.envId newConfig.envName = newConfig.env } // 从 context 中获取 secret if (!newConfig.secretId && !newConfig.secretKey) { newConfig.secretId = extendedContext?.tmpSecret?.secretId newConfig.secretKey = extendedContext?.tmpSecret?.secretKey newConfig.sessionToken = extendedContext?.tmpSecret?.token } } this.config = newConfig 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) }, 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?.statusMessage || 'OK', json: async () => await Promise.resolve(data || {}), text: async () => await Promise.resolve( typeof data === 'string' ? data : JSON.stringify(data || {}) ), headers: new Headers(result?.headers || {}) } return res as Response } }) return mysqlClient } this.mysql = (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: ICloudBaseConfig = {}) { return newDb(this, dbConfig) } public async callFunction(callFunctionOptions: ICallFunctionOptions, opts?: ICustomReqOpts) { return await callFunction(this, callFunctionOptions, opts) } public async callContainer(callContainerOptions: ICallContainerOptions, opts?: ICustomReqOpts) { return await callContainer(this, callContainerOptions, opts) } public async callApis(callApiOptions: ICallApisOptions, opts?: ICustomReqOpts) { return await callApis(this, callApiOptions, opts) } public async callWxOpenApi(wxOpenApiOptions: ICallWxOpenApiOptions, opts?: ICustomReqOpts): Promise { return await callWxOpenApi(this, wxOpenApiOptions, opts) } public async callWxPayApi(wxOpenApiOptions: ICallWxOpenApiOptions, opts?: ICustomReqOpts): Promise { return await callWxPayApi(this, wxOpenApiOptions, opts) } public async wxCallContainerApi(wxOpenApiOptions: ICallWxOpenApiOptions, opts?: ICustomReqOpts): Promise { return await wxCallContainerApi(this, wxOpenApiOptions, opts) } public async callCompatibleWxOpenApi(wxOpenApiOptions: ICallWxOpenApiOptions, opts?: ICustomReqOpts): Promise { return await callCompatibleWxOpenApi(this, wxOpenApiOptions, opts) } public async uploadFile({ cloudPath, fileContent }: IUploadFileOptions, opts?: ICustomReqOpts): Promise { return await uploadFile(this, { cloudPath, fileContent }, opts) } public async downloadFile({ fileID, urlType, tempFilePath }: IDownloadFileOptions, opts?: ICustomReqOpts): Promise { 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 { return await copyFile(this, { fileList }, opts) } public async deleteFile({ fileList }: IDeleteFileOptions, opts?: ICustomReqOpts): Promise { return await deleteFile(this, { fileList }, opts) } public async getTempFileURL({ fileList }: IGetFileUrlOptions, opts?: ICustomReqOpts): Promise { return await getTempFileURL(this, { fileList }, opts) } public async getFileInfo({ fileList }: IGetFileInfoOptions, opts?: ICustomReqOpts): Promise { return await getFileInfo(this, { fileList }, opts) } public async getUploadMetadata({ cloudPath }: IGetUploadMetadataOptions, opts?: ICustomReqOpts): Promise { return await getUploadMetadata(this, { cloudPath }, opts) } public async getFileAuthority({ fileList }: IGetFileAuthorityOptions, opts?: ICustomReqOpts): Promise { return await getFileAuthority(this, { fileList }, opts) } /** * @deprecated */ public async analytics(reportData: IReportData): Promise { await analytics(this, reportData) } public registerExtension(ext: Extension): void { this.extensionMap.set(ext.name, ext) } public async invokeExtension(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 { if (!headers) { return {} } const ret: Record = {} 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 } }