@pnt-team/pnt-component-frontend 0.0.92 → 0.0.96
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/README.md +26 -4
- package/lib/cjs/api/cos.d.ts +103 -0
- package/lib/cjs/api/cos.js +137 -0
- package/lib/cjs/components/PntCosUploader/index.d.ts +135 -0
- package/lib/cjs/components/PntCosUploader/index.js +209 -0
- package/lib/cjs/components/PntImage/index.d.ts +9 -3
- package/lib/cjs/components/PntImage/index.js +9 -1
- package/lib/cjs/components/PntSecureBox/index.js +8 -3
- package/lib/cjs/components/PntUpload/index.d.ts +149 -0
- package/lib/cjs/components/PntUpload/index.js +228 -0
- package/lib/cjs/components/PntUpload/index.scss +101 -0
- package/lib/cjs/hooks/useHighSecurity.d.ts +55 -15
- package/lib/cjs/hooks/useHighSecurity.js +163 -35
- package/lib/cjs/index.d.ts +6 -0
- package/lib/cjs/index.js +18 -2
- package/lib/esm/api/cos.d.ts +103 -0
- package/lib/esm/api/cos.js +189 -0
- package/lib/esm/components/PntCosUploader/index.d.ts +135 -0
- package/lib/esm/components/PntCosUploader/index.js +289 -0
- package/lib/esm/components/PntImage/index.d.ts +9 -3
- package/lib/esm/components/PntImage/index.js +22 -3
- package/lib/esm/components/PntSecureBox/index.js +9 -3
- package/lib/esm/components/PntUpload/index.d.ts +149 -0
- package/lib/esm/components/PntUpload/index.js +276 -0
- package/lib/esm/components/PntUpload/index.scss +101 -0
- package/lib/esm/hooks/useHighSecurity.d.ts +55 -15
- package/lib/esm/hooks/useHighSecurity.js +174 -32
- package/lib/esm/index.d.ts +6 -0
- package/lib/esm/index.js +9 -1
- package/lib/umd/pnt-component-frontend.min.css +1 -1
- package/lib/umd/pnt-component-frontend.min.js +1 -1
- package/package.json +1 -1
|
@@ -25,47 +25,175 @@ module.exports = __toCommonJS(useHighSecurity_exports);
|
|
|
25
25
|
var import_api = require("../api");
|
|
26
26
|
var import_config = require("../config");
|
|
27
27
|
var import_react = require("react");
|
|
28
|
+
var buildCacheKey = (gameId, env, key) => `${gameId}:${env}:${key}`;
|
|
28
29
|
var securityCache = /* @__PURE__ */ new Map();
|
|
29
|
-
var
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
30
|
+
var inflightRequests = /* @__PURE__ */ new Map();
|
|
31
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
32
|
+
var emitChange = () => {
|
|
33
|
+
listeners.forEach((listener) => listener());
|
|
34
|
+
};
|
|
35
|
+
var subscribe = (listener) => {
|
|
36
|
+
listeners.add(listener);
|
|
37
|
+
return () => {
|
|
38
|
+
listeners.delete(listener);
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
var getSnapshot = () => securityCache;
|
|
42
|
+
var setEntries = (gameId, env, items) => {
|
|
43
|
+
if (!items.length)
|
|
44
|
+
return;
|
|
45
|
+
const next = new Map(securityCache);
|
|
46
|
+
items.forEach((item) => {
|
|
47
|
+
next.set(buildCacheKey(gameId, env, item.key), item.value);
|
|
48
|
+
});
|
|
49
|
+
securityCache = next;
|
|
50
|
+
emitChange();
|
|
51
|
+
};
|
|
52
|
+
var matchFilter = (cacheKey, filter) => {
|
|
53
|
+
if (!filter || filter.gameId === void 0 && filter.env === void 0) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
const parts = cacheKey.split(":");
|
|
57
|
+
const [cachedGameId, cachedEnv] = parts;
|
|
58
|
+
if (filter.gameId !== void 0 && cachedGameId !== String(filter.gameId)) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
if (filter.env !== void 0 && cachedEnv !== String(filter.env)) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
};
|
|
66
|
+
var clearEntries = (filter) => {
|
|
67
|
+
const next = /* @__PURE__ */ new Map();
|
|
68
|
+
securityCache.forEach((value, key) => {
|
|
69
|
+
if (!matchFilter(key, filter)) {
|
|
70
|
+
next.set(key, value);
|
|
38
71
|
}
|
|
72
|
+
});
|
|
73
|
+
if (next.size !== securityCache.size) {
|
|
74
|
+
securityCache = next;
|
|
75
|
+
emitChange();
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
var getDefaultPageId = () => typeof window !== "undefined" && window.location ? window.location.pathname : "";
|
|
79
|
+
var useHighSecurity = (options) => {
|
|
80
|
+
const { http } = (0, import_config.usePntConfig)();
|
|
81
|
+
const snapshot = (0, import_react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
|
|
82
|
+
const scopeGameId = (options == null ? void 0 : options.gameId) === void 0 ? void 0 : String(options.gameId);
|
|
83
|
+
const scopeEnv = (options == null ? void 0 : options.env) === void 0 ? void 0 : String(options.env);
|
|
84
|
+
const hasScope = scopeGameId !== void 0 && scopeEnv !== void 0;
|
|
85
|
+
const securityData = (0, import_react.useMemo)(() => {
|
|
86
|
+
if (!hasScope)
|
|
87
|
+
return {};
|
|
88
|
+
const prefix = `${scopeGameId}:${scopeEnv}:`;
|
|
89
|
+
const result = {};
|
|
90
|
+
snapshot.forEach((value, cacheKey) => {
|
|
91
|
+
if (cacheKey.startsWith(prefix)) {
|
|
92
|
+
result[cacheKey.slice(prefix.length)] = value;
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
return result;
|
|
96
|
+
}, [snapshot, hasScope, scopeGameId, scopeEnv]);
|
|
97
|
+
const isUnlocked = (0, import_react.useCallback)(
|
|
98
|
+
(key) => {
|
|
99
|
+
if (!hasScope)
|
|
100
|
+
return false;
|
|
101
|
+
return snapshot.has(buildCacheKey(scopeGameId, scopeEnv, key));
|
|
102
|
+
},
|
|
103
|
+
[snapshot, hasScope, scopeGameId, scopeEnv]
|
|
39
104
|
);
|
|
40
|
-
const requestSecurityData =
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
105
|
+
const requestSecurityData = (0, import_react.useCallback)(
|
|
106
|
+
async ({
|
|
107
|
+
gameId,
|
|
108
|
+
env,
|
|
109
|
+
pageId,
|
|
110
|
+
param
|
|
111
|
+
}) => {
|
|
112
|
+
const gid = String(gameId);
|
|
113
|
+
const eid = String(env);
|
|
114
|
+
const result = new Array(
|
|
115
|
+
param.length
|
|
116
|
+
);
|
|
117
|
+
const missing = [];
|
|
118
|
+
const missingIndexes = [];
|
|
119
|
+
param.forEach((item, index) => {
|
|
120
|
+
const cached = snapshot.get(buildCacheKey(gid, eid, item.key));
|
|
121
|
+
if (cached !== void 0) {
|
|
122
|
+
result[index] = { key: item.key, value: cached };
|
|
123
|
+
} else {
|
|
124
|
+
missing.push({ key: item.key, value: item.value });
|
|
125
|
+
missingIndexes.push(index);
|
|
126
|
+
}
|
|
62
127
|
});
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
128
|
+
if (!missing.length) {
|
|
129
|
+
return result.filter(
|
|
130
|
+
(item) => item !== void 0
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
const body = {
|
|
134
|
+
game_id: Number(gameId),
|
|
135
|
+
env: Number(env),
|
|
136
|
+
page_id: pageId || getDefaultPageId(),
|
|
137
|
+
param: missing
|
|
138
|
+
};
|
|
139
|
+
const requestKey = `${gid}:${eid}:${missing.map((item) => `${item.key}${item.value}`).join("")}`;
|
|
140
|
+
const existing = inflightRequests.get(requestKey);
|
|
141
|
+
if (existing)
|
|
142
|
+
return existing;
|
|
143
|
+
const promise = (async () => {
|
|
144
|
+
var _a;
|
|
145
|
+
try {
|
|
146
|
+
const res = await (0, import_api.businessGameInfoHighSecurityDecrypt)(http, body);
|
|
147
|
+
if (res.code === 0 && Array.isArray(res.data)) {
|
|
148
|
+
const decrypted = res.data.map((raw) => {
|
|
149
|
+
var _a2;
|
|
150
|
+
if (raw && typeof raw === "object" && "key" in raw) {
|
|
151
|
+
const item = raw;
|
|
152
|
+
if (typeof item.key === "string") {
|
|
153
|
+
return { key: item.key, value: String((_a2 = item.value) != null ? _a2 : "") };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}).filter((item) => item !== null);
|
|
158
|
+
setEntries(gid, eid, decrypted);
|
|
159
|
+
decrypted.forEach((item) => {
|
|
160
|
+
const index = missingIndexes.find(
|
|
161
|
+
(i) => param[i].key === item.key
|
|
162
|
+
);
|
|
163
|
+
if (index !== void 0)
|
|
164
|
+
result[index] = item;
|
|
165
|
+
});
|
|
166
|
+
} else {
|
|
167
|
+
console.warn(
|
|
168
|
+
"[pnt-component-frontend] high-security-decrypt 解锁失败",
|
|
169
|
+
(_a = res == null ? void 0 : res.msg) != null ? _a : res == null ? void 0 : res.code
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
} catch (error) {
|
|
173
|
+
console.warn(
|
|
174
|
+
"[pnt-component-frontend] high-security-decrypt 请求异常",
|
|
175
|
+
error
|
|
176
|
+
);
|
|
177
|
+
} finally {
|
|
178
|
+
inflightRequests.delete(requestKey);
|
|
179
|
+
}
|
|
180
|
+
return result.filter(
|
|
181
|
+
(item) => item !== void 0
|
|
182
|
+
);
|
|
183
|
+
})();
|
|
184
|
+
inflightRequests.set(requestKey, promise);
|
|
185
|
+
return promise;
|
|
186
|
+
},
|
|
187
|
+
[http, snapshot]
|
|
188
|
+
);
|
|
189
|
+
const clearSecurityData = (0, import_react.useCallback)((filter) => {
|
|
190
|
+
clearEntries(filter);
|
|
191
|
+
}, []);
|
|
66
192
|
return {
|
|
67
193
|
securityData,
|
|
68
|
-
requestSecurityData
|
|
194
|
+
requestSecurityData,
|
|
195
|
+
clearSecurityData,
|
|
196
|
+
isUnlocked
|
|
69
197
|
};
|
|
70
198
|
};
|
|
71
199
|
// Annotate the CommonJS export names for ESM import in node:
|
package/lib/cjs/index.d.ts
CHANGED
|
@@ -2,4 +2,10 @@ export { PntProvider, usePntConfig, type PntConfig, type commonEesType, } from '
|
|
|
2
2
|
export { default as PntImage } from './components/PntImage';
|
|
3
3
|
export { default as PntBusinessSelect } from './components/PntBusinessSelect';
|
|
4
4
|
export { default as PntSecureBox } from './components/PntSecureBox';
|
|
5
|
+
export { default as PntUpload } from './components/PntUpload';
|
|
6
|
+
export type { PntUploadErrorPhase, PntUploadProps, PntUploadTexts, } from './components/PntUpload';
|
|
7
|
+
export { createPntCosUploader, normalizeSnakeCredential, default as usePntCosUploader, type CosScene, type FileNameStrategy, type GetCosCredentialParams, type NormalizeCredential, type NormalizedCosCredential, type PntCosRequestOverrides, type PntCosUploadOptions, type PntCosUploadResult, type PntCosUploader as PntCosUploaderInstance, } from './components/PntCosUploader';
|
|
8
|
+
export { useHighSecurity } from './hooks/useHighSecurity';
|
|
9
|
+
export type { ClearSecurityDataFilter, HighSecurityParamItem, HighSecurityRequestParams, UseHighSecurityOptions, UseHighSecurityResult, } from './hooks/useHighSecurity';
|
|
10
|
+
export { isSecurityData } from './utils';
|
|
5
11
|
export { default as PntGeneralLayout } from './components/PntGeneralLayout';
|
package/lib/cjs/index.js
CHANGED
|
@@ -34,13 +34,23 @@ __export(src_exports, {
|
|
|
34
34
|
PntImage: () => import_PntImage.default,
|
|
35
35
|
PntProvider: () => import_config.PntProvider,
|
|
36
36
|
PntSecureBox: () => import_PntSecureBox.default,
|
|
37
|
-
|
|
37
|
+
PntUpload: () => import_PntUpload.default,
|
|
38
|
+
createPntCosUploader: () => import_PntCosUploader.createPntCosUploader,
|
|
39
|
+
isSecurityData: () => import_utils.isSecurityData,
|
|
40
|
+
normalizeSnakeCredential: () => import_PntCosUploader.normalizeSnakeCredential,
|
|
41
|
+
useHighSecurity: () => import_useHighSecurity.useHighSecurity,
|
|
42
|
+
usePntConfig: () => import_config.usePntConfig,
|
|
43
|
+
usePntCosUploader: () => import_PntCosUploader.default
|
|
38
44
|
});
|
|
39
45
|
module.exports = __toCommonJS(src_exports);
|
|
40
46
|
var import_config = require("./config");
|
|
41
47
|
var import_PntImage = __toESM(require("./components/PntImage"));
|
|
42
48
|
var import_PntBusinessSelect = __toESM(require("./components/PntBusinessSelect"));
|
|
43
49
|
var import_PntSecureBox = __toESM(require("./components/PntSecureBox"));
|
|
50
|
+
var import_PntUpload = __toESM(require("./components/PntUpload"));
|
|
51
|
+
var import_PntCosUploader = __toESM(require("./components/PntCosUploader"));
|
|
52
|
+
var import_useHighSecurity = require("./hooks/useHighSecurity");
|
|
53
|
+
var import_utils = require("./utils");
|
|
44
54
|
var import_PntGeneralLayout = __toESM(require("./components/PntGeneralLayout"));
|
|
45
55
|
// Annotate the CommonJS export names for ESM import in node:
|
|
46
56
|
0 && (module.exports = {
|
|
@@ -49,5 +59,11 @@ var import_PntGeneralLayout = __toESM(require("./components/PntGeneralLayout"));
|
|
|
49
59
|
PntImage,
|
|
50
60
|
PntProvider,
|
|
51
61
|
PntSecureBox,
|
|
52
|
-
|
|
62
|
+
PntUpload,
|
|
63
|
+
createPntCosUploader,
|
|
64
|
+
isSecurityData,
|
|
65
|
+
normalizeSnakeCredential,
|
|
66
|
+
useHighSecurity,
|
|
67
|
+
usePntConfig,
|
|
68
|
+
usePntCosUploader
|
|
53
69
|
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { AxiosInstance } from 'axios';
|
|
2
|
+
/**
|
|
3
|
+
* 归一化后的 COS 临时凭证
|
|
4
|
+
* @description 各后端接口字段命名不统一(snake/camel),组件库内部统一为 camelCase
|
|
5
|
+
*/
|
|
6
|
+
export interface NormalizedCosCredential {
|
|
7
|
+
/** COS 存储桶 */
|
|
8
|
+
bucket: string;
|
|
9
|
+
/** COS 地域 */
|
|
10
|
+
region: string;
|
|
11
|
+
/** 临时密钥 ID */
|
|
12
|
+
secretId: string;
|
|
13
|
+
/** 临时密钥 Key */
|
|
14
|
+
secretKey: string;
|
|
15
|
+
/** 临时会话令牌 */
|
|
16
|
+
sessionToken: string;
|
|
17
|
+
/** 凭证生效时间(秒) */
|
|
18
|
+
startTime: number;
|
|
19
|
+
/** 凭证过期时间(秒) */
|
|
20
|
+
expiredTime: number;
|
|
21
|
+
/**
|
|
22
|
+
* 业务路径映射
|
|
23
|
+
* @description 后端按业务类型返回的可写路径 map;消费方按 pathKey 选取
|
|
24
|
+
*/
|
|
25
|
+
path?: Record<string, string>;
|
|
26
|
+
/**
|
|
27
|
+
* 受控目录前缀(SDK 定制场景返回)
|
|
28
|
+
* @description 前端在该前缀下自行拼接文件名;可与 pathKey 继续拼接子目录
|
|
29
|
+
*/
|
|
30
|
+
filePath?: string;
|
|
31
|
+
/**
|
|
32
|
+
* CDN 访问域名(不含尾斜杠)
|
|
33
|
+
* @description SDK 定制场景返回 cdn_url;通用场景无此字段时组件按 COS Location 拼接
|
|
34
|
+
*/
|
|
35
|
+
cosHost?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 通用 COS 凭证响应(snake_case,pnt-tools / account-business)
|
|
39
|
+
*/
|
|
40
|
+
interface SnakeCosCredential {
|
|
41
|
+
bucket: string;
|
|
42
|
+
region: string;
|
|
43
|
+
secret_id: string;
|
|
44
|
+
secret_key: string;
|
|
45
|
+
token: string;
|
|
46
|
+
start_time: number;
|
|
47
|
+
expired_time: number;
|
|
48
|
+
path?: Record<string, string>;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 内置业务场景
|
|
52
|
+
* @description 决定走哪个后端凭证接口
|
|
53
|
+
* - default: 通用上传(GET /v2/business/operation/get-cos-temp-token)
|
|
54
|
+
* - default_download: 通用下载/上传(POST /v2/business/operation/get-download-cos-temp-token,snake_case)
|
|
55
|
+
* - sdk_customize: SDK 定制服务(POST /internal/sdk_customize/upload_token,含 cdn_url/file_path)
|
|
56
|
+
* - ai_translate: AI 翻译服务(POST /business/aiTranslate/import/cosToken,camelCase)
|
|
57
|
+
*/
|
|
58
|
+
export type CosScene = 'default' | 'default_download' | 'sdk_customize' | 'ai_translate' | (string & Record<never, never>);
|
|
59
|
+
/**
|
|
60
|
+
* 凭证获取参数
|
|
61
|
+
*/
|
|
62
|
+
export interface GetCosCredentialParams {
|
|
63
|
+
/** 业务场景 */
|
|
64
|
+
scene: CosScene;
|
|
65
|
+
/** 业务类型路径 key(透传到后端 path map 选取;sdk_customize 场景作为受控目录下的子目录) */
|
|
66
|
+
pathKey?: string;
|
|
67
|
+
/**
|
|
68
|
+
* 业务上下文参数(gameId/env/updType 等,按 scene 不同传递)
|
|
69
|
+
* @description default/default_download 场景下 gameId 会自动映射到请求头 x-service-type,
|
|
70
|
+
* 其余字段透传到 query/body;ai_translate 场景整体透传到 body
|
|
71
|
+
*/
|
|
72
|
+
context?: Record<string, string | number>;
|
|
73
|
+
/** 文件大小(字节),后端用于校验配额 */
|
|
74
|
+
contentLength?: number;
|
|
75
|
+
/** 已上传文件 URL/Key(下载场景使用) */
|
|
76
|
+
fileUrl?: string;
|
|
77
|
+
/**
|
|
78
|
+
* 网关 audience
|
|
79
|
+
* @default '/player-network'
|
|
80
|
+
* @description 账号管理等子系统需覆盖为对应 audience(如 '/laas-pnt');
|
|
81
|
+
* 消费方注入的 http 实例若已统一追加 audience,URL 上存在该参数时拦截器不会重复追加
|
|
82
|
+
*/
|
|
83
|
+
audience?: string;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* 自定义原始响应归一化函数
|
|
87
|
+
* @description 后端返回字段不符合内置 scene 约定时使用;入参为后端响应的原始 data
|
|
88
|
+
*/
|
|
89
|
+
export type NormalizeCredential = (raw: unknown) => NormalizedCosCredential;
|
|
90
|
+
/**
|
|
91
|
+
* snake_case 凭证归一化(导出供 getCredential 自定义逃生舱复用)
|
|
92
|
+
* @description 消费方自行请求凭证(如项目 http 实例为非标准签名)时,
|
|
93
|
+
* 可在 getCredential 内调用本函数把后端 data 转为组件库统一结构
|
|
94
|
+
*/
|
|
95
|
+
export declare const normalizeSnakeCredential: (raw: SnakeCosCredential) => NormalizedCosCredential;
|
|
96
|
+
/**
|
|
97
|
+
* 获取 COS 临时凭证(按 scene 路由到不同后端接口,内部归一化字段命名)
|
|
98
|
+
* @param http 由 PntProvider 注入的 axios 实例
|
|
99
|
+
* @param params 凭证获取参数
|
|
100
|
+
* @param customNormalize 可选的原始响应自定义归一化
|
|
101
|
+
*/
|
|
102
|
+
export declare const fetchCosCredential: (http: AxiosInstance, params: GetCosCredentialParams, customNormalize?: NormalizeCredential) => Promise<NormalizedCosCredential>;
|
|
103
|
+
export {};
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// ==========================================
|
|
2
|
+
// COS 临时凭证 - 归一化类型
|
|
3
|
+
// 组件库内部统一为 camelCase;各后端接口的 snake_case / camelCase
|
|
4
|
+
// 在 fetchCosCredential 内部归一化为此结构后返回
|
|
5
|
+
// ==========================================
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 归一化后的 COS 临时凭证
|
|
9
|
+
* @description 各后端接口字段命名不统一(snake/camel),组件库内部统一为 camelCase
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// ==========================================
|
|
13
|
+
// 各后端接口的原始响应类型
|
|
14
|
+
// ==========================================
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 通用 COS 凭证响应(snake_case,pnt-tools / account-business)
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* player-network aiTranslate COS 凭证响应(camelCase)
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* SDK 定制服务上传凭证响应(snake_case,含 file_path / cdn_url)
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
// ==========================================
|
|
29
|
+
// Scene 路由表
|
|
30
|
+
// ==========================================
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 内置业务场景
|
|
34
|
+
* @description 决定走哪个后端凭证接口
|
|
35
|
+
* - default: 通用上传(GET /v2/business/operation/get-cos-temp-token)
|
|
36
|
+
* - default_download: 通用下载/上传(POST /v2/business/operation/get-download-cos-temp-token,snake_case)
|
|
37
|
+
* - sdk_customize: SDK 定制服务(POST /internal/sdk_customize/upload_token,含 cdn_url/file_path)
|
|
38
|
+
* - ai_translate: AI 翻译服务(POST /business/aiTranslate/import/cosToken,camelCase)
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 凭证获取参数
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 自定义原始响应归一化函数
|
|
47
|
+
* @description 后端返回字段不符合内置 scene 约定时使用;入参为后端响应的原始 data
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
// ==========================================
|
|
51
|
+
// 字段归一化层
|
|
52
|
+
// ==========================================
|
|
53
|
+
|
|
54
|
+
const normalizeSnake = raw => ({
|
|
55
|
+
bucket: raw.bucket,
|
|
56
|
+
region: raw.region,
|
|
57
|
+
secretId: raw.secret_id,
|
|
58
|
+
secretKey: raw.secret_key,
|
|
59
|
+
sessionToken: raw.token,
|
|
60
|
+
startTime: raw.start_time,
|
|
61
|
+
expiredTime: raw.expired_time,
|
|
62
|
+
path: raw.path
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* snake_case 凭证归一化(导出供 getCredential 自定义逃生舱复用)
|
|
67
|
+
* @description 消费方自行请求凭证(如项目 http 实例为非标准签名)时,
|
|
68
|
+
* 可在 getCredential 内调用本函数把后端 data 转为组件库统一结构
|
|
69
|
+
*/
|
|
70
|
+
export const normalizeSnakeCredential = raw => normalizeSnake(raw);
|
|
71
|
+
const normalizeAiTranslate = raw => ({
|
|
72
|
+
bucket: raw.bucket || '',
|
|
73
|
+
region: raw.region || '',
|
|
74
|
+
secretId: raw.secretId || '',
|
|
75
|
+
secretKey: raw.secretKey || '',
|
|
76
|
+
sessionToken: raw.token || '',
|
|
77
|
+
startTime: raw.startTime ?? 0,
|
|
78
|
+
expiredTime: raw.expiredTime ?? 0,
|
|
79
|
+
// aiTranslate path 类型为 Record<string, unknown>,这里收窄为 string
|
|
80
|
+
path: raw.path ? Object.fromEntries(Object.entries(raw.path).filter(v => typeof v[1] === 'string')) : undefined
|
|
81
|
+
});
|
|
82
|
+
const normalizeSdkCustomize = raw => ({
|
|
83
|
+
bucket: raw.bucket,
|
|
84
|
+
region: raw.region,
|
|
85
|
+
secretId: raw.secret_id,
|
|
86
|
+
secretKey: raw.secret_key,
|
|
87
|
+
sessionToken: raw.token,
|
|
88
|
+
startTime: raw.start_time,
|
|
89
|
+
expiredTime: raw.expired_time,
|
|
90
|
+
filePath: raw.file_path,
|
|
91
|
+
cosHost: raw.cdn_url
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// ==========================================
|
|
95
|
+
// 凭证获取接口封装(按 scene 路由)
|
|
96
|
+
// ==========================================
|
|
97
|
+
|
|
98
|
+
const DEFAULT_AUDIENCE = '/player-network';
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 统一校验后端响应并执行归一化
|
|
102
|
+
*/
|
|
103
|
+
const resolveCredential = (res, normalize, customNormalize) => {
|
|
104
|
+
// 兼容两套成功标识:新接口 code===0,老接口(账号域 get-cos-temp-token 等)ret===0
|
|
105
|
+
const success = res.code === 0 || res.ret === 0;
|
|
106
|
+
if (!success || res.data === null || typeof res.data === 'undefined') {
|
|
107
|
+
throw new Error(`[PntCosUploader] 获取 COS 凭证失败: code=${res.code ?? 'null'} ret=${res.ret ?? 'null'} ${res.msg || ''}`);
|
|
108
|
+
}
|
|
109
|
+
return customNormalize ? customNormalize(res.data) : normalize(res.data);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* 获取 COS 临时凭证(按 scene 路由到不同后端接口,内部归一化字段命名)
|
|
114
|
+
* @param http 由 PntProvider 注入的 axios 实例
|
|
115
|
+
* @param params 凭证获取参数
|
|
116
|
+
* @param customNormalize 可选的原始响应自定义归一化
|
|
117
|
+
*/
|
|
118
|
+
export const fetchCosCredential = async (http, params, customNormalize) => {
|
|
119
|
+
const {
|
|
120
|
+
scene,
|
|
121
|
+
context = {},
|
|
122
|
+
contentLength,
|
|
123
|
+
fileUrl,
|
|
124
|
+
audience = DEFAULT_AUDIENCE
|
|
125
|
+
} = params;
|
|
126
|
+
switch (scene) {
|
|
127
|
+
case 'default':
|
|
128
|
+
{
|
|
129
|
+
// 通用上传:GET /v2/business/operation/get-cos-temp-token
|
|
130
|
+
// pnt-tools CosApi 约定:gameId 走请求头 x-service-type
|
|
131
|
+
const {
|
|
132
|
+
gameId,
|
|
133
|
+
...restContext
|
|
134
|
+
} = context;
|
|
135
|
+
// 第二泛型 R 显式指定为解包后的 body(约定消费方 http 响应拦截器返回 response.data)
|
|
136
|
+
const res = await http.get(`/v2/business/operation/get-cos-temp-token?audience=${audience}`, {
|
|
137
|
+
params: {
|
|
138
|
+
content_length: contentLength,
|
|
139
|
+
...restContext
|
|
140
|
+
},
|
|
141
|
+
headers: gameId !== undefined ? {
|
|
142
|
+
'x-service-type': String(gameId)
|
|
143
|
+
} : undefined
|
|
144
|
+
});
|
|
145
|
+
return resolveCredential(res, normalizeSnake, customNormalize);
|
|
146
|
+
}
|
|
147
|
+
case 'default_download':
|
|
148
|
+
{
|
|
149
|
+
// 通用下载/上传:POST /v2/business/operation/get-download-cos-temp-token
|
|
150
|
+
const {
|
|
151
|
+
gameId,
|
|
152
|
+
...restContext
|
|
153
|
+
} = context;
|
|
154
|
+
const res = await http.post(`/v2/business/operation/get-download-cos-temp-token?audience=${audience}`, {
|
|
155
|
+
file_url: fileUrl,
|
|
156
|
+
content_length: contentLength,
|
|
157
|
+
...restContext
|
|
158
|
+
}, {
|
|
159
|
+
headers: gameId !== undefined ? {
|
|
160
|
+
'x-service-type': String(gameId)
|
|
161
|
+
} : undefined
|
|
162
|
+
});
|
|
163
|
+
return resolveCredential(res, normalizeSnake, customNormalize);
|
|
164
|
+
}
|
|
165
|
+
case 'sdk_customize':
|
|
166
|
+
{
|
|
167
|
+
// SDK 定制服务:POST /internal/sdk_customize/upload_token
|
|
168
|
+
// 原项目调用不带 audience(http 拦截器默认补 /player-network)
|
|
169
|
+
const res = await http.post('/internal/sdk_customize/upload_token', {
|
|
170
|
+
content_length: contentLength,
|
|
171
|
+
...context
|
|
172
|
+
});
|
|
173
|
+
return resolveCredential(res, normalizeSdkCustomize, customNormalize);
|
|
174
|
+
}
|
|
175
|
+
case 'ai_translate':
|
|
176
|
+
{
|
|
177
|
+
// AI 翻译服务:POST /business/aiTranslate/import/cosToken(camelCase)
|
|
178
|
+
const res = await http.post('/business/aiTranslate/import/cosToken', {
|
|
179
|
+
contentLength,
|
|
180
|
+
...context
|
|
181
|
+
});
|
|
182
|
+
return resolveCredential(res, normalizeAiTranslate, customNormalize);
|
|
183
|
+
}
|
|
184
|
+
default:
|
|
185
|
+
{
|
|
186
|
+
throw new Error(`[PntCosUploader] 未支持的 scene: ${scene};请通过 getCredential 自定义`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
};
|