@cloudbase/app
Version:
cloudbase javascript sdk core
167 lines (153 loc) • 4.53 kB
text/typescript
/* 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<string, string | string[]> = {}
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