@cloudbase/app 3.0.3 → 3.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.d.ts +1 -0
- package/dist/cjs/index.js +36 -7
- package/dist/cjs/libs/adapter-node/constants.d.ts +33 -0
- package/dist/cjs/libs/adapter-node/constants.js +38 -0
- package/dist/cjs/libs/adapter-node/context.d.ts +13 -0
- package/dist/cjs/libs/adapter-node/context.js +162 -0
- package/dist/cjs/libs/adapter-node/index.d.ts +23 -0
- package/dist/cjs/libs/adapter-node/index.js +124 -0
- package/dist/cjs/libs/adapter-node/metadata.d.ts +14 -0
- package/dist/cjs/libs/adapter-node/metadata.js +130 -0
- package/dist/cjs/libs/adapter-node/request.d.ts +38 -0
- package/dist/cjs/libs/adapter-node/request.js +432 -0
- package/dist/cjs/libs/adapter-node/tool.d.ts +2 -0
- package/dist/cjs/libs/adapter-node/tool.js +227 -0
- package/dist/cjs/libs/adapter-node/types.d.ts +78 -0
- package/dist/cjs/libs/adapter-node/types.js +3 -0
- package/dist/cjs/libs/adapter-node/utils.d.ts +17 -0
- package/dist/cjs/libs/adapter-node/utils.js +221 -0
- package/dist/cjs/libs/request.d.ts +1 -0
- package/dist/cjs/libs/request.js +3 -2
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +36 -7
- package/dist/esm/libs/adapter-node/constants.d.ts +33 -0
- package/dist/esm/libs/adapter-node/constants.js +35 -0
- package/dist/esm/libs/adapter-node/context.d.ts +13 -0
- package/dist/esm/libs/adapter-node/context.js +156 -0
- package/dist/esm/libs/adapter-node/index.d.ts +23 -0
- package/dist/esm/libs/adapter-node/index.js +118 -0
- package/dist/esm/libs/adapter-node/metadata.d.ts +14 -0
- package/dist/esm/libs/adapter-node/metadata.js +123 -0
- package/dist/esm/libs/adapter-node/request.d.ts +38 -0
- package/dist/esm/libs/adapter-node/request.js +429 -0
- package/dist/esm/libs/adapter-node/tool.d.ts +2 -0
- package/dist/esm/libs/adapter-node/tool.js +223 -0
- package/dist/esm/libs/adapter-node/types.d.ts +78 -0
- package/dist/esm/libs/adapter-node/types.js +2 -0
- package/dist/esm/libs/adapter-node/utils.d.ts +17 -0
- package/dist/esm/libs/adapter-node/utils.js +205 -0
- package/dist/esm/libs/request.d.ts +1 -0
- package/dist/esm/libs/request.js +3 -2
- package/dist/miniprogram/index.js +1 -1
- package/package.json +20 -4
- package/src/index.ts +63 -7
- package/src/libs/adapter-node/constants.ts +42 -0
- package/src/libs/adapter-node/context.ts +238 -0
- package/src/libs/adapter-node/index.ts +166 -0
- package/src/libs/adapter-node/metadata.ts +69 -0
- package/src/libs/adapter-node/request.ts +480 -0
- package/src/libs/adapter-node/tool.ts +223 -0
- package/src/libs/adapter-node/types.ts +116 -0
- package/src/libs/adapter-node/utils.ts +182 -0
- package/src/libs/request.ts +4 -2
- package/webpack/web.prod.js +13 -13
- package/webpack/webpack.miniprogram.js +9 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudbase/app",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.5",
|
|
4
4
|
"description": "cloudbase javascript sdk core",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -31,8 +31,24 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@cloudbase/adapter-interface": "^0.7.1",
|
|
33
33
|
"@cloudbase/adapter-wx_mp": "^1.2.1",
|
|
34
|
-
"@cloudbase/types": "3.0.
|
|
35
|
-
"@cloudbase/utilities": "3.0.
|
|
34
|
+
"@cloudbase/types": "3.0.5",
|
|
35
|
+
"@cloudbase/utilities": "3.0.5"
|
|
36
36
|
},
|
|
37
|
-
"
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"@cloudbase/signature-nodejs": ">=1.0.0",
|
|
39
|
+
"jsonwebtoken": ">=8.0.0",
|
|
40
|
+
"ws": "^8.18.0"
|
|
41
|
+
},
|
|
42
|
+
"peerDependenciesMeta": {
|
|
43
|
+
"@cloudbase/signature-nodejs": {
|
|
44
|
+
"optional": true
|
|
45
|
+
},
|
|
46
|
+
"jsonwebtoken": {
|
|
47
|
+
"optional": true
|
|
48
|
+
},
|
|
49
|
+
"ws": {
|
|
50
|
+
"optional": true
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"gitHead": "5f712e638aa0a0f082986a31c6288b18125a3e24"
|
|
38
54
|
}
|
package/src/index.ts
CHANGED
|
@@ -101,12 +101,6 @@ class Cloudbase implements ICloudbase {
|
|
|
101
101
|
],
|
|
102
102
|
})
|
|
103
103
|
public init(config: ICloudbaseConfig & { lang?: LANGS }): Cloudbase {
|
|
104
|
-
if (!config.env) {
|
|
105
|
-
throw new Error(JSON.stringify({
|
|
106
|
-
code: ERRORS.INVALID_PARAMS,
|
|
107
|
-
msg: 'env must not be specified',
|
|
108
|
-
}),)
|
|
109
|
-
}
|
|
110
104
|
config.endPointMode = config.endPointMode || 'GATEWAY'
|
|
111
105
|
|
|
112
106
|
// 初始化时若未兼容平台,则使用默认adapter
|
|
@@ -114,11 +108,22 @@ class Cloudbase implements ICloudbase {
|
|
|
114
108
|
this.useDefaultAdapter()
|
|
115
109
|
}
|
|
116
110
|
|
|
117
|
-
|
|
111
|
+
// 在判断config.env之前,先处理node环境的配置
|
|
112
|
+
config = this.dealNodeAdapterConfig(config)
|
|
113
|
+
|
|
114
|
+
if (!config.env) {
|
|
115
|
+
throw new Error(JSON.stringify({
|
|
116
|
+
code: ERRORS.INVALID_PARAMS,
|
|
117
|
+
msg: 'env must not be specified',
|
|
118
|
+
}),)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const reqConfig: IRequestConfig & { auth: ICloudbaseConfig['auth'] } = {
|
|
118
122
|
timeout: config.timeout || 5000,
|
|
119
123
|
timeoutMsg: `[${getSdkName()}][REQUEST TIMEOUT] request had been abort since didn't finished within${
|
|
120
124
|
config.timeout / 1000
|
|
121
125
|
}s`,
|
|
126
|
+
auth: config.auth,
|
|
122
127
|
}
|
|
123
128
|
|
|
124
129
|
this.requestClient = new Platform.adapter.reqClass(reqConfig)
|
|
@@ -148,12 +153,18 @@ class Cloudbase implements ICloudbase {
|
|
|
148
153
|
_fromApp: app,
|
|
149
154
|
i18n,
|
|
150
155
|
endPointMode: config.endPointMode,
|
|
156
|
+
auth: config.auth,
|
|
151
157
|
})
|
|
152
158
|
app.requestClient = this.requestClient
|
|
153
159
|
;(this as any)?.fire?.('cloudbase_init', app)
|
|
154
160
|
|
|
155
161
|
this.try2InitAuth(app)
|
|
156
162
|
|
|
163
|
+
// node环境可以从adapter获取nodeTool的方法
|
|
164
|
+
if (Platform.adapter?.nodeTool) {
|
|
165
|
+
Platform.adapter?.nodeTool(app, this.cloudbaseConfig)
|
|
166
|
+
}
|
|
167
|
+
|
|
157
168
|
return app
|
|
158
169
|
}
|
|
159
170
|
|
|
@@ -273,6 +284,40 @@ class Cloudbase implements ICloudbase {
|
|
|
273
284
|
console.log('try2InitAuth error:', error)
|
|
274
285
|
}
|
|
275
286
|
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* 处理 Node 环境下 adapter 的配置信息
|
|
290
|
+
* 在 Node 环境中,通过 adapter 自动获取密钥、环境ID、accessKey 等配置,
|
|
291
|
+
* 减少用户手动传入配置的负担
|
|
292
|
+
* @param config - 云开发初始化配置对象
|
|
293
|
+
* @returns 补充了 Node adapter 信息后的配置对象
|
|
294
|
+
*/
|
|
295
|
+
private dealNodeAdapterConfig(config: ICloudbaseConfig) {
|
|
296
|
+
// node环境可以从adapter获取默认的secretId和secretKey
|
|
297
|
+
if (!process.env.IS_BROWSER_BUILD && Platform.adapter?.getSecretInfo) {
|
|
298
|
+
const secretInfo = Platform.adapter?.getSecretInfo(config)
|
|
299
|
+
// 将 adapter 提供的密钥信息写入 config.auth
|
|
300
|
+
config.auth = {
|
|
301
|
+
...config.auth,
|
|
302
|
+
secretId: secretInfo.secretId,
|
|
303
|
+
secretKey: secretInfo.secretKey,
|
|
304
|
+
sessionToken: secretInfo.sessionToken,
|
|
305
|
+
secretType: secretInfo.secretType as any,
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// node 环境可能可以从上下文获取env,用户未显式指定时自动填充
|
|
309
|
+
if (!config.env) {
|
|
310
|
+
config.env = secretInfo.env
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// node adapter环境能读取到 process.env.CLOUDBASE_APIKEY 时,在没有传入accessKey时,使用 node adapter 提供的 accessKey(process.env.CLOUDBASE_APIKEY)
|
|
314
|
+
if (!config.accessKey && secretInfo.accessKey) {
|
|
315
|
+
config.accessKey = secretInfo.accessKey
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return config
|
|
320
|
+
}
|
|
276
321
|
}
|
|
277
322
|
|
|
278
323
|
// 类型导出
|
|
@@ -283,5 +328,16 @@ export const cloudbase: ICloudbase = new Cloudbase()
|
|
|
283
328
|
|
|
284
329
|
cloudbase.useAdapters(getWxDefaultAdapter())
|
|
285
330
|
|
|
331
|
+
// Node 环境下动态引入 adapter,浏览器/小程序打包时 IS_BROWSER_BUILD=true,此分支会被 tree-shaking 移除
|
|
332
|
+
if (!process.env.IS_BROWSER_BUILD) {
|
|
333
|
+
try {
|
|
334
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
|
|
335
|
+
const nodeAdapter = require('./libs/adapter-node').default
|
|
336
|
+
cloudbase.useAdapters(nodeAdapter)
|
|
337
|
+
} catch (error) {
|
|
338
|
+
throw error
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
286
342
|
// 默认导出实例
|
|
287
343
|
export default cloudbase
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/naming-convention */
|
|
2
|
+
/** 统一错误码定义 */
|
|
3
|
+
export const ERROR = {
|
|
4
|
+
INVALID_PARAM: {
|
|
5
|
+
code: 'INVALID_PARAM',
|
|
6
|
+
message: 'invalid param',
|
|
7
|
+
},
|
|
8
|
+
SYS_ERR: {
|
|
9
|
+
code: 'SYS_ERR',
|
|
10
|
+
message: 'system error',
|
|
11
|
+
},
|
|
12
|
+
STORAGE_REQUEST_FAIL: {
|
|
13
|
+
code: 'STORAGE_REQUEST_FAIL',
|
|
14
|
+
message: 'storage request fail',
|
|
15
|
+
},
|
|
16
|
+
STORAGE_FILE_NONEXIST: {
|
|
17
|
+
code: 'STORAGE_FILE_NONEXIST',
|
|
18
|
+
message: 'storage file not exist',
|
|
19
|
+
},
|
|
20
|
+
TCB_CLS_UNOPEN: {
|
|
21
|
+
code: 'TCB_CLS_UNOPEN',
|
|
22
|
+
message: '需要先开通日志检索功能',
|
|
23
|
+
},
|
|
24
|
+
INVALID_CONTEXT: {
|
|
25
|
+
code: 'INVALID_CONTEXT',
|
|
26
|
+
message: '无效的context对象,请使用 云函数入口的context参数',
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** 管理端 API 路径,用于替换 /web 路径(需要密钥签名时) */
|
|
31
|
+
export const ADMIN_PATH = '/admin'
|
|
32
|
+
|
|
33
|
+
/** 管理端获取客户端凭证 access_token,为管理员权限 */
|
|
34
|
+
export const CLIENT_AUTH_PATH = '/auth/v1/token/clientCredential'
|
|
35
|
+
|
|
36
|
+
/** 自定义用户 ID 校验规则:4~32 位,允许字母、数字及部分特殊字符 */
|
|
37
|
+
export const checkCustomUserIdRegex = /^[a-zA-Z0-9_\-#@~=*(){}[\]:.,<>+]{4,32}$/
|
|
38
|
+
|
|
39
|
+
export enum SECRET_TYPE {
|
|
40
|
+
'SECRET' = 'SECRET',
|
|
41
|
+
'SESSION_SECRET' = 'SESSION_SECRET',
|
|
42
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/* eslint-disable import/no-mutable-exports */
|
|
2
|
+
/* eslint-disable @typescript-eslint/naming-convention */
|
|
3
|
+
/* eslint-disable no-restricted-syntax */
|
|
4
|
+
import { ERROR, SECRET_TYPE } from './constants'
|
|
5
|
+
import { checkIsInScf, getEnv } from './utils'
|
|
6
|
+
import type { IContextParam, ICompleteCloudbaseContext } from './types'
|
|
7
|
+
import { ICloudbaseConfig } from '@cloudbase/types'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 解析并校验云函数入口的 Context 参数
|
|
11
|
+
* 将下划线命名的字段转为驼峰格式,并解析环境变量
|
|
12
|
+
*
|
|
13
|
+
* @param context - 云函数入口传入的 context 对象
|
|
14
|
+
* @returns 解析后的上下文对象,包含 environment(新架构)或 environ(老架构)
|
|
15
|
+
* @throws INVALID_CONTEXT - context 不是对象或解析失败时
|
|
16
|
+
*/
|
|
17
|
+
export function parseContext(context: IContextParam) {
|
|
18
|
+
if (typeof context !== 'object') {
|
|
19
|
+
throw { ...ERROR.INVALID_CONTEXT, message: 'context 必须为对象类型' }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const parseResult: any = {}
|
|
23
|
+
|
|
24
|
+
const {
|
|
25
|
+
memory_limit_in_mb,
|
|
26
|
+
time_limit_in_ms,
|
|
27
|
+
request_id,
|
|
28
|
+
function_version,
|
|
29
|
+
namespace,
|
|
30
|
+
function_name,
|
|
31
|
+
|
|
32
|
+
environ,
|
|
33
|
+
environment,
|
|
34
|
+
} = context
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
parseResult.memoryLimitInMb = memory_limit_in_mb
|
|
38
|
+
parseResult.timeLimitIns = time_limit_in_ms
|
|
39
|
+
parseResult.requestId = request_id
|
|
40
|
+
parseResult.functionVersion = function_version
|
|
41
|
+
parseResult.namespace = namespace
|
|
42
|
+
parseResult.functionName = function_name
|
|
43
|
+
|
|
44
|
+
if (environment) {
|
|
45
|
+
// 新架构:environment 为 JSON 字符串,直接解析
|
|
46
|
+
parseResult.environment = JSON.parse(environment)
|
|
47
|
+
} else {
|
|
48
|
+
// TODO: 考虑移除老架构的兼容逻辑
|
|
49
|
+
// 老架构:environ 为分号分隔的 key=value 字符串
|
|
50
|
+
// 存在 bug:value 含特殊字符时可能影响解析
|
|
51
|
+
const parseEnviron: any = environ?.split(';')
|
|
52
|
+
const parseEnvironObj: any = {}
|
|
53
|
+
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/no-for-in-array
|
|
55
|
+
for (const i in parseEnviron) {
|
|
56
|
+
if (parseEnviron[i].includes('=')) {
|
|
57
|
+
// 只按第一个 = 切割,保留 value 中的 =
|
|
58
|
+
const equalIndex = parseEnviron[i].indexOf('=')
|
|
59
|
+
const key = parseEnviron[i].slice(0, equalIndex)
|
|
60
|
+
let value: any = parseEnviron[i].slice(equalIndex + 1)
|
|
61
|
+
|
|
62
|
+
// value 含逗号时视为数组
|
|
63
|
+
if (value.indexOf(',') >= 0) {
|
|
64
|
+
value = value.split(',')
|
|
65
|
+
}
|
|
66
|
+
parseEnvironObj[key] = value
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
parseResult.environ = parseEnvironObj
|
|
71
|
+
}
|
|
72
|
+
} catch (_err: any) {
|
|
73
|
+
throw { ...ERROR.INVALID_CONTEXT }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return parseResult
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 获取当前云函数内的所有环境变量
|
|
81
|
+
* 统一从 process.env 和 context 两个来源合并取值
|
|
82
|
+
*
|
|
83
|
+
* 取值优先级:context > process.env
|
|
84
|
+
* 通过 TCB_CONTEXT_KEYS / WX_CONTEXT_KEYS 动态扩展需要读取的环境变量列表
|
|
85
|
+
*
|
|
86
|
+
* @param context - 可选,云函数入口的 context 参数
|
|
87
|
+
* @returns 合并后的完整环境变量上下文
|
|
88
|
+
*/
|
|
89
|
+
export const getCloudbaseContext = (context?: IContextParam): ICompleteCloudbaseContext => {
|
|
90
|
+
// 云函数环境下校验必要环境变量
|
|
91
|
+
if (checkIsInScf()) {
|
|
92
|
+
if (!getEnv('TENCENTCLOUD_REGION')) {
|
|
93
|
+
console.error('[TCB][ERROR] missing `TENCENTCLOUD_REGION` environment')
|
|
94
|
+
}
|
|
95
|
+
if (!getEnv('SCF_NAMESPACE')) {
|
|
96
|
+
console.error('[TCB][ERROR] missing `SCF_NAMESPACE` environment')
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 第一步:从 process.env 读取基础环境变量
|
|
101
|
+
const envObj = getEnv()
|
|
102
|
+
const envFromProcessEnv: any = {
|
|
103
|
+
TRIGGER_SRC: envObj.TRIGGER_SRC,
|
|
104
|
+
_SCF_TCB_LOG: envObj._SCF_TCB_LOG,
|
|
105
|
+
SCF_NAMESPACE: envObj.SCF_NAMESPACE,
|
|
106
|
+
TENCENTCLOUD_RUNENV: envObj.TENCENTCLOUD_RUNENV,
|
|
107
|
+
TENCENTCLOUD_SECRETID: envObj.TENCENTCLOUD_SECRETID,
|
|
108
|
+
TENCENTCLOUD_SECRETKEY: envObj.TENCENTCLOUD_SECRETKEY,
|
|
109
|
+
TENCENTCLOUD_SESSIONTOKEN: envObj.TENCENTCLOUD_SESSIONTOKEN,
|
|
110
|
+
WX_CONTEXT_KEYS: envObj.WX_CONTEXT_KEYS,
|
|
111
|
+
WX_TRIGGER_API_TOKEN_V0: envObj.WX_TRIGGER_API_TOKEN_V0,
|
|
112
|
+
WX_CLIENTIP: envObj.WX_CLIENTIP,
|
|
113
|
+
WX_CLIENTIPV6: envObj.WX_CLIENTIPV6,
|
|
114
|
+
TCB_CONTEXT_KEYS: envObj.TCB_CONTEXT_KEYS,
|
|
115
|
+
TCB_CONTEXT_CNFG: envObj.TCB_CONTEXT_CNFG,
|
|
116
|
+
LOGINTYPE: envObj.LOGINTYPE,
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 第二步:从 context 中解析环境变量
|
|
120
|
+
let envFromContext: any = {}
|
|
121
|
+
if (context) {
|
|
122
|
+
const { environment, environ } = parseContext(context)
|
|
123
|
+
envFromContext = environment || environ || {}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 第三步:根据 CONTEXT_KEYS 动态扩展变量列表(优先取 context 中的值)
|
|
127
|
+
const tcbContextKeys = envFromContext.TCB_CONTEXT_KEYS || envObj.TCB_CONTEXT_KEYS
|
|
128
|
+
const wxContextKeys = envFromContext.WX_CONTEXT_KEYS || envObj.WX_CONTEXT_KEYS
|
|
129
|
+
|
|
130
|
+
if (tcbContextKeys) {
|
|
131
|
+
try {
|
|
132
|
+
const tcbKeysList = tcbContextKeys.split(',')
|
|
133
|
+
for (const item of tcbKeysList) {
|
|
134
|
+
envFromProcessEnv[item] = envFromContext[item] || getEnv(item)
|
|
135
|
+
}
|
|
136
|
+
} catch (_e) {
|
|
137
|
+
//
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (wxContextKeys) {
|
|
142
|
+
try {
|
|
143
|
+
const wxKeysList = wxContextKeys.split(',')
|
|
144
|
+
for (const item of wxKeysList) {
|
|
145
|
+
envFromProcessEnv[item] = envFromContext[item] || getEnv(item)
|
|
146
|
+
}
|
|
147
|
+
} catch (_e) {
|
|
148
|
+
//
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 第四步:合并所有来源,context 的值覆盖 process.env,过滤 undefined
|
|
153
|
+
const allContext = { ...envFromProcessEnv, ...envFromContext }
|
|
154
|
+
|
|
155
|
+
const finalContext: any = {}
|
|
156
|
+
for (const key in allContext) {
|
|
157
|
+
if (allContext[key] !== undefined) {
|
|
158
|
+
finalContext[key] = allContext[key]
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return finalContext
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** 缓存 extendedContext,供其他模块读取(如 tool.ts 中获取 userId) */
|
|
166
|
+
export let INIT_CONFIG: ICloudbaseConfig = {} as unknown as ICloudbaseConfig
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* 从环境变量中获取腾讯云临时密钥信息
|
|
170
|
+
* 用于 API 请求签名
|
|
171
|
+
*/
|
|
172
|
+
export const getSecretInfo = (config?: ICloudbaseConfig) => {
|
|
173
|
+
INIT_CONFIG = JSON.parse(JSON.stringify(config || {}))
|
|
174
|
+
|
|
175
|
+
const { TCB_ENV, SCF_NAMESPACE } = getCloudbaseContext()
|
|
176
|
+
|
|
177
|
+
let env = config?.env || TCB_ENV || SCF_NAMESPACE || ''
|
|
178
|
+
const defaultInfo = {
|
|
179
|
+
env,
|
|
180
|
+
secretId: '',
|
|
181
|
+
secretKey: '',
|
|
182
|
+
sessionToken: '',
|
|
183
|
+
accessKey: '',
|
|
184
|
+
secretType: '',
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (config?.accessKey) {
|
|
188
|
+
return defaultInfo
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (config?.auth?.secretId && config?.auth?.secretKey) {
|
|
192
|
+
return {
|
|
193
|
+
...defaultInfo,
|
|
194
|
+
secretId: config.auth.secretId,
|
|
195
|
+
secretKey: config.auth.secretKey,
|
|
196
|
+
secretType: SECRET_TYPE.SECRET,
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const secretEnv = getEnv()
|
|
201
|
+
const { TENCENTCLOUD_SECRETID } = secretEnv
|
|
202
|
+
const { TENCENTCLOUD_SECRETKEY } = secretEnv
|
|
203
|
+
const { TENCENTCLOUD_SESSIONTOKEN } = secretEnv
|
|
204
|
+
const { CLOUDBASE_APIKEY } = secretEnv
|
|
205
|
+
|
|
206
|
+
if (CLOUDBASE_APIKEY) {
|
|
207
|
+
return {
|
|
208
|
+
...defaultInfo,
|
|
209
|
+
accessKey: CLOUDBASE_APIKEY,
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (TENCENTCLOUD_SECRETID && TENCENTCLOUD_SECRETKEY) {
|
|
214
|
+
return {
|
|
215
|
+
...defaultInfo,
|
|
216
|
+
secretId: TENCENTCLOUD_SECRETID,
|
|
217
|
+
secretKey: TENCENTCLOUD_SECRETKEY,
|
|
218
|
+
sessionToken: TENCENTCLOUD_SESSIONTOKEN || '',
|
|
219
|
+
secretType: SECRET_TYPE.SESSION_SECRET,
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (config?.context?.extendedContext) {
|
|
224
|
+
const { extendedContext } = config.context
|
|
225
|
+
if (!env) {
|
|
226
|
+
env = extendedContext.envId || ''
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
...defaultInfo,
|
|
231
|
+
secretId: extendedContext?.tmpSecret?.secretId || '',
|
|
232
|
+
secretKey: extendedContext?.tmpSecret?.secretKey || '',
|
|
233
|
+
sessionToken: extendedContext?.tmpSecret?.token || '',
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return { ...defaultInfo }
|
|
238
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/* eslint-disable prefer-destructuring */
|
|
2
|
+
/* eslint-disable @typescript-eslint/naming-convention */
|
|
3
|
+
import {
|
|
4
|
+
SDKAdapterInterface,
|
|
5
|
+
StorageInterface,
|
|
6
|
+
StorageType,
|
|
7
|
+
WebSocketContructor as WebSocketConstructor,
|
|
8
|
+
} from '@cloudbase/adapter-interface'
|
|
9
|
+
|
|
10
|
+
import { NodeRequest } from './request'
|
|
11
|
+
import { nodeTool } from './tool'
|
|
12
|
+
import { getSecretInfo } from './context'
|
|
13
|
+
import { parseQueryString } from './utils'
|
|
14
|
+
import { ICloudbaseConfig } from '@cloudbase/types'
|
|
15
|
+
|
|
16
|
+
// ws 延迟加载,避免非 Node 环境打包时引入
|
|
17
|
+
let _WS: any = null
|
|
18
|
+
function getWS() {
|
|
19
|
+
if (!_WS) {
|
|
20
|
+
try {
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
|
|
22
|
+
_WS = require('ws')
|
|
23
|
+
} catch (e) {
|
|
24
|
+
throw new Error('缺少依赖 ws,请执行以下命令安装:\n\n'
|
|
25
|
+
+ ' npm install ws\n\n'
|
|
26
|
+
+ '该依赖用于 Node 环境下的 WebSocket 连接。',)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return _WS
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Re-export for external consumers
|
|
33
|
+
export { NodeRequest } from './request'
|
|
34
|
+
export { parseQueryString, createWebStreamFromNodeReadableStream } from './utils'
|
|
35
|
+
export type {
|
|
36
|
+
ICreateTicketOpts,
|
|
37
|
+
IGetUserInfoResult,
|
|
38
|
+
IGetEndUserInfoResult,
|
|
39
|
+
IUserInfoQuery,
|
|
40
|
+
IContextParam,
|
|
41
|
+
ICompleteCloudbaseContext,
|
|
42
|
+
} from './types'
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 环境匹配检测:判断当前运行环境是否为 Node.js
|
|
46
|
+
* SDK 通过此方法决定是否使用当前 adapter
|
|
47
|
+
*/
|
|
48
|
+
function isMatch(): boolean {
|
|
49
|
+
// eslint-disable-next-line eqeqeq
|
|
50
|
+
return typeof process !== 'undefined' && process.versions != null && process.versions.node != null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 基于 Map 的内存存储实现
|
|
55
|
+
* Node.js 环境下无浏览器 Storage API,使用内存 Map 模拟
|
|
56
|
+
*/
|
|
57
|
+
const storage: StorageInterface = (() => {
|
|
58
|
+
const db = new Map()
|
|
59
|
+
return {
|
|
60
|
+
mode: 'sync',
|
|
61
|
+
setItem(key, value) {
|
|
62
|
+
db.set(key, value)
|
|
63
|
+
},
|
|
64
|
+
getItem(key) {
|
|
65
|
+
return db.get(key)
|
|
66
|
+
},
|
|
67
|
+
removeItem(key) {
|
|
68
|
+
db.delete(key)
|
|
69
|
+
},
|
|
70
|
+
clear() {
|
|
71
|
+
db.clear()
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
})()
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 生成 Node.js 环境的 SDK Adapter
|
|
78
|
+
* 组装请求类、存储、WebSocket、认证等能力
|
|
79
|
+
*
|
|
80
|
+
* @param options - 配置项,包含 EventBus(用于验证码回调)
|
|
81
|
+
* @returns SDK Adapter 实例
|
|
82
|
+
*/
|
|
83
|
+
function genAdapter(options: any) {
|
|
84
|
+
const adapter: SDKAdapterInterface & {
|
|
85
|
+
getSecretInfo: (config?: ICloudbaseConfig) => {
|
|
86
|
+
env: string
|
|
87
|
+
secretId: string
|
|
88
|
+
secretKey: string
|
|
89
|
+
sessionToken: string
|
|
90
|
+
accessKey: string
|
|
91
|
+
secretType: string
|
|
92
|
+
}
|
|
93
|
+
nodeTool: (app: any, config: ICloudbaseConfig) => void
|
|
94
|
+
} = {
|
|
95
|
+
nodeTool,
|
|
96
|
+
getSecretInfo,
|
|
97
|
+
root: { globalThis: {} },
|
|
98
|
+
reqClass: NodeRequest,
|
|
99
|
+
wsClass: getWS() as WebSocketConstructor,
|
|
100
|
+
sessionStorage: storage,
|
|
101
|
+
localStorage: storage,
|
|
102
|
+
primaryStorage: StorageType.session,
|
|
103
|
+
captchaOptions: {
|
|
104
|
+
/**
|
|
105
|
+
* 验证码处理回调
|
|
106
|
+
* 解析 data: URI 中的验证码参数,通过 EventBus 通知业务层
|
|
107
|
+
* 等待业务层返回验证结果后 resolve
|
|
108
|
+
*/
|
|
109
|
+
openURIWithCallback: (_url: string) => {
|
|
110
|
+
const { EventBus } = options
|
|
111
|
+
let queryObj: Record<string, string | string[]> = {}
|
|
112
|
+
let url = _url
|
|
113
|
+
|
|
114
|
+
// 从 data: URI 中提取查询参数
|
|
115
|
+
const matched = _url.match(/^(data:.*?)(\?[^#\s]*)?$/)
|
|
116
|
+
if (matched) {
|
|
117
|
+
url = matched[1]
|
|
118
|
+
const search = matched[2]
|
|
119
|
+
if (search) {
|
|
120
|
+
queryObj = parseQueryString(search)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const { token, ...restQueryObj } = queryObj
|
|
125
|
+
|
|
126
|
+
// data: 协议但无 token,视为无效验证码数据
|
|
127
|
+
if (/^data:/.test(url) && !token) {
|
|
128
|
+
return Promise.reject({
|
|
129
|
+
error: 'invalid_argument',
|
|
130
|
+
error_description: `invalie captcha data: ${_url}`,
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
if (!token) {
|
|
134
|
+
return Promise.reject({
|
|
135
|
+
error: 'unimplemented',
|
|
136
|
+
error_description: 'need to impl captcha data',
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return new Promise((resolve) => {
|
|
141
|
+
// 通知业务层展示验证码
|
|
142
|
+
EventBus.$emit('CAPTCHA_DATA_CHANGE', {
|
|
143
|
+
...restQueryObj,
|
|
144
|
+
token,
|
|
145
|
+
url,
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
// 等待业务层返回验证结果
|
|
149
|
+
EventBus.$once('RESOLVE_CAPTCHA_DATA', (res: { captcha_token: string; expires_in: number }) => {
|
|
150
|
+
resolve(res)
|
|
151
|
+
})
|
|
152
|
+
})
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
}
|
|
156
|
+
return adapter
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Node.js Adapter 入口导出 */
|
|
160
|
+
const adapter = {
|
|
161
|
+
genAdapter,
|
|
162
|
+
isMatch,
|
|
163
|
+
runtime: 'node-adapter',
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export default adapter
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/naming-convention */
|
|
2
|
+
// 注意:改地址已经不是一定可以访问的了
|
|
3
|
+
export const kMetadataBaseUrl = 'http://metadata.tencentyun.com'
|
|
4
|
+
|
|
5
|
+
export const kAppIdPath = 'meta-data/app-id'
|
|
6
|
+
export const kSecurityCredentialsPath = 'meta-data/cam/security-credentials'
|
|
7
|
+
|
|
8
|
+
export enum kMetadataVersions {
|
|
9
|
+
'v20170919' = '2017-09-19',
|
|
10
|
+
'v1.0' = '1.0',
|
|
11
|
+
'latest' = 'latest',
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function isAppId(appIdStr: string) {
|
|
15
|
+
return /^[1-9][0-9]{4,64}$/gim.test(appIdStr)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function lookup(path: string, options: { timeout?: number } = {}): Promise<string> {
|
|
19
|
+
const url = `${kMetadataBaseUrl}/${kMetadataVersions.latest}/${path}`
|
|
20
|
+
|
|
21
|
+
const controller = new AbortController()
|
|
22
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
23
|
+
if (options.timeout) {
|
|
24
|
+
timer = setTimeout(() => controller.abort(), options.timeout)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const resp = await fetch(url, { signal: controller.signal })
|
|
29
|
+
if (resp.status === 200) {
|
|
30
|
+
return await resp.text()
|
|
31
|
+
}
|
|
32
|
+
throw new Error(`[ERROR] GET ${url} status: ${resp.status}`)
|
|
33
|
+
} finally {
|
|
34
|
+
if (timer) clearTimeout(timer)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const metadataCache: { appId: string | undefined } = {
|
|
39
|
+
appId: undefined,
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* lookupAppId - 该方法主要用于判断是否在云上环境
|
|
43
|
+
* @returns
|
|
44
|
+
*/
|
|
45
|
+
export async function lookupAppId(): Promise<string> {
|
|
46
|
+
if (metadataCache.appId === undefined) {
|
|
47
|
+
try {
|
|
48
|
+
// 只有首次会请求且要求快速返回,超时时间很短,DNS无法解析将会超时返回
|
|
49
|
+
// 在云环境中,这个时间通常在 10ms 内,部分耗时长(30+ms)的情况是 DNS 解析耗时长(27+ms)
|
|
50
|
+
const appId = await lookup(kAppIdPath, { timeout: 30 })
|
|
51
|
+
if (isAppId(appId)) {
|
|
52
|
+
metadataCache.appId = appId
|
|
53
|
+
} else {
|
|
54
|
+
metadataCache.appId = ''
|
|
55
|
+
}
|
|
56
|
+
} catch (_e) {
|
|
57
|
+
// ignore
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return metadataCache.appId || ''
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function lookupCredentials(ruleName: string): Promise<string> {
|
|
64
|
+
// `${kMetadataBaseUrl}/meta-data/cam/security-credentials/TCB_QcsRole`
|
|
65
|
+
// 这里设置了一个较短的超时时间,因为这个请求是在云环境中发起的,通常会很快返回
|
|
66
|
+
return await lookup(`${kSecurityCredentialsPath}/${ruleName}`, {
|
|
67
|
+
timeout: 200,
|
|
68
|
+
})
|
|
69
|
+
}
|