t-comm
Version:
专业、稳定、纯粹的工具库
225 lines (218 loc) • 10.2 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var _classCallCheck = require('@babel/runtime/helpers/classCallCheck');
var _createClass = require('@babel/runtime/helpers/createClass');
var base64_decode = require('../../../../base64/decode.js');
require('../../../../base64/config.js');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var _classCallCheck__default = /*#__PURE__*/_interopDefaultLegacy(_classCallCheck);
var _createClass__default = /*#__PURE__*/_interopDefaultLegacy(_createClass);
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
/** 默认存储 key */
var DEFAULT_STORAGE_KEY = 'cocos_login_info';
/**
* Cocos 端防刷拦截器
*
* 加到请求拦截器的最后,会根据加密协议在请求 header 里加上防刷签名参数。
* 依赖业务提前请求 GetTs 接口获取加密参数(ts + encryptData)。
*
* 加密协议见文档:
* https://doc.weixin.qq.com/flowchart/f4_APsAyQbdAFwbbhTs281TFe1ncmazz?scode=AJEAIQdfAAor0oxY1IAPsAyQbdAFw
*
* 参考 pmd-npm/packages/business/src/network/interceptor/request/common/fangshua/fangshuaInterceptor.ts
*
* @example
* ```ts
* import { CocosFangshuaInterceptor } from 't-comm/cocos/network';
*
* const fangshuaInterceptor = new CocosFangshuaInterceptor({
* storage,
* getTs: () => appInfo.ts,
* getEncryptData: () => appInfo.encryptData,
* crypto: {
* md5: (data) => CryptoJS.MD5(data).toString(),
* aesDecrypt: (key, encryptedHex, iv) => { ... },
* },
* debug: true, // 开启调试日志(默认 false)
* });
* ```
*/
var CocosFangshuaInterceptor = /*#__PURE__*/function () {
function CocosFangshuaInterceptor(options) {
_classCallCheck__default["default"](this, CocosFangshuaInterceptor);
this.options = options;
}
return _createClass__default["default"](CocosFangshuaInterceptor, [{
key: "interceptor",
value: function interceptor(param) {
var _a, _b;
if (!needSign(param)) {
return [false, param];
}
var _this$options = this.options,
storage = _this$options.storage,
_this$options$storage = _this$options.storageKey,
storageKey = _this$options$storage === void 0 ? DEFAULT_STORAGE_KEY : _this$options$storage,
crypto = _this$options.crypto;
var ts = this.options.getTs();
var loginInfo = (_a = storage === null || storage === void 0 ? void 0 : storage.get) === null || _a === void 0 ? void 0 : _a.call(storage, storageKey);
var ouid = (loginInfo === null || loginInfo === void 0 ? void 0 : loginInfo.ouid) || '';
var accesstoken = (loginInfo === null || loginInfo === void 0 ? void 0 : loginInfo.accesstoken) || '';
var encryptData = this.options.getEncryptData();
var iv = 'ZdPwUq+f3OzuEWHP';
this.log('[fangshuaInterceptor]', {
ts: ts,
ouid: ouid,
accesstoken: accesstoken,
encryptData: encryptData
});
if (!ts || !ouid || !accesstoken || !encryptData) {
return [false, param];
}
var body = param === null || param === void 0 ? void 0 : param.reqData;
// 上报类请求需要检测是否为活跃上报
if (((_b = param === null || param === void 0 ? void 0 : param.url) === null || _b === void 0 ? void 0 : _b.indexOf('/tipmp.user.commreport.commreport/CommReport')) !== -1 && !isActiveReport(body)) {
this.log('[fangshuaInterceptor] 不是活跃上报');
return [false, param];
}
try {
var decryptedText = crypto.aesDecrypt(crypto.md5(ouid + accesstoken), decodeEncryptData(encryptData), iv);
var decryptedJson = JSON.parse(decryptedText);
this.log('[fangshuaInterceptor] decryptedJson:', decryptedJson);
var ukey = decryptedJson.ukey;
var tipId = decryptedJson.req_id;
var mock = isMock() ? '1' : '0';
var sig = crypto.md5(ts + ouid + crypto.md5(JSON.stringify(body)) + ukey + accesstoken + mock);
// Cocos 小游戏环境统一使用 header(与小程序一致)
param.header = param.header || {};
param.header.Tip_sig = sig;
param.header.Tip_id = tipId;
param.header.Tip_ts = ts;
param.header.Tip_mock = mock;
return [false, param];
} catch (error) {
console.error('[CocosFangshuaInterceptor] 签名计算失败:', error);
return [false, param];
}
}
/** 仅 debug=true 时打印日志 */
}, {
key: "log",
value: function log() {
if (this.options.debug) {
var _console;
(_console = console).log.apply(_console, arguments);
}
}
}]);
}();
// 需要签名的 CGI 列表
var SIGN_CGI_LIST = [
// 防刷
'/tipmp.user.commreport.commreport/CommReport', '/pmdtrpc.commcgi.tipcgi.tipcgi/WaterProofReconfirm',
// 高校
'/merc.plugin.usermgr_cgi.usermgr_cgi/VerifyAgeRange', '/merc.plugin.usermgr_cgi.usermgr_cgi/CheckStuAgeByIdCard',
// 转化产品
'/merc.plugin.brandlott_cgi.brandlott_cgi/QueryBrandLottByMoreGameAttribute', '/merc.plugin.brandlott_cgi.brandlott_cgi/QueryFissionBrandLottActInfoByPlguin', '/merc.plugin.brandlott_cgi.brandlott_cgi/CheckInviteLoginGame', '/merc.plugin.brandlott_cgi.brandlott_cgi/V2CheckInviteLoginGame', '/merc.plugin.brandlott_cgi.brandlott_cgi/AcceptInviteLoginGame', '/merc.plugin.brandlott_cgi.brandlott_cgi/PointsWithdraw', '/merc.plugin.brandlott_cgi.brandlott_cgi/RecvExtraGift', '/merc.plugin.brandlott_cgi.brandlott_cgi/RecvLottTask', '/merc.plugin.brandlott_cgi.brandlott_cgi/LockTask'];
/**
* 判断请求是否需要带上防刷签名信息
* @param param 请求参数
* @returns 是否需要签名
*/
function needSign(param) {
var url = (param === null || param === void 0 ? void 0 : param.url) || '';
// eslint-disable-next-line no-useless-escape
var pathRegex = /^(?:https?:\/\/)?[^\/?#]+(\/[^?#]*)/;
var match = url.match(pathRegex);
var urlPath = match ? match[1] || url : url;
var isFound = SIGN_CGI_LIST.find(function (item) {
return urlPath.includes(item);
});
if (isFound) {
return true;
}
// 检查接口是否要求开启:适合业务接口指定
if ((param === null || param === void 0 ? void 0 : param.enableTipSig) === true) {
return true;
}
return false;
}
/**
* 增加需要签名的 CGI 接口
* @param cgiList 需要签名的接口地址列表
*/
function appendSignCGI() {
var cgiList = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
cgiList === null || cgiList === void 0 ? void 0 : cgiList.forEach(function (url) {
if (!SIGN_CGI_LIST.includes(url)) {
SIGN_CGI_LIST.push(url);
}
});
}
/**
* 判断是否是活跃上报
* @param body post 请求的 body 内容
* @returns 本次上报内容是否是活跃上报
*/
function isActiveReport(body) {
var list = body === null || body === void 0 ? void 0 : body.list;
if (list) {
var _iterator = _createForOfIteratorHelper(list),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var event = _step.value;
if (event.event_id === 'active') {
return true;
}
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
}
return false;
}
/**
* 对后台数据进行解密(Base64 → Hex)
* 使用 t-comm 自带的 innerDecode(兼容小游戏环境,无需 window.atob)
* @param encryptData 加密数据
* @returns 解密后的 hex 字符串
*/
function decodeEncryptData(encryptData) {
var decodedData = base64_decode.innerDecode(encryptData);
var hexString = '';
for (var i = 0; i < decodedData.length; i++) {
var hex = decodedData.charCodeAt(i).toString(16);
hexString += hex.length === 1 ? "0".concat(hex) : hex;
}
hexString = hexString.toUpperCase();
return hexString;
}
/**
* 当前运行环境是否是无头浏览器/自动化工具
* 在小游戏环境中通常返回 false,但保留检测逻辑以兼容开发者工具等场景
* @returns 是否是自动化环境
*/
function isMock() {
// 小游戏环境中 navigator/window 可能不存在,做安全检测
if (typeof navigator !== 'undefined') {
var isHeadlessChrome = /HeadlessChrome/.test(navigator.userAgent || '');
if (isHeadlessChrome) return true;
var isPuppeteer = 'puppeteer' in navigator;
if (isPuppeteer) return true;
if (navigator.webdriver) return true;
}
if (typeof window !== 'undefined') {
var isPhantomJS = 'callPhantom' in window || '_phantom' in window;
if (isPhantomJS) return true;
}
return false;
}
exports.CocosFangshuaInterceptor = CocosFangshuaInterceptor;
exports.appendSignCGI = appendSignCGI;
exports.isActiveReport = isActiveReport;
exports.needSign = needSign;