/* eslint-disable prefer-destructuring */ /* eslint-disable @typescript-eslint/naming-convention */ import { SDKAdapterInterface, StorageInterface, StorageType, WebSocketContructor as WebSocketConstructor, } from '@cloudbase/adapter-interface' import { NodeRequest } from './request' import { nodeTool } from './tool' import { getSecretInfo } from './context' import { parseQueryString } from './utils' import { ICloudbaseConfig } from '@cloudbase/types' // ws 延迟加载,避免非 Node 环境打包时引入 let _WS: any = null function getWS() { if (!_WS) { try { // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports _WS = require('ws') } catch (e) { console.error('缺少依赖 ws,请执行以下命令安装:\n\n' + ' npm install ws\n\n' + '该依赖用于 Node 环境下的 WebSocket 连接。',) } } return _WS } // Re-export for external consumers export { NodeRequest } from './request' export { parseQueryString, createWebStreamFromNodeReadableStream } from './utils' export type { ICreateTicketOpts, IGetUserInfoResult, IGetEndUserInfoResult, IUserInfoQuery, IContextParam, ICompleteCloudbaseContext, } from './types' /** * 环境匹配检测:判断当前运行环境是否为 Node.js * SDK 通过此方法决定是否使用当前 adapter */ function isMatch(): boolean { // eslint-disable-next-line eqeqeq return typeof process !== 'undefined' && process.versions != null && process.versions.node != null } /** * 基于 Map 的内存存储实现 * Node.js 环境下无浏览器 Storage API,使用内存 Map 模拟 */ const storage: StorageInterface = (() => { const db = new Map() return { mode: 'sync', setItem(key, value) { db.set(key, value) }, getItem(key) { return db.get(key) }, removeItem(key) { db.delete(key) }, clear() { db.clear() }, } })() /** * 生成 Node.js 环境的 SDK Adapter * 组装请求类、存储、WebSocket、认证等能力 * * @param options - 配置项,包含 EventBus(用于验证码回调) * @returns SDK Adapter 实例 */ function genAdapter(options: any) { const adapter: SDKAdapterInterface & { getSecretInfo: (config?: ICloudbaseConfig) => { env: string secretId: string secretKey: string sessionToken: string accessKey: string secretType: string } nodeTool: (app: any, config: ICloudbaseConfig) => void } = { nodeTool, getSecretInfo, root: { globalThis: {} }, reqClass: NodeRequest, wsClass: getWS() as WebSocketConstructor, sessionStorage: storage, localStorage: storage, primaryStorage: StorageType.session, captchaOptions: { /** * 验证码处理回调 * 解析 data: URI 中的验证码参数,通过 EventBus 通知业务层 * 等待业务层返回验证结果后 resolve */ openURIWithCallback: (_url: string) => { const { EventBus } = options let queryObj: Record = {} let url = _url // 从 data: URI 中提取查询参数 const matched = _url.match(/^(data:.*?)(\?[^#\s]*)?$/) if (matched) { url = matched[1] const search = matched[2] if (search) { queryObj = parseQueryString(search) } } const { token, ...restQueryObj } = queryObj // data: 协议但无 token,视为无效验证码数据 if (/^data:/.test(url) && !token) { return Promise.reject({ error: 'invalid_argument', error_description: `invalie captcha data: ${_url}`, }) } if (!token) { return Promise.reject({ error: 'unimplemented', error_description: 'need to impl captcha data', }) } return new Promise((resolve) => { // 通知业务层展示验证码 EventBus.$emit('CAPTCHA_DATA_CHANGE', { ...restQueryObj, token, url, }) // 等待业务层返回验证结果 EventBus.$once('RESOLVE_CAPTCHA_DATA', (res: { captcha_token: string; expires_in: number }) => { resolve(res) }) }) }, }, } return adapter } /** Node.js Adapter 入口导出 */ const adapter = { genAdapter, isMatch, runtime: 'node-adapter', } export default adapter