keytops-game-framework 1.0.26 → 2.0.0

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/index.js CHANGED
@@ -881,1155 +881,1492 @@ Storage.created = false;
881
881
  */
882
882
  const storage = new Storage();
883
883
 
884
- var InternalProperty;
885
- (function (InternalProperty) {
886
- /**设备系统版本 */
887
- InternalProperty["DEVICE_SYSTEM"] = "_ds_";
888
- /**设备操作系统类型 */
889
- InternalProperty["DEVICE_PLATFORM"] = "_dp_";
890
- /**设备手机品牌 */
891
- InternalProperty["DEVICE_BRAND"] = "_db_";
892
- /**设备手机型号 */
893
- InternalProperty["DEVICE_MODEL"] = "_dm_";
894
- /**宿主名称 */
895
- InternalProperty["HOST_NAME"] = "_hn_";
896
- /**宿主版本 */
897
- InternalProperty["HOST_VERSION"] = "_hv_";
898
- /**启动时间 */
899
- InternalProperty["LAUNCH_TIME"] = "_lt_";
900
- /**启动次数 */
901
- InternalProperty["LAUNCH_TIMES"] = "_lts_";
902
- /**安装时间 */
903
- InternalProperty["INSTALL_TIME"] = "_it_";
904
- /**app运载(安装)时长 */
905
- InternalProperty["DURATION_OF_INSTALL"] = "_doi_";
906
- /**app运行(启动)时长 */
907
- InternalProperty["DURATION_OF_LAUNCH"] = "_dol_";
908
- /**累积登录天数 */
909
- InternalProperty["LOGIN_DAYS"] = "_lds_";
910
- /**安装来源type */
911
- InternalProperty["INSTALL_SCENE"] = "_is_";
912
- /**安装来源source */
913
- InternalProperty["INSTALL_CHANNEL"] = "_ic_";
914
- /**启动来源场景 */
915
- InternalProperty["LAUNCH_SCENE"] = "_ls_";
916
- /**启动来源渠道 */
917
- InternalProperty["LAUNCH_CHANNEL"] = "_lc_";
918
- /**事件计时时长 */
919
- InternalProperty["DURATION"] = "_dt_";
920
- /**游戏版本 */
921
- InternalProperty["APP_VERSION"] = "_av_";
922
- })(InternalProperty || (InternalProperty = {}));
923
- class EmptyAnalyticsSender {
924
- /**
925
- * @param [exclude] 其他排除的上报日志, 默认不排除
926
- */
927
- constructor(exclude) {
928
- this._exclude = exclude || [];
929
- }
930
- /**
931
- * 设置排除
932
- * @param excludeList 排除事件名称或者名称列表。
933
- */
934
- exclude(...excludeList) {
935
- this._exclude = excludeList.concat();
936
- }
937
- sendAble(eventName) {
938
- if (this._exclude && this._exclude.indexOf(eventName) != -1) {
939
- return false;
940
- }
941
- return true;
942
- }
943
- send(eventName, data) {
944
- return __awaiter(this, void 0, void 0, function* () {
945
- });
946
- }
947
- }
948
884
  /**
949
- * 统计分析能力
885
+ * 通用计时器实现。
886
+ * 使用 setInterval 驱动 Timer 的内部更新循环。
950
887
  */
951
- class AnalyticsAble {
888
+ class TimerImpl {
952
889
  constructor() {
953
- this._inited = false;
954
- this._hasSetDefaultProperty = false;
955
- this._debugMode = false;
956
- this._senderList = [];
957
- this._dynamicCalls = [];
890
+ this._intervalMap = new WeakMap();
958
891
  }
959
- get inited() { return this._inited; }
892
+ enable(timer) {
893
+ if (this._intervalMap.has(timer)) {
894
+ return;
895
+ }
896
+ const intervalId = setInterval(() => {
897
+ timer["_update"]();
898
+ }, 100);
899
+ this._intervalMap.set(timer, intervalId);
900
+ }
901
+ }
902
+
903
+ class MathUtils {
960
904
  /**
961
- * 初始化
905
+ * 随机
906
+ * @param min
907
+ * @param max
962
908
  */
963
- init(senders, debugMode = false) {
964
- if (this._inited)
965
- return this;
966
- this._debugMode = debugMode;
967
- App.onShow(this._onAppShow, this);
968
- App.onHide(this._onAppHide, this);
969
- this._initSenderList(senders);
970
- this._inited = true;
971
- return this;
909
+ static random(min, max) {
910
+ this.seed = (this.seed * 9301 + 49297) % 233280;
911
+ let c = this.seed / (233280.0);
912
+ return min + c * (max - min);
972
913
  }
973
- _initSenderList(senders) {
974
- for (let i = 0; i < senders.length; i++) {
975
- const sender = senders[i];
976
- this._senderList.push(sender);
977
- }
914
+ /**
915
+ * 随机整数值
916
+ * @param min
917
+ * @param max
918
+ */
919
+ static randomInt(min, max) {
920
+ return Math.floor(this.random(min, max + 0.999));
978
921
  }
979
922
  /**
980
- * 设置排除
981
- * @param excludeEvents 排除事件名称或者名称列表。
982
- * @param senderClass [可选] 特定的SenderClass
923
+ * 随机唯一稳定值
924
+ * @param min
925
+ * @param max
926
+ * @param clear 是否清除唯一记录
983
927
  */
984
- setExclude(excludeEvents, senderClass) {
985
- for (let i = 0; i < this._senderList.length; i++) {
986
- const sender = this._senderList[i];
987
- if (!senderClass || sender instanceof senderClass) {
988
- if (typeof excludeEvents === "string") {
989
- sender.exclude(excludeEvents);
990
- }
991
- else {
992
- sender.exclude(...excludeEvents);
993
- }
928
+ static randomOnlyInt(min, max, clear) {
929
+ if (clear) {
930
+ this.onlyMap.clear();
931
+ }
932
+ let value = this.randomInt(min, max);
933
+ let tryCount = (max - min) * 2;
934
+ let tryIndex = 0;
935
+ while (this.onlyMap.has(value)) {
936
+ value = this.randomInt(min, max);
937
+ if (tryIndex > tryCount) {
938
+ console.error("所有唯一值已取完");
939
+ return 0;
994
940
  }
941
+ tryIndex++;
995
942
  }
943
+ this.onlyMap.set(value, true);
944
+ return value;
996
945
  }
997
- _onAppShow() {
998
- this.time("app_hide");
999
- this.report("app_show", publisher.publisher.enterOptions.getAllOption());
946
+ /**
947
+ * 获取两点间的角度
948
+ * @param p1 点1
949
+ * @param p2 点2
950
+ */
951
+ static getAngle(p1, p2) {
952
+ return Math.atan((p2.y - p1.y) / (p2.x - p1.x));
1000
953
  }
1001
- _onAppHide() {
1002
- this.report("app_hide");
954
+ /**
955
+ * 获取两点间的距离
956
+ * @param p1 点1
957
+ * @param p2 点2
958
+ */
959
+ static getDistance(p1, p2) {
960
+ return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
1003
961
  }
1004
962
  /**
1005
- * 设置所有上报事件的基础属性
963
+ * 将角度转为弧度
964
+ * @param angle 角度
1006
965
  */
1007
- _setDefaultProperty() {
1008
- if (this._hasSetDefaultProperty) {
1009
- return;
1010
- }
1011
- //设备信息
1012
- let deviceInfo = publisher.device.deviceInfo;
1013
- this.setProperty(InternalProperty.DEVICE_SYSTEM, deviceInfo.system);
1014
- this.setProperty(InternalProperty.DEVICE_PLATFORM, deviceInfo.platform);
1015
- this.setProperty(InternalProperty.DEVICE_BRAND, deviceInfo.brand);
1016
- this.setProperty(InternalProperty.DEVICE_MODEL, deviceInfo.model);
1017
- //宿主信息
1018
- let hostInfo = publisher.device.hostInfo;
1019
- this.setProperty(InternalProperty.HOST_NAME, hostInfo.name);
1020
- this.setProperty(InternalProperty.HOST_VERSION, hostInfo.version);
1021
- //安装时间
1022
- this.setProperty(InternalProperty.INSTALL_TIME, Math.floor(App.installTime / 1000));
1023
- //启动时间
1024
- this.setProperty(InternalProperty.LAUNCH_TIME, Math.floor(App.launchTime / 1000));
1025
- //启动次数
1026
- this.setProperty(InternalProperty.LAUNCH_TIMES, App.launchTimes);
1027
- //游戏版本
1028
- this.setProperty(InternalProperty.APP_VERSION, publisher.publisher.appVersion);
1029
- this.setDynamicProperties(() => {
1030
- let dynamicData = {};
1031
- //app运载(安装)时长
1032
- dynamicData[InternalProperty.DURATION_OF_INSTALL] = Math.floor(App.durationOfInstall / 1000);
1033
- //app运行(启动)时长
1034
- dynamicData[InternalProperty.DURATION_OF_LAUNCH] = Math.floor(App.durationOfLaunch / 1000);
1035
- return dynamicData;
1036
- });
1037
- //累积登录天数
1038
- this.userSet(InternalProperty.LOGIN_DAYS, storage.loginDays);
1039
- //安装来源type
1040
- this.userSet(InternalProperty.INSTALL_SCENE, publisher.publisher.installOptions.channelScene);
1041
- //安装来源source
1042
- this.userSet(InternalProperty.INSTALL_CHANNEL, publisher.publisher.installOptions.channelSource);
1043
- //启动来源场景
1044
- this.userSet(InternalProperty.LAUNCH_SCENE, publisher.publisher.enterOptions.channelScene);
1045
- //启动来源渠道
1046
- this.userSet(InternalProperty.LAUNCH_CHANNEL, publisher.publisher.enterOptions.channelSource);
1047
- this._hasSetDefaultProperty = true;
966
+ static angleToRadian(angle) {
967
+ return angle * Math.PI / 180;
1048
968
  }
1049
969
  /**
1050
- * 设置事件公共属性
1051
- * @param key 公共属性key
1052
- * @param value
970
+ * 求旋转后的点坐标
971
+ * @param angle 角度
972
+ * @param point 旋转前的坐标点
973
+ * @param result
1053
974
  */
1054
- setProperty(key, value) {
1055
- if (!this._staticProperties) {
1056
- this._staticProperties = {};
1057
- }
1058
- if (typeof value === "boolean") {
1059
- value = value ? 1 : 0;
975
+ static getRotationPoint(angle, pointOrDir, result) {
976
+ if (result == null) {
977
+ result = { x: 0, y: 0 };
1060
978
  }
1061
- if (this._debugMode && Object.prototype.hasOwnProperty.call(this._staticProperties, key)) {
1062
- console.warn(`静态公共属性中已经存在${key}:${this._staticProperties[key]}, 将替换为${value}`);
979
+ //角度转弧度
980
+ angle = angle * (Math.PI / 180);
981
+ result.x = Math.cos(angle) * pointOrDir.x - Math.sin(angle) * pointOrDir.y;
982
+ result.y = Math.cos(angle) * pointOrDir.y + Math.sin(angle) * pointOrDir.x;
983
+ return result;
984
+ }
985
+ }
986
+ /**
987
+ * 随机种子
988
+ */
989
+ MathUtils.seed = 10;
990
+ /**
991
+ * 唯一记录
992
+ */
993
+ MathUtils.onlyMap = new Map();
994
+
995
+ class StringUtils {
996
+ static random(length, chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") {
997
+ var result = '';
998
+ for (var i = length; i > 0; --i) {
999
+ result += chars[Math.floor(Math.random() * chars.length)];
1063
1000
  }
1064
- this._staticProperties[key] = value;
1001
+ return result;
1065
1002
  }
1066
1003
  /**
1067
- * 删除事件的公共属性
1068
- * @param key
1004
+ * 判断是否是远程资源
1005
+ * @param url
1069
1006
  * @returns
1070
1007
  */
1071
- unsetProperty(key) {
1072
- if (this._staticProperties) {
1073
- return delete this._staticProperties[key];
1008
+ static isRemote(url) {
1009
+ if (url.indexOf("http://") == 0 || url.indexOf("https://") == 0) {
1010
+ return true;
1074
1011
  }
1075
1012
  return false;
1076
1013
  }
1077
1014
  /**
1078
- * 设置事件公共动态属性,上报事件时会触发回调函数,并把返回的AnalyticsData加入到事件属性中
1079
- * @param dynamicCall 动态属性的生成函数,事件上报时实时调用
1015
+ * 是否为空
1016
+ * @param str
1080
1017
  */
1081
- setDynamicProperties(dynamicCall) {
1082
- this._dynamicCalls.push(dynamicCall);
1083
- }
1084
- /**
1085
- * 对于一般的用户属性,您可以调用 userSet 来进行设置。
1086
- * 使用该接口上传的属性将会覆盖原有的属性值,如果之前不存在该用户属性,则会新建该用户属性,类型与传入属性的类型一致。
1087
- * 用户名为例:
1088
- * ```
1089
- * // username为TA
1090
- * userSet("username", "TA");
1091
- * //username为TE
1092
- * userSet("username", "TE");
1093
- * ```
1094
- * @param key 属性key
1095
- * @param value
1018
+ static isEmpty(str) {
1019
+ if (str == null || str == undefined || str.length == 0) {
1020
+ return true;
1021
+ }
1022
+ return false;
1023
+ }
1024
+ /**
1025
+ * 替换全部字符串
1026
+ * @param string src 源串
1027
+ * @param string from_ch 被替换的字符
1028
+ * @param string to_ch 替换的字符
1029
+ *
1030
+ * @return string 结果字符串
1031
+ */
1032
+ static replaceAll(src, from_ch, to_ch) {
1033
+ let regExp = new RegExp(from_ch, "g");
1034
+ return src.replace(regExp, to_ch);
1035
+ }
1036
+ /**
1037
+ * 参数替换
1038
+ * @param str
1039
+ * @param rest
1040
+ *
1041
+ * @example
1042
+ *
1043
+ * var str:string = "here is some info '{0}' and {1}";
1044
+ * trace(StringUtil.substitute(str, 15.4, true));
1045
+ *
1046
+ * // this will output the following string:
1047
+ * // "here is some info '15.4' and true"
1096
1048
  */
1097
- userSet(key, value) {
1098
- if (!this._userProperties) {
1099
- this._userProperties = {};
1049
+ static format(str, ...rest) {
1050
+ if (str == null)
1051
+ return '';
1052
+ // Replace all of the parameters in the msg string.
1053
+ var len = rest.length;
1054
+ var args;
1055
+ if (len == 1 && rest[0] instanceof Array) {
1056
+ args = rest[0];
1057
+ len = args.length;
1100
1058
  }
1101
- if (typeof value === "boolean") {
1102
- value = value ? 1 : 0;
1059
+ else {
1060
+ args = rest;
1103
1061
  }
1104
- if (this._debugMode && Object.prototype.hasOwnProperty.call(this._userProperties, key)) {
1105
- console.warn(`用户属性中已经存在${key}:${this._userProperties[key]}, 将替换为${value}`);
1062
+ for (var i = 0; i < len; i++) {
1063
+ str = str.replace(new RegExp("\\{" + i + "\\}", "g"), args[i]);
1106
1064
  }
1107
- this._userProperties[key] = value;
1065
+ return str;
1108
1066
  }
1109
1067
  /**
1110
- * 如果您要上传的用户属性只要设置一次,则可以调用 userSetOnce 来进行设置。
1111
- * 当该属性之前已经有值的时候,将会忽略这条信息,比如设置首次付费时间:
1112
- * ```
1113
- * //first_payment_time为2018-01-01 01:23:45.678
1114
- * userOnce("first_payment_time", "2018-01-01 01:23:45.678");
1115
- * //first_payment_time仍然为2018-01-01 01:23:45.678
1116
- * userOnce("first_payment_time", "2018-12-31 01:23:45.678");
1117
- * ```
1118
- * @param key 属性key
1119
- * @param value
1068
+ *
1069
+ * 格式化数字
1070
+ * @param amount 目标数字
1071
+ * @param format 支持{1:"", 100:"百", 1000:"千", 10000:"万", 100000:"十万", 1000000:"百万", 10000000:"千万", 100000000:"亿", 1000000000000:"兆"}等
1120
1072
  * @returns
1121
1073
  */
1122
- userOnce(key, value) {
1123
- if (!this._userOnceProperties) {
1124
- this._userOnceProperties = {};
1074
+ static formatNumber(amount, format) {
1075
+ if (!format || amount == 0) {
1076
+ return `${amount}`;
1125
1077
  }
1126
- if (typeof value === "boolean") {
1127
- value = value ? 1 : 0;
1078
+ let formatKey = [];
1079
+ for (const key in format) {
1080
+ if (format.hasOwnProperty(key)) {
1081
+ formatKey.push(Number(key));
1082
+ }
1128
1083
  }
1129
- if (Object.prototype.hasOwnProperty.call(this._userOnceProperties, key)) {
1130
- this._debugMode && console.info(`用户属性中已经存在once属性${key}:${this._userOnceProperties[key]}`);
1131
- return;
1084
+ if (!formatKey || formatKey.length == 0) {
1085
+ return `${amount}`;
1132
1086
  }
1133
- let storageValue = storage.getUValue(`analytics_user_once_${key}`, "");
1134
- if (storageValue != null && storageValue != "") {
1135
- this._debugMode && console.info(`用户属性中已经存在once属性${key}:${storageValue}`);
1136
- this._userOnceProperties[key] = typeof value === "number" ? Number(storageValue) : storageValue;
1137
- return;
1087
+ formatKey.sort((a, b) => b - a);
1088
+ let result = [];
1089
+ let total = amount;
1090
+ for (let i = 0; i < formatKey.length; i++) {
1091
+ const key = formatKey[i];
1092
+ let count = Math.floor(Math.abs(total / key)) * (total / Math.abs(total));
1093
+ if (count == 0) {
1094
+ continue;
1095
+ }
1096
+ total = total % key;
1097
+ result.push(`${count}${format[key]}`);
1138
1098
  }
1139
- storage.setUValue(`analytics_user_once_${key}`, value);
1140
- this._userOnceProperties[key] = value;
1099
+ return result.length ? result.join("") : `${amount}`;
1141
1100
  }
1142
1101
  /**
1143
- *设置用户累积属性,当您要上传数值型的属性时,您可以调用 userAdd 来对该属性进行累加操作,
1144
- * 如果该属性还未被设置,则会赋值 0 后再进行计算。
1145
- * 如果传入负值,等同于减法操作。
1146
- * ```
1147
- * userAdd("total_revenue", 30); //此时total_revenue为30
1148
- * userAdd("total_revenue", 648); //此时total_revenue为678
1149
- * ```
1150
- * @param key 累积属性key
1151
- * @param value 累积的偏移值
1102
+ * 带自动补全的秒数格式。
1103
+ * @param second 秒数
1104
+ * @param format 格式化文本
1105
+ * @param pad 填充文本,默认为“0”
1106
+ * @example
1107
+ * formatSecond(86400, "ddd天hh小时mm分ss秒")=>"001天00小时00分00秒"
1108
+ * formatSecond(86400, "dd天hh小时mm分ss秒")=>"01天00小时00分00秒"
1109
+ * formatSecond(86400, "hh小时mm分ss秒")=>"24小时00分00秒"
1110
+ * formatSecond(86400, "mm分ss秒")=>"1440分00秒"
1111
+ * @returns
1152
1112
  */
1153
- userAdd(key, value) {
1154
- if (!this._userAddProperties) {
1155
- this._userAddProperties = {};
1156
- }
1157
- if (typeof value === "boolean") {
1158
- value = value ? 1 : 0;
1113
+ static formatSecond(second, format, pad = "0") {
1114
+ const formatArr = [
1115
+ { unit: 86400, regExp: /D{2,}/i },
1116
+ { unit: 3600, regExp: /H{1,2}/i },
1117
+ { unit: 60, regExp: /m{1,2}/i },
1118
+ { unit: 1, regExp: /s{1,2}/i },
1119
+ ];
1120
+ for (let i = 0; i < formatArr.length; i++) {
1121
+ const element = formatArr[i];
1122
+ let match = element.regExp.exec(format);
1123
+ if (match) {
1124
+ format = format.replace(element.regExp, Math.floor(second / element.unit).toFixed(0).padStart(match[0].length, pad));
1125
+ second = second % element.unit;
1126
+ }
1159
1127
  }
1160
- let total = Number(this._userAddProperties[key] || storage.getUValue(`analytics_user_add_${key}`, 0) || 0);
1161
- this._userAddProperties[key] = total + value;
1162
- storage.setUValue(`analytics_user_add_${key}`, total + value);
1128
+ return format;
1163
1129
  }
1164
1130
  /**
1165
- * 移除用户属性。当您要清空用户的用户属性值时,您可以调用 userUnset 来对指定属性进行清空操作,如果该属性还未在集群中被创建,则 user_unset 不会创建该属性
1166
- * @param key
1131
+ * 带自动补全的时间戳格式化
1132
+ * @param milliseconds
1133
+ * @param format
1134
+ * @param pad 填充文本,默认为“0”
1135
+ * @returns
1167
1136
  */
1168
- userUnset(key) {
1169
- this._userProperties && delete this._userProperties[key];
1170
- this._userOnceProperties && delete this._userOnceProperties[key];
1171
- this._userAddProperties && delete this._userAddProperties[key];
1172
- storage.removeUValue(`analytics_user_once_${key}`);
1173
- storage.removeUValue(`analytics_user_add_${key}`);
1137
+ static formatTimestamp(milliseconds, format, pad = "0") {
1138
+ let date = new Date(milliseconds);
1139
+ date.getHours();
1140
+ const formatArr = [
1141
+ { r: /Y{4}/, to: date.getFullYear() },
1142
+ { r: /M{1,2}/, to: date.getMonth() + 1 },
1143
+ { r: /D{1,2}/, to: date.getDate() },
1144
+ { r: /H{1,2}/, to: date.getHours() },
1145
+ { r: /m{1,2}/, to: date.getMinutes() },
1146
+ { r: /s{1,2}/, to: date.getSeconds() },
1147
+ ];
1148
+ for (let i = 0; i < formatArr.length; i++) {
1149
+ const element = formatArr[i];
1150
+ let match = element.r.exec(format);
1151
+ if (match) {
1152
+ format = format.replace(element.r, element.to.toString().padStart(match[0].length, "0"));
1153
+ }
1154
+ }
1155
+ return format;
1156
+ }
1157
+ }
1158
+
1159
+ /**
1160
+ * 对象池
1161
+ */
1162
+ class Pool {
1163
+ constructor(clazz, maxCount) {
1164
+ /**池中闲置对象 */
1165
+ this._cacheStack = new Array();
1166
+ /**正在使用的对象 */
1167
+ this._usingArray = new Array();
1168
+ /**池中对象最大数 */
1169
+ this._maxCount = 0;
1170
+ this._class = clazz;
1171
+ if (!this._class) {
1172
+ throw new Error("构造函数不能为空!");
1173
+ }
1174
+ this._maxCount = maxCount == undefined ? Number.MAX_SAFE_INTEGER : maxCount;
1174
1175
  }
1175
1176
  /**
1176
- * 上报错误
1177
- * @param error 错误信息
1178
- */
1179
- error(error) {
1180
- this.report("app_error", { error: error });
1177
+ * 在池中的对象
1178
+ */
1179
+ get count() {
1180
+ return this._cacheStack.length;
1181
1181
  }
1182
1182
  /**
1183
- * 开始事件计时
1184
- * 如果您需要记录某个事件的持续时长,可以调用 timeEvent 来开始计时。
1185
- * 配置您想要计时的事件名称,当您上传该事件时,将会自动在您的事件属性中加入 _dt_ 这一属性来表示记录的时长,单位为秒。
1186
- * 需要注意的是,同一个事件名只能有一个在计时的任务。
1187
- * @param eventName
1183
+ * 使用中的数量
1188
1184
  */
1189
- time(eventName, once = true, offset = 0) {
1190
- this._timeEvents = this._timeEvents || new Map();
1191
- this._debugMode && this._timeEvents.has(eventName) && console.warn(`已经存在${eventName}的计时,将被重置。`);
1192
- this.cancelTime(eventName);
1193
- this._timeEvents.set(eventName, { start: App.durationOfLaunch, once: once, offset: offset });
1185
+ get usingCount() {
1186
+ return this._usingArray.length;
1194
1187
  }
1195
1188
  /**
1196
- * 取消事件计时
1197
- * @param eventName
1189
+ * 分配
1198
1190
  * @returns
1199
1191
  */
1200
- cancelTime(eventName) {
1201
- return this._timeEvents && this._timeEvents.has(eventName) && this._timeEvents.delete(eventName);
1192
+ allocate(createData) {
1193
+ if (this.count + this.usingCount < this._maxCount) {
1194
+ let element = this._cacheStack.length > 0 ? this._cacheStack.pop() : (typeof createData === 'function' ? createData() : new this._class(createData));
1195
+ this._usingArray.push(element);
1196
+ return element;
1197
+ }
1198
+ throw new Error("对象池最大数量超出:" + this._maxCount);
1202
1199
  }
1203
1200
  /**
1204
- * 上报事件
1205
- * @param eventName 事件名称
1206
- * @param data 事件数据
1201
+ * 回收到池中
1202
+ * @param value
1203
+ * @returns
1207
1204
  */
1208
- report(eventName, data) {
1209
- return __awaiter(this, void 0, void 0, function* () {
1210
- if (!this._sendAble(eventName)) {
1211
- this._debugMode && console.log(`[Analytics] ${eventName}事件不上报`);
1212
- return;
1213
- }
1214
- if (!data) {
1215
- this._debugMode && console.warn(`[Analytics] ${eventName}无上报事件数据`);
1216
- data = {};
1217
- }
1218
- else {
1219
- data = Object.assign({}, data);
1220
- }
1221
- this._setDefaultProperty();
1222
- for (let i = 0; i < this._dynamicCalls.length; i++) {
1223
- const call = this._dynamicCalls[i];
1224
- this._copyToData(eventName, data, call());
1225
- }
1226
- this._copyToData(eventName, data, this._userProperties);
1227
- this._copyToData(eventName, data, this._userAddProperties);
1228
- this._copyToData(eventName, data, this._userOnceProperties);
1229
- this._copyToData(eventName, data, this._staticProperties);
1230
- if (this._timeEvents && this._timeEvents.has(eventName)) {
1231
- let timeInfo = this._timeEvents.get(eventName);
1232
- let duration = Math.floor((App.durationOfLaunch - timeInfo.start + timeInfo.offset) / 1000 * 100) / 100;
1233
- data[InternalProperty.DURATION] = duration;
1234
- timeInfo.once && this._timeEvents.delete(eventName);
1235
- }
1236
- this._debugMode && console.log("[Analytics]", eventName, JSON.stringify(data));
1237
- return this._sendReport(eventName, data);
1238
- });
1205
+ recover(value) {
1206
+ if (this._cacheStack.indexOf(value) > -1) {
1207
+ throw new Error("重复回收!");
1208
+ }
1209
+ let index = this._usingArray.indexOf(value);
1210
+ if (index < 0) {
1211
+ throw new Error("对象不属于改对象池!");
1212
+ }
1213
+ //重置
1214
+ value.reset();
1215
+ this._usingArray.splice(index, 1);
1216
+ this._cacheStack.push(value);
1239
1217
  }
1240
- _sendAble(eventName) {
1241
- for (let i = 0; i < this._senderList.length; i++) {
1242
- const sender = this._senderList[i];
1243
- if (sender.sendAble(eventName)) {
1244
- return true;
1245
- }
1218
+ /**
1219
+ * 批量回收
1220
+ * @param list
1221
+ */
1222
+ recoverList(list) {
1223
+ for (let index = 0; index < list.length; index++) {
1224
+ const element = list[index];
1225
+ this.recover(element);
1246
1226
  }
1247
- return false;
1248
1227
  }
1249
- _copyToData(eventName, data, target) {
1250
- for (const key in target) {
1251
- if (Object.prototype.hasOwnProperty.call(target, key)) {
1252
- const value = target[key];
1253
- if (data[key] == undefined) {
1254
- data[key] = value;
1255
- }
1256
- else {
1257
- console.warn(`[Analytics] 事件${eventName}存在重复属性${key}=${data[key]}`);
1258
- }
1259
- }
1228
+ /**
1229
+ * 将所有使用中的对象都回收到池中
1230
+ */
1231
+ recoverAll() {
1232
+ for (let index = 0; index < this._usingArray.length; index++) {
1233
+ const element = this._usingArray[index];
1234
+ this.recover(element);
1260
1235
  }
1261
1236
  }
1262
- _sendReport(eventName, data) {
1263
- return __awaiter(this, void 0, void 0, function* () {
1264
- return Promise.all(this._senderList.map((sender) => {
1265
- return sender.send(eventName, data);
1266
- })).then();
1267
- });
1237
+ destroy() {
1238
+ this.recoverAll();
1239
+ for (let index = 0; index < this._cacheStack.length; index++) {
1240
+ const element = this._cacheStack[index];
1241
+ element.destroy();
1242
+ }
1243
+ this._cacheStack.length = 0;
1244
+ this._cacheStack = null;
1245
+ this._usingArray.length = 0;
1246
+ this._usingArray = null;
1268
1247
  }
1269
1248
  }
1270
1249
 
1271
- const ALI_ANALYTICS_EVENT_CACHE = "ALI_ANALYTICS_EVENT_CACHE";
1272
1250
  /**
1273
- * 内部阿里云日志事件上报器
1251
+ * <p><code>Handler</code> 是事件处理器类。</p>
1252
+ * <p>推荐使用 Handler.create() 方法从对象池创建,减少对象创建消耗。创建的 Handler 对象不再使用后,可以使用 Handler.recover() 将其回收到对象池,回收后不要再使用此对象,否则会导致不可预料的错误。</p>
1253
+ * <p><b>注意:</b>由于鼠标事件也用本对象池,不正确的回收及调用,可能会影响鼠标事件的执行。</p>
1274
1254
  */
1275
- class ALiAnalyticsSender extends EmptyAnalyticsSender {
1255
+ class Handler {
1276
1256
  /**
1277
- *
1278
- * @param project
1279
- * @param logstore
1280
- * @param host
1281
- * @param exclude
1282
- * @param cd
1283
- * @param count
1257
+ * 根据指定的属性值,创建一个 <code>Handler</code> 类的实例。
1258
+ * @param caller 执行域。
1259
+ * @param method 处理函数。
1260
+ * @param args 函数参数。
1261
+ * @param once 是否只执行一次。
1284
1262
  */
1285
- constructor(config, exclude) {
1286
- super(exclude);
1287
- this._eventCache = [];
1288
- this._index = 0;
1289
- this._storageKey = ALI_ANALYTICS_EVENT_CACHE;
1290
- this._index = 0;
1291
- this._setConfig(config);
1292
- this._eventCache = this._loadStoredEvents();
1293
- this._tracker = window['SLS_Tracker'] && new window['SLS_Tracker']({
1294
- host: config.host || "cn-hangzhou.log.aliyuncs.com",
1295
- project: config.project,
1296
- logstore: config.logstore,
1297
- time: config.cd,
1298
- count: config.count, // 定义数据条数,默认是10条,number类型,选填
1299
- });
1300
- this._addListen();
1301
- this._startLoop();
1302
- this._doReport();
1263
+ constructor(caller = null, method = null, args = null, once = false) {
1264
+ /** 表示是否只执行一次。如果为true,回调后执行recover()进行回收,回收后会被再利用,默认为false 。*/
1265
+ this.once = false;
1266
+ /**@private */
1267
+ this._id = 0;
1268
+ this.setTo(caller, method, args, once);
1303
1269
  }
1304
- _setConfig(config) {
1305
- config.cd = config.cd || 2;
1306
- config.count = config.count || 5;
1307
- this._config = config;
1270
+ /**
1271
+ * 设置此对象的指定属性值。
1272
+ * @param caller 执行域(this)。
1273
+ * @param method 回调方法。
1274
+ * @param args 携带的参数。
1275
+ * @param once 是否只执行一次,如果为true,执行后执行recover()进行回收。
1276
+ * @return 返回 handler 本身。
1277
+ */
1278
+ setTo(caller, method, args, once = false) {
1279
+ this._id = Handler._gid++;
1280
+ this.caller = caller;
1281
+ this.method = method;
1282
+ this.args = args;
1283
+ this.once = once;
1284
+ return this;
1308
1285
  }
1309
- _loadStoredEvents() {
1310
- const storedValue = storage.getGValue(this._storageKey, []);
1311
- const events = Array.isArray(storedValue) ? storedValue : [];
1312
- if (events.length > 0) {
1313
- storage.removeGValue(this._storageKey);
1314
- }
1315
- return events;
1286
+ /**
1287
+ * 执行处理器。
1288
+ */
1289
+ run() {
1290
+ if (this.method == null)
1291
+ return null;
1292
+ var id = this._id;
1293
+ var result = this.method.apply(this.caller, this.args);
1294
+ this._id === id && this.once && this.recover();
1295
+ return result;
1316
1296
  }
1317
- _storeEvents(events) {
1318
- if (!events.length) {
1319
- storage.removeGValue(this._storageKey);
1320
- return;
1321
- }
1322
- storage.setGValue(this._storageKey, events);
1297
+ /**
1298
+ * 执行处理器,并携带额外数据。
1299
+ * @param data 附加的回调数据,可以是单数据或者Array(作为多参)
1300
+ */
1301
+ runWith(data) {
1302
+ if (this.method == null)
1303
+ return null;
1304
+ var id = this._id;
1305
+ if (data == null)
1306
+ var result = this.method.apply(this.caller, this.args);
1307
+ else if (!this.args && !data.unshift)
1308
+ result = this.method.call(this.caller, data);
1309
+ else if (this.args)
1310
+ result = this.method.apply(this.caller, this.args.concat(data));
1311
+ else
1312
+ result = this.method.apply(this.caller, data);
1313
+ this._id === id && this.once && this.recover();
1314
+ return result;
1323
1315
  }
1324
- _startLoop() {
1325
- timer.loop((this._config.cd || 2) * 1000, this, this._doReport, [this._config.count]);
1316
+ /**
1317
+ * 清理对象引用。
1318
+ */
1319
+ clear() {
1320
+ this.caller = null;
1321
+ this.method = null;
1322
+ this.args = null;
1323
+ return this;
1326
1324
  }
1327
- _addListen() {
1328
- App.onHide(() => {
1329
- this._doReport(this._eventCache.length, true);
1330
- });
1331
- App.onShow(this._restoreEvents, this);
1325
+ reset() {
1326
+ this.clear();
1332
1327
  }
1333
- _restoreEvents() {
1334
- const eventsInLocalStore = this._loadStoredEvents();
1335
- if (eventsInLocalStore.length > 0) {
1336
- this._eventCache.unshift(...eventsInLocalStore);
1337
- console.log(`[ALiAnalyticsSender] 恢复本地events(length:${eventsInLocalStore.length}, total:${this._eventCache.length})`);
1338
- }
1328
+ destroy() {
1329
+ this.clear();
1339
1330
  }
1340
- _doReport(maxCount = 5, store = false) {
1341
- if (this._eventCache.length <= 0) {
1342
- return false;
1343
- }
1344
- if (!this._tracker) {
1345
- return false;
1346
- }
1347
- const events = this._eventCache.splice(0, Math.min(this._eventCache.length, maxCount));
1348
- try {
1349
- this._tracker.sendBatchLogsImmediate(events);
1350
- return true;
1351
- }
1352
- catch (e) {
1353
- if (!store) {
1354
- this._eventCache.unshift(...events);
1355
- console.log(`[ALiAnalyticsSender] 事件发送失败,恢复事件到队列,等待下次发送。`);
1356
- }
1357
- else {
1358
- this._storeEvents([...events, ...this._eventCache]);
1359
- this._eventCache.length = 0;
1360
- console.log(`[ALiAnalyticsSender] 事件发送失败,存储到缓存,等待下次切回游戏。`);
1361
- }
1362
- return false;
1331
+ /**
1332
+ * 清理并回收到 Handler 对象池内。
1333
+ */
1334
+ recover() {
1335
+ if (this._id > 0) {
1336
+ this._id = 0;
1337
+ Handler._pool.recover(this);
1363
1338
  }
1364
1339
  }
1365
- send(eventName, data) {
1366
- return __awaiter(this, void 0, void 0, function* () {
1367
- data['_event'] = eventName;
1368
- let msg_data = {
1369
- appid: publisher.appId,
1370
- openid: storage.userId || publisher.login.openID || "",
1371
- deviceid: App.deviceId,
1372
- sessionid: App.sessionId,
1373
- level: ++this._index,
1374
- data: JSON.stringify(data),
1375
- event: eventName,
1376
- timestamp: this._config.timestampCall() || Date.now()
1377
- };
1378
- this._eventCache.push(msg_data);
1379
- });
1340
+ equal(value) {
1341
+ return value === this;
1380
1342
  }
1381
- }
1382
-
1383
- var DimensionType;
1384
- (function (DimensionType) {
1385
- DimensionType[DimensionType["ACTIVITY"] = 0] = "ACTIVITY";
1386
- DimensionType[DimensionType["AD"] = 1] = "AD";
1387
- DimensionType[DimensionType["PAYMENT"] = 2] = "PAYMENT";
1388
- DimensionType[DimensionType["SOCIAL"] = 3] = "SOCIAL";
1389
- DimensionType[DimensionType["PLAYER_ABILITY"] = 4] = "PLAYER_ABILITY";
1390
- DimensionType[DimensionType["LEVEL_STATS"] = 5] = "LEVEL_STATS";
1391
- })(DimensionType || (DimensionType = {}));
1392
-
1393
- class BaseDimension {
1394
- constructor(name) {
1395
- this._data = {};
1396
- this._initialized = false;
1397
- this._dirty = false;
1398
- this._name = name;
1343
+ /**
1344
+ * 从对象池内创建一个Handler,默认会执行一次并立即回收,如果不需要自动回收,设置once参数为false。
1345
+ * @param caller 执行域(this)。
1346
+ * @param method 回调方法。
1347
+ * @param args 携带的参数。
1348
+ * @param once 是否只执行一次,如果为true,回调后执行recover()进行回收,默认为true。
1349
+ * @return 返回创建的handler实例。
1350
+ */
1351
+ static create(caller, method, args = null, once = true) {
1352
+ return Handler._pool.allocate().setTo(caller, method, args, once);
1399
1353
  }
1400
- get name() {
1401
- return this._name;
1354
+ }
1355
+ /**@private handler对象池*/
1356
+ Handler._pool = new Pool(Handler);
1357
+ /**@private */
1358
+ Handler._gid = 1;
1359
+
1360
+ var InternalProperty;
1361
+ (function (InternalProperty) {
1362
+ /**设备系统版本 */
1363
+ InternalProperty["DEVICE_SYSTEM"] = "device_system";
1364
+ /**设备操作系统类型 */
1365
+ InternalProperty["DEVICE_PLATFORM"] = "device_platform";
1366
+ /**设备手机品牌 */
1367
+ InternalProperty["DEVICE_BRAND"] = "device_brand";
1368
+ /**设备手机型号 */
1369
+ InternalProperty["DEVICE_MODEL"] = "device_model";
1370
+ /**宿主名称 */
1371
+ InternalProperty["HOST_NAME"] = "host_name";
1372
+ /**宿主版本 */
1373
+ InternalProperty["HOST_VERSION"] = "host_version";
1374
+ /**启动时间 */
1375
+ InternalProperty["LAUNCH_TIME"] = "launch_time";
1376
+ /**启动次数 */
1377
+ InternalProperty["LAUNCH_TIMES"] = "launch_times";
1378
+ /**安装时间 */
1379
+ InternalProperty["INSTALL_TIME"] = "install_time";
1380
+ /**app运载(安装)时长 */
1381
+ InternalProperty["DURATION_OF_INSTALL"] = "duration_of_install";
1382
+ /**app运行(启动)时长 */
1383
+ InternalProperty["DURATION_OF_LAUNCH"] = "duration_of_launch";
1384
+ /**累积登录天数 */
1385
+ InternalProperty["LOGIN_DAYS"] = "login_days";
1386
+ /**安装来源type */
1387
+ InternalProperty["INSTALL_SCENE"] = "install_scene";
1388
+ /**安装来源source */
1389
+ InternalProperty["INSTALL_CHANNEL"] = "install_channel";
1390
+ /**启动来源场景 */
1391
+ InternalProperty["LAUNCH_SCENE"] = "launch_scene";
1392
+ /**启动来源渠道 */
1393
+ InternalProperty["LAUNCH_CHANNEL"] = "launch_channel";
1394
+ /**事件计时时长 */
1395
+ InternalProperty["DURATION"] = "duration";
1396
+ /**游戏版本 */
1397
+ InternalProperty["APP_VERSION"] = "app_version";
1398
+ })(InternalProperty || (InternalProperty = {}));
1399
+ class EmptyAnalyticsSender {
1400
+ /**
1401
+ * @param [exclude] 其他排除的上报日志, 默认不排除
1402
+ */
1403
+ constructor(exclude) {
1404
+ this._exclude = exclude || [];
1402
1405
  }
1403
- _ensureInit() {
1404
- if (!this._initialized) {
1405
- this._loadFromStorage();
1406
- this._initialized = true;
1407
- }
1406
+ /**
1407
+ * 设置排除
1408
+ * @param excludeList 排除事件名称或者名称列表。
1409
+ */
1410
+ exclude(...excludeList) {
1411
+ this._exclude = excludeList.concat();
1408
1412
  }
1409
- _loadFromStorage() {
1410
- const key = `dim_${this._name}`;
1411
- const data = storage.getUValue(key, "");
1412
- if (data) {
1413
- try {
1414
- this._data = this._decompress(data);
1415
- }
1416
- catch (e) {
1417
- console.error(`加载维度${this._name}失败:`, e);
1418
- this._data = {};
1419
- }
1413
+ sendAble(eventName) {
1414
+ if (this._exclude && this._exclude.indexOf(eventName) != -1) {
1415
+ return false;
1420
1416
  }
1417
+ return true;
1421
1418
  }
1422
- _saveToStorage() {
1419
+ send(eventName, data) {
1423
1420
  return __awaiter(this, void 0, void 0, function* () {
1424
- if (!this._dirty)
1425
- return;
1426
- const key = `dim_${this._name}`;
1427
- const compressed = this._compress(this._data);
1428
- storage.setUValue(key, compressed);
1429
- this._dirty = false;
1430
- });
1431
- }
1432
- _updateValue(key, value) {
1433
- this._ensureInit();
1434
- this._data[key] = value;
1435
- this._dirty = true;
1436
- this._saveToStorage();
1437
- }
1438
- _incrementValue(key, delta = 1) {
1439
- this._ensureInit();
1440
- this._data[key] = (this._data[key] || 0) + delta;
1441
- this._dirty = true;
1442
- this._saveToStorage();
1443
- }
1444
- _getValue(key, defaultValue = 0) {
1445
- var _a;
1446
- this._ensureInit();
1447
- return (_a = this._data[key]) !== null && _a !== void 0 ? _a : defaultValue;
1448
- }
1449
- getData() {
1450
- this._ensureInit();
1451
- return Object.assign({}, this._data);
1452
- }
1453
- _compress(data) {
1454
- return Object.entries(data)
1455
- .map(([k, v]) => `${k}:${v}`)
1456
- .join(',');
1457
- }
1458
- _decompress(str) {
1459
- const data = {};
1460
- if (!str)
1461
- return data;
1462
- str.split(',').forEach(pair => {
1463
- const [k, v] = pair.split(':');
1464
- if (k && v !== undefined) {
1465
- data[k] = Number(v);
1466
- }
1467
1421
  });
1468
- return data;
1469
- }
1470
- clear() {
1471
- this._data = {};
1472
- this._dirty = true;
1473
- this._saveToStorage();
1474
1422
  }
1475
1423
  }
1476
-
1477
- var AdDataKey;
1478
- (function (AdDataKey) {
1479
- // 累计展示次数
1480
- AdDataKey["SHOW_COUNT"] = "sc";
1481
- // 展示天数
1482
- AdDataKey["SHOW_DAYS"] = "sd";
1483
- // 累计完成次数
1484
- AdDataKey["COMPLETE_COUNT"] = "cpc";
1485
- // 最后展示天数
1486
- AdDataKey["LAST_SHOW_DAY"] = "lsd";
1487
- // 今日展示次数
1488
- AdDataKey["TODAY_SHOW_COUNT"] = "tsc";
1489
- // 首次展示时间(相对安装时间,秒)
1490
- AdDataKey["FIRST_SHOW_TIME"] = "fst";
1491
- // 上次展示时间(相对安装时间,秒)
1492
- AdDataKey["LAST_SHOW_TIME"] = "lst";
1493
- // 平均展示间隔(秒)
1494
- AdDataKey["AVG_SHOW_INTERVAL"] = "asi";
1495
- // 今日平均展示间隔(秒)
1496
- AdDataKey["TODAY_AVG_SHOW_INTERVAL"] = "tasi";
1497
- })(AdDataKey || (AdDataKey = {}));
1498
- const AD_METRIC_ORDER_V1 = [
1499
- 'AD.show_count',
1500
- 'AD.show_days',
1501
- 'AD.complete_count',
1502
- 'AD.last_show_day',
1503
- 'AD.today_show_count',
1504
- 'AD.first_show_time',
1505
- 'AD.last_show_time',
1506
- 'AD.avg_show_interval',
1507
- 'AD.today_avg_show_interval',
1508
- 'AD.avg_show_per_day',
1509
- 'AD.avg_complete_per_day',
1510
- ];
1511
1424
  /**
1512
- * 广告维度,记录广告展示次数、完成次数、展示天数、展示间隔等信息
1425
+ * 统计分析能力
1513
1426
  */
1514
- class AdDimension extends BaseDimension {
1427
+ class AnalyticsAble {
1515
1428
  constructor() {
1516
- super(DimensionType[DimensionType.AD]);
1517
- this._todayPrevShowTime = 0; // 内存中记录今日上次展示时间(相对启动),用于计算今日间隔
1518
- this._checkAndResetDailyData();
1429
+ this._inited = false;
1430
+ this._hasSetDefaultProperty = false;
1431
+ this._debugMode = false;
1432
+ this._senderList = [];
1433
+ this._dynamicCalls = [];
1434
+ this._userId = null;
1435
+ this._hasSetUserId = false;
1519
1436
  }
1437
+ get inited() { return this._inited; }
1520
1438
  /**
1521
- * 检测并重置今日数据(仅在新的一天的新启动时)
1439
+ * 初始化
1522
1440
  */
1523
- _checkAndResetDailyData() {
1524
- return __awaiter(this, void 0, void 0, function* () {
1525
- this._ensureInit();
1526
- const today = App.daysInstall;
1527
- const lastDay = this._getValue(AdDataKey.LAST_SHOW_DAY, -1);
1528
- // 只有在新的一天才重置今日数据
1529
- if (lastDay !== -1 && lastDay !== today) {
1530
- this._updateValue(AdDataKey.TODAY_SHOW_COUNT, 0);
1531
- this._updateValue(AdDataKey.TODAY_AVG_SHOW_INTERVAL, 0);
1532
- this._todayPrevShowTime = 0;
1533
- }
1534
- });
1441
+ init(senders, debugMode = false) {
1442
+ if (this._inited)
1443
+ return this;
1444
+ this._debugMode = debugMode;
1445
+ App.onShow(this._onAppShow, this);
1446
+ App.onHide(this._onAppHide, this);
1447
+ this._initSenderList(senders);
1448
+ this._inited = true;
1449
+ return this;
1535
1450
  }
1536
- recordShow() {
1537
- const showCount = this._getValue(AdDataKey.SHOW_COUNT, 0);
1538
- const prevShowTime = this._getValue(AdDataKey.LAST_SHOW_TIME, 0);
1539
- this._incrementValue(AdDataKey.SHOW_COUNT);
1540
- this._incrementValue(AdDataKey.TODAY_SHOW_COUNT);
1541
- const today = App.daysInstall;
1542
- const lastDay = this._getValue(AdDataKey.LAST_SHOW_DAY, -1);
1543
- // 更新展示天数和最后展示天数
1544
- if (lastDay !== today) {
1545
- this._incrementValue(AdDataKey.SHOW_DAYS);
1546
- this._updateValue(AdDataKey.LAST_SHOW_DAY, today);
1547
- }
1548
- // 记录当前展示时间(相对安装时间)
1549
- const currentTime = Math.floor(App.durationOfInstall / 1000);
1550
- const currentLaunchTime = Math.floor(App.durationOfLaunch / 1000);
1551
- // 计算总体平均间隔(从第二次展示开始)
1552
- if (showCount > 0 && prevShowTime > 0) {
1553
- const interval = currentTime - prevShowTime;
1554
- const oldAvg = this._getValue(AdDataKey.AVG_SHOW_INTERVAL, 0);
1555
- // 增量平均公式: 新平均 = 旧平均 + (本次值 - 旧平均) / 新的数据个数
1556
- const newAvg = Math.floor(oldAvg + (interval - oldAvg) / showCount);
1557
- this._updateValue(AdDataKey.AVG_SHOW_INTERVAL, newAvg);
1451
+ _initSenderList(senders) {
1452
+ for (let i = 0; i < senders.length; i++) {
1453
+ const sender = senders[i];
1454
+ this._senderList.push(sender);
1455
+ this._syncSenderUserState(sender);
1558
1456
  }
1559
- // 计算今日平均间隔(从今日第二次展示开始)
1560
- const todayShowCount = this._getValue(AdDataKey.TODAY_SHOW_COUNT, 0);
1561
- if (todayShowCount > 1 && this._todayPrevShowTime > 0) {
1562
- const todayInterval = currentLaunchTime - this._todayPrevShowTime;
1563
- const oldTodayAvg = this._getValue(AdDataKey.TODAY_AVG_SHOW_INTERVAL, 0);
1564
- // 今日间隔数 = 今日展示次数 - 1
1565
- const newTodayAvg = Math.floor(oldTodayAvg + (todayInterval - oldTodayAvg) / (todayShowCount - 1));
1566
- this._updateValue(AdDataKey.TODAY_AVG_SHOW_INTERVAL, newTodayAvg);
1457
+ }
1458
+ _usesSenderUserProperties(sender) {
1459
+ if (sender.userPropertyMode !== "sender") {
1460
+ return false;
1567
1461
  }
1568
- // 记录首次展示时间(仅首次)
1569
- if (showCount === 0) {
1570
- this._updateValue(AdDataKey.FIRST_SHOW_TIME, currentTime);
1462
+ if (typeof sender.setUserProperty === "function") {
1463
+ return true;
1571
1464
  }
1572
- // 更新时间记录
1573
- this._updateValue(AdDataKey.LAST_SHOW_TIME, currentTime);
1574
- this._todayPrevShowTime = currentLaunchTime;
1465
+ this._debugMode && console.error("[Analytics] userPropertyMode=sender 的 Sender 必须实现 setUserProperty,已回退为 event 模式。");
1466
+ return false;
1575
1467
  }
1576
- recordComplete() {
1577
- this._incrementValue(AdDataKey.COMPLETE_COUNT);
1468
+ _getUserProperties() {
1469
+ return Object.assign({}, this._userOnceProperties || {}, this._userAddProperties || {}, this._userProperties || {});
1578
1470
  }
1579
- /**
1580
- * 获取日均展示次数
1581
- */
1582
- get avgShowPerDay() {
1583
- const total = this._getValue(AdDataKey.SHOW_COUNT, 0);
1584
- const days = this._getValue(AdDataKey.SHOW_DAYS, 0);
1585
- return days > 0 ? Math.floor(total / days) : 0;
1586
- }
1587
- /**
1588
- * 获取日均完成次数
1589
- */
1590
- get avgCompletePerDay() {
1591
- const total = this._getValue(AdDataKey.COMPLETE_COUNT, 0);
1592
- const days = this._getValue(AdDataKey.SHOW_DAYS, 0);
1593
- return days > 0 ? Math.floor(total / days) : 0;
1471
+ _syncSenderUserState(sender) {
1472
+ if (this._hasSetUserId && typeof sender.setUserId === "function") {
1473
+ sender.setUserId(this._userId);
1474
+ }
1475
+ if (!this._usesSenderUserProperties(sender)) {
1476
+ return;
1477
+ }
1478
+ const properties = this._getUserProperties();
1479
+ for (const key in properties) {
1480
+ if (Object.prototype.hasOwnProperty.call(properties, key)) {
1481
+ sender.setUserProperty(key, properties[key]);
1482
+ }
1483
+ }
1594
1484
  }
1595
- /**
1596
- * 获取平均广告展示间隔(秒)
1597
- * 使用增量平均算法计算相邻两次展示的平均间隔
1598
- */
1599
- get avgShowInterval() {
1600
- return this._getValue(AdDataKey.AVG_SHOW_INTERVAL, 0);
1485
+ _notifyUserProperty(key, value) {
1486
+ for (let i = 0; i < this._senderList.length; i++) {
1487
+ const sender = this._senderList[i];
1488
+ if (this._usesSenderUserProperties(sender)) {
1489
+ sender.setUserProperty(key, value);
1490
+ }
1491
+ }
1601
1492
  }
1602
1493
  /**
1603
- * 获取今日平均广告展示间隔(秒)
1604
- * 使用增量平均算法计算今日相邻两次展示的平均间隔
1494
+ * 设置排除
1495
+ * @param excludeEvents 排除事件名称或者名称列表。
1496
+ * @param senderClass [可选] 特定的SenderClass
1605
1497
  */
1606
- get todayAvgShowInterval() {
1607
- return this._getValue(AdDataKey.TODAY_AVG_SHOW_INTERVAL, 0);
1608
- }
1609
- getMetrics(order = [
1610
- AdDataKey.SHOW_COUNT,
1611
- AdDataKey.SHOW_DAYS,
1612
- AdDataKey.COMPLETE_COUNT,
1613
- AdDataKey.LAST_SHOW_DAY,
1614
- AdDataKey.TODAY_SHOW_COUNT,
1615
- AdDataKey.FIRST_SHOW_TIME,
1616
- AdDataKey.LAST_SHOW_TIME,
1617
- AdDataKey.AVG_SHOW_INTERVAL,
1618
- AdDataKey.TODAY_AVG_SHOW_INTERVAL
1619
- ]) {
1620
- this._ensureInit();
1621
- const result = [];
1622
- for (const key of order) {
1623
- result.push(this._getValue(key, 0));
1498
+ setExclude(excludeEvents, senderClass) {
1499
+ for (let i = 0; i < this._senderList.length; i++) {
1500
+ const sender = this._senderList[i];
1501
+ if (!senderClass || sender instanceof senderClass) {
1502
+ if (typeof excludeEvents === "string") {
1503
+ sender.exclude(excludeEvents);
1504
+ }
1505
+ else {
1506
+ sender.exclude(...excludeEvents);
1507
+ }
1508
+ }
1624
1509
  }
1625
- result.push(this.avgShowPerDay);
1626
- result.push(this.avgCompletePerDay);
1627
- return result;
1628
1510
  }
1629
- }
1630
-
1631
- var ADType;
1632
- (function (ADType) {
1633
- ADType[ADType["BANNER"] = 0] = "BANNER";
1634
- ADType[ADType["INTERSTITIAL"] = 1] = "INTERSTITIAL";
1635
- ADType[ADType["REWARD_VIDEO"] = 2] = "REWARD_VIDEO";
1636
- ADType[ADType["NATIVE"] = 3] = "NATIVE";
1637
- })(ADType || (ADType = {}));
1638
- var ADEvent;
1639
- (function (ADEvent) {
1640
- ADEvent[ADEvent["LOAD"] = 0] = "LOAD";
1641
- ADEvent[ADEvent["LOAD_COMPLETE"] = 1] = "LOAD_COMPLETE";
1642
- ADEvent[ADEvent["LOAD_FAIL"] = 2] = "LOAD_FAIL";
1643
- ADEvent[ADEvent["SHOW"] = 3] = "SHOW";
1644
- ADEvent[ADEvent["SHOW_COMPLETE"] = 4] = "SHOW_COMPLETE";
1645
- ADEvent[ADEvent["SHOW_FAIL"] = 5] = "SHOW_FAIL";
1646
- ADEvent[ADEvent["CLOSE_BY_USER"] = 6] = "CLOSE_BY_USER";
1647
- })(ADEvent || (ADEvent = {}));
1648
- class ADBehaviorReporter {
1649
- constructor(baseAble) {
1650
- this.baseAble = baseAble;
1651
- this._groupCount = {};
1511
+ _onAppShow() {
1512
+ this.time("app_hide");
1513
+ this.report("app_show", publisher.publisher.enterOptions.getAllOption());
1514
+ }
1515
+ _onAppHide() {
1516
+ this.report("app_hide");
1652
1517
  }
1653
1518
  /**
1654
- * 重置单个模块的广告计数
1655
- * @param group 模块名称,默认为game
1519
+ * 设置所有上报事件的基础属性
1656
1520
  */
1657
- resetGroupCount(group = "game") {
1658
- this._groupCount[group] = 0;
1521
+ _setDefaultProperty() {
1522
+ if (this._hasSetDefaultProperty) {
1523
+ return;
1524
+ }
1525
+ //设备信息
1526
+ let deviceInfo = publisher.device.deviceInfo;
1527
+ this.setProperty(InternalProperty.DEVICE_SYSTEM, deviceInfo.system);
1528
+ this.setProperty(InternalProperty.DEVICE_PLATFORM, deviceInfo.platform);
1529
+ this.setProperty(InternalProperty.DEVICE_BRAND, deviceInfo.brand);
1530
+ this.setProperty(InternalProperty.DEVICE_MODEL, deviceInfo.model);
1531
+ //宿主信息
1532
+ let hostInfo = publisher.device.hostInfo;
1533
+ this.setProperty(InternalProperty.HOST_NAME, hostInfo.name);
1534
+ this.setProperty(InternalProperty.HOST_VERSION, hostInfo.version);
1535
+ //安装时间
1536
+ this.setProperty(InternalProperty.INSTALL_TIME, Math.floor(App.installTime / 1000));
1537
+ //启动时间
1538
+ this.setProperty(InternalProperty.LAUNCH_TIME, Math.floor(App.launchTime / 1000));
1539
+ //启动次数
1540
+ this.setProperty(InternalProperty.LAUNCH_TIMES, App.launchTimes);
1541
+ //游戏版本
1542
+ this.setProperty(InternalProperty.APP_VERSION, publisher.publisher.appVersion);
1543
+ this.setDynamicProperties(() => {
1544
+ let dynamicData = {};
1545
+ //app运载(安装)时长
1546
+ dynamicData[InternalProperty.DURATION_OF_INSTALL] = Math.floor(App.durationOfInstall / 1000);
1547
+ //app运行(启动)时长
1548
+ dynamicData[InternalProperty.DURATION_OF_LAUNCH] = Math.floor(App.durationOfLaunch / 1000);
1549
+ return dynamicData;
1550
+ });
1551
+ //累积登录天数
1552
+ this.userSet(InternalProperty.LOGIN_DAYS, storage.loginDays);
1553
+ //安装来源type
1554
+ this.userSet(InternalProperty.INSTALL_SCENE, publisher.publisher.installOptions.channelScene);
1555
+ //安装来源source
1556
+ this.userSet(InternalProperty.INSTALL_CHANNEL, publisher.publisher.installOptions.channelSource);
1557
+ //启动来源场景
1558
+ this.userSet(InternalProperty.LAUNCH_SCENE, publisher.publisher.enterOptions.channelScene);
1559
+ //启动来源渠道
1560
+ this.userSet(InternalProperty.LAUNCH_CHANNEL, publisher.publisher.enterOptions.channelSource);
1561
+ this._hasSetDefaultProperty = true;
1659
1562
  }
1660
1563
  /**
1661
- * 开始加载广告
1662
- * @param type 广告类型
1663
- * @param from 广告来源
1664
- * @param msg 广告失败原因
1564
+ * 设置事件公共属性
1565
+ * @param key 公共属性key
1566
+ * @param value
1665
1567
  */
1666
- onStartLoad(type, from, msg) {
1667
- this.report(type, ADEvent.LOAD, from, msg);
1568
+ setProperty(key, value) {
1569
+ if (!this._staticProperties) {
1570
+ this._staticProperties = {};
1571
+ }
1572
+ if (typeof value === "boolean") {
1573
+ value = value ? 1 : 0;
1574
+ }
1575
+ if (this._debugMode && Object.prototype.hasOwnProperty.call(this._staticProperties, key)) {
1576
+ console.warn(`静态公共属性中已经存在${key}:${this._staticProperties[key]}, 将替换为${value}`);
1577
+ }
1578
+ this._staticProperties[key] = value;
1668
1579
  }
1669
1580
  /**
1670
- * 广告加载完成
1671
- * @param type 广告类型
1672
- * @param from 广告来源
1581
+ * 删除事件的公共属性
1582
+ * @param key
1583
+ * @returns
1673
1584
  */
1674
- onLoadComplete(type, from) {
1675
- this.report(type, ADEvent.LOAD_COMPLETE, from);
1585
+ unsetProperty(key) {
1586
+ if (this._staticProperties) {
1587
+ return delete this._staticProperties[key];
1588
+ }
1589
+ return false;
1676
1590
  }
1677
1591
  /**
1678
- * 广告加载失败
1679
- * @param type 广告类型
1680
- * @param from 广告来源
1681
- * @param msg 广告失败原因
1592
+ * 设置事件公共动态属性,上报事件时会触发回调函数,并把返回的AnalyticsData加入到事件属性中
1593
+ * @param dynamicCall 动态属性的生成函数,事件上报时实时调用
1682
1594
  */
1683
- onLoadFail(type, from, msg) {
1684
- this.report(type, ADEvent.LOAD_FAIL, from, msg);
1595
+ setDynamicProperties(dynamicCall) {
1596
+ this._dynamicCalls.push(dynamicCall);
1685
1597
  }
1686
1598
  /**
1687
- * 显示广告
1688
- * @param type 广告类型
1689
- * @param from 广告来源
1599
+ * 设置平台用户标识。该值不会自动作为事件参数发送。
1690
1600
  */
1691
- onShow(type, from) {
1692
- this.report(type, ADEvent.SHOW, from);
1601
+ setUserId(userId) {
1602
+ this._userId = userId;
1603
+ this._hasSetUserId = true;
1604
+ for (let i = 0; i < this._senderList.length; i++) {
1605
+ const sender = this._senderList[i];
1606
+ if (typeof sender.setUserId === "function") {
1607
+ sender.setUserId(userId);
1608
+ }
1609
+ }
1693
1610
  }
1694
1611
  /**
1695
- * 广告显示完成
1696
- * @param type 广告类型
1697
- * @param from 广告来源
1612
+ * 对于一般的用户属性,您可以调用 userSet 来进行设置。
1613
+ * 使用该接口上传的属性将会覆盖原有的属性值,如果之前不存在该用户属性,则会新建该用户属性,类型与传入属性的类型一致。
1614
+ * 用户名为例:
1615
+ * ```
1616
+ * // username为TA
1617
+ * userSet("username", "TA");
1618
+ * //username为TE
1619
+ * userSet("username", "TE");
1620
+ * ```
1621
+ * @param key 属性key
1622
+ * @param value
1698
1623
  */
1699
- onShowComplete(type, from) {
1700
- this.report(type, ADEvent.SHOW_COMPLETE, from);
1624
+ userSet(key, value) {
1625
+ if (!this._userProperties) {
1626
+ this._userProperties = {};
1627
+ }
1628
+ if (typeof value === "boolean") {
1629
+ value = value ? 1 : 0;
1630
+ }
1631
+ if (this._debugMode && Object.prototype.hasOwnProperty.call(this._userProperties, key)) {
1632
+ console.warn(`用户属性中已经存在${key}:${this._userProperties[key]}, 将替换为${value}`);
1633
+ }
1634
+ this._userProperties[key] = value;
1635
+ this._notifyUserProperty(key, value);
1701
1636
  }
1702
1637
  /**
1703
- * 广告显示失败
1704
- * @param type 广告类型
1705
- * @param from 广告来源
1706
- * @param msg 广告失败原因
1638
+ * 如果您要上传的用户属性只要设置一次,则可以调用 userSetOnce 来进行设置。
1639
+ * 当该属性之前已经有值的时候,将会忽略这条信息,比如设置首次付费时间:
1640
+ * ```
1641
+ * //first_payment_time为2018-01-01 01:23:45.678
1642
+ * userOnce("first_payment_time", "2018-01-01 01:23:45.678");
1643
+ * //first_payment_time仍然为2018-01-01 01:23:45.678
1644
+ * userOnce("first_payment_time", "2018-12-31 01:23:45.678");
1645
+ * ```
1646
+ * @param key 属性key
1647
+ * @param value
1648
+ * @returns
1707
1649
  */
1708
- onShowFail(type, from, msg) {
1709
- this.report(type, ADEvent.SHOW_FAIL, from, msg);
1650
+ userOnce(key, value) {
1651
+ if (!this._userOnceProperties) {
1652
+ this._userOnceProperties = {};
1653
+ }
1654
+ if (typeof value === "boolean") {
1655
+ value = value ? 1 : 0;
1656
+ }
1657
+ if (Object.prototype.hasOwnProperty.call(this._userOnceProperties, key)) {
1658
+ this._debugMode && console.info(`用户属性中已经存在once属性${key}:${this._userOnceProperties[key]}`);
1659
+ return;
1660
+ }
1661
+ let storageValue = storage.getUValue(`analytics_user_once_${key}`, "");
1662
+ if (storageValue != null && storageValue != "") {
1663
+ this._debugMode && console.info(`用户属性中已经存在once属性${key}:${storageValue}`);
1664
+ this._userOnceProperties[key] = typeof value === "number" ? Number(storageValue) : storageValue;
1665
+ this._notifyUserProperty(key, this._userOnceProperties[key]);
1666
+ return;
1667
+ }
1668
+ storage.setUValue(`analytics_user_once_${key}`, value);
1669
+ this._userOnceProperties[key] = value;
1670
+ this._notifyUserProperty(key, value);
1710
1671
  }
1711
1672
  /**
1712
- * 用户关闭广告
1713
- * @param type 广告类型
1714
- * @param from 广告来源
1715
- */
1716
- onCloseByUser(type, from) {
1717
- this.report(type, ADEvent.CLOSE_BY_USER, from);
1718
- }
1719
- /**
1720
- * 上报广告事件
1721
- * @param type 广告类型
1722
- * @param event 广告事件
1723
- * @param from 广告来源
1724
- * @param msg 广告失败原因
1673
+ *设置用户累积属性,当您要上传数值型的属性时,您可以调用 userAdd 来对该属性进行累加操作,
1674
+ * 如果该属性还未被设置,则会赋值 0 后再进行计算。
1675
+ * 如果传入负值,等同于减法操作。
1676
+ * ```
1677
+ * userAdd("total_revenue", 30); //此时total_revenue为30
1678
+ * userAdd("total_revenue", 648); //此时total_revenue为678
1679
+ * ```
1680
+ * @param key 累积属性key
1681
+ * @param value 累积的偏移值
1725
1682
  */
1726
- report(type, event, from, msg) {
1727
- from = from || {};
1728
- const group = from.group || "game";
1729
- from.group && delete from.group;
1730
- from._adtype = type;
1731
- from._adevent = event;
1732
- if (!this._groupCount[group]) {
1733
- this._groupCount[group] = 0;
1734
- }
1735
- from.count = event == ADEvent.SHOW_COMPLETE ? ++this._groupCount[group] : this._groupCount[group];
1736
- if (msg) {
1737
- from.msg = msg;
1683
+ userAdd(key, value) {
1684
+ if (!this._userAddProperties) {
1685
+ this._userAddProperties = {};
1738
1686
  }
1739
- this.baseAble.report("ad", from);
1740
- }
1741
- }
1742
-
1743
- /**
1744
- * 对象池
1745
- */
1746
- class Pool {
1747
- constructor(clazz, maxCount) {
1748
- /**池中闲置对象 */
1749
- this._cacheStack = new Array();
1750
- /**正在使用的对象 */
1751
- this._usingArray = new Array();
1752
- /**池中对象最大数 */
1753
- this._maxCount = 0;
1754
- this._class = clazz;
1755
- if (!this._class) {
1756
- throw new Error("构造函数不能为空!");
1687
+ if (typeof value === "boolean") {
1688
+ value = value ? 1 : 0;
1757
1689
  }
1758
- this._maxCount = maxCount == undefined ? Number.MAX_SAFE_INTEGER : maxCount;
1690
+ let total = Number(this._userAddProperties[key] || storage.getUValue(`analytics_user_add_${key}`, 0) || 0);
1691
+ this._userAddProperties[key] = total + value;
1692
+ storage.setUValue(`analytics_user_add_${key}`, total + value);
1693
+ this._notifyUserProperty(key, total + value);
1759
1694
  }
1760
1695
  /**
1761
- * 在池中的对象
1762
- */
1763
- get count() {
1764
- return this._cacheStack.length;
1696
+ * 移除用户属性。当您要清空用户的用户属性值时,您可以调用 userUnset 来对指定属性进行清空操作,如果该属性还未在集群中被创建,则 user_unset 不会创建该属性
1697
+ * @param key
1698
+ */
1699
+ userUnset(key) {
1700
+ this._userProperties && delete this._userProperties[key];
1701
+ this._userOnceProperties && delete this._userOnceProperties[key];
1702
+ this._userAddProperties && delete this._userAddProperties[key];
1703
+ storage.removeUValue(`analytics_user_once_${key}`);
1704
+ storage.removeUValue(`analytics_user_add_${key}`);
1705
+ this._notifyUserProperty(key, null);
1765
1706
  }
1766
1707
  /**
1767
- * 使用中的数量
1708
+ * 上报错误
1709
+ * @param error 错误信息
1768
1710
  */
1769
- get usingCount() {
1770
- return this._usingArray.length;
1711
+ error(error) {
1712
+ this.report("app_error", { error: error });
1771
1713
  }
1772
1714
  /**
1773
- * 分配
1774
- * @returns
1715
+ * 开始事件计时
1716
+ * 如果您需要记录某个事件的持续时长,可以调用 timeEvent 来开始计时。
1717
+ * 配置您想要计时的事件名称,当您上传该事件时,将会自动在您的事件属性中加入 duration 属性来表示记录的时长,单位为秒。
1718
+ * 需要注意的是,同一个事件名只能有一个在计时的任务。
1719
+ * @param eventName
1775
1720
  */
1776
- allocate(createData) {
1777
- if (this.count + this.usingCount < this._maxCount) {
1778
- let element = this._cacheStack.length > 0 ? this._cacheStack.pop() : (typeof createData === 'function' ? createData() : new this._class(createData));
1779
- this._usingArray.push(element);
1780
- return element;
1781
- }
1782
- throw new Error("对象池最大数量超出:" + this._maxCount);
1721
+ time(eventName, once = true, offset = 0) {
1722
+ this._timeEvents = this._timeEvents || new Map();
1723
+ this._debugMode && this._timeEvents.has(eventName) && console.warn(`已经存在${eventName}的计时,将被重置。`);
1724
+ this.cancelTime(eventName);
1725
+ this._timeEvents.set(eventName, { start: App.durationOfLaunch, once: once, offset: offset });
1783
1726
  }
1784
1727
  /**
1785
- * 回收到池中
1786
- * @param value
1728
+ * 取消事件计时
1729
+ * @param eventName
1787
1730
  * @returns
1788
1731
  */
1789
- recover(value) {
1790
- if (this._cacheStack.indexOf(value) > -1) {
1791
- throw new Error("重复回收!");
1792
- }
1793
- let index = this._usingArray.indexOf(value);
1794
- if (index < 0) {
1795
- throw new Error("对象不属于改对象池!");
1796
- }
1797
- //重置
1798
- value.reset();
1799
- this._usingArray.splice(index, 1);
1800
- this._cacheStack.push(value);
1732
+ cancelTime(eventName) {
1733
+ return this._timeEvents && this._timeEvents.has(eventName) && this._timeEvents.delete(eventName);
1801
1734
  }
1802
1735
  /**
1803
- * 批量回收
1804
- * @param list
1736
+ * 上报事件
1737
+ * @param eventName 事件名称
1738
+ * @param data 事件数据
1805
1739
  */
1806
- recoverList(list) {
1807
- for (let index = 0; index < list.length; index++) {
1808
- const element = list[index];
1809
- this.recover(element);
1810
- }
1740
+ report(eventName, data) {
1741
+ return __awaiter(this, void 0, void 0, function* () {
1742
+ if (!this._sendAble(eventName)) {
1743
+ this._debugMode && console.log(`[Analytics] ${eventName}事件不上报`);
1744
+ return;
1745
+ }
1746
+ if (!data) {
1747
+ this._debugMode && console.warn(`[Analytics] ${eventName}无上报事件数据`);
1748
+ data = {};
1749
+ }
1750
+ else {
1751
+ data = Object.assign({}, data);
1752
+ }
1753
+ this._setDefaultProperty();
1754
+ for (let i = 0; i < this._dynamicCalls.length; i++) {
1755
+ const call = this._dynamicCalls[i];
1756
+ this._copyToData(eventName, data, call());
1757
+ }
1758
+ this._copyToData(eventName, data, this._staticProperties);
1759
+ if (this._timeEvents && this._timeEvents.has(eventName)) {
1760
+ let timeInfo = this._timeEvents.get(eventName);
1761
+ let duration = Math.floor((App.durationOfLaunch - timeInfo.start + timeInfo.offset) / 1000 * 100) / 100;
1762
+ data[InternalProperty.DURATION] = duration;
1763
+ timeInfo.once && this._timeEvents.delete(eventName);
1764
+ }
1765
+ this._debugMode && console.log("[Analytics]", eventName, JSON.stringify(data));
1766
+ return this._sendReport(eventName, data);
1767
+ });
1811
1768
  }
1812
- /**
1813
- * 将所有使用中的对象都回收到池中
1814
- */
1815
- recoverAll() {
1816
- for (let index = 0; index < this._usingArray.length; index++) {
1817
- const element = this._usingArray[index];
1818
- this.recover(element);
1769
+ _sendAble(eventName) {
1770
+ for (let i = 0; i < this._senderList.length; i++) {
1771
+ const sender = this._senderList[i];
1772
+ if (sender.sendAble(eventName)) {
1773
+ return true;
1774
+ }
1819
1775
  }
1776
+ return false;
1820
1777
  }
1821
- destroy() {
1822
- this.recoverAll();
1823
- for (let index = 0; index < this._cacheStack.length; index++) {
1824
- const element = this._cacheStack[index];
1825
- element.destroy();
1778
+ _copyToData(eventName, data, target) {
1779
+ for (const key in target) {
1780
+ if (Object.prototype.hasOwnProperty.call(target, key)) {
1781
+ const value = target[key];
1782
+ if (data[key] == undefined) {
1783
+ data[key] = value;
1784
+ }
1785
+ else {
1786
+ console.warn(`[Analytics] 事件${eventName}存在重复属性${key}=${data[key]}`);
1787
+ }
1788
+ }
1826
1789
  }
1827
- this._cacheStack.length = 0;
1828
- this._cacheStack = null;
1829
- this._usingArray.length = 0;
1830
- this._usingArray = null;
1790
+ }
1791
+ _sendReport(eventName, data) {
1792
+ return __awaiter(this, void 0, void 0, function* () {
1793
+ const userProperties = this._getUserProperties();
1794
+ return Promise.all(this._senderList
1795
+ .filter((sender) => sender.sendAble(eventName))
1796
+ .map((sender) => {
1797
+ const senderData = Object.assign({}, data);
1798
+ if (!this._usesSenderUserProperties(sender)) {
1799
+ this._copyToData(eventName, senderData, userProperties);
1800
+ }
1801
+ return sender.send(eventName, senderData);
1802
+ })).then();
1803
+ });
1831
1804
  }
1832
1805
  }
1833
1806
 
1807
+ const ALI_ANALYTICS_EVENT_CACHE = "ALI_ANALYTICS_EVENT_CACHE";
1834
1808
  /**
1835
- * <p><code>Handler</code> 是事件处理器类。</p>
1836
- * <p>推荐使用 Handler.create() 方法从对象池创建,减少对象创建消耗。创建的 Handler 对象不再使用后,可以使用 Handler.recover() 将其回收到对象池,回收后不要再使用此对象,否则会导致不可预料的错误。</p>
1837
- * <p><b>注意:</b>由于鼠标事件也用本对象池,不正确的回收及调用,可能会影响鼠标事件的执行。</p>
1809
+ * 内部阿里云日志事件上报器
1838
1810
  */
1839
- class Handler {
1811
+ class ALiAnalyticsSender extends EmptyAnalyticsSender {
1840
1812
  /**
1841
- * 根据指定的属性值,创建一个 <code>Handler</code> 类的实例。
1842
- * @param caller 执行域。
1843
- * @param method 处理函数。
1844
- * @param args 函数参数。
1845
- * @param once 是否只执行一次。
1813
+ *
1814
+ * @param project
1815
+ * @param logstore
1816
+ * @param host
1817
+ * @param exclude
1818
+ * @param cd
1819
+ * @param count
1846
1820
  */
1847
- constructor(caller = null, method = null, args = null, once = false) {
1848
- /** 表示是否只执行一次。如果为true,回调后执行recover()进行回收,回收后会被再利用,默认为false 。*/
1849
- this.once = false;
1850
- /**@private */
1851
- this._id = 0;
1852
- this.setTo(caller, method, args, once);
1821
+ constructor(config, exclude) {
1822
+ super(exclude);
1823
+ this._eventCache = [];
1824
+ this._index = 0;
1825
+ this._storageKey = ALI_ANALYTICS_EVENT_CACHE;
1826
+ this._index = 0;
1827
+ this._setConfig(config);
1828
+ this._eventCache = this._loadStoredEvents();
1829
+ this._tracker = window['SLS_Tracker'] && new window['SLS_Tracker']({
1830
+ host: config.host || "cn-hangzhou.log.aliyuncs.com",
1831
+ project: config.project,
1832
+ logstore: config.logstore,
1833
+ time: config.cd,
1834
+ count: config.count, // 定义数据条数,默认是10条,number类型,选填
1835
+ });
1836
+ this._addListen();
1837
+ this._startLoop();
1838
+ this._doReport();
1853
1839
  }
1854
- /**
1855
- * 设置此对象的指定属性值。
1856
- * @param caller 执行域(this)。
1857
- * @param method 回调方法。
1858
- * @param args 携带的参数。
1859
- * @param once 是否只执行一次,如果为true,执行后执行recover()进行回收。
1860
- * @return 返回 handler 本身。
1861
- */
1862
- setTo(caller, method, args, once = false) {
1863
- this._id = Handler._gid++;
1864
- this.caller = caller;
1865
- this.method = method;
1866
- this.args = args;
1867
- this.once = once;
1868
- return this;
1840
+ _setConfig(config) {
1841
+ config.cd = config.cd || 2;
1842
+ config.count = config.count || 5;
1843
+ this._config = config;
1869
1844
  }
1870
- /**
1871
- * 执行处理器。
1872
- */
1873
- run() {
1874
- if (this.method == null)
1875
- return null;
1876
- var id = this._id;
1877
- var result = this.method.apply(this.caller, this.args);
1878
- this._id === id && this.once && this.recover();
1879
- return result;
1880
- }
1881
- /**
1882
- * 执行处理器,并携带额外数据。
1883
- * @param data 附加的回调数据,可以是单数据或者Array(作为多参)。
1884
- */
1885
- runWith(data) {
1886
- if (this.method == null)
1887
- return null;
1888
- var id = this._id;
1889
- if (data == null)
1890
- var result = this.method.apply(this.caller, this.args);
1891
- else if (!this.args && !data.unshift)
1892
- result = this.method.call(this.caller, data);
1893
- else if (this.args)
1894
- result = this.method.apply(this.caller, this.args.concat(data));
1895
- else
1896
- result = this.method.apply(this.caller, data);
1897
- this._id === id && this.once && this.recover();
1898
- return result;
1845
+ _loadStoredEvents() {
1846
+ const storedValue = storage.getGValue(this._storageKey, []);
1847
+ const events = Array.isArray(storedValue) ? storedValue : [];
1848
+ if (events.length > 0) {
1849
+ storage.removeGValue(this._storageKey);
1850
+ }
1851
+ return events;
1899
1852
  }
1900
- /**
1901
- * 清理对象引用。
1902
- */
1903
- clear() {
1904
- this.caller = null;
1905
- this.method = null;
1906
- this.args = null;
1907
- return this;
1853
+ _storeEvents(events) {
1854
+ if (!events.length) {
1855
+ storage.removeGValue(this._storageKey);
1856
+ return;
1857
+ }
1858
+ storage.setGValue(this._storageKey, events);
1908
1859
  }
1909
- reset() {
1910
- this.clear();
1860
+ _startLoop() {
1861
+ timer.loop((this._config.cd || 2) * 1000, this, this._doReport, [this._config.count]);
1911
1862
  }
1912
- destroy() {
1913
- this.clear();
1863
+ _addListen() {
1864
+ App.onHide(() => {
1865
+ this._doReport(this._eventCache.length, true);
1866
+ });
1867
+ App.onShow(this._restoreEvents, this);
1914
1868
  }
1915
- /**
1916
- * 清理并回收到 Handler 对象池内。
1917
- */
1918
- recover() {
1919
- if (this._id > 0) {
1920
- this._id = 0;
1921
- Handler._pool.recover(this);
1869
+ _restoreEvents() {
1870
+ const eventsInLocalStore = this._loadStoredEvents();
1871
+ if (eventsInLocalStore.length > 0) {
1872
+ this._eventCache.unshift(...eventsInLocalStore);
1873
+ console.log(`[ALiAnalyticsSender] 恢复本地events(length:${eventsInLocalStore.length}, total:${this._eventCache.length})`);
1922
1874
  }
1923
1875
  }
1924
- equal(value) {
1925
- return value === this;
1876
+ _doReport(maxCount = 5, store = false) {
1877
+ if (this._eventCache.length <= 0) {
1878
+ return false;
1879
+ }
1880
+ if (!this._tracker) {
1881
+ return false;
1882
+ }
1883
+ const events = this._eventCache.splice(0, Math.min(this._eventCache.length, maxCount));
1884
+ try {
1885
+ this._tracker.sendBatchLogsImmediate(events);
1886
+ return true;
1887
+ }
1888
+ catch (e) {
1889
+ if (!store) {
1890
+ this._eventCache.unshift(...events);
1891
+ console.log(`[ALiAnalyticsSender] 事件发送失败,恢复事件到队列,等待下次发送。`);
1892
+ }
1893
+ else {
1894
+ this._storeEvents([...events, ...this._eventCache]);
1895
+ this._eventCache.length = 0;
1896
+ console.log(`[ALiAnalyticsSender] 事件发送失败,存储到缓存,等待下次切回游戏。`);
1897
+ }
1898
+ return false;
1899
+ }
1926
1900
  }
1927
- /**
1928
- * 从对象池内创建一个Handler,默认会执行一次并立即回收,如果不需要自动回收,设置once参数为false。
1929
- * @param caller 执行域(this)
1930
- * @param method 回调方法。
1931
- * @param args 携带的参数。
1932
- * @param once 是否只执行一次,如果为true,回调后执行recover()进行回收,默认为true。
1933
- * @return 返回创建的handler实例。
1934
- */
1935
- static create(caller, method, args = null, once = true) {
1936
- return Handler._pool.allocate().setTo(caller, method, args, once);
1901
+ send(eventName, data) {
1902
+ return __awaiter(this, void 0, void 0, function* () {
1903
+ const payload = data && typeof data === "object" && !Array.isArray(data)
1904
+ ? Object.assign({}, data) : { value: data };
1905
+ payload['_event'] = eventName;
1906
+ let msg_data = {
1907
+ appid: publisher.appId,
1908
+ openid: storage.userId || publisher.login.openID || "",
1909
+ deviceid: App.deviceId,
1910
+ sessionid: App.sessionId,
1911
+ level: ++this._index,
1912
+ data: JSON.stringify(payload),
1913
+ event: eventName,
1914
+ timestamp: this._config.timestampCall() || Date.now()
1915
+ };
1916
+ this._eventCache.push(msg_data);
1917
+ });
1937
1918
  }
1938
1919
  }
1939
- /**@private handler对象池*/
1940
- Handler._pool = new Pool(Handler);
1941
- /**@private */
1942
- Handler._gid = 1;
1943
1920
 
1944
- /**
1945
- * <code>EventDispatcher</code> 类是可调度事件的所有类的基类。
1946
- */
1947
- class EventDispatcher {
1948
- //[IF-JS]Object.defineProperty(EventDispatcher.prototype, "_events", {enumerable: false,writable:true});
1949
- /**
1950
- * 检查 EventDispatcher 对象是否为特定事件类型注册了任何侦听器。
1951
- * @param type 事件的类型。
1952
- * @return 如果指定类型的侦听器已注册,则值为 true;否则,值为 false。
1953
- */
1954
- hasListener(type) {
1955
- var listener = this._events && this._events[type];
1956
- return !!listener;
1921
+ var DimensionType;
1922
+ (function (DimensionType) {
1923
+ DimensionType[DimensionType["ACTIVITY"] = 0] = "ACTIVITY";
1924
+ DimensionType[DimensionType["AD"] = 1] = "AD";
1925
+ DimensionType[DimensionType["PAYMENT"] = 2] = "PAYMENT";
1926
+ DimensionType[DimensionType["SOCIAL"] = 3] = "SOCIAL";
1927
+ DimensionType[DimensionType["PLAYER_ABILITY"] = 4] = "PLAYER_ABILITY";
1928
+ DimensionType[DimensionType["LEVEL_STATS"] = 5] = "LEVEL_STATS";
1929
+ })(DimensionType || (DimensionType = {}));
1930
+
1931
+ class BaseDimension {
1932
+ constructor(name) {
1933
+ this._data = {};
1934
+ this._initialized = false;
1935
+ this._dirty = false;
1936
+ this._name = name;
1957
1937
  }
1958
- /**
1959
- * 派发事件。
1960
- * @param type 事件类型。
1961
- * @param data (可选)回调数据。<b>注意:</b>如果是需要传递多个参数 p1,p2,p3,...可以使用数组结构如:[p1,p2,p3,...] ;如果需要回调单个参数 p ,且 p 是一个数组,则需要使用结构如:[p],其他的单个参数 p ,可以直接传入参数 p。
1962
- * @return 此事件类型是否有侦听者,如果有侦听者则值为 true,否则值为 false。
1963
- */
1964
- event(type, data = null) {
1965
- if (!this._events || !this._events[type])
1966
- return false;
1967
- var listeners = this._events[type];
1968
- if (listeners.run) {
1969
- if (listeners.once)
1970
- delete this._events[type];
1971
- data != null ? listeners.runWith(data) : listeners.run();
1938
+ get name() {
1939
+ return this._name;
1940
+ }
1941
+ _ensureInit() {
1942
+ if (!this._initialized) {
1943
+ this._loadFromStorage();
1944
+ this._initialized = true;
1972
1945
  }
1973
- else {
1974
- for (var i = 0, n = listeners.length; i < n; i++) {
1975
- var listener = listeners[i];
1976
- if (listener) {
1977
- (data != null) ? listener.runWith(data) : listener.run();
1978
- }
1979
- if (!listener || listener.once) {
1980
- listeners.splice(i, 1);
1981
- i--;
1982
- n--;
1983
- }
1946
+ }
1947
+ _loadFromStorage() {
1948
+ const key = `dim_${this._name}`;
1949
+ const data = storage.getUValue(key, "");
1950
+ if (data) {
1951
+ try {
1952
+ this._data = this._decompress(data);
1953
+ }
1954
+ catch (e) {
1955
+ console.error(`加载维度${this._name}失败:`, e);
1956
+ this._data = {};
1984
1957
  }
1985
- if (listeners.length === 0 && this._events)
1986
- delete this._events[type];
1987
1958
  }
1988
- return true;
1989
- }
1990
- /**
1991
- * 使用 EventDispatcher 对象注册指定类型的事件侦听器对象,以使侦听器能够接收事件通知。
1992
- * @param type 事件的类型。
1993
- * @param caller 事件侦听函数的执行域。
1994
- * @param listener 事件侦听函数。
1995
- * @param args (可选)事件侦听函数的回调参数。
1996
- * @return 此 EventDispatcher 对象。
1997
- */
1998
- on(type, caller, listener, args = null) {
1999
- return this._createListener(type, caller, listener, args, false);
2000
1959
  }
2001
- /**
2002
- * 使用 EventDispatcher 对象注册指定类型的事件侦听器对象,以使侦听器能够接收事件通知,此侦听事件响应一次后自动移除。
2003
- * @param type 事件的类型。
2004
- * @param caller 事件侦听函数的执行域。
2005
- * @param listener 事件侦听函数。
2006
- * @param args (可选)事件侦听函数的回调参数。
2007
- * @return 此 EventDispatcher 对象。
2008
- */
2009
- once(type, caller, listener, args = null) {
2010
- return this._createListener(type, caller, listener, args, true);
1960
+ _saveToStorage() {
1961
+ return __awaiter(this, void 0, void 0, function* () {
1962
+ if (!this._dirty)
1963
+ return;
1964
+ const key = `dim_${this._name}`;
1965
+ const compressed = this._compress(this._data);
1966
+ storage.setUValue(key, compressed);
1967
+ this._dirty = false;
1968
+ });
2011
1969
  }
2012
- /**@internal */
2013
- _createListener(type, caller, listener, args, once, offBefore = true) {
2014
- //移除之前相同的监听
2015
- offBefore && this.off(type, caller, listener, once);
2016
- //使用对象池进行创建回收
2017
- var handler = Handler.create(caller || this, listener, args, once);
2018
- this._events || (this._events = {});
2019
- var events = this._events;
2020
- //默认单个,每个对象只有多个监听才用数组,节省一个数组的消耗
2021
- if (!events[type])
2022
- events[type] = handler;
2023
- else {
2024
- if (!events[type].run)
2025
- events[type].push(handler);
2026
- else
2027
- events[type] = [events[type], handler];
2028
- }
2029
- return this;
1970
+ _updateValue(key, value) {
1971
+ this._ensureInit();
1972
+ this._data[key] = value;
1973
+ this._dirty = true;
1974
+ this._saveToStorage();
2030
1975
  }
2031
- /**
2032
- * 从 EventDispatcher 对象中删除侦听器。
1976
+ _incrementValue(key, delta = 1) {
1977
+ this._ensureInit();
1978
+ this._data[key] = (this._data[key] || 0) + delta;
1979
+ this._dirty = true;
1980
+ this._saveToStorage();
1981
+ }
1982
+ _getValue(key, defaultValue = 0) {
1983
+ var _a;
1984
+ this._ensureInit();
1985
+ return (_a = this._data[key]) !== null && _a !== void 0 ? _a : defaultValue;
1986
+ }
1987
+ getData() {
1988
+ this._ensureInit();
1989
+ return Object.assign({}, this._data);
1990
+ }
1991
+ _compress(data) {
1992
+ return Object.entries(data)
1993
+ .map(([k, v]) => `${k}:${v}`)
1994
+ .join(',');
1995
+ }
1996
+ _decompress(str) {
1997
+ const data = {};
1998
+ if (!str)
1999
+ return data;
2000
+ str.split(',').forEach(pair => {
2001
+ const [k, v] = pair.split(':');
2002
+ if (k && v !== undefined) {
2003
+ data[k] = Number(v);
2004
+ }
2005
+ });
2006
+ return data;
2007
+ }
2008
+ clear() {
2009
+ this._data = {};
2010
+ this._dirty = true;
2011
+ this._saveToStorage();
2012
+ }
2013
+ }
2014
+
2015
+ var AdDataKey;
2016
+ (function (AdDataKey) {
2017
+ // 累计展示次数
2018
+ AdDataKey["SHOW_COUNT"] = "sc";
2019
+ // 展示天数
2020
+ AdDataKey["SHOW_DAYS"] = "sd";
2021
+ // 累计完成次数
2022
+ AdDataKey["COMPLETE_COUNT"] = "cpc";
2023
+ // 最后展示天数
2024
+ AdDataKey["LAST_SHOW_DAY"] = "lsd";
2025
+ // 今日展示次数
2026
+ AdDataKey["TODAY_SHOW_COUNT"] = "tsc";
2027
+ // 首次展示时间(相对安装时间,秒)
2028
+ AdDataKey["FIRST_SHOW_TIME"] = "fst";
2029
+ // 上次展示时间(相对安装时间,秒)
2030
+ AdDataKey["LAST_SHOW_TIME"] = "lst";
2031
+ // 平均展示间隔(秒)
2032
+ AdDataKey["AVG_SHOW_INTERVAL"] = "asi";
2033
+ // 今日平均展示间隔(秒)
2034
+ AdDataKey["TODAY_AVG_SHOW_INTERVAL"] = "tasi";
2035
+ })(AdDataKey || (AdDataKey = {}));
2036
+ const AD_METRIC_ORDER_V1 = [
2037
+ 'AD.show_count',
2038
+ 'AD.show_days',
2039
+ 'AD.complete_count',
2040
+ 'AD.last_show_day',
2041
+ 'AD.today_show_count',
2042
+ 'AD.first_show_time',
2043
+ 'AD.last_show_time',
2044
+ 'AD.avg_show_interval',
2045
+ 'AD.today_avg_show_interval',
2046
+ 'AD.avg_show_per_day',
2047
+ 'AD.avg_complete_per_day',
2048
+ ];
2049
+ /**
2050
+ * 广告维度,记录广告展示次数、完成次数、展示天数、展示间隔等信息
2051
+ */
2052
+ class AdDimension extends BaseDimension {
2053
+ constructor() {
2054
+ super(DimensionType[DimensionType.AD]);
2055
+ this._todayPrevShowTime = 0; // 内存中记录今日上次展示时间(相对启动),用于计算今日间隔
2056
+ this._checkAndResetDailyData();
2057
+ }
2058
+ /**
2059
+ * 检测并重置今日数据(仅在新的一天的新启动时)
2060
+ */
2061
+ _checkAndResetDailyData() {
2062
+ return __awaiter(this, void 0, void 0, function* () {
2063
+ this._ensureInit();
2064
+ const today = App.daysInstall;
2065
+ const lastDay = this._getValue(AdDataKey.LAST_SHOW_DAY, -1);
2066
+ // 只有在新的一天才重置今日数据
2067
+ if (lastDay !== -1 && lastDay !== today) {
2068
+ this._updateValue(AdDataKey.TODAY_SHOW_COUNT, 0);
2069
+ this._updateValue(AdDataKey.TODAY_AVG_SHOW_INTERVAL, 0);
2070
+ this._todayPrevShowTime = 0;
2071
+ }
2072
+ });
2073
+ }
2074
+ recordShow() {
2075
+ const showCount = this._getValue(AdDataKey.SHOW_COUNT, 0);
2076
+ const prevShowTime = this._getValue(AdDataKey.LAST_SHOW_TIME, 0);
2077
+ this._incrementValue(AdDataKey.SHOW_COUNT);
2078
+ this._incrementValue(AdDataKey.TODAY_SHOW_COUNT);
2079
+ const today = App.daysInstall;
2080
+ const lastDay = this._getValue(AdDataKey.LAST_SHOW_DAY, -1);
2081
+ // 更新展示天数和最后展示天数
2082
+ if (lastDay !== today) {
2083
+ this._incrementValue(AdDataKey.SHOW_DAYS);
2084
+ this._updateValue(AdDataKey.LAST_SHOW_DAY, today);
2085
+ }
2086
+ // 记录当前展示时间(相对安装时间)
2087
+ const currentTime = Math.floor(App.durationOfInstall / 1000);
2088
+ const currentLaunchTime = Math.floor(App.durationOfLaunch / 1000);
2089
+ // 计算总体平均间隔(从第二次展示开始)
2090
+ if (showCount > 0 && prevShowTime > 0) {
2091
+ const interval = currentTime - prevShowTime;
2092
+ const oldAvg = this._getValue(AdDataKey.AVG_SHOW_INTERVAL, 0);
2093
+ // 增量平均公式: 新平均 = 旧平均 + (本次值 - 旧平均) / 新的数据个数
2094
+ const newAvg = Math.floor(oldAvg + (interval - oldAvg) / showCount);
2095
+ this._updateValue(AdDataKey.AVG_SHOW_INTERVAL, newAvg);
2096
+ }
2097
+ // 计算今日平均间隔(从今日第二次展示开始)
2098
+ const todayShowCount = this._getValue(AdDataKey.TODAY_SHOW_COUNT, 0);
2099
+ if (todayShowCount > 1 && this._todayPrevShowTime > 0) {
2100
+ const todayInterval = currentLaunchTime - this._todayPrevShowTime;
2101
+ const oldTodayAvg = this._getValue(AdDataKey.TODAY_AVG_SHOW_INTERVAL, 0);
2102
+ // 今日间隔数 = 今日展示次数 - 1
2103
+ const newTodayAvg = Math.floor(oldTodayAvg + (todayInterval - oldTodayAvg) / (todayShowCount - 1));
2104
+ this._updateValue(AdDataKey.TODAY_AVG_SHOW_INTERVAL, newTodayAvg);
2105
+ }
2106
+ // 记录首次展示时间(仅首次)
2107
+ if (showCount === 0) {
2108
+ this._updateValue(AdDataKey.FIRST_SHOW_TIME, currentTime);
2109
+ }
2110
+ // 更新时间记录
2111
+ this._updateValue(AdDataKey.LAST_SHOW_TIME, currentTime);
2112
+ this._todayPrevShowTime = currentLaunchTime;
2113
+ }
2114
+ recordComplete() {
2115
+ this._incrementValue(AdDataKey.COMPLETE_COUNT);
2116
+ }
2117
+ /**
2118
+ * 获取日均展示次数
2119
+ */
2120
+ get avgShowPerDay() {
2121
+ const total = this._getValue(AdDataKey.SHOW_COUNT, 0);
2122
+ const days = this._getValue(AdDataKey.SHOW_DAYS, 0);
2123
+ return days > 0 ? Math.floor(total / days) : 0;
2124
+ }
2125
+ /**
2126
+ * 获取日均完成次数
2127
+ */
2128
+ get avgCompletePerDay() {
2129
+ const total = this._getValue(AdDataKey.COMPLETE_COUNT, 0);
2130
+ const days = this._getValue(AdDataKey.SHOW_DAYS, 0);
2131
+ return days > 0 ? Math.floor(total / days) : 0;
2132
+ }
2133
+ /**
2134
+ * 获取平均广告展示间隔(秒)
2135
+ * 使用增量平均算法计算相邻两次展示的平均间隔
2136
+ */
2137
+ get avgShowInterval() {
2138
+ return this._getValue(AdDataKey.AVG_SHOW_INTERVAL, 0);
2139
+ }
2140
+ /**
2141
+ * 获取今日平均广告展示间隔(秒)
2142
+ * 使用增量平均算法计算今日相邻两次展示的平均间隔
2143
+ */
2144
+ get todayAvgShowInterval() {
2145
+ return this._getValue(AdDataKey.TODAY_AVG_SHOW_INTERVAL, 0);
2146
+ }
2147
+ getMetrics(order = [
2148
+ AdDataKey.SHOW_COUNT,
2149
+ AdDataKey.SHOW_DAYS,
2150
+ AdDataKey.COMPLETE_COUNT,
2151
+ AdDataKey.LAST_SHOW_DAY,
2152
+ AdDataKey.TODAY_SHOW_COUNT,
2153
+ AdDataKey.FIRST_SHOW_TIME,
2154
+ AdDataKey.LAST_SHOW_TIME,
2155
+ AdDataKey.AVG_SHOW_INTERVAL,
2156
+ AdDataKey.TODAY_AVG_SHOW_INTERVAL
2157
+ ]) {
2158
+ this._ensureInit();
2159
+ const result = [];
2160
+ for (const key of order) {
2161
+ result.push(this._getValue(key, 0));
2162
+ }
2163
+ result.push(this.avgShowPerDay);
2164
+ result.push(this.avgCompletePerDay);
2165
+ return result;
2166
+ }
2167
+ }
2168
+
2169
+ var ADType;
2170
+ (function (ADType) {
2171
+ ADType[ADType["BANNER"] = 0] = "BANNER";
2172
+ ADType[ADType["INTERSTITIAL"] = 1] = "INTERSTITIAL";
2173
+ ADType[ADType["REWARD_VIDEO"] = 2] = "REWARD_VIDEO";
2174
+ ADType[ADType["NATIVE"] = 3] = "NATIVE";
2175
+ })(ADType || (ADType = {}));
2176
+ var ADEvent;
2177
+ (function (ADEvent) {
2178
+ ADEvent[ADEvent["LOAD"] = 0] = "LOAD";
2179
+ ADEvent[ADEvent["LOAD_COMPLETE"] = 1] = "LOAD_COMPLETE";
2180
+ ADEvent[ADEvent["LOAD_FAIL"] = 2] = "LOAD_FAIL";
2181
+ ADEvent[ADEvent["SHOW"] = 3] = "SHOW";
2182
+ ADEvent[ADEvent["SHOW_COMPLETE"] = 4] = "SHOW_COMPLETE";
2183
+ ADEvent[ADEvent["SHOW_FAIL"] = 5] = "SHOW_FAIL";
2184
+ ADEvent[ADEvent["CLOSE_BY_USER"] = 6] = "CLOSE_BY_USER";
2185
+ })(ADEvent || (ADEvent = {}));
2186
+ class ADBehaviorReporter {
2187
+ constructor(baseAble) {
2188
+ this.baseAble = baseAble;
2189
+ this._groupCount = {};
2190
+ }
2191
+ /**
2192
+ * 重置单个模块的广告计数
2193
+ * @param group 模块名称,默认为game
2194
+ */
2195
+ resetGroupCount(group = "game") {
2196
+ this._groupCount[group] = 0;
2197
+ }
2198
+ /**
2199
+ * 开始加载广告
2200
+ * @param type 广告类型
2201
+ * @param from 广告来源
2202
+ * @param msg 广告失败原因
2203
+ */
2204
+ onStartLoad(type, from, msg) {
2205
+ this.report(type, ADEvent.LOAD, from, msg);
2206
+ }
2207
+ /**
2208
+ * 广告加载完成
2209
+ * @param type 广告类型
2210
+ * @param from 广告来源
2211
+ */
2212
+ onLoadComplete(type, from) {
2213
+ this.report(type, ADEvent.LOAD_COMPLETE, from);
2214
+ }
2215
+ /**
2216
+ * 广告加载失败
2217
+ * @param type 广告类型
2218
+ * @param from 广告来源
2219
+ * @param msg 广告失败原因
2220
+ */
2221
+ onLoadFail(type, from, msg) {
2222
+ this.report(type, ADEvent.LOAD_FAIL, from, msg);
2223
+ }
2224
+ /**
2225
+ * 显示广告
2226
+ * @param type 广告类型
2227
+ * @param from 广告来源
2228
+ */
2229
+ onShow(type, from) {
2230
+ this.report(type, ADEvent.SHOW, from);
2231
+ }
2232
+ /**
2233
+ * 广告显示完成
2234
+ * @param type 广告类型
2235
+ * @param from 广告来源
2236
+ */
2237
+ onShowComplete(type, from) {
2238
+ this.report(type, ADEvent.SHOW_COMPLETE, from);
2239
+ }
2240
+ /**
2241
+ * 广告显示失败
2242
+ * @param type 广告类型
2243
+ * @param from 广告来源
2244
+ * @param msg 广告失败原因
2245
+ */
2246
+ onShowFail(type, from, msg) {
2247
+ this.report(type, ADEvent.SHOW_FAIL, from, msg);
2248
+ }
2249
+ /**
2250
+ * 用户关闭广告
2251
+ * @param type 广告类型
2252
+ * @param from 广告来源
2253
+ */
2254
+ onCloseByUser(type, from) {
2255
+ this.report(type, ADEvent.CLOSE_BY_USER, from);
2256
+ }
2257
+ /**
2258
+ * 上报广告事件
2259
+ * @param type 广告类型
2260
+ * @param event 广告事件
2261
+ * @param from 广告来源
2262
+ * @param msg 广告失败原因
2263
+ */
2264
+ report(type, event, from, msg) {
2265
+ from = from || {};
2266
+ const group = from.group || "game";
2267
+ from.group && delete from.group;
2268
+ from._adtype = type;
2269
+ from._adevent = event;
2270
+ if (!this._groupCount[group]) {
2271
+ this._groupCount[group] = 0;
2272
+ }
2273
+ from.count = event == ADEvent.SHOW_COMPLETE ? ++this._groupCount[group] : this._groupCount[group];
2274
+ if (msg) {
2275
+ from.msg = msg;
2276
+ }
2277
+ this.baseAble.report("ad", from);
2278
+ }
2279
+ }
2280
+
2281
+ /**
2282
+ * <code>EventDispatcher</code> 类是可调度事件的所有类的基类。
2283
+ */
2284
+ class EventDispatcher {
2285
+ //[IF-JS]Object.defineProperty(EventDispatcher.prototype, "_events", {enumerable: false,writable:true});
2286
+ /**
2287
+ * 检查 EventDispatcher 对象是否为特定事件类型注册了任何侦听器。
2288
+ * @param type 事件的类型。
2289
+ * @return 如果指定类型的侦听器已注册,则值为 true;否则,值为 false。
2290
+ */
2291
+ hasListener(type) {
2292
+ var listener = this._events && this._events[type];
2293
+ return !!listener;
2294
+ }
2295
+ /**
2296
+ * 派发事件。
2297
+ * @param type 事件类型。
2298
+ * @param data (可选)回调数据。<b>注意:</b>如果是需要传递多个参数 p1,p2,p3,...可以使用数组结构如:[p1,p2,p3,...] ;如果需要回调单个参数 p ,且 p 是一个数组,则需要使用结构如:[p],其他的单个参数 p ,可以直接传入参数 p。
2299
+ * @return 此事件类型是否有侦听者,如果有侦听者则值为 true,否则值为 false。
2300
+ */
2301
+ event(type, data = null) {
2302
+ if (!this._events || !this._events[type])
2303
+ return false;
2304
+ var listeners = this._events[type];
2305
+ if (listeners.run) {
2306
+ if (listeners.once)
2307
+ delete this._events[type];
2308
+ data != null ? listeners.runWith(data) : listeners.run();
2309
+ }
2310
+ else {
2311
+ for (var i = 0, n = listeners.length; i < n; i++) {
2312
+ var listener = listeners[i];
2313
+ if (listener) {
2314
+ (data != null) ? listener.runWith(data) : listener.run();
2315
+ }
2316
+ if (!listener || listener.once) {
2317
+ listeners.splice(i, 1);
2318
+ i--;
2319
+ n--;
2320
+ }
2321
+ }
2322
+ if (listeners.length === 0 && this._events)
2323
+ delete this._events[type];
2324
+ }
2325
+ return true;
2326
+ }
2327
+ /**
2328
+ * 使用 EventDispatcher 对象注册指定类型的事件侦听器对象,以使侦听器能够接收事件通知。
2329
+ * @param type 事件的类型。
2330
+ * @param caller 事件侦听函数的执行域。
2331
+ * @param listener 事件侦听函数。
2332
+ * @param args (可选)事件侦听函数的回调参数。
2333
+ * @return 此 EventDispatcher 对象。
2334
+ */
2335
+ on(type, caller, listener, args = null) {
2336
+ return this._createListener(type, caller, listener, args, false);
2337
+ }
2338
+ /**
2339
+ * 使用 EventDispatcher 对象注册指定类型的事件侦听器对象,以使侦听器能够接收事件通知,此侦听事件响应一次后自动移除。
2340
+ * @param type 事件的类型。
2341
+ * @param caller 事件侦听函数的执行域。
2342
+ * @param listener 事件侦听函数。
2343
+ * @param args (可选)事件侦听函数的回调参数。
2344
+ * @return 此 EventDispatcher 对象。
2345
+ */
2346
+ once(type, caller, listener, args = null) {
2347
+ return this._createListener(type, caller, listener, args, true);
2348
+ }
2349
+ /**@internal */
2350
+ _createListener(type, caller, listener, args, once, offBefore = true) {
2351
+ //移除之前相同的监听
2352
+ offBefore && this.off(type, caller, listener, once);
2353
+ //使用对象池进行创建回收
2354
+ var handler = Handler.create(caller || this, listener, args, once);
2355
+ this._events || (this._events = {});
2356
+ var events = this._events;
2357
+ //默认单个,每个对象只有多个监听才用数组,节省一个数组的消耗
2358
+ if (!events[type])
2359
+ events[type] = handler;
2360
+ else {
2361
+ if (!events[type].run)
2362
+ events[type].push(handler);
2363
+ else
2364
+ events[type] = [events[type], handler];
2365
+ }
2366
+ return this;
2367
+ }
2368
+ /**
2369
+ * 从 EventDispatcher 对象中删除侦听器。
2033
2370
  * @param type 事件的类型。
2034
2371
  * @param caller 事件侦听函数的执行域。
2035
2372
  * @param listener 事件侦听函数。
@@ -5367,983 +5704,708 @@ class Socket extends EventDispatcher {
5367
5704
  this._onError(e);
5368
5705
  };
5369
5706
  }
5370
- /**
5371
- * 清理Socket:关闭Socket链接,关闭事件监听,重置Socket
5372
- */
5373
- cleanSocket() {
5374
- this.close();
5375
- this._connected = false;
5376
- this._socket.onopen = null;
5377
- this._socket.onmessage = null;
5378
- this._socket.onclose = null;
5379
- this._socket.onerror = null;
5380
- this._socket = null;
5381
- }
5382
- /**
5383
- * 关闭连接。
5384
- */
5385
- close(code, reason) {
5386
- if (this._socket != null) {
5387
- let event = null;
5388
- try {
5389
- this._socket.close(code, reason);
5390
- }
5391
- catch (e) {
5392
- event = e;
5393
- }
5394
- return event == null;
5395
- }
5396
- }
5397
- /**
5398
- * @private
5399
- * 连接建立成功 。
5400
- */
5401
- _onOpen(e) {
5402
- this._connected = true;
5403
- this.event(Event$2.OPEN, e);
5404
- }
5405
- /**
5406
- * @private
5407
- * 接收到数据处理方法。
5408
- * @param msg 数据。
5409
- */
5410
- _onMessage(msg) {
5411
- if (!msg || !msg.data)
5412
- return;
5413
- var data = msg.data;
5414
- if (this.disableInput && data) {
5415
- let byte = new ByteView(data);
5416
- byte.endian = this.endian;
5417
- this.event(Event$2.MESSAGE, byte);
5418
- return;
5419
- }
5420
- if (this._input.length > 0 && this._input.bytesAvailable < 1) {
5421
- this._input.clear();
5422
- this._addInputPosition = 0;
5423
- }
5424
- var pre = this._input.pos;
5425
- !this._addInputPosition && (this._addInputPosition = 0);
5426
- this._input.pos = this._addInputPosition;
5427
- if (data) {
5428
- if (typeof (data) == 'string') {
5429
- this._input.writeUTFBytes(data);
5430
- }
5431
- else {
5432
- this._input.writeArrayBuffer(data);
5433
- }
5434
- this._addInputPosition = this._input.pos;
5435
- this._input.pos = pre;
5436
- }
5437
- this.event(Event$2.MESSAGE, data);
5438
- }
5439
- /**
5440
- * @private
5441
- * 连接被关闭处理方法。
5442
- */
5443
- _onClose(e) {
5444
- this._connected = false;
5445
- this.event(Event$2.CLOSE, e);
5446
- }
5447
- /**
5448
- * @private
5449
- * 出现异常处理方法。
5450
- */
5451
- _onError(e) {
5452
- this.event(Event$2.ERROR, e);
5453
- }
5454
- /**
5455
- * 发送数据到服务器。
5456
- * @param data 需要发送的数据,可以是String或者ArrayBuffer。
5457
- */
5458
- send(data) {
5459
- if (this._socket && this._socket.readyState === WebSocket.CONNECTING) {
5460
- this._socket.send(data);
5461
- return true;
5462
- }
5463
- return false;
5464
- }
5465
- /**
5466
- * 发送缓冲区中的数据到服务器。
5467
- */
5468
- flush() {
5469
- if (this._output && this._output.length > 0) {
5470
- var evt;
5471
- try {
5472
- this._socket && this._socket.send(this._output._getBuffer().slice(0, this._output.length));
5473
- }
5474
- catch (e) {
5475
- evt = e;
5476
- }
5477
- this._output.endian = this.endian;
5478
- this._output.clear();
5479
- if (evt)
5480
- this.event(Event$2.ERROR, evt);
5481
- }
5482
- }
5483
- get state() { return this._socket ? this._socket.readyState : WebSocket.CLOSED; }
5484
- get url() { return this._socket ? this._socket.url : ""; }
5485
- get binaryType() { return this._socket ? this._socket.binaryType : "arraybuffer"; }
5486
- }
5487
- /**
5488
- * <p>主机字节序,是 CPU 存放数据的两种不同顺序,包括小端字节序和大端字节序。</p>
5489
- * <p> LITTLE_ENDIAN :小端字节序,地址低位存储值的低位,地址高位存储值的高位。</p>
5490
- * <p> BIG_ENDIAN :大端字节序,地址低位存储值的高位,地址高位存储值的低位。有时也称之为网络字节序。</p>
5491
- */
5492
- Socket.LITTLE_ENDIAN = "littleEndian";
5493
- /**
5494
- * <p>主机字节序,是 CPU 存放数据的两种不同顺序,包括小端字节序和大端字节序。</p>
5495
- * <p> BIG_ENDIAN :大端字节序,地址低位存储值的高位,地址高位存储值的低位。有时也称之为网络字节序。</p>
5496
- * <p> LITTLE_ENDIAN :小端字节序,地址低位存储值的低位,地址高位存储值的高位。</p>
5497
- */
5498
- Socket.BIG_ENDIAN = "bigEndian";
5499
-
5500
- var SocketLogType;
5501
- (function (SocketLogType) {
5502
- SocketLogType["Connected"] = "[C==S]";
5503
- SocketLogType["C2S"] = "[C=>S]";
5504
- SocketLogType["S2C"] = "[S=>C]";
5505
- SocketLogType["S2xC"] = "[S=xC]";
5506
- SocketLogType["CxS"] = "[CxS]";
5507
- })(SocketLogType || (SocketLogType = {}));
5508
- /**
5509
- * Socket节点,实现断线重连、心跳等机制
5510
- */
5511
- class SocketNode {
5512
- constructor() {
5513
- this._socket = null;
5514
- this._protocolHelper = null;
5515
- this._networkTips = null;
5516
- this._reconnectTimes = 3;
5517
- this._loger = null;
5518
- this._reconnectCD = 3 * 1000;
5519
- this._reconnectCount = null;
5520
- this._heartbeatID = 0;
5521
- this._heartbeatCD = 5 * 1000;
5522
- }
5523
- init(socket, protocolHelper, networkTips) {
5524
- if (this._socket)
5525
- return;
5526
- this._socket = socket;
5527
- this._protocolHelper = protocolHelper;
5528
- this._networkTips = networkTips;
5529
- }
5530
- _addEvent() {
5531
- this._socket.on(Event$2.OPEN, this, this._onConnected);
5532
- this._socket.on(Event$2.MESSAGE, this, this._onMessage);
5533
- this._socket.on(Event$2.ERROR, this, this._onError);
5534
- this._socket.on(Event$2.CLOSE, this, this._onClosed);
5535
- }
5536
- _removeEvent() {
5537
- this._socket.off(Event$2.OPEN, this, this._onConnected);
5538
- this._socket.off(Event$2.MESSAGE, this, this._onMessage);
5539
- this._socket.off(Event$2.ERROR, this, this._onError);
5540
- this._socket.off(Event$2.CLOSE, this, this._onClosed);
5541
- }
5542
- /**
5543
- * 连接服务器
5544
- * @param url 服务器地址 wss://localhost:80
5545
- * @param binaryType BinaryType "arraybuffer" | "blob"
5546
- * @param reconnectTimes 重连次数 -1: 一直尝试重连 0: 不重连 >0: 重连尝试次数
5547
- * @returns
5548
- */
5549
- connect(url, binaryType = "arraybuffer", reconnectTimes = 3) {
5550
- this._removeEvent();
5551
- this._addEvent();
5552
- this._networkTips.onConnecting();
5553
- this._reconnectTimes = reconnectTimes;
5554
- this._socket.connectByUrl(url, binaryType);
5707
+ /**
5708
+ * 清理Socket:关闭Socket链接,关闭事件监听,重置Socket
5709
+ */
5710
+ cleanSocket() {
5711
+ this.close();
5712
+ this._connected = false;
5713
+ this._socket.onopen = null;
5714
+ this._socket.onmessage = null;
5715
+ this._socket.onclose = null;
5716
+ this._socket.onerror = null;
5717
+ this._socket = null;
5555
5718
  }
5556
5719
  /**
5557
- * 发送数据
5558
- * @returns
5720
+ * 关闭连接。
5559
5721
  */
5560
- flush() {
5561
- if (this._socket.connected) {
5562
- this._networkTips.onFlush();
5722
+ close(code, reason) {
5723
+ if (this._socket != null) {
5724
+ let event = null;
5563
5725
  try {
5564
- this._socket.flush();
5565
- return true;
5726
+ this._socket.close(code, reason);
5566
5727
  }
5567
5728
  catch (e) {
5568
- return false;
5729
+ event = e;
5569
5730
  }
5570
- }
5571
- return false;
5572
- }
5573
- log(type, data) {
5574
- if (this._loger) {
5575
- this._loger(type, typeof data === 'string' ? data : `ID:${data.id} Len:${data.len} Data:${JSON.stringify(data.data)}`);
5731
+ return event == null;
5576
5732
  }
5577
5733
  }
5578
5734
  /**
5579
- * 关闭连接
5580
- * @param code
5581
- * @param reason
5735
+ * @private
5736
+ * 连接建立成功
5582
5737
  */
5583
- close(code, reason) {
5584
- this._cleanupTimeout();
5585
- this._removeEvent();
5586
- this._socket.close(code, reason);
5587
- this.log(SocketLogType.CxS, `${this._socket.url} By Client`);
5588
- }
5589
- _onConnected(event) {
5590
- this.log(SocketLogType.Connected, this._socket.url);
5591
- this._reconnectCount = null;
5592
- if (this._socket.state == WebSocket.OPEN) {
5593
- this._networkTips.onConnectSuccess();
5594
- }
5595
- else {
5596
- this._onError(event);
5597
- }
5598
- }
5599
- _onMessage(data) {
5600
- this._protocolHelper.decode(this._socket.disableInput ? data : this._socket.input);
5601
- this._networkTips.onMessage();
5602
- this._socket.input.clear();
5603
- this._heartbeat(); //重置心跳
5604
- }
5605
- _onError(event) {
5606
- this._networkTips.onConnectFailed();
5607
- this.log(SocketLogType.CxS, `${this._socket.url}`);
5608
- this._retry();
5609
- }
5610
- _onClosed(event) {
5611
- this._networkTips.onClosed();
5612
- this.log(SocketLogType.CxS, `${this._socket.url} Code:${event.code} Reason:${event.reason} WasClean:${event.wasClean}`);
5613
- this._retry();
5614
- }
5615
- _retry() {
5616
- if (this._reconnectTimes != 0) {
5617
- if (this._reconnectCount == null) {
5618
- this._reconnectCount = this._reconnectTimes == -1 ? 9999 : this._reconnectTimes + 1;
5619
- }
5620
- if (--this._reconnectCount > 0) {
5621
- this._reconnectHandler = setTimeout(() => {
5622
- this.connect(this._socket.url, this._socket.binaryType, this._reconnectTimes);
5623
- }, this._reconnectCD);
5624
- }
5625
- }
5626
- }
5627
- _heartbeat() {
5628
- this._cleanupTimeout();
5629
- this._keepAliveHandler = setTimeout(() => {
5630
- this._protocolHelper.heartbeat();
5631
- }, this._heartbeatCD);
5632
- }
5633
- _cleanupTimeout() {
5634
- clearTimeout(this._keepAliveHandler);
5635
- clearTimeout(this._reconnectHandler);
5636
- }
5637
- set logable(value) {
5638
- this._loger = value ? console.log : null;
5639
- }
5640
- get reconnectCD() {
5641
- return this._reconnectCD;
5642
- }
5643
- set reconnectCD(value) {
5644
- this._reconnectCD = value;
5645
- }
5646
- get heartbeatCD() {
5647
- return this._heartbeatCD;
5648
- }
5649
- set heartbeatCD(value) {
5650
- this._heartbeatCD = value;
5651
- }
5652
- get output() { return this._socket.output; }
5653
- ;
5654
- get input() { return this._socket.input; }
5655
- ;
5656
- }
5657
-
5658
- class NetManager {
5659
- constructor() {
5660
- this._logable = false;
5661
- this._socket = null;
5662
- this._http = null;
5663
- if (NetManager.created) {
5664
- throw new Error("NetManager 是单例");
5665
- }
5666
- NetManager.created = true;
5667
- }
5668
- initSocket() {
5669
- if (this._socket)
5670
- return;
5671
- this._protocol = Injector.getInject(NetManager.PROTOCOL_HELPER_KEY);
5672
- this._netTips = Injector.getInject(NetManager.NETWORK_TIPS_KEY);
5673
- this._socket = new SocketNode();
5674
- let socket = new Socket();
5675
- socket.disableInput = true;
5676
- // socket.protocols = [];
5677
- // socket.endian = Socket.BIG_ENDIAN;
5678
- this._socket.init(socket, this._protocol, this._netTips);
5679
- this._socket.logable = this._logable;
5738
+ _onOpen(e) {
5739
+ this._connected = true;
5740
+ this.event(Event$2.OPEN, e);
5680
5741
  }
5681
- initHttp() {
5682
- if (this._http)
5742
+ /**
5743
+ * @private
5744
+ * 接收到数据处理方法。
5745
+ * @param msg 数据。
5746
+ */
5747
+ _onMessage(msg) {
5748
+ if (!msg || !msg.data)
5683
5749
  return;
5684
- this._http = new HttpNode();
5685
- }
5686
- set logable(value) {
5687
- this._logable = value;
5688
- if (this._socket) {
5689
- this._socket.logable = value;
5690
- }
5691
- }
5692
- get socket() {
5693
- this.initSocket();
5694
- return this._socket;
5695
- }
5696
- get http() {
5697
- this.initHttp();
5698
- return this._http;
5699
- }
5700
- get server() {
5701
- this.initSocket();
5702
- return this._protocol;
5703
- }
5704
- }
5705
- NetManager.PROTOCOL_HELPER_KEY = "net.protocolHelper";
5706
- NetManager.NETWORK_TIPS_KEY = "net.networkTips";
5707
- NetManager.created = false;
5708
- /**
5709
- * 网络
5710
- */
5711
- const net = new NetManager();
5712
-
5713
- const KEYTOPS_ANALYTICS_EVENT_CACHE = "KEYTOPS_ANALYTICS_EVENT_CACHE";
5714
- const KEYTOPS_RETRY_DELAYS = [100, 500, 2000];
5715
- const KEYTOPS_REQUEST_TIMEOUT = 10000;
5716
- class KeytopsAnalyticsSender extends EmptyAnalyticsSender {
5717
- constructor(config, exclude) {
5718
- super(exclude);
5719
- this._sending = false;
5720
- this._flushScheduled = false;
5721
- this._eventCache = [];
5722
- this._index = 0;
5723
- this._index = 0;
5724
- this._setConfig(config);
5725
- this._storageKey = this._config.cacheKey || KEYTOPS_ANALYTICS_EVENT_CACHE;
5726
- this._eventCache = this._loadStoredEvents();
5727
- this._addListen();
5728
- this._startLoop();
5729
- void this._doReport();
5730
- }
5731
- _setConfig(config) {
5732
- config.cd = config.cd || 1;
5733
- config.count = config.count || 20;
5734
- config.maxBufferedBytes = config.maxBufferedBytes || 512 * 1024;
5735
- this._config = config;
5736
- }
5737
- _loadStoredEvents() {
5738
- const storedValue = storage.getGValue(this._storageKey, []);
5739
- const events = Array.isArray(storedValue) ? storedValue : [];
5740
- if (events.length > 0) {
5741
- storage.removeGValue(this._storageKey);
5742
- }
5743
- return events;
5744
- }
5745
- _storeEvents(events) {
5746
- if (!events.length) {
5747
- storage.removeGValue(this._storageKey);
5750
+ var data = msg.data;
5751
+ if (this.disableInput && data) {
5752
+ let byte = new ByteView(data);
5753
+ byte.endian = this.endian;
5754
+ this.event(Event$2.MESSAGE, byte);
5748
5755
  return;
5749
5756
  }
5750
- storage.setGValue(this._storageKey, events);
5751
- }
5752
- _estimateBytes(value) {
5753
- try {
5754
- const text = JSON.stringify(value) || "";
5755
- return encodeURIComponent(text).replace(/%[A-F\d]{2}/g, "U").length;
5757
+ if (this._input.length > 0 && this._input.bytesAvailable < 1) {
5758
+ this._input.clear();
5759
+ this._addInputPosition = 0;
5756
5760
  }
5757
- catch (error) {
5758
- return 0;
5761
+ var pre = this._input.pos;
5762
+ !this._addInputPosition && (this._addInputPosition = 0);
5763
+ this._input.pos = this._addInputPosition;
5764
+ if (data) {
5765
+ if (typeof (data) == 'string') {
5766
+ this._input.writeUTFBytes(data);
5767
+ }
5768
+ else {
5769
+ this._input.writeArrayBuffer(data);
5770
+ }
5771
+ this._addInputPosition = this._input.pos;
5772
+ this._input.pos = pre;
5759
5773
  }
5774
+ this.event(Event$2.MESSAGE, data);
5760
5775
  }
5761
- _currentBufferedBytes() {
5762
- return this._estimateBytes(this._eventCache);
5763
- }
5764
- _startLoop() {
5765
- timer.loop((this._config.cd || 1) * 1000, this, this._doReport, [this._config.count], true);
5766
- }
5767
- _addListen() {
5768
- App.onHide(() => {
5769
- void this._doReport(this._eventCache.length, true);
5770
- });
5771
- App.onShow(this._restoreEvents, this);
5776
+ /**
5777
+ * @private
5778
+ * 连接被关闭处理方法。
5779
+ */
5780
+ _onClose(e) {
5781
+ this._connected = false;
5782
+ this.event(Event$2.CLOSE, e);
5772
5783
  }
5773
- _restoreEvents() {
5774
- const eventsInLocalStore = this._loadStoredEvents();
5775
- if (eventsInLocalStore.length > 0) {
5776
- this._eventCache.unshift(...eventsInLocalStore);
5777
- console.log(`[KeytopsAnalyticsSender] 恢复本地events(length:${eventsInLocalStore.length}, total:${this._eventCache.length})`);
5778
- this._scheduleFlush(0);
5784
+ /**
5785
+ * @private
5786
+ * 出现异常处理方法。
5787
+ */
5788
+ _onError(e) {
5789
+ this.event(Event$2.ERROR, e);
5790
+ }
5791
+ /**
5792
+ * 发送数据到服务器。
5793
+ * @param data 需要发送的数据,可以是String或者ArrayBuffer。
5794
+ */
5795
+ send(data) {
5796
+ if (this._socket && this._socket.readyState === WebSocket.CONNECTING) {
5797
+ this._socket.send(data);
5798
+ return true;
5779
5799
  }
5800
+ return false;
5780
5801
  }
5781
- _scheduleFlush(delay = 0) {
5782
- if (this._flushScheduled) {
5783
- return;
5802
+ /**
5803
+ * 发送缓冲区中的数据到服务器。
5804
+ */
5805
+ flush() {
5806
+ if (this._output && this._output.length > 0) {
5807
+ var evt;
5808
+ try {
5809
+ this._socket && this._socket.send(this._output._getBuffer().slice(0, this._output.length));
5810
+ }
5811
+ catch (e) {
5812
+ evt = e;
5813
+ }
5814
+ this._output.endian = this.endian;
5815
+ this._output.clear();
5816
+ if (evt)
5817
+ this.event(Event$2.ERROR, evt);
5784
5818
  }
5785
- this._flushScheduled = true;
5786
- timer.once(delay, this, () => {
5787
- this._flushScheduled = false;
5788
- void this._doReport();
5789
- }, null, false);
5790
5819
  }
5791
- _buildRequestBody(payload) {
5792
- const body = JSON.stringify(payload);
5793
- return {
5794
- data: body,
5795
- headers: ["Content-Type", "application/json"],
5796
- };
5820
+ get state() { return this._socket ? this._socket.readyState : WebSocket.CLOSED; }
5821
+ get url() { return this._socket ? this._socket.url : ""; }
5822
+ get binaryType() { return this._socket ? this._socket.binaryType : "arraybuffer"; }
5823
+ }
5824
+ /**
5825
+ * <p>主机字节序,是 CPU 存放数据的两种不同顺序,包括小端字节序和大端字节序。</p>
5826
+ * <p> LITTLE_ENDIAN :小端字节序,地址低位存储值的低位,地址高位存储值的高位。</p>
5827
+ * <p> BIG_ENDIAN :大端字节序,地址低位存储值的高位,地址高位存储值的低位。有时也称之为网络字节序。</p>
5828
+ */
5829
+ Socket.LITTLE_ENDIAN = "littleEndian";
5830
+ /**
5831
+ * <p>主机字节序,是 CPU 存放数据的两种不同顺序,包括小端字节序和大端字节序。</p>
5832
+ * <p> BIG_ENDIAN :大端字节序,地址低位存储值的高位,地址高位存储值的低位。有时也称之为网络字节序。</p>
5833
+ * <p> LITTLE_ENDIAN :小端字节序,地址低位存储值的低位,地址高位存储值的高位。</p>
5834
+ */
5835
+ Socket.BIG_ENDIAN = "bigEndian";
5836
+
5837
+ var SocketLogType;
5838
+ (function (SocketLogType) {
5839
+ SocketLogType["Connected"] = "[C==S]";
5840
+ SocketLogType["C2S"] = "[C=>S]";
5841
+ SocketLogType["S2C"] = "[S=>C]";
5842
+ SocketLogType["S2xC"] = "[S=xC]";
5843
+ SocketLogType["CxS"] = "[CxS]";
5844
+ })(SocketLogType || (SocketLogType = {}));
5845
+ /**
5846
+ * Socket节点,实现断线重连、心跳等机制
5847
+ */
5848
+ class SocketNode {
5849
+ constructor() {
5850
+ this._socket = null;
5851
+ this._protocolHelper = null;
5852
+ this._networkTips = null;
5853
+ this._reconnectTimes = 3;
5854
+ this._loger = null;
5855
+ this._reconnectCD = 3 * 1000;
5856
+ this._reconnectCount = null;
5857
+ this._heartbeatID = 0;
5858
+ this._heartbeatCD = 5 * 1000;
5797
5859
  }
5798
- buildRequestId() {
5799
- return `relay-${Date.now()}-${Math.random().toString(16).slice(2, 10)}`;
5860
+ init(socket, protocolHelper, networkTips) {
5861
+ if (this._socket)
5862
+ return;
5863
+ this._socket = socket;
5864
+ this._protocolHelper = protocolHelper;
5865
+ this._networkTips = networkTips;
5800
5866
  }
5801
- _sendBatch(events) {
5802
- return __awaiter(this, void 0, void 0, function* () {
5803
- if (!this._config.url) {
5804
- throw new Error("Keytops analytics relay url is empty");
5805
- }
5806
- const payload = {
5807
- requestId: this.buildRequestId(),
5808
- appid: publisher.appId,
5809
- sentAt: this._config.timestampCall ? (this._config.timestampCall() || Date.now()) : Date.now(),
5810
- events,
5811
- };
5812
- const request = this._buildRequestBody(payload);
5813
- const response = yield net.http.sendsync(this._config.url, {
5814
- method: "post",
5815
- responseType: "json",
5816
- data: request.data,
5817
- headers: request.headers,
5818
- timeout: KEYTOPS_REQUEST_TIMEOUT,
5819
- });
5820
- const code = response && response.code;
5821
- if (!(code === 0 || code === "0" || code === 200 || code === "200")) {
5822
- throw new Error(response && response.message ? response.message : "relay request failed");
5823
- }
5824
- });
5867
+ _addEvent() {
5868
+ this._socket.on(Event$2.OPEN, this, this._onConnected);
5869
+ this._socket.on(Event$2.MESSAGE, this, this._onMessage);
5870
+ this._socket.on(Event$2.ERROR, this, this._onError);
5871
+ this._socket.on(Event$2.CLOSE, this, this._onClosed);
5825
5872
  }
5826
- _sendBatchWithRetry(events) {
5827
- return __awaiter(this, void 0, void 0, function* () {
5828
- let lastError = null;
5829
- for (let i = 0; i <= KEYTOPS_RETRY_DELAYS.length; i++) {
5830
- try {
5831
- yield this._sendBatch(events);
5832
- return;
5833
- }
5834
- catch (error) {
5835
- lastError = error;
5836
- if (i >= KEYTOPS_RETRY_DELAYS.length) {
5837
- break;
5838
- }
5839
- yield new Promise((resolve) => timer.once(KEYTOPS_RETRY_DELAYS[i], this, resolve, null, false));
5840
- }
5841
- }
5842
- throw lastError || new Error("relay request failed");
5843
- });
5873
+ _removeEvent() {
5874
+ this._socket.off(Event$2.OPEN, this, this._onConnected);
5875
+ this._socket.off(Event$2.MESSAGE, this, this._onMessage);
5876
+ this._socket.off(Event$2.ERROR, this, this._onError);
5877
+ this._socket.off(Event$2.CLOSE, this, this._onClosed);
5844
5878
  }
5845
- _doReport(maxCount = this._config.count || 20, store = false) {
5846
- return __awaiter(this, void 0, void 0, function* () {
5847
- if (this._sending || this._eventCache.length <= 0) {
5848
- return false;
5849
- }
5850
- this._sending = true;
5851
- const events = this._eventCache.splice(0, Math.min(this._eventCache.length, maxCount));
5879
+ /**
5880
+ * 连接服务器
5881
+ * @param url 服务器地址 wss://localhost:80
5882
+ * @param binaryType BinaryType "arraybuffer" | "blob"
5883
+ * @param reconnectTimes 重连次数 -1: 一直尝试重连 0: 不重连 >0: 重连尝试次数
5884
+ * @returns
5885
+ */
5886
+ connect(url, binaryType = "arraybuffer", reconnectTimes = 3) {
5887
+ this._removeEvent();
5888
+ this._addEvent();
5889
+ this._networkTips.onConnecting();
5890
+ this._reconnectTimes = reconnectTimes;
5891
+ this._socket.connectByUrl(url, binaryType);
5892
+ }
5893
+ /**
5894
+ * 发送数据
5895
+ * @returns
5896
+ */
5897
+ flush() {
5898
+ if (this._socket.connected) {
5899
+ this._networkTips.onFlush();
5852
5900
  try {
5853
- yield this._sendBatchWithRetry(events);
5901
+ this._socket.flush();
5854
5902
  return true;
5855
5903
  }
5856
5904
  catch (e) {
5857
- if (!store) {
5858
- this._eventCache.unshift(...events);
5859
- console.log(`[KeytopsAnalyticsSender] 事件发送失败,恢复事件到队列,等待下次发送。`);
5860
- }
5861
- else {
5862
- this._storeEvents([...events, ...this._eventCache]);
5863
- this._eventCache.length = 0;
5864
- console.log(`[KeytopsAnalyticsSender] 事件发送失败,存储到缓存,等待下次切回游戏。`);
5865
- }
5866
5905
  return false;
5867
5906
  }
5868
- finally {
5869
- this._sending = false;
5870
- if (!store && this._eventCache.length > 0) {
5871
- const countLimit = this._config.count || 20;
5872
- const byteLimit = this._config.maxBufferedBytes || 512 * 1024;
5873
- if (this._eventCache.length >= countLimit || this._currentBufferedBytes() >= byteLimit) {
5874
- this._scheduleFlush(0);
5875
- }
5876
- }
5877
- }
5878
- });
5907
+ }
5908
+ return false;
5879
5909
  }
5880
- send(eventName, data) {
5881
- return __awaiter(this, void 0, void 0, function* () {
5882
- const payload = data && typeof data === "object" && !Array.isArray(data)
5883
- ? Object.assign({}, data) : { value: data };
5884
- let msg_data = {
5885
- appid: publisher.appId,
5886
- openid: String(storage.userId || publisher.login.openID || ""),
5887
- deviceid: App.deviceId,
5888
- sessionid: App.sessionId,
5889
- level: ++this._index,
5890
- data: payload,
5891
- event: eventName,
5892
- timestamp: this._config.timestampCall() || Date.now()
5893
- };
5894
- this._eventCache.push(msg_data);
5895
- const countLimit = this._config.count || 20;
5896
- const byteLimit = this._config.maxBufferedBytes || 512 * 1024;
5897
- if (this._eventCache.length >= countLimit || this._currentBufferedBytes() >= byteLimit) {
5898
- this._scheduleFlush(0);
5910
+ log(type, data) {
5911
+ if (this._loger) {
5912
+ this._loger(type, typeof data === 'string' ? data : `ID:${data.id} Len:${data.len} Data:${JSON.stringify(data.data)}`);
5913
+ }
5914
+ }
5915
+ /**
5916
+ * 关闭连接
5917
+ * @param code
5918
+ * @param reason
5919
+ */
5920
+ close(code, reason) {
5921
+ this._cleanupTimeout();
5922
+ this._removeEvent();
5923
+ this._socket.close(code, reason);
5924
+ this.log(SocketLogType.CxS, `${this._socket.url} By Client`);
5925
+ }
5926
+ _onConnected(event) {
5927
+ this.log(SocketLogType.Connected, this._socket.url);
5928
+ this._reconnectCount = null;
5929
+ if (this._socket.state == WebSocket.OPEN) {
5930
+ this._networkTips.onConnectSuccess();
5931
+ }
5932
+ else {
5933
+ this._onError(event);
5934
+ }
5935
+ }
5936
+ _onMessage(data) {
5937
+ this._protocolHelper.decode(this._socket.disableInput ? data : this._socket.input);
5938
+ this._networkTips.onMessage();
5939
+ this._socket.input.clear();
5940
+ this._heartbeat(); //重置心跳
5941
+ }
5942
+ _onError(event) {
5943
+ this._networkTips.onConnectFailed();
5944
+ this.log(SocketLogType.CxS, `${this._socket.url}`);
5945
+ this._retry();
5946
+ }
5947
+ _onClosed(event) {
5948
+ this._networkTips.onClosed();
5949
+ this.log(SocketLogType.CxS, `${this._socket.url} Code:${event.code} Reason:${event.reason} WasClean:${event.wasClean}`);
5950
+ this._retry();
5951
+ }
5952
+ _retry() {
5953
+ if (this._reconnectTimes != 0) {
5954
+ if (this._reconnectCount == null) {
5955
+ this._reconnectCount = this._reconnectTimes == -1 ? 9999 : this._reconnectTimes + 1;
5899
5956
  }
5900
- });
5957
+ if (--this._reconnectCount > 0) {
5958
+ this._reconnectHandler = setTimeout(() => {
5959
+ this.connect(this._socket.url, this._socket.binaryType, this._reconnectTimes);
5960
+ }, this._reconnectCD);
5961
+ }
5962
+ }
5963
+ }
5964
+ _heartbeat() {
5965
+ this._cleanupTimeout();
5966
+ this._keepAliveHandler = setTimeout(() => {
5967
+ this._protocolHelper.heartbeat();
5968
+ }, this._heartbeatCD);
5969
+ }
5970
+ _cleanupTimeout() {
5971
+ clearTimeout(this._keepAliveHandler);
5972
+ clearTimeout(this._reconnectHandler);
5973
+ }
5974
+ set logable(value) {
5975
+ this._loger = value ? console.log : null;
5976
+ }
5977
+ get reconnectCD() {
5978
+ return this._reconnectCD;
5979
+ }
5980
+ set reconnectCD(value) {
5981
+ this._reconnectCD = value;
5982
+ }
5983
+ get heartbeatCD() {
5984
+ return this._heartbeatCD;
5985
+ }
5986
+ set heartbeatCD(value) {
5987
+ this._heartbeatCD = value;
5901
5988
  }
5989
+ get output() { return this._socket.output; }
5990
+ ;
5991
+ get input() { return this._socket.input; }
5992
+ ;
5902
5993
  }
5903
5994
 
5904
- class AnalyticsManager {
5995
+ class NetManager {
5905
5996
  constructor() {
5906
- this._base = null;
5907
- this._level = null;
5908
- this._ad = null;
5909
- this._session = null;
5910
- this._launch = null;
5911
- this._social = null;
5912
- this._assignmentSender = null;
5997
+ this._logable = false;
5998
+ this._socket = null;
5999
+ this._http = null;
6000
+ if (NetManager.created) {
6001
+ throw new Error("NetManager 是单例");
6002
+ }
6003
+ NetManager.created = true;
5913
6004
  }
5914
- hasRelayUrl(config) {
5915
- return !!String(config && config.url || "").trim();
6005
+ initSocket() {
6006
+ if (this._socket)
6007
+ return;
6008
+ this._protocol = Injector.getInject(NetManager.PROTOCOL_HELPER_KEY);
6009
+ this._netTips = Injector.getInject(NetManager.NETWORK_TIPS_KEY);
6010
+ this._socket = new SocketNode();
6011
+ let socket = new Socket();
6012
+ socket.disableInput = true;
6013
+ // socket.protocols = [];
6014
+ // socket.endian = Socket.BIG_ENDIAN;
6015
+ this._socket.init(socket, this._protocol, this._netTips);
6016
+ this._socket.logable = this._logable;
5916
6017
  }
5917
- hasAliDirectConfig(config) {
5918
- return !!String(config && config.project || "").trim() && !!String(config && config.logstore || "").trim();
6018
+ initHttp() {
6019
+ if (this._http)
6020
+ return;
6021
+ this._http = new HttpNode();
5919
6022
  }
5920
- createBehaviorSenders() {
5921
- const analyticsConfig = remoteConfigService.config(SystemOnlineConfig).getAnalyticsConfig();
5922
- if (this.hasRelayUrl(analyticsConfig)) {
5923
- return [new KeytopsAnalyticsSender(analyticsConfig)];
5924
- }
5925
- if (this.hasAliDirectConfig(analyticsConfig)) {
5926
- return [new ALiAnalyticsSender(analyticsConfig)];
6023
+ set logable(value) {
6024
+ this._logable = value;
6025
+ if (this._socket) {
6026
+ this._socket.logable = value;
5927
6027
  }
5928
- return [];
5929
6028
  }
5930
- /**
5931
- * 基础统计能力
5932
- */
5933
- get base() {
5934
- if (!this._base) {
5935
- this._base = new AnalyticsAble();
5936
- }
5937
- return this._base;
6029
+ get socket() {
6030
+ this.initSocket();
6031
+ return this._socket;
5938
6032
  }
5939
- /**
5940
- * 关卡统计能力,用于关卡分析
5941
- */
5942
- get level() {
5943
- if (!this.base.inited) {
5944
- throw new Error("请先通过enable初始化基础统计能力");
5945
- }
5946
- if (!this._level) {
5947
- this._level = new LevelAnalyticsAble(this.base);
5948
- }
5949
- return this._level;
6033
+ get http() {
6034
+ this.initHttp();
6035
+ return this._http;
5950
6036
  }
5951
- /**
5952
- * 广告统计能力,用于广告分析
5953
- */
5954
- get ad() {
5955
- if (!this.base.inited) {
5956
- throw new Error("请先通过enable初始化基础统计能力");
5957
- }
5958
- if (!this._ad) {
5959
- this._ad = new ADAnalyticsAble(this.base);
5960
- }
5961
- return this._ad;
6037
+ get server() {
6038
+ this.initSocket();
6039
+ return this._protocol;
5962
6040
  }
5963
- /**
5964
- * 会话统计能力,用于会话分析
5965
- */
5966
- get session() {
5967
- if (!this.base.inited) {
5968
- throw new Error("请先通过enable初始化基础统计能力");
5969
- }
5970
- if (!this._session) {
5971
- this._session = new SessionAnalyticsAble(this.base);
5972
- }
5973
- return this._session;
6041
+ }
6042
+ NetManager.PROTOCOL_HELPER_KEY = "net.protocolHelper";
6043
+ NetManager.NETWORK_TIPS_KEY = "net.networkTips";
6044
+ NetManager.created = false;
6045
+ /**
6046
+ * 网络
6047
+ */
6048
+ const net = new NetManager();
6049
+
6050
+ const KEYTOPS_ANALYTICS_EVENT_CACHE = "KEYTOPS_ANALYTICS_EVENT_CACHE";
6051
+ const KEYTOPS_RETRY_DELAYS = [100, 500, 2000];
6052
+ const KEYTOPS_REQUEST_TIMEOUT = 10000;
6053
+ class KeytopsAnalyticsSender extends EmptyAnalyticsSender {
6054
+ constructor(config, exclude) {
6055
+ super(exclude);
6056
+ this._sending = false;
6057
+ this._flushScheduled = false;
6058
+ this._eventCache = [];
6059
+ this._index = 0;
6060
+ this._index = 0;
6061
+ this._setConfig(config);
6062
+ this._storageKey = this._config.cacheKey || KEYTOPS_ANALYTICS_EVENT_CACHE;
6063
+ this._eventCache = this._loadStoredEvents();
6064
+ this._addListen();
6065
+ this._startLoop();
6066
+ void this._doReport();
5974
6067
  }
5975
- /**
5976
- * 启动统计能力,用于启动性能分析
5977
- */
5978
- get launch() {
5979
- if (!this.base.inited) {
5980
- throw new Error("请先通过enable初始化基础统计能力");
6068
+ _setConfig(config) {
6069
+ config.cd = config.cd || 1;
6070
+ config.count = config.count || 20;
6071
+ config.maxBufferedBytes = config.maxBufferedBytes || 512 * 1024;
6072
+ this._config = config;
6073
+ }
6074
+ _loadStoredEvents() {
6075
+ const storedValue = storage.getGValue(this._storageKey, []);
6076
+ const events = Array.isArray(storedValue) ? storedValue : [];
6077
+ if (events.length > 0) {
6078
+ storage.removeGValue(this._storageKey);
5981
6079
  }
5982
- if (!this._launch) {
5983
- this._launch = new LaunchAnalyticsAble(this.base);
6080
+ return events;
6081
+ }
6082
+ _storeEvents(events) {
6083
+ if (!events.length) {
6084
+ storage.removeGValue(this._storageKey);
6085
+ return;
5984
6086
  }
5985
- return this._launch;
6087
+ storage.setGValue(this._storageKey, events);
5986
6088
  }
5987
- /**
5988
- * 社交统计能力
5989
- */
5990
- get social() {
5991
- if (!this.base.inited) {
5992
- throw new Error("请先通过enable初始化基础统计能力");
6089
+ _estimateBytes(value) {
6090
+ try {
6091
+ const text = JSON.stringify(value) || "";
6092
+ return encodeURIComponent(text).replace(/%[A-F\d]{2}/g, "U").length;
5993
6093
  }
5994
- if (!this._social) {
5995
- this._social = new SocialAnalyticsAble(this.base);
6094
+ catch (error) {
6095
+ return 0;
5996
6096
  }
5997
- return this._social;
5998
6097
  }
5999
- /**
6000
- * 启用统计(基础统计能力)
6001
- * @param debugMode 调试模式,是否打印日志信息,默认为false
6002
- * @param senders 上报器列表,允许多个 默认为ALiAnalyticsSender
6003
- * @returns
6004
- */
6005
- enable(debugMode = false, senders) {
6006
- if (this.base.inited) {
6007
- return;
6008
- }
6009
- senders = senders || this.createBehaviorSenders();
6010
- this.base.init(senders, debugMode);
6011
- this.launch.report("enabled");
6098
+ _currentBufferedBytes() {
6099
+ return this._estimateBytes(this._eventCache);
6012
6100
  }
6013
- get version() {
6014
- return "v1";
6101
+ _startLoop() {
6102
+ timer.loop((this._config.cd || 1) * 1000, this, this._doReport, [this._config.count], true);
6015
6103
  }
6016
- /**
6017
- * 获取所有维度的数据
6018
- * @returns 所有维度的数据
6019
- */
6020
- getMetrics() {
6021
- const result = {};
6022
- result[DimensionType[DimensionType.ACTIVITY]] = this.session.getMetrics();
6023
- result[DimensionType[DimensionType.AD]] = this.ad.getMetrics();
6024
- result[DimensionType[DimensionType.SOCIAL]] = this.social.getMetrics();
6025
- return result;
6104
+ _addListen() {
6105
+ App.onHide(() => {
6106
+ void this._doReport(this._eventCache.length, true);
6107
+ });
6108
+ App.onShow(this._restoreEvents, this);
6026
6109
  }
6027
- get assignmentSender() {
6028
- if (!this._assignmentSender) {
6029
- const analyticsConfig = remoteConfigService.config(SystemOnlineConfig).getAssignmentConfig();
6030
- this._assignmentSender = this.hasRelayUrl(analyticsConfig)
6031
- ? new KeytopsAnalyticsSender(analyticsConfig, analyticsConfig.exclude || ["view"])
6032
- : new EmptyAnalyticsSender();
6110
+ _restoreEvents() {
6111
+ const eventsInLocalStore = this._loadStoredEvents();
6112
+ if (eventsInLocalStore.length > 0) {
6113
+ this._eventCache.unshift(...eventsInLocalStore);
6114
+ console.log(`[KeytopsAnalyticsSender] 恢复本地events(length:${eventsInLocalStore.length}, total:${this._eventCache.length})`);
6115
+ this._scheduleFlush(0);
6033
6116
  }
6034
- return this._assignmentSender;
6035
6117
  }
6036
- reportAssignments(resolveMeta, levelid) {
6037
- if (!resolveMeta) {
6118
+ _scheduleFlush(delay = 0) {
6119
+ if (this._flushScheduled) {
6038
6120
  return;
6039
6121
  }
6040
- const method = resolveMeta.method;
6041
- if (!method) {
6042
- throw new Error("method is required");
6043
- }
6044
- const eventName = `${method}_assignment`;
6045
- (resolveMeta.matchedUnits || []).forEach((unit) => {
6122
+ this._flushScheduled = true;
6123
+ timer.once(delay, this, () => {
6124
+ this._flushScheduled = false;
6125
+ void this._doReport();
6126
+ }, null, false);
6127
+ }
6128
+ _buildRequestBody(payload) {
6129
+ const body = JSON.stringify(payload);
6130
+ return {
6131
+ data: body,
6132
+ headers: ["Content-Type", "application/json"],
6133
+ };
6134
+ }
6135
+ buildRequestId() {
6136
+ return `relay-${Date.now()}-${Math.random().toString(16).slice(2, 10)}`;
6137
+ }
6138
+ _sendBatch(events) {
6139
+ return __awaiter(this, void 0, void 0, function* () {
6140
+ if (!this._config.url) {
6141
+ throw new Error("Keytops analytics relay url is empty");
6142
+ }
6046
6143
  const payload = {
6047
- method: method,
6048
- resolve_key: resolveMeta.resolveKey,
6049
- config_name: unit.configName,
6050
- fingerprint: unit.fingerprint,
6144
+ requestId: this.buildRequestId(),
6145
+ appid: publisher.appId,
6146
+ sentAt: this._config.timestampCall ? (this._config.timestampCall() || Date.now()) : Date.now(),
6147
+ events,
6051
6148
  };
6052
- if (unit.publishId) {
6053
- payload.publish_id = unit.publishId;
6149
+ const request = this._buildRequestBody(payload);
6150
+ const response = yield net.http.sendsync(this._config.url, {
6151
+ method: "post",
6152
+ responseType: "json",
6153
+ data: request.data,
6154
+ headers: request.headers,
6155
+ timeout: KEYTOPS_REQUEST_TIMEOUT,
6156
+ });
6157
+ const code = response && response.code;
6158
+ if (!(code === 0 || code === "0" || code === 200 || code === "200")) {
6159
+ throw new Error(response && response.message ? response.message : "relay request failed");
6054
6160
  }
6055
- if (unit.variantId) {
6056
- payload.variant_id = unit.variantId;
6161
+ });
6162
+ }
6163
+ _sendBatchWithRetry(events) {
6164
+ return __awaiter(this, void 0, void 0, function* () {
6165
+ let lastError = null;
6166
+ for (let i = 0; i <= KEYTOPS_RETRY_DELAYS.length; i++) {
6167
+ try {
6168
+ yield this._sendBatch(events);
6169
+ return;
6170
+ }
6171
+ catch (error) {
6172
+ lastError = error;
6173
+ if (i >= KEYTOPS_RETRY_DELAYS.length) {
6174
+ break;
6175
+ }
6176
+ yield new Promise((resolve) => timer.once(KEYTOPS_RETRY_DELAYS[i], this, resolve, null, false));
6177
+ }
6057
6178
  }
6058
- if (unit.variantFingerprint) {
6059
- payload.variant_fingerprint = unit.variantFingerprint;
6179
+ throw lastError || new Error("relay request failed");
6180
+ });
6181
+ }
6182
+ _doReport(maxCount = this._config.count || 20, store = false) {
6183
+ return __awaiter(this, void 0, void 0, function* () {
6184
+ if (this._sending || this._eventCache.length <= 0) {
6185
+ return false;
6060
6186
  }
6061
- if (levelid !== undefined) {
6062
- payload.level_id = levelid;
6187
+ this._sending = true;
6188
+ const events = this._eventCache.splice(0, Math.min(this._eventCache.length, maxCount));
6189
+ try {
6190
+ yield this._sendBatchWithRetry(events);
6191
+ return true;
6192
+ }
6193
+ catch (e) {
6194
+ if (!store) {
6195
+ this._eventCache.unshift(...events);
6196
+ console.log(`[KeytopsAnalyticsSender] 事件发送失败,恢复事件到队列,等待下次发送。`);
6197
+ }
6198
+ else {
6199
+ this._storeEvents([...events, ...this._eventCache]);
6200
+ this._eventCache.length = 0;
6201
+ console.log(`[KeytopsAnalyticsSender] 事件发送失败,存储到缓存,等待下次切回游戏。`);
6202
+ }
6203
+ return false;
6204
+ }
6205
+ finally {
6206
+ this._sending = false;
6207
+ if (!store && this._eventCache.length > 0) {
6208
+ const countLimit = this._config.count || 20;
6209
+ const byteLimit = this._config.maxBufferedBytes || 512 * 1024;
6210
+ if (this._eventCache.length >= countLimit || this._currentBufferedBytes() >= byteLimit) {
6211
+ this._scheduleFlush(0);
6212
+ }
6213
+ }
6063
6214
  }
6064
- this.assignmentSender.send(eventName, payload);
6065
6215
  });
6066
6216
  }
6067
- }
6068
- /**
6069
- * 埋点统计能力
6070
- */
6071
- const analytics = new AnalyticsManager();
6072
-
6073
- /**
6074
- * 通用计时器实现。
6075
- * 使用 setInterval 驱动 Timer 的内部更新循环。
6076
- */
6077
- class TimerImpl {
6078
- constructor() {
6079
- this._intervalMap = new WeakMap();
6080
- }
6081
- enable(timer) {
6082
- if (this._intervalMap.has(timer)) {
6083
- return;
6084
- }
6085
- const intervalId = setInterval(() => {
6086
- timer["_update"]();
6087
- }, 100);
6088
- this._intervalMap.set(timer, intervalId);
6089
- }
6090
- }
6091
-
6092
- class MathUtils {
6093
- /**
6094
- * 随机
6095
- * @param min
6096
- * @param max
6097
- */
6098
- static random(min, max) {
6099
- this.seed = (this.seed * 9301 + 49297) % 233280;
6100
- let c = this.seed / (233280.0);
6101
- return min + c * (max - min);
6102
- }
6103
- /**
6104
- * 随机整数值
6105
- * @param min
6106
- * @param max
6107
- */
6108
- static randomInt(min, max) {
6109
- return Math.floor(this.random(min, max + 0.999));
6110
- }
6111
- /**
6112
- * 随机唯一稳定值
6113
- * @param min
6114
- * @param max
6115
- * @param clear 是否清除唯一记录
6116
- */
6117
- static randomOnlyInt(min, max, clear) {
6118
- if (clear) {
6119
- this.onlyMap.clear();
6120
- }
6121
- let value = this.randomInt(min, max);
6122
- let tryCount = (max - min) * 2;
6123
- let tryIndex = 0;
6124
- while (this.onlyMap.has(value)) {
6125
- value = this.randomInt(min, max);
6126
- if (tryIndex > tryCount) {
6127
- console.error("所有唯一值已取完");
6128
- return 0;
6217
+ send(eventName, data) {
6218
+ return __awaiter(this, void 0, void 0, function* () {
6219
+ const payload = data && typeof data === "object" && !Array.isArray(data)
6220
+ ? Object.assign({}, data) : { value: data };
6221
+ let msg_data = {
6222
+ appid: publisher.appId,
6223
+ openid: String(storage.userId || publisher.login.openID || ""),
6224
+ deviceid: App.deviceId,
6225
+ sessionid: App.sessionId,
6226
+ level: ++this._index,
6227
+ data: payload,
6228
+ event: eventName,
6229
+ timestamp: this._config.timestampCall() || Date.now()
6230
+ };
6231
+ this._eventCache.push(msg_data);
6232
+ const countLimit = this._config.count || 20;
6233
+ const byteLimit = this._config.maxBufferedBytes || 512 * 1024;
6234
+ if (this._eventCache.length >= countLimit || this._currentBufferedBytes() >= byteLimit) {
6235
+ this._scheduleFlush(0);
6129
6236
  }
6130
- tryIndex++;
6131
- }
6132
- this.onlyMap.set(value, true);
6133
- return value;
6134
- }
6135
- /**
6136
- * 获取两点间的角度
6137
- * @param p1 点1
6138
- * @param p2 点2
6139
- */
6140
- static getAngle(p1, p2) {
6141
- return Math.atan((p2.y - p1.y) / (p2.x - p1.x));
6142
- }
6143
- /**
6144
- * 获取两点间的距离
6145
- * @param p1 点1
6146
- * @param p2 点2
6147
- */
6148
- static getDistance(p1, p2) {
6149
- return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
6150
- }
6151
- /**
6152
- * 将角度转为弧度
6153
- * @param angle 角度
6154
- */
6155
- static angleToRadian(angle) {
6156
- return angle * Math.PI / 180;
6157
- }
6158
- /**
6159
- * 求旋转后的点坐标
6160
- * @param angle 角度
6161
- * @param point 旋转前的坐标点
6162
- * @param result
6163
- */
6164
- static getRotationPoint(angle, pointOrDir, result) {
6165
- if (result == null) {
6166
- result = { x: 0, y: 0 };
6167
- }
6168
- //角度转弧度
6169
- angle = angle * (Math.PI / 180);
6170
- result.x = Math.cos(angle) * pointOrDir.x - Math.sin(angle) * pointOrDir.y;
6171
- result.y = Math.cos(angle) * pointOrDir.y + Math.sin(angle) * pointOrDir.x;
6172
- return result;
6237
+ });
6173
6238
  }
6174
6239
  }
6175
- /**
6176
- * 随机种子
6177
- */
6178
- MathUtils.seed = 10;
6179
- /**
6180
- * 唯一记录
6181
- */
6182
- MathUtils.onlyMap = new Map();
6183
6240
 
6184
- class StringUtils {
6185
- static random(length, chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") {
6186
- var result = '';
6187
- for (var i = length; i > 0; --i) {
6188
- result += chars[Math.floor(Math.random() * chars.length)];
6241
+ class AnalyticsManager {
6242
+ constructor() {
6243
+ this._base = null;
6244
+ this._level = null;
6245
+ this._ad = null;
6246
+ this._session = null;
6247
+ this._launch = null;
6248
+ this._social = null;
6249
+ this._assignmentSender = null;
6250
+ }
6251
+ hasRelayUrl(config) {
6252
+ return !!String(config && config.url || "").trim();
6253
+ }
6254
+ hasAliDirectConfig(config) {
6255
+ return !!String(config && config.project || "").trim() && !!String(config && config.logstore || "").trim();
6256
+ }
6257
+ createBehaviorSenders() {
6258
+ const analyticsConfig = remoteConfigService.config(SystemOnlineConfig).getAnalyticsConfig();
6259
+ if (this.hasRelayUrl(analyticsConfig)) {
6260
+ return [new KeytopsAnalyticsSender(analyticsConfig)];
6189
6261
  }
6190
- return result;
6262
+ if (this.hasAliDirectConfig(analyticsConfig)) {
6263
+ return [new ALiAnalyticsSender(analyticsConfig)];
6264
+ }
6265
+ return [];
6191
6266
  }
6192
6267
  /**
6193
- * 判断是否是远程资源
6194
- * @param url
6195
- * @returns
6268
+ * 基础统计能力
6196
6269
  */
6197
- static isRemote(url) {
6198
- if (url.indexOf("http://") == 0 || url.indexOf("https://") == 0) {
6199
- return true;
6270
+ get base() {
6271
+ if (!this._base) {
6272
+ this._base = new AnalyticsAble();
6200
6273
  }
6201
- return false;
6274
+ return this._base;
6202
6275
  }
6203
6276
  /**
6204
- * 是否为空
6205
- * @param str
6277
+ * 关卡统计能力,用于关卡分析
6206
6278
  */
6207
- static isEmpty(str) {
6208
- if (str == null || str == undefined || str.length == 0) {
6209
- return true;
6279
+ get level() {
6280
+ if (!this.base.inited) {
6281
+ throw new Error("请先通过enable初始化基础统计能力");
6210
6282
  }
6211
- return false;
6283
+ if (!this._level) {
6284
+ this._level = new LevelAnalyticsAble(this.base);
6285
+ }
6286
+ return this._level;
6212
6287
  }
6213
6288
  /**
6214
- * 替换全部字符串
6215
- * @param string src 源串
6216
- * @param string from_ch 被替换的字符
6217
- * @param string to_ch 替换的字符
6218
- *
6219
- * @return string 结果字符串
6220
- */
6221
- static replaceAll(src, from_ch, to_ch) {
6222
- let regExp = new RegExp(from_ch, "g");
6223
- return src.replace(regExp, to_ch);
6289
+ * 广告统计能力,用于广告分析
6290
+ */
6291
+ get ad() {
6292
+ if (!this.base.inited) {
6293
+ throw new Error("请先通过enable初始化基础统计能力");
6294
+ }
6295
+ if (!this._ad) {
6296
+ this._ad = new ADAnalyticsAble(this.base);
6297
+ }
6298
+ return this._ad;
6224
6299
  }
6225
6300
  /**
6226
- * 参数替换
6227
- * @param str
6228
- * @param rest
6229
- *
6230
- * @example
6231
- *
6232
- * var str:string = "here is some info '{0}' and {1}";
6233
- * trace(StringUtil.substitute(str, 15.4, true));
6234
- *
6235
- * // this will output the following string:
6236
- * // "here is some info '15.4' and true"
6301
+ * 会话统计能力,用于会话分析
6237
6302
  */
6238
- static format(str, ...rest) {
6239
- if (str == null)
6240
- return '';
6241
- // Replace all of the parameters in the msg string.
6242
- var len = rest.length;
6243
- var args;
6244
- if (len == 1 && rest[0] instanceof Array) {
6245
- args = rest[0];
6246
- len = args.length;
6247
- }
6248
- else {
6249
- args = rest;
6303
+ get session() {
6304
+ if (!this.base.inited) {
6305
+ throw new Error("请先通过enable初始化基础统计能力");
6250
6306
  }
6251
- for (var i = 0; i < len; i++) {
6252
- str = str.replace(new RegExp("\\{" + i + "\\}", "g"), args[i]);
6307
+ if (!this._session) {
6308
+ this._session = new SessionAnalyticsAble(this.base);
6253
6309
  }
6254
- return str;
6310
+ return this._session;
6255
6311
  }
6256
6312
  /**
6257
- *
6258
- * 格式化数字
6259
- * @param amount 目标数字
6260
- * @param format 支持{1:"", 100:"百", 1000:"千", 10000:"万", 100000:"十万", 1000000:"百万", 10000000:"千万", 100000000:"亿", 1000000000000:"兆"}等
6261
- * @returns
6313
+ * 启动统计能力,用于启动性能分析
6262
6314
  */
6263
- static formatNumber(amount, format) {
6264
- if (!format || amount == 0) {
6265
- return `${amount}`;
6315
+ get launch() {
6316
+ if (!this.base.inited) {
6317
+ throw new Error("请先通过enable初始化基础统计能力");
6266
6318
  }
6267
- let formatKey = [];
6268
- for (const key in format) {
6269
- if (format.hasOwnProperty(key)) {
6270
- formatKey.push(Number(key));
6271
- }
6319
+ if (!this._launch) {
6320
+ this._launch = new LaunchAnalyticsAble(this.base);
6272
6321
  }
6273
- if (!formatKey || formatKey.length == 0) {
6274
- return `${amount}`;
6322
+ return this._launch;
6323
+ }
6324
+ /**
6325
+ * 社交统计能力
6326
+ */
6327
+ get social() {
6328
+ if (!this.base.inited) {
6329
+ throw new Error("请先通过enable初始化基础统计能力");
6275
6330
  }
6276
- formatKey.sort((a, b) => b - a);
6277
- let result = [];
6278
- let total = amount;
6279
- for (let i = 0; i < formatKey.length; i++) {
6280
- const key = formatKey[i];
6281
- let count = Math.floor(Math.abs(total / key)) * (total / Math.abs(total));
6282
- if (count == 0) {
6283
- continue;
6284
- }
6285
- total = total % key;
6286
- result.push(`${count}${format[key]}`);
6331
+ if (!this._social) {
6332
+ this._social = new SocialAnalyticsAble(this.base);
6287
6333
  }
6288
- return result.length ? result.join("") : `${amount}`;
6334
+ return this._social;
6289
6335
  }
6290
6336
  /**
6291
- * 带自动补全的秒数格式。
6292
- * @param second 秒数
6293
- * @param format 格式化文本
6294
- * @param pad 填充文本,默认为“0”
6295
- * @example
6296
- * formatSecond(86400, "ddd天hh小时mm分ss秒")=>"001天00小时00分00秒"
6297
- * formatSecond(86400, "dd天hh小时mm分ss秒")=>"01天00小时00分00秒"
6298
- * formatSecond(86400, "hh小时mm分ss秒")=>"24小时00分00秒"
6299
- * formatSecond(86400, "mm分ss秒")=>"1440分00秒"
6337
+ * 启用统计(基础统计能力)
6338
+ * @param debugMode 调试模式,是否打印日志信息,默认为false
6339
+ * @param senders 上报器列表,允许多个 默认为ALiAnalyticsSender
6300
6340
  * @returns
6301
6341
  */
6302
- static formatSecond(second, format, pad = "0") {
6303
- const formatArr = [
6304
- { unit: 86400, regExp: /D{2,}/i },
6305
- { unit: 3600, regExp: /H{1,2}/i },
6306
- { unit: 60, regExp: /m{1,2}/i },
6307
- { unit: 1, regExp: /s{1,2}/i },
6308
- ];
6309
- for (let i = 0; i < formatArr.length; i++) {
6310
- const element = formatArr[i];
6311
- let match = element.regExp.exec(format);
6312
- if (match) {
6313
- format = format.replace(element.regExp, Math.floor(second / element.unit).toFixed(0).padStart(match[0].length, pad));
6314
- second = second % element.unit;
6315
- }
6342
+ enable(debugMode = false, senders) {
6343
+ if (this.base.inited) {
6344
+ return;
6316
6345
  }
6317
- return format;
6346
+ senders = senders || this.createBehaviorSenders();
6347
+ this.base.init(senders, debugMode);
6348
+ this.launch.report("enabled");
6349
+ }
6350
+ get version() {
6351
+ return "v1";
6318
6352
  }
6319
6353
  /**
6320
- * 带自动补全的时间戳格式化
6321
- * @param milliseconds
6322
- * @param format
6323
- * @param pad 填充文本,默认为“0”
6324
- * @returns
6354
+ * 获取所有维度的数据
6355
+ * @returns 所有维度的数据
6325
6356
  */
6326
- static formatTimestamp(milliseconds, format, pad = "0") {
6327
- let date = new Date(milliseconds);
6328
- date.getHours();
6329
- const formatArr = [
6330
- { r: /Y{4}/, to: date.getFullYear() },
6331
- { r: /M{1,2}/, to: date.getMonth() + 1 },
6332
- { r: /D{1,2}/, to: date.getDate() },
6333
- { r: /H{1,2}/, to: date.getHours() },
6334
- { r: /m{1,2}/, to: date.getMinutes() },
6335
- { r: /s{1,2}/, to: date.getSeconds() },
6336
- ];
6337
- for (let i = 0; i < formatArr.length; i++) {
6338
- const element = formatArr[i];
6339
- let match = element.r.exec(format);
6340
- if (match) {
6341
- format = format.replace(element.r, element.to.toString().padStart(match[0].length, "0"));
6342
- }
6357
+ getMetrics() {
6358
+ const result = {};
6359
+ result[DimensionType[DimensionType.ACTIVITY]] = this.session.getMetrics();
6360
+ result[DimensionType[DimensionType.AD]] = this.ad.getMetrics();
6361
+ result[DimensionType[DimensionType.SOCIAL]] = this.social.getMetrics();
6362
+ return result;
6363
+ }
6364
+ get assignmentSender() {
6365
+ if (!this._assignmentSender) {
6366
+ const analyticsConfig = remoteConfigService.config(SystemOnlineConfig).getAssignmentConfig();
6367
+ this._assignmentSender = this.hasRelayUrl(analyticsConfig)
6368
+ ? new KeytopsAnalyticsSender(analyticsConfig, analyticsConfig.exclude || ["view"])
6369
+ : new EmptyAnalyticsSender();
6343
6370
  }
6344
- return format;
6371
+ return this._assignmentSender;
6372
+ }
6373
+ reportAssignments(resolveMeta, levelid) {
6374
+ if (!resolveMeta) {
6375
+ return;
6376
+ }
6377
+ const method = resolveMeta.method;
6378
+ if (!method) {
6379
+ throw new Error("method is required");
6380
+ }
6381
+ const eventName = `${method}_assignment`;
6382
+ (resolveMeta.matchedUnits || []).forEach((unit) => {
6383
+ const payload = {
6384
+ method: method,
6385
+ resolve_key: resolveMeta.resolveKey,
6386
+ config_name: unit.configName,
6387
+ fingerprint: unit.fingerprint,
6388
+ };
6389
+ if (unit.publishId) {
6390
+ payload.publish_id = unit.publishId;
6391
+ }
6392
+ if (unit.variantId) {
6393
+ payload.variant_id = unit.variantId;
6394
+ }
6395
+ if (unit.variantFingerprint) {
6396
+ payload.variant_fingerprint = unit.variantFingerprint;
6397
+ }
6398
+ if (levelid !== undefined) {
6399
+ payload.level_id = levelid;
6400
+ }
6401
+ this.assignmentSender.send(eventName, payload);
6402
+ });
6345
6403
  }
6346
6404
  }
6405
+ /**
6406
+ * 埋点统计能力
6407
+ */
6408
+ const analytics = new AnalyticsManager();
6347
6409
 
6348
6410
  class KKTServer {
6349
6411
  requestConfig(context) {
@@ -6462,7 +6524,6 @@ class Application {
6462
6524
  this.impl.launch();
6463
6525
  this._resetLaunchTimes();
6464
6526
  timer.enable();
6465
- analytics.enable(debug);
6466
6527
  let isFirstLaunch = this.isFirstLaunch;
6467
6528
  console.log(`App Launch Done. isFirstLaunch:${isFirstLaunch}`);
6468
6529
  });