export default class CySDK { //sdk 0.4.1 //1、增加A/B测功能(增加是否开启AB处理) //2、关卡埋点 //3、时长分析面板 //4、抖跳微 //5、首日次日用户,新老用户 //6、快手跳微信接入 //7、iaa2.0插件接入 static isABTest: boolean = false;//如果用不到AB测功能则关掉 static appKey: string = "";//后台策略appKey static version: string = "GameName1.0.3-cy0.4.1"; //若不使用ab测试时的本地版本号(同一个版本号,每一个关卡序号需要跟每一个关卡名称一致) //#region 其他参数 static logUrl = "https://log.liehujt.com/game_log" static openId: string = ""; //用户唯一标识 static scene: number = -1; //场景值 static gameName: string = ""; static companyName: string = ""; static code: string = ""; static shareTitle: string = "分享游戏"; static shareImageUrl: string = ""; static launchRes: Object = {};//launch的参数 //#endregion private static storageData: cyStoreData;//sdk存档数据 static init(cb: Function) { console.log("=========创游SDK版本:0.4.1"); const self = this; //=========创游SDK初始化 let finishCb = (data: any) => { setInterval(() => { self.storageData.playTime++; self.saveCyStoreData(); }, 1000);//每秒更新一次时间 if (data?.shareTitle) { this.shareTitle = data.shareTitle; } if (data?.shareImageUrl) { this.shareImageUrl = data.shareImageUrl; } cb(data); } if (this.appKey == "") { console.warn("=========创游SDK appkey 未设置,跳过sdk初始化"); this.cyStoreDataDeal(); finishCb(null); if (window["wx"]) { self.launchRes = window["wx"].getLaunchOptionsSync(); self.trackEvent_uma("launch", { res: JSON.stringify(self.launchRes) }); } self.trackEvent({ event: "login", level_type: "normal" }); } else { this.login((data: any) => { finishCb(data); self.trackEvent({ event: "login", level_type: "normal" }); }) } } //#region 埋点接口 /**友盟打点 */ static trackEvent_uma(event, obj = {}) { obj["playTime"] = this.storageData.playTime; obj["version"] = this.version; obj["fisrtLoginDateStr"] = this.storageData.firstLoginDateStr; for (let key in this.iaaData) { const umaKey = "IAA" + key; obj[umaKey] = this.iaaData[key]; } console.log("友盟尝试打点", event, obj); if (window["wx"] && window['wx'].uma) { window['wx'].uma.trackEvent(event, obj); console.log("wx友盟打点发送", event, obj); } } /**游戏内埋点统一接口(主要减少一些通用参数的传输) */ private static trackEvent(data: object, cb?: Function) { if (this.appKey == "") { console.warn("=========创游SDKappKey缺失,无法发送埋点数据"); return; } if (this.openId == "") { console.warn("=========创游SDKopenId缺失,无法发送埋点数据"); return; } if (this.scene == -1) { console.warn("=========创游SDKscene缺失,无法发送埋点数据"); return; } const self = this; let sendData = { appkey: self.appKey, scene: self.scene, version: self.version, isNew: self.storageData.isNew,//true 是新用户,false是老用户,此处信息前端会给到,用于判定新老用户 hasPlayDay: (self.getNowDay() - self.storageData.firstLoginDay),//用户已玩天数,此处信息前端传入,用户首次日筛选,(若没有则后端自行处理成0,给之前的版本做兼容) gameTime: self.storageData.playTime,//游玩时长(秒),此处信息前端传入,用于后续制作用户时长分布面板 firstLoginDateStr: self.storageData.firstLoginDateStr,//用户首次登录日期字符串,格式yyyy-MM-dd,用于记录筛选对应日期进来用户的后续行为数据 data: { event: "", // login, level_start, level_success, level_fail, ad_request, ad_cancel, ad_finish,encrypted_data openid: "", level_type: "", level_name: "", level_index: -1, dur: -1, mode: "", mode_type: "", mode_index: -1, why: "", encryptedData: "", iv: "", } } sendData.data["openid"] = self.openId; sendData.data["dur"] = Math.ceil(sendData.data["dur"] || -1); // 确保dur为整数 for (let key in data) { if (sendData.data.hasOwnProperty(key)) { sendData.data[key] = data[key]; } } const sendStr = JSON.stringify(sendData); console.log("=========创游SDK埋点数据发送", sendStr); if (!window["wx"]) { fetch(self.logUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: sendStr }) .then((value) => { console.log("=========创游SDK埋点数据发送返回", sendStr); cb && cb(value) }) .catch(err => { console.log("=========创游SDK埋点数据发送失败", sendStr, err); }); } else { window["wx"].request( { url: self.logUrl, method: "POST", data: JSON.stringify(sendData), success(res) { console.log("=========创游SDK埋点数据发送成功", sendData); cb && cb(res); }, fail(err) { console.log("=========创游SDK埋点数据发送失败", sendData, err) } } ) } } //#endregion //#region 登录初始化 /** SDK登录 * @param version 版本(用来分辨不同版本的数据) * @param cb 回调函数(data) =>{} data为后台策略返回数据 */ private static login(cb: Function) { const self = this; if (this.appKey == "") { cb(null); return; } if (window["wx"]) { let count = 2; let loginCode = -1; let scene = -1; let clue_token = ""; let req_id = ""; let ad_id = ""; let clickid = ""; let callback = ""; let ksCampaignId = ""; let ksUnitId = ""; let ksCreativeId = ""; let ksChannel = ""; let ksSiteAccountId = ""; let ksPageId = ""; //拿登录 const loginCb = (backCb: Function) => { window['wx'].login({ success: (res) => { backCb(res.code); }, fail: (err) => { console.log("wx登录失败", err); backCb(-1); } }) }; //拿场景值 const getSceneCb = (backCb: Function) => { console.log("=========创游SDK获取场景值"); const res = window["wx"].getLaunchOptionsSync(); self.launchRes = res; console.log("=========创游SDK冷启动成功", res, JSON.stringify(res)); const sceneGet = res.scene || -1; if (sceneGet == 1069) {//如果是从外部app导入的用户 //抖音 clue_token = res.query["clue_token"] || ""; req_id = res.query["req_id"] || ""; ad_id = res.query["ad_id"] || ""; clickid = res.query["clickid"] || ""; //快手 callback = res.query["callback"] || ""; ksCampaignId = res.query["ksCampaignId"] || ""; ksUnitId = res.query["ksUnitId"] || ""; ksCreativeId = res.query["ksCreativeId"] || ""; ksChannel = res.query["ksChannel"] || ""; ksSiteAccountId = res.query["ksSiteAccountId"] || ""; ksPageId = res.query["ksPageId"] || ""; } console.log("=========创游SDK场景值获取成功1", sceneGet); backCb(sceneGet); } const finishCb = () => { count--; console.log("=========创游SDKFinishCb", count); if (count > 0) return; //一些sdk的存档数据 self.cyStoreDataDeal(); //拿配置 window["wx"].request( { url: "https://liehujt.com//api/campaign/strategy-new-info/" + self.appKey + "/" + self.version, success(res) { cb(res.data); }, fail(err) { console.log("=========创游SDK策略参数获取失败", err) cb(null); } } ) } loginCb((code:any) => { loginCode = code; let url = "https://log.liehujt.com/login?appkey=" + self.appKey + "&code=" + loginCode; if (clue_token != "") {//抖音跳转微信 url = url + "&clue_token=" + clue_token + "&project_id=___&promotion_id=___&mid1=___&mid2=___&mid3=___&mid4=___&mid5=___" + "&req_id=" + req_id + "&advertiser_id=" + ad_id + "&clickid=" + clickid; } if (ksCampaignId != "")//快手跳转微信 { url = url + "&callback=" + callback + "&ksCampaignId=" + ksCampaignId + "&ksUnitId=" + ksUnitId + "&ksCreativeId=" + ksCreativeId + "&ksChannel=" + ksChannel + "&ksSiteAccountId=" + ksSiteAccountId + "&ksPageId=" + ksPageId; } //登录 window["wx"].request( { method: "GET", url: url, success(res) { console.log("=========创游SDK登录返回", res); self.openId = res.data.openid || ""; if (self.isABTest) { self.version = res.data.version || self.version; } else { self.version = self.version; } console.log("=========创游SDK登录成功", self.openId, self.version); finishCb(); }, fail(err:any) { console.log("=========创游SDK登录失败", err) self.openId = ""; self.version = self.version; finishCb(); } } ) }) getSceneCb((sceneGet:any) => { scene = sceneGet; self.scene = scene; console.log("=========创游SDK场景值获取成功2", self.scene); finishCb(); }) } else { this.scene = 0; //默认场景值为0,等于web端 this.openId = localStorage.getItem("cy_sdk_web_openid") as string || ""; if (this.openId == null || this.openId == "") { this.openId = "web_" + Date.now(); //web端默认openId为当前时间戳 localStorage.setItem("cy_sdk_web_openid", this.openId); console.log("=========创游SDK web端登录成功(新用户)", this.openId); } else { console.log("=========创游SDK web端登录成功(老用户)", this.openId); } this.cyStoreDataDeal(); if (window["wx"]) { self.trackEvent_uma("launch", { res: self.launchRes }); } console.log("获取策略", this.appKey, this.version); //拿配置 fetch("https://liehujt.com//api/campaign/strategy-new-info/" + this.appKey + "/" + this.version).then((response: Response) => { // fetch("https://liehujt.com//api/campaign/strategy-info/" + this.appKey).then((response: Response) => { return response.text() }).then((value) => { cb(JSON.parse(value)) }).catch((err) => { console.log("创游策略参数获取失败", err) cb(null); }) } } //#endregion //#region sdk存档 /**cySDK存档数据处理 */ private static cyStoreDataDeal() { console.log("=========创游SDK存档数据处理"); const customData = new cyStoreData(this.version, this.getNowDay()) const localData = localStorage.getItem("cy_sdk_data"); this.storageData = localData ? JSON.parse(localData) : null; if (!this.storageData) { this.storageData = customData; this.saveCyStoreData(); } else { this.storageData.isNew = false; for (let key in customData) { if (!this.storageData.hasOwnProperty(key)) { this.storageData[key] = customData[key]; } } this.saveCyStoreData(); } } private static saveCyStoreData() { localStorage.setItem("cy_sdk_data", JSON.stringify(this.storageData)); } //#endregion //#region 关卡埋点 static levelStartTime = 0; //关卡开始时间 /**关卡开始 * @param level_type 关卡入口("normal"表示普通关卡,为后续拓展预留位置) * @param level_name 关卡名称(全局唯一) * @param level_index 关卡索引(全局唯一,与level_name一一对应) */ static onLevelStart(level_type: string, level_name: string, level_index: number) { this.trackEvent({ event: "level_start", level_type: level_type, level_name: level_name, level_index: level_index, }) this.levelStartTime = new Date().getTime(); this.trackEvent_uma("startStage", { type: level_type, level: level_index }); } /**关卡成功 * @param level_type 关卡入口("normal"表示普通关卡,为后续拓展预留位置) * @param level_name 关卡名称(全局唯一) * @param level_index 关卡索引(全局唯一,与level_name一一对应) * @param dur 关卡耗时(单位:秒) */ static onLevelSuccess(level_type: string, level_name: string, level_index: number) { let dur = Math.ceil((new Date().getTime() - this.levelStartTime) / 1000); this.trackEvent({ event: "level_success", level_type: level_type, level_name: level_name, level_index: level_index, dur: dur }); this.trackEvent_uma("passStage", { type: level_type, level: level_index }); } /**关卡失败 * @param level_type 关卡类型("normal"表示普通关卡) * @param level_name 关卡名称(全局唯一) * @param level_index 关卡索引(全局唯一,与level_name一一对应) * @param dur 关卡耗时(单位:秒) */ static onLevelFail(level_type: string, level_name: string, level_index: number) { let dur = Math.ceil((new Date().getTime() - this.levelStartTime) / 1000); this.trackEvent({ event: "level_fail", level_type: level_type, level_name: level_name, level_index: level_index, dur: dur }); this.trackEvent_uma("failStage", { type: level_type, level: level_index }); } /**广告请求 * @param mode 游戏模式("level"表示关卡模式,为后续拓展预留位置) * @param mode_type 对应模式的类型(例如"normal"表示普通关卡) * @param mode_index 对应模式的索引(例如:mode= "level" ,mode_type ="normal"时,等于关卡游戏模式,normal类型,的level_index,方便做关卡分析看板用) * @param why 广告原因(例如:"tips") */ static onAdRequest(mode: string, mode_type: string, mode_index: number, why: string) { this.trackEvent({ event: "ad_request", mode: mode, mode_type: mode_type, mode_index: mode_index, why: why, }); this.trackEvent_uma("videoAdRequest", { type: why, mode: mode_type, level: mode_index }); this.getIaaInfo(mode_type, why, false); } /**广告取消 * @param mode 游戏模式("level"表示关卡模式,为后续拓展预留位置) * @param mode_type 对应模式的类型(例如"normal"表示普通关卡) * @param mode_index 对应模式的索引(例如:mode= "level" ,mode_type ="normal"时,等于关卡游戏模式,normal类型,的level_index,方便做关卡分析看板用) * @param why 广告原因(例如:"tips") */ static onAdCancel(mode: string, mode_type: string, mode_index: number, why: string) { this.trackEvent({ event: "ad_cancel", mode: mode, mode_type: mode_type, mode_index: mode_index, why: why, }); this.trackEvent_uma("videoAdCancel", { type: why, mode: mode_type, level: mode_index }); } /**广告完成 * @param mode 游戏模式("level"表示关卡模式,为后续拓展预留位置) * @param mode_type 对应模式的类型(例如"normal"表示普通关卡) * @param mode_index 对应模式的索引(例如:mode= "level" ,mode_type ="normal"时,等于关卡游戏模式,normal类型,的level_index,方便做关卡分析看板用) * @param why 广告原因(例如:"tips") */ static onAdFinish(mode: string, mode_type: string, mode_index: number, why: string) { this.trackEvent({ event: "ad_finish", mode: mode, mode_type: mode_type, mode_index: mode_index, why: why, }); this.trackEvent_uma("videoAd", { type: why, mode: mode_type, level: mode_index }); this.getIaaInfo(mode_type, why, true); } //#endregion //#region 时间获取接口 static getNowTime() { return new Date().getTime() - new Date().getTimezoneOffset() * 60000; } static getNowDay() { return Math.floor(this.getNowTime() / 86400000); } static getNowDateStr() { const now = new Date(this.getNowTime()); const year = now.getFullYear(); const month = now.getMonth() + 1; const day = now.getDate(); return year + "-" + (month < 10 ? "0" + month : month) + "-" + (day < 10 ? "0" + day : day); } //#endregion //#region 加密接口 static encrypted(encryptedData, iv, cb) { console.log("=========创游SDK上传用户加密数据"); this.trackEvent({ event: "encrypted_data", encryptedData: encryptedData, iv: iv }, (res) => { console.log("=========创游SDK用户加密数据返回", res); if (res.data) { cb(res.data); } } ); } //#endregion //#region iaa插件 static iaaData = { isOn: false, value: -1, today_play_duration: -1, weekly_login_cnt: -1, continuous_login_days: -1, value_exposure: -1, // 新增,本周期曝光次数,仅'labelId=='iaa_feature_v2'返回 }; static getIaaInfo(scene: string, type: string, isSuccess: boolean) { let value: number; let successCount: number; for (let i = 0; i < this.storageData.videoCount.length; i++) { let info = this.storageData.videoCount[i]; if (info.scene == scene && info.type == type) { if (isSuccess) { info.successTime++; } else { info.requestTime++; } value = info.requestTime; successCount = info.successTime; break; } } if (!value) { let info = new VideoCountInfo(scene, type); if (isSuccess) { info.successTime = 1; info.requestTime = 1; } else { info.successTime = 0; info.requestTime = 1; } value = info.requestTime; successCount = info.successTime; this.storageData.videoCount.push(info); } console.log("iaa广告次数计算,scene:", scene, "type:", type, "value:", value, "successCount:", successCount); this.saveCyStoreData(); if (!isSuccess) return; if (!window["miniGameCommon"] || !window["miniGameCommon"].getUserLabel) { console.warn("=========创游SDK iaa插件未接入,无法获取iaa信息"); return; } const data = { gift_scene: scene,//广告触发场景(如玩法单局内,局外签到,局外数值养成等) gift_name: type,//广告设计点(如单局内的复活卡、刷新卡,单局外养成资源等) gift_value: value,//游戏内用户点击该广告位的次数(指点击按钮的次数、无论用户是否成功拉起广告,都视为1次点击) gift_value_success: successCount//游戏内用户成功看完广告的次数 } const self = this; //30秒后执行 setTimeout(() => { window["miniGameCommon"].getUserLabel({ reportData: JSON.stringify(data),// reportdata是字符串,注意不要传入object labelId: 'iaa_feature_v2' // 注意labelid必填 iaa_feature,若传入其他labelId会报错 }).then((res) => { console.log("=========创游SDK iaa2.0插件获取信息成功", res); self.trackEvent_uma("iaa2First", { res: res }); if (res.encryptedData) { self.encrypted(res.encryptedData, res.iv, (data) => { self.iaaData.isOn = true; self.iaaData.value = data.value; self.iaaData.today_play_duration = data.today_play_duration; self.iaaData.weekly_login_cnt = data.weekly_login_cnt; self.iaaData.continuous_login_days = data.continuous_login_days; self.iaaData.value_exposure = data.value_exposure; self.trackEvent_uma("iaa2Second"); }); } }).catch((err) => { console.log("=========创游SDK iaa2.0插件获取失败", err); }); }, 30 * 1000); } //#endregion } class VideoCountInfo { scene: string; type: string; requestTime: number = 0; successTime: number = 0; constructor(scene, type) { this.scene = scene; this.type = type; this.requestTime = 0; this.successTime = 0; } } class cyStoreData { version: string; playTime: number; isNew: boolean; firstLoginDay: number; videoCount: VideoCountInfo[]; firstLoginDateStr: string; constructor(version, firstDay) { this.version = version; this.playTime = 0; this.isNew = true; this.firstLoginDay = firstDay; this.videoCount = []; this.firstLoginDateStr = CySDK.getNowDateStr(); } }