@cloudbase/app 3.0.4 → 3.0.6

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.
Files changed (47) hide show
  1. package/dist/cjs/index.js +13 -9
  2. package/dist/cjs/libs/adapter-node/constants.d.ts +33 -0
  3. package/dist/cjs/libs/adapter-node/constants.js +38 -0
  4. package/dist/cjs/libs/adapter-node/context.d.ts +13 -0
  5. package/dist/cjs/libs/adapter-node/context.js +162 -0
  6. package/dist/cjs/libs/adapter-node/index.d.ts +23 -0
  7. package/dist/cjs/libs/adapter-node/index.js +124 -0
  8. package/dist/cjs/libs/adapter-node/metadata.d.ts +14 -0
  9. package/dist/cjs/libs/adapter-node/metadata.js +130 -0
  10. package/dist/cjs/libs/adapter-node/request.d.ts +38 -0
  11. package/dist/cjs/libs/adapter-node/request.js +440 -0
  12. package/dist/cjs/libs/adapter-node/tool.d.ts +2 -0
  13. package/dist/cjs/libs/adapter-node/tool.js +227 -0
  14. package/dist/cjs/libs/adapter-node/types.d.ts +78 -0
  15. package/dist/cjs/libs/adapter-node/types.js +3 -0
  16. package/dist/cjs/libs/adapter-node/utils.d.ts +17 -0
  17. package/dist/cjs/libs/adapter-node/utils.js +221 -0
  18. package/dist/esm/index.js +13 -9
  19. package/dist/esm/libs/adapter-node/constants.d.ts +33 -0
  20. package/dist/esm/libs/adapter-node/constants.js +35 -0
  21. package/dist/esm/libs/adapter-node/context.d.ts +13 -0
  22. package/dist/esm/libs/adapter-node/context.js +156 -0
  23. package/dist/esm/libs/adapter-node/index.d.ts +23 -0
  24. package/dist/esm/libs/adapter-node/index.js +118 -0
  25. package/dist/esm/libs/adapter-node/metadata.d.ts +14 -0
  26. package/dist/esm/libs/adapter-node/metadata.js +123 -0
  27. package/dist/esm/libs/adapter-node/request.d.ts +38 -0
  28. package/dist/esm/libs/adapter-node/request.js +437 -0
  29. package/dist/esm/libs/adapter-node/tool.d.ts +2 -0
  30. package/dist/esm/libs/adapter-node/tool.js +223 -0
  31. package/dist/esm/libs/adapter-node/types.d.ts +78 -0
  32. package/dist/esm/libs/adapter-node/types.js +2 -0
  33. package/dist/esm/libs/adapter-node/utils.d.ts +17 -0
  34. package/dist/esm/libs/adapter-node/utils.js +205 -0
  35. package/dist/miniprogram/index.js +1 -1
  36. package/package.json +20 -4
  37. package/src/index.ts +14 -2
  38. package/src/libs/adapter-node/constants.ts +42 -0
  39. package/src/libs/adapter-node/context.ts +238 -0
  40. package/src/libs/adapter-node/index.ts +166 -0
  41. package/src/libs/adapter-node/metadata.ts +69 -0
  42. package/src/libs/adapter-node/request.ts +486 -0
  43. package/src/libs/adapter-node/tool.ts +223 -0
  44. package/src/libs/adapter-node/types.ts +116 -0
  45. package/src/libs/adapter-node/utils.ts +182 -0
  46. package/webpack/web.prod.js +14 -13
  47. package/webpack/webpack.miniprogram.js +10 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudbase/app",
3
- "version": "3.0.4",
3
+ "version": "3.0.6",
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.4",
35
- "@cloudbase/utilities": "3.0.4"
34
+ "@cloudbase/types": "3.0.6",
35
+ "@cloudbase/utilities": "3.0.6"
36
36
  },
37
- "gitHead": "b5f5f8541b7e0d944bf345f8aa23c4e0ccb42b4f"
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": "157feb77139fd344ddcb80fd3bd94c282e853664"
38
54
  }
package/src/index.ts CHANGED
@@ -119,7 +119,7 @@ class Cloudbase implements ICloudbase {
119
119
  }
120
120
 
121
121
  const reqConfig: IRequestConfig & { auth: ICloudbaseConfig['auth'] } = {
122
- timeout: config.timeout || 5000,
122
+ timeout: config.timeout || 15000,
123
123
  timeoutMsg: `[${getSdkName()}][REQUEST TIMEOUT] request had been abort since didn't finished within${
124
124
  config.timeout / 1000
125
125
  }s`,
@@ -294,10 +294,11 @@ class Cloudbase implements ICloudbase {
294
294
  */
295
295
  private dealNodeAdapterConfig(config: ICloudbaseConfig) {
296
296
  // node环境可以从adapter获取默认的secretId和secretKey
297
- if (Platform.adapter?.getSecretInfo) {
297
+ if (!process.env.IS_BROWSER_BUILD && Platform.adapter?.getSecretInfo) {
298
298
  const secretInfo = Platform.adapter?.getSecretInfo(config)
299
299
  // 将 adapter 提供的密钥信息写入 config.auth
300
300
  config.auth = {
301
+ ...config.auth,
301
302
  secretId: secretInfo.secretId,
302
303
  secretKey: secretInfo.secretKey,
303
304
  sessionToken: secretInfo.sessionToken,
@@ -327,5 +328,16 @@ export const cloudbase: ICloudbase = new Cloudbase()
327
328
 
328
329
  cloudbase.useAdapters(getWxDefaultAdapter())
329
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
+
330
342
  // 默认导出实例
331
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
+ }