@cloudbase/app
Version:
cloudbase javascript sdk core
239 lines (207 loc) • 7.35 kB
text/typescript
/* eslint-disable import/no-mutable-exports */
/* eslint-disable @typescript-eslint/naming-convention */
/* eslint-disable no-restricted-syntax */
import { ERROR, SECRET_TYPE } from './constants'
import { checkIsInScf, getEnv } from './utils'
import type { IContextParam, ICompleteCloudbaseContext } from './types'
import { ICloudbaseConfig } from '@cloudbase/types'
/**
* 解析并校验云函数入口的 Context 参数
* 将下划线命名的字段转为驼峰格式,并解析环境变量
*
* @param context - 云函数入口传入的 context 对象
* @returns 解析后的上下文对象,包含 environment(新架构)或 environ(老架构)
* @throws INVALID_CONTEXT - context 不是对象或解析失败时
*/
export function parseContext(context: IContextParam) {
if (typeof context !== 'object') {
throw { ...ERROR.INVALID_CONTEXT, message: 'context 必须为对象类型' }
}
const parseResult: any = {}
const {
memory_limit_in_mb,
time_limit_in_ms,
request_id,
function_version,
namespace,
function_name,
environ,
environment,
} = context
try {
parseResult.memoryLimitInMb = memory_limit_in_mb
parseResult.timeLimitIns = time_limit_in_ms
parseResult.requestId = request_id
parseResult.functionVersion = function_version
parseResult.namespace = namespace
parseResult.functionName = function_name
if (environment) {
// 新架构:environment 为 JSON 字符串,直接解析
parseResult.environment = JSON.parse(environment)
} else {
// TODO: 考虑移除老架构的兼容逻辑
// 老架构:environ 为分号分隔的 key=value 字符串
// 存在 bug:value 含特殊字符时可能影响解析
const parseEnviron: any = environ?.split(';')
const parseEnvironObj: any = {}
// eslint-disable-next-line @typescript-eslint/no-for-in-array
for (const i in parseEnviron) {
if (parseEnviron[i].includes('=')) {
// 只按第一个 = 切割,保留 value 中的 =
const equalIndex = parseEnviron[i].indexOf('=')
const key = parseEnviron[i].slice(0, equalIndex)
let value: any = parseEnviron[i].slice(equalIndex + 1)
// value 含逗号时视为数组
if (value.indexOf(',') >= 0) {
value = value.split(',')
}
parseEnvironObj[key] = value
}
}
parseResult.environ = parseEnvironObj
}
} catch (_err: any) {
throw { ...ERROR.INVALID_CONTEXT }
}
return parseResult
}
/**
* 获取当前云函数内的所有环境变量
* 统一从 process.env 和 context 两个来源合并取值
*
* 取值优先级:context > process.env
* 通过 TCB_CONTEXT_KEYS / WX_CONTEXT_KEYS 动态扩展需要读取的环境变量列表
*
* @param context - 可选,云函数入口的 context 参数
* @returns 合并后的完整环境变量上下文
*/
export const getCloudbaseContext = (context?: IContextParam): ICompleteCloudbaseContext => {
// 云函数环境下校验必要环境变量
if (checkIsInScf()) {
if (!getEnv('TENCENTCLOUD_REGION')) {
console.error('[TCB][ERROR] missing `TENCENTCLOUD_REGION` environment')
}
if (!getEnv('SCF_NAMESPACE')) {
console.error('[TCB][ERROR] missing `SCF_NAMESPACE` environment')
}
}
// 第一步:从 process.env 读取基础环境变量
const envObj = getEnv()
const envFromProcessEnv: any = {
TRIGGER_SRC: envObj.TRIGGER_SRC,
_SCF_TCB_LOG: envObj._SCF_TCB_LOG,
SCF_NAMESPACE: envObj.SCF_NAMESPACE,
TENCENTCLOUD_RUNENV: envObj.TENCENTCLOUD_RUNENV,
TENCENTCLOUD_SECRETID: envObj.TENCENTCLOUD_SECRETID,
TENCENTCLOUD_SECRETKEY: envObj.TENCENTCLOUD_SECRETKEY,
TENCENTCLOUD_SESSIONTOKEN: envObj.TENCENTCLOUD_SESSIONTOKEN,
WX_CONTEXT_KEYS: envObj.WX_CONTEXT_KEYS,
WX_TRIGGER_API_TOKEN_V0: envObj.WX_TRIGGER_API_TOKEN_V0,
WX_CLIENTIP: envObj.WX_CLIENTIP,
WX_CLIENTIPV6: envObj.WX_CLIENTIPV6,
TCB_CONTEXT_KEYS: envObj.TCB_CONTEXT_KEYS,
TCB_CONTEXT_CNFG: envObj.TCB_CONTEXT_CNFG,
LOGINTYPE: envObj.LOGINTYPE,
}
// 第二步:从 context 中解析环境变量
let envFromContext: any = {}
if (context) {
const { environment, environ } = parseContext(context)
envFromContext = environment || environ || {}
}
// 第三步:根据 CONTEXT_KEYS 动态扩展变量列表(优先取 context 中的值)
const tcbContextKeys = envFromContext.TCB_CONTEXT_KEYS || envObj.TCB_CONTEXT_KEYS
const wxContextKeys = envFromContext.WX_CONTEXT_KEYS || envObj.WX_CONTEXT_KEYS
if (tcbContextKeys) {
try {
const tcbKeysList = tcbContextKeys.split(',')
for (const item of tcbKeysList) {
envFromProcessEnv[item] = envFromContext[item] || getEnv(item)
}
} catch (_e) {
//
}
}
if (wxContextKeys) {
try {
const wxKeysList = wxContextKeys.split(',')
for (const item of wxKeysList) {
envFromProcessEnv[item] = envFromContext[item] || getEnv(item)
}
} catch (_e) {
//
}
}
// 第四步:合并所有来源,context 的值覆盖 process.env,过滤 undefined
const allContext = { ...envFromProcessEnv, ...envFromContext }
const finalContext: any = {}
for (const key in allContext) {
if (allContext[key] !== undefined) {
finalContext[key] = allContext[key]
}
}
return finalContext
}
/** 缓存 extendedContext,供其他模块读取(如 tool.ts 中获取 userId) */
export let INIT_CONFIG: ICloudbaseConfig = {} as unknown as ICloudbaseConfig
/**
* 从环境变量中获取腾讯云临时密钥信息
* 用于 API 请求签名
*/
export const getSecretInfo = (config?: ICloudbaseConfig) => {
INIT_CONFIG = JSON.parse(JSON.stringify(config || {}))
const { TCB_ENV, SCF_NAMESPACE } = getCloudbaseContext()
let env = config?.env || TCB_ENV || SCF_NAMESPACE || ''
const defaultInfo = {
env,
secretId: '',
secretKey: '',
sessionToken: '',
accessKey: '',
secretType: '',
}
if (config?.accessKey) {
return defaultInfo
}
if (config?.auth?.secretId && config?.auth?.secretKey) {
return {
...defaultInfo,
secretId: config.auth.secretId,
secretKey: config.auth.secretKey,
secretType: SECRET_TYPE.SECRET,
}
}
const secretEnv = getEnv()
const { TENCENTCLOUD_SECRETID } = secretEnv
const { TENCENTCLOUD_SECRETKEY } = secretEnv
const { TENCENTCLOUD_SESSIONTOKEN } = secretEnv
const { CLOUDBASE_APIKEY } = secretEnv
if (CLOUDBASE_APIKEY) {
return {
...defaultInfo,
accessKey: CLOUDBASE_APIKEY,
}
}
if (TENCENTCLOUD_SECRETID && TENCENTCLOUD_SECRETKEY) {
return {
...defaultInfo,
secretId: TENCENTCLOUD_SECRETID,
secretKey: TENCENTCLOUD_SECRETKEY,
sessionToken: TENCENTCLOUD_SESSIONTOKEN || '',
secretType: SECRET_TYPE.SESSION_SECRET,
}
}
if (config?.context?.extendedContext) {
const { extendedContext } = config.context
if (!env) {
env = extendedContext.envId || ''
}
return {
...defaultInfo,
secretId: extendedContext?.tmpSecret?.secretId || '',
secretKey: extendedContext?.tmpSecret?.secretKey || '',
sessionToken: extendedContext?.tmpSecret?.token || '',
}
}
return { ...defaultInfo }
}