import { adapters, constants, utils, helpers } from '@cloudbase/utilities' import { SDKAdapterInterface, CloudbaseAdapter, IRequestConfig } from '@cloudbase/adapter-interface' import { ICloudbaseConfig, ICloudbaseUpgradedConfig, ICloudbase, ICloudbaseExtension, KV, ICloudbasePlatformInfo, EndPointKey, ICloudbaseApis, } from '@cloudbase/types' import { ICloudbaseAuth } from '@cloudbase/types/auth' import { registerComponent, registerHook } from './libs/component' import { getWxDefaultAdapter, Platform } from './libs/adapter' import { ICloudbaseComponent, ICloudbaseHook } from '@cloudbase/types/component' import { ICloudbaseCache } from '@cloudbase/types/cache' import { initCache, getCacheByEnvId, getLocalCache } from './libs/cache' import { ICloudbaseRequest } from '@cloudbase/types/request' import { initRequest, getRequestByEnvId } from './libs/request' import { getSdkName, setSdkVersion, setRegionLevelEndpoint, setSdkName, setGatewayEndPointWithEnv, type ISetEndPointWithKey, setEndPointInfo, getEndPointInfo, getSdkVersion, DEFAULT_PROTOCOL, } from './constants/common' import { i18nProxy, LANGS } from './libs/lang' export { getBaseEndPoint } from './constants/common' export { LANGS } from './libs/lang' const { useAdapters, useDefaultAdapter } = adapters const { ERRORS, COMMUNITY_SITE_URL } = constants const { printWarn } = utils const { catchErrorsDecorator } = helpers /** * @constant 默认配置 */ const DEFAULT_INIT_CONFIG: Partial = { timeout: 15000, persistence: 'local', // 持久化存储类型 } // timeout上限10分钟 const MAX_TIMEOUT = 1000 * 60 * 10 // timeout下限100ms const MIN_TIMEOUT = 100 const extensionMap: KV = {} class Cloudbase implements ICloudbase { public authInstance: ICloudbaseAuth public oauthInstance: any public requestClient: any public oauthClient: any public version: string public auth: ICloudbase['auth'] & ICloudbaseAuth public apis: ICloudbaseApis private cloudbaseConfig: ICloudbaseConfig constructor(config?: ICloudbaseConfig) { this.cloudbaseConfig = config ? config : this.cloudbaseConfig this.authInstance = null this.oauthInstance = null this.version = getSdkVersion() } get config() { return this.cloudbaseConfig } get platform(): ICloudbasePlatformInfo { return Platform } get cache(): ICloudbaseCache { return getCacheByEnvId(this.cloudbaseConfig.env) } get localCache(): ICloudbaseCache { return getLocalCache(this.cloudbaseConfig.env) } get request(): ICloudbaseRequest { return getRequestByEnvId(this.cloudbaseConfig.env) } @catchErrorsDecorator({ mode: 'sync', title: 'Cloudbase 初始化失败', messages: [ '请确认以下各项:', ' 1 - 调用 cloudbase.init() 的语法或参数是否正确', ' 2 - 如果是非浏览器环境,是否配置了安全应用来源(https://docs.cloudbase.net/api-reference/webv3/adapter#%E7%AC%AC-2-%E6%AD%A5%E9%85%8D%E7%BD%AE%E5%AE%89%E5%85%A8%E5%BA%94%E7%94%A8%E6%9D%A5%E6%BA%90)', `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`, ], }) public init(config: ICloudbaseConfig & { lang?: LANGS } = { env: '' }): Cloudbase { config.endPointMode = config.endPointMode || 'CLOUD_API' // 初始化时若未兼容平台,则使用默认adapter if (!Platform.adapter) { this.useDefaultAdapter() } // 在判断config.env之前,先处理node环境的配置 config = this.dealNodeAdapterConfig(config) if (!config.env) { throw new Error(JSON.stringify({ code: ERRORS.INVALID_PARAMS, msg: 'env must not be specified', }),) } this.isInitialized(config) const reqConfig: IRequestConfig & { auth: ICloudbaseConfig['auth'] } = { timeout: config.timeout || 15000, timeoutMsg: `[${getSdkName()}][REQUEST TIMEOUT] request had been abort since didn't finished within${ config.timeout / 1000 }s`, auth: config.auth, } this.requestClient = new Platform.adapter.reqClass(reqConfig) this.cloudbaseConfig = { ...DEFAULT_INIT_CONFIG, ...config, i18n: i18nProxy(Platform, config), } delete (this.cloudbaseConfig as any).lang // 修正timeout取值 this.cloudbaseConfig.timeout = this.formatTimeout(this.cloudbaseConfig.timeout) // 初始化cache和request const { env, persistence, debug, timeout, oauthClient, i18n } = this.cloudbaseConfig initCache({ env, persistence, debug, platformInfo: this.platform }) setRegionLevelEndpoint(env, config.region || '') setGatewayEndPointWithEnv(env, DEFAULT_PROTOCOL, config.region || '') const app = new Cloudbase(this.cloudbaseConfig) initRequest({ env, region: config.region || '', timeout, oauthClient, _fromApp: app, i18n, endPointMode: config.endPointMode, auth: config.auth, }) app.requestClient = this.requestClient ;(this as any)?.fire?.('cloudbase_init', app) this.try2InitAuth(app) // node环境可以从adapter获取nodeTool的方法 if (Platform.adapter?.nodeTool) { Platform.adapter?.nodeTool(app, this.cloudbaseConfig) } return app } public updateConfig(config: ICloudbaseUpgradedConfig) { const { persistence, debug } = config this.cloudbaseConfig.persistence = persistence this.cloudbaseConfig.debug = debug // persistence改动影响cache initCache({ env: this.cloudbaseConfig.env, persistence, debug, platformInfo: this.platform }) } public updateLang(lang: LANGS) { if (!lang || lang === this.cloudbaseConfig.i18n?.lang) return this.cloudbaseConfig.i18n.lang = lang } public registerExtension(ext: ICloudbaseExtension) { extensionMap[ext.name] = ext } @catchErrorsDecorator({ title: '调用扩展能力失败', messages: [ '请确认以下各项:', ' 1 - 调用 invokeExtension() 的语法或参数是否正确', ' 2 - 被调用的扩展能力是否已经安装并通过 registerExtension() 注册', `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`, ], }) public async invokeExtension(name: string, opts: any) { const ext = extensionMap[name] if (!ext) { throw new Error(JSON.stringify({ code: ERRORS.INVALID_PARAMS, msg: `extension:${name} must be registered before invoke`, }),) } return await ext.invoke(opts, this) } public useAdapters(adapters: CloudbaseAdapter | CloudbaseAdapter[], options?: any) { const { adapter, runtime } = useAdapters(adapters, options) || {} adapter && (Platform.adapter = adapter as SDKAdapterInterface) runtime && (Platform.runtime = runtime as string) } public registerHook(hook: ICloudbaseHook) { registerHook(Cloudbase, hook) } public registerComponent(component: ICloudbaseComponent) { registerComponent(Cloudbase, component) } public registerVersion(version: string) { setSdkVersion(version) this.version = version } public registerSdkName(name: string) { setSdkName(name) } /** * 检查当前 Cloudbase 实例是否已完成初始化 * * @example * ```javascript * import cloudbase from '@cloudbase/js-sdk' * * // 初始化前 * console.log(cloudbase.isInitialized()) // false * * const app = cloudbase.init({ env: 'your-env-id' }) * * // 初始化后 * console.log(app.isInitialized()) // true * ``` * * @returns {boolean} 是否已初始化 */ public isInitialized(config = this.cloudbaseConfig): boolean { // 检测常见占位符 env 值,在开发阶段给出友好提示 const PLACEHOLDER_PATTERNS = [ 'your-env-id', 'your-envid', 'your_env_id', 'xxx-yyy', 'xxxx-yyy', 'envId', 'env-id', 'your-environment-id', 'REPLACE_ME', ] if (PLACEHOLDER_PATTERNS.some(p => config.env.toLowerCase() === p.toLowerCase())) { printWarn( ERRORS.INVALID_PARAMS, `[CloudBase] 检测到环境 ID "${config.env}" 可能是占位符,请替换为真实的环境 ID。\n` + ' 获取方式:登录腾讯云开发平台 → 环境管理 → 环境设置 → 环境 ID\n' + ' 建议通过环境变量配置:process.env.CLOUDBASE_ENV 或 import.meta.env.VITE_CLOUDBASE_ENV', ) } return !!(config?.env) } /** 设置 tcb api 的 endpoint */ public registerEndPoint(url: string, protocol?: 'http' | 'https') { setEndPointInfo({ baseUrl: url, protocol, env: this.config.env, endPointKey: 'CLOUD_API' }) } /** 设置网关/tcb api的 endPoint,通过 key 指定 */ public registerEndPointWithKey(props: ISetEndPointWithKey) { setEndPointInfo({ env: this.config.env, endPointKey: props.key, baseUrl: props.url, protocol: props.protocol, }) } /** 拿网关/tcb api的 endPoint,通过 key 指定 */ public getEndPointWithKey(key: EndPointKey) { const info = getEndPointInfo(this.config.env, key) return { BASE_URL: info.baseUrl, PROTOCOL: info.protocol, } } // 解析URL参数 public parseCaptcha(url) { return utils.parseCaptcha(url) } private useDefaultAdapter() { const { adapter, runtime } = useDefaultAdapter.bind(this)() Platform.adapter = adapter as SDKAdapterInterface Platform.runtime = runtime as string } private formatTimeout(timeout: number) { switch (true) { case timeout > MAX_TIMEOUT: printWarn(ERRORS.INVALID_PARAMS, 'timeout is greater than maximum value[10min]') return MAX_TIMEOUT case timeout < MIN_TIMEOUT: printWarn(ERRORS.INVALID_PARAMS, 'timeout is less than maximum value[100ms]') return MIN_TIMEOUT default: return timeout } } private try2InitAuth(app) { try { app.auth() } catch (error) { console.log('try2InitAuth error:', error) } } /** * 处理 Node 环境下 adapter 的配置信息 * 在 Node 环境中,通过 adapter 自动获取密钥、环境ID、accessKey 等配置, * 减少用户手动传入配置的负担 * @param config - 云开发初始化配置对象 * @returns 补充了 Node adapter 信息后的配置对象 */ private dealNodeAdapterConfig(config: ICloudbaseConfig) { // node环境可以从adapter获取默认的secretId和secretKey if (typeof process !== 'undefined' && typeof process.env !== 'undefined' && !process.env.IS_BROWSER_BUILD && Platform.adapter?.getSecretInfo) { const secretInfo = Platform.adapter?.getSecretInfo(config) // 将 adapter 提供的密钥信息写入 config.auth config.auth = { ...config.auth, secretId: secretInfo.secretId, secretKey: secretInfo.secretKey, sessionToken: secretInfo.sessionToken, secretType: secretInfo.secretType as any, } // node 环境可能可以从上下文获取env,用户未显式指定时自动填充 if (!config.env) { config.env = secretInfo.env } // node adapter环境能读取到 process.env.CLOUDBASE_APIKEY 时,在没有传入accessKey时,使用 node adapter 提供的 accessKey(process.env.CLOUDBASE_APIKEY) if (!config.accessKey && secretInfo.accessKey) { config.accessKey = secretInfo.accessKey } } return config } } // 类型导出 export type { Cloudbase } // 值导出 export const cloudbase: ICloudbase = new Cloudbase() cloudbase.useAdapters(getWxDefaultAdapter()) // 默认导出实例 export default cloudbase