@cloudbase/app
Version:
cloudbase javascript sdk core
224 lines (203 loc) • 6.82 kB
text/typescript
import { ERROR } from './constants'
import { getCloudbaseContext, parseContext } from './context'
import { validateUid } from './utils'
import type {
ICreateTicketOpts,
IGetUserInfoResult,
IGetEndUserInfoResult,
IUserInfoQuery,
ITemplateNotifyReq,
} from './types'
import { ICloudbaseConfig } from '@cloudbase/types'
// 延迟加载,避免非 Node 环境打包时引入此包
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let jwtSign: ((...args: any[]) => any) | null = null
async function getJwtSign() {
if (!jwtSign) {
try {
// @ts-ignore — 该包仅在 Node 运行时存在,开发环境可能未安装
const mod = await import('jsonwebtoken')
jwtSign = mod.default?.sign || mod.sign
} catch (e) {
console.error('缺少依赖 jsonwebtoken,请执行以下命令安装:\n\n'
+ ' npm install jsonwebtoken\n\n'
+ '该依赖用于 Node 环境下的自定义登录票据生成。')
}
}
return jwtSign!
}
/**
* 从云函数运行时上下文中获取当前请求的用户信息
* 数据来源为环境变量(微信 openId、TCB uuid 等)
*/
function getDefaultUserInfo(): IGetUserInfoResult {
const { WX_OPENID, WX_APPID, TCB_UUID, TCB_CUSTOM_USER_ID, TCB_ISANONYMOUS_USER } = getCloudbaseContext()
return {
openId: WX_OPENID || '',
appId: WX_APPID || '',
uid: TCB_UUID || '',
customUserId: TCB_CUSTOM_USER_ID || '',
isAnonymous: TCB_ISANONYMOUS_USER === 'true',
}
}
/**
* 调用后端管理接口查询用户信息
* 统一封装 auth.getUserInfoForAdmin 的请求逻辑
*/
function sendUserInfoRequest(app: any, params: Record<string, any>): Promise<any> {
return app?.request?.send?.('auth.getUserInfoForAdmin', params, {
pathname: 'web',
endPointMode: 'CLOUD_API',
})
}
/**
* 初始化 Node 端工具方法,挂载到 js-sdk app 实例上
* 包含:auth 相关方法、模板消息推送、context 解析
*
* @param app - js-sdk cloudbase 实例
* @param config - 配置信息,包含认证凭证和环境 ID
*/
export const nodeTool = (app: any, config: ICloudbaseConfig) => {
// 仅当 app 已初始化 auth 模块时,才注入 auth 相关方法
if (app.auth) {
const auth = {
/** 获取当前请求的用户信息(从环境变量读取,同步) */
getUserInfo(): IGetUserInfoResult {
return getDefaultUserInfo()
},
/**
* 获取终端用户信息
* 不传 uid 时返回当前请求用户信息,传 uid 时查询指定用户
*/
async getEndUserInfo(uid?: string): Promise<IGetEndUserInfoResult> {
const defaultUserInfo = getDefaultUserInfo()
if (uid === undefined) {
return { userInfo: defaultUserInfo }
}
validateUid(uid)
return sendUserInfoRequest(app, {
uuid: uid,
envName: config.env,
}).then((result: any) => {
if (result.code) {
return result
}
return {
userInfo: { ...defaultUserInfo, ...result.data },
requestId: result.requestId,
}
})
},
/**
* 创建自定义登录 Ticket
* 使用 RSA 私钥签发 JWT,客户端凭此 Ticket 换取登录态
*
* @param uid - 自定义用户 ID(4~32 位)
* @param options - 刷新间隔和过期时间配置
* @returns 格式为 "{private_key_id}/@@/{jwt_token}" 的 Ticket 字符串
*/
async createTicket(uid: string, options: ICreateTicketOpts = {}): Promise<string> {
validateUid(uid)
const timestamp = new Date().getTime()
const { credentials } = config.auth || {}
const { env } = config
if (!env) {
throw { ...ERROR.INVALID_PARAM, message: 'no env in config' }
}
if (!credentials?.env_id) {
throw {
...ERROR.INVALID_PARAM,
message: '当前私钥未包含env_id 信息, 请前往腾讯云云开发控制台,获取自定义登录最新私钥',
}
}
if (credentials.env_id !== env) {
throw {
...ERROR.INVALID_PARAM,
message: '当前私钥所属环境与 init 指定环境不一致!',
}
}
const {
refresh = 3600 * 1000, // 默认 1 小时刷新
expire = timestamp + 7 * 24 * 60 * 60 * 1000, // 默认 7 天过期
} = options
const sign = await getJwtSign()
const token = sign(
{
alg: 'RS256',
env,
iat: timestamp,
exp: timestamp + 10 * 60 * 1000, // Ticket 本身 10 分钟有效
uid,
refresh,
expire,
},
credentials.private_key,
{
allowInsecureKeySizes: true,
algorithm: 'RS256',
},
)
return `${credentials.private_key_id}/@@/${token}`
},
/**
* 按条件查询用户信息(管理端接口)
* 支持按 uid、platform、platformId 查询
*/
async queryUserInfo(query: IUserInfoQuery): Promise<any> {
const { uid, platform, platformId } = query
return sendUserInfoRequest(app, {
uuid: uid,
platform,
platformId,
envName: config.env,
}).then((result: any) => {
if (result.code) {
return result
}
return {
userInfo: { ...result.data },
requestId: result.requestId,
}
})
},
/** 获取客户端 IP 地址 */
getClientIP(): string {
const { TCB_SOURCE_IP } = getCloudbaseContext()
return TCB_SOURCE_IP || ''
},
}
// 将 auth 方法逐一挂载到 app.auth 上
Object.keys(auth).forEach((key) => {
app.auth[key] = (auth as Record<string, any>)[key]
})
}
/**
* 发送模板消息通知
* 通过调用 lowcode-datasource 云函数间接调用微搭 API
*
* @param params - 通知参数(策略 ID、模板变量、跳转链接)
* @param opts - 可选配置,如超时时间
*/
app.sendTemplateNotification = async (params: ITemplateNotifyReq, opts?: { timeout?: number }) => await app?.callFunction?.(
{
name: 'lowcode-datasource',
data: {
methodName: 'callWedaApi',
params: {
action: 'PushNotifyMsg',
data: {
NotifyId: params.notifyId,
Data: JSON.stringify(params.data),
NotifyUsers: undefined,
Url: params.url,
},
},
mode: 'c',
},
},
undefined,
opts,
)
/** 挂载 context 解析工具,方便用户在云函数中使用 */
app.parseContext = parseContext
}