@tmsfe/tms-core 0.0.161 → 0.0.162
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/package.json +1 -1
- package/src/aes.js +84 -0
- package/src/encrypt.js +327 -0
- package/src/index.js +2 -1
- package/src/jsencrypt.min.js +2 -0
- package/src/md5.js +1 -1
- package/src/request.js +175 -60
- package/src/runtime/login.ts +2 -0
- package/src/traceUtils.js +23 -0
- package/src/encrypt/encrypt-util.ts +0 -552
- package/src/encrypt/index.ts +0 -186
- package/src/encrypt/md5.js +0 -2
- package/src/encrypt/nacl-util.min.js +0 -2
- package/src/encrypt/nacl.min.js +0 -2
- package/src/encrypt/traceUtils.js +0 -24
package/src/request.js
CHANGED
|
@@ -11,10 +11,12 @@
|
|
|
11
11
|
import md5 from './md5';
|
|
12
12
|
import { getEnvInfo, getAuthInfo } from './env';
|
|
13
13
|
import { safeJsonParse } from './objUtils';
|
|
14
|
-
import
|
|
14
|
+
import encryptObj from './encrypt';
|
|
15
15
|
import reporter from './report/index';
|
|
16
|
+
import { genTraceparent } from './traceUtils';
|
|
16
17
|
|
|
17
18
|
const logger = wx.getLogManager({});
|
|
19
|
+
let refreshPromise = null;
|
|
18
20
|
|
|
19
21
|
/**
|
|
20
22
|
* 用于序列化需要签名的参数
|
|
@@ -159,16 +161,7 @@ export default class Request {
|
|
|
159
161
|
*/
|
|
160
162
|
static defaultSecretKey = '';
|
|
161
163
|
|
|
162
|
-
static
|
|
163
|
-
// 加密模块初始化
|
|
164
|
-
encryptObjInit({
|
|
165
|
-
composeParamsFunc: async (data) => {
|
|
166
|
-
const params = await composeParam(data, true, {});
|
|
167
|
-
return sign(params, secretKey);
|
|
168
|
-
},
|
|
169
|
-
report: (...args) => reporter.reportPerformance(...args),
|
|
170
|
-
});
|
|
171
|
-
}
|
|
164
|
+
static requestEncryptOpen = true;
|
|
172
165
|
|
|
173
166
|
static getInstance() {
|
|
174
167
|
if (reqInstance === null) {
|
|
@@ -355,6 +348,94 @@ export default class Request {
|
|
|
355
348
|
return Promise.resolve({ url });
|
|
356
349
|
}
|
|
357
350
|
|
|
351
|
+
// wx请求封装成promise
|
|
352
|
+
async wxRequest(path, method, header = {}, data, needReport, seqId) {
|
|
353
|
+
if (needReport && path.indexOf('basic/event/upload') < 0) {
|
|
354
|
+
logger.log(
|
|
355
|
+
'tms-performance-log',
|
|
356
|
+
'request_encrypt_log', 'main', 'send_unencrypt_request', seqId,
|
|
357
|
+
!!wx.$_publicKey, Request.requestEncryptOpen, path,
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
// 如果是获取pubKey的接口,但是没带userId, 则需要取消这次请求
|
|
361
|
+
if (path.indexOf('basic/crypto/lastkey') > -1 && !data.userId) {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
return new Promise((resolve, reject) => {
|
|
365
|
+
wx.request({
|
|
366
|
+
url: path,
|
|
367
|
+
header: { ...header, 'X-Seq-Id': seqId, Traceparent: genTraceparent() },
|
|
368
|
+
method,
|
|
369
|
+
data,
|
|
370
|
+
enableHttp2: true,
|
|
371
|
+
success: (res) => {
|
|
372
|
+
resolve(res);
|
|
373
|
+
},
|
|
374
|
+
fail: (err) => {
|
|
375
|
+
reject(err);
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async refreshEncryptKey() {
|
|
382
|
+
if (wx.$_encrypt_refreshDisable) {
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
if (!refreshPromise) {
|
|
386
|
+
refreshPromise = new Promise(async (resolve) => {
|
|
387
|
+
try {
|
|
388
|
+
const requestParam = await composeParam({}, true, {});
|
|
389
|
+
const data = sign(requestParam, this.secretKey);
|
|
390
|
+
const finalUrl = this.makeUrl('basic/crypto/lastkey');
|
|
391
|
+
const res = await this.wxRequest(finalUrl, 'GET', {}, data, true, data.seqId);
|
|
392
|
+
const success = res.data.errCode === 0;
|
|
393
|
+
if (!success) {
|
|
394
|
+
// 如果请求失败,则停止刷新加密秘钥,运行时永久关闭
|
|
395
|
+
wx.$_encrypt_refreshDisable = true;
|
|
396
|
+
}
|
|
397
|
+
encryptObj.updateDecryptKey(res.data.resData);
|
|
398
|
+
resolve(success);
|
|
399
|
+
} catch (e) {
|
|
400
|
+
encryptObj.updateDecryptKey(null);
|
|
401
|
+
resolve(false);
|
|
402
|
+
}
|
|
403
|
+
refreshPromise = null;
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
return refreshPromise;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// 如果全局请求加密开关关闭 || 当前请求内容不满足加密规则(publickey不为空且走sinan网关且非性能上报埋点)
|
|
410
|
+
checkIfNeedEncrypt(url, data) {
|
|
411
|
+
if (!Request.requestEncryptOpen || !encryptObj.needsEncryption(url, data)) {
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
return true;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
async dealEncryptionSwitch(resHeader, forceRefresh = false) {
|
|
418
|
+
if (!forceRefresh && (!resHeader || this.dealEncryptionSwitching)) {
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
this.dealEncryptionSwitching = true;
|
|
422
|
+
const formatHeader = {};
|
|
423
|
+
Object.keys(resHeader).forEach(key => formatHeader[key.toLowerCase()] = resHeader[key]);
|
|
424
|
+
const encryptionAvaliable = formatHeader['x-encryption-available'] === '0';
|
|
425
|
+
if (encryptionAvaliable) {
|
|
426
|
+
// 关闭接下来请求的加密开关
|
|
427
|
+
wx.$_publicKey = null;
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
430
|
+
let refreshSuccess = true;
|
|
431
|
+
// 打开开关,如果当前是关闭状态,则需要刷新加密key
|
|
432
|
+
if (forceRefresh || !wx.$_publicKey) {
|
|
433
|
+
refreshSuccess = await this.refreshEncryptKey();
|
|
434
|
+
}
|
|
435
|
+
this.dealEncryptionSwitching = false;
|
|
436
|
+
return refreshSuccess;
|
|
437
|
+
}
|
|
438
|
+
|
|
358
439
|
/**
|
|
359
440
|
* 创建发送请求任务
|
|
360
441
|
* @memberof Request
|
|
@@ -364,61 +445,95 @@ export default class Request {
|
|
|
364
445
|
* @param {object} header 自定义的请求头
|
|
365
446
|
* @returns {Promise} 接口返回结果
|
|
366
447
|
*/
|
|
367
|
-
async createRequestTask(path, param = {}, method = 'POST', header = {}) {
|
|
448
|
+
async createRequestTask(path, param = {}, method = 'POST', header = {}, doEncrypt = true, retryTimes = 0) {
|
|
368
449
|
const requestParam = await composeParam(param, this.withAuth, this.baseParam);
|
|
369
450
|
const data = sign(requestParam, this.secretKey);
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
// 打车、代驾日志不截断,方便用于回放
|
|
379
|
-
const fullApi = path.indexOf('/takecar/') !== -1 || path.indexOf('dd/api/v2/order') !== -1;
|
|
451
|
+
const finalUrl = this.makeUrl(path);
|
|
452
|
+
const requestTime = Date.now();
|
|
453
|
+
const printLog = (isSuccess, res) => {
|
|
454
|
+
// 埋点已经单独打日志了,接口请求数据日志太长影响分析
|
|
455
|
+
if (path.indexOf('basic/event/upload') !== -1) {
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
380
458
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
result = `${result.substring(0, 500)} 内容太长被截断`;
|
|
384
|
-
}
|
|
459
|
+
// 打车、代驾日志不截断,方便用于回放
|
|
460
|
+
const fullApi = path.indexOf('/takecar/') !== -1 || path.indexOf('dd/api/v2/order') !== -1;
|
|
385
461
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
params: JSON.stringify(data),
|
|
391
|
-
duration: Date.now() - requestTime,
|
|
392
|
-
};
|
|
393
|
-
if (isSuccess) {
|
|
394
|
-
obj.res = result;
|
|
395
|
-
} else {
|
|
396
|
-
obj.err = result;
|
|
397
|
-
}
|
|
462
|
+
let result = JSON.stringify(res);
|
|
463
|
+
if (isSuccess && !fullApi && result.length > 500) {
|
|
464
|
+
result = `${result.substring(0, 500)} 内容太长被截断`;
|
|
465
|
+
}
|
|
398
466
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
467
|
+
const obj = {
|
|
468
|
+
path,
|
|
469
|
+
method,
|
|
470
|
+
header: JSON.stringify(header),
|
|
471
|
+
params: JSON.stringify(data),
|
|
472
|
+
duration: Date.now() - requestTime,
|
|
405
473
|
};
|
|
474
|
+
if (isSuccess) {
|
|
475
|
+
obj.res = result;
|
|
476
|
+
} else {
|
|
477
|
+
obj.err = result;
|
|
478
|
+
}
|
|
406
479
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
480
|
+
const str = JSON.stringify(obj, null, 2).replace(/\\"/ig, '\'');
|
|
481
|
+
if (isSuccess) {
|
|
482
|
+
logger.log(`接口请求成功:\n${str}`);
|
|
483
|
+
} else {
|
|
484
|
+
logger.warn(`接口请求失败:\n${str}`);
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
const { seqId } = data;
|
|
488
|
+
try {
|
|
489
|
+
// 当前请求不加密 || 不满足加密前提
|
|
490
|
+
if (!doEncrypt || !this.checkIfNeedEncrypt(finalUrl, data)) {
|
|
491
|
+
const result = await this.wxRequest(finalUrl, method, header, data, true, seqId);
|
|
492
|
+
const { header: resHeader, data: resData } = result;
|
|
493
|
+
// 根据返回的响应头来控制接下来运行时的加密开关
|
|
494
|
+
this.dealEncryptionSwitch(resHeader);
|
|
495
|
+
printLog(true, resData);
|
|
496
|
+
return result;
|
|
497
|
+
}
|
|
498
|
+
// 1. 加密请求
|
|
499
|
+
const {
|
|
500
|
+
header: encryptHeader, data: encryptData, aesKey,
|
|
501
|
+
} = encryptObj.reqEncrypt(method, data, header, '');
|
|
502
|
+
// 2. 发送请求
|
|
503
|
+
encryptHeader['content-type'] = 'text/plain';
|
|
504
|
+
const result = await this.wxRequest(finalUrl, method, encryptHeader, encryptData, false, seqId);
|
|
505
|
+
const { header: resHeader, data: resData } = result;
|
|
506
|
+
// 3. 解密响应
|
|
507
|
+
const { success, header: decryptHeader, data: decryptData } = encryptObj.resDecrypt(aesKey, resHeader, resData);
|
|
508
|
+
if (!success) {
|
|
509
|
+
reporter.reportPerformance('request_encrypt_log', 'main', 'local_response_decrypt_fail', seqId);
|
|
510
|
+
return this.createRequestTask(path, param, method, header, false);
|
|
511
|
+
}
|
|
512
|
+
// 4. 处理解密失败的响应
|
|
513
|
+
const errCodeType = encryptObj.getErrcodeType(decryptData.errCode, decryptData.errMsg);
|
|
514
|
+
if (errCodeType === encryptObj.reqErrType.pubKeyInvalid) { // 秘钥失效
|
|
515
|
+
const encryptSwitch = await this.dealEncryptionSwitch(resHeader, true);
|
|
516
|
+
if (retryTimes > 2) {
|
|
517
|
+
throw new Error('解密失败,请重试');
|
|
518
|
+
}
|
|
519
|
+
return this.createRequestTask(path, param, method, header, encryptSwitch, retryTimes + 1);
|
|
520
|
+
}
|
|
521
|
+
if (errCodeType === encryptObj.reqErrType.decryptError) { // 解密失败
|
|
522
|
+
return this.createRequestTask(path, param, method, header, false);
|
|
523
|
+
}
|
|
524
|
+
if (errCodeType === encryptObj.reqErrType.cryptoDisabled) { // 加密关闭
|
|
525
|
+
wx.$_publicKey = null;
|
|
526
|
+
return this.createRequestTask(path, param, method, header, false);
|
|
527
|
+
}
|
|
528
|
+
result.header = decryptHeader;
|
|
529
|
+
result.data = decryptData;
|
|
530
|
+
printLog(true, result.data);
|
|
531
|
+
// 根据返回的响应头来控制接下来运行时的加密开关
|
|
532
|
+
this.dealEncryptionSwitch(resHeader);
|
|
533
|
+
return result;
|
|
534
|
+
} catch (err) {
|
|
535
|
+
printLog(false, err);
|
|
536
|
+
throw err;
|
|
537
|
+
}
|
|
423
538
|
}
|
|
424
539
|
}
|
package/src/runtime/login.ts
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 生成traceparent Id
|
|
3
|
+
* 用途:用于生成version - trace-id - parent-id/span-id - trace-flags规则的id,方便链路追踪
|
|
4
|
+
*/
|
|
5
|
+
import { CryptoJS } from './aes';
|
|
6
|
+
|
|
7
|
+
// 生成随机的16字节trace-id和8字节parent-id/span-id
|
|
8
|
+
function generateRandomHex(size) {
|
|
9
|
+
return CryptoJS.lib.WordArray.random(size).toString(CryptoJS.enc.Hex);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// 生成traceparent ID
|
|
13
|
+
function genTraceparent() {
|
|
14
|
+
const version = '00';
|
|
15
|
+
const traceId = generateRandomHex(16); // 16字节的trace-id
|
|
16
|
+
const parentId = generateRandomHex(8); // 8字节的parent-id/span-id
|
|
17
|
+
const traceFlags = '01'; // 示例标志,表示采样
|
|
18
|
+
return `${version}-${traceId}-${parentId}-${traceFlags}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export {
|
|
22
|
+
genTraceparent,
|
|
23
|
+
};
|