import { assetManager, game, sys } from "cc"; import { BYTEDANCE, DEBUG, DEV, EDITOR, WECHAT } from "cc/env"; import { RewardedVideoAd } from "./functions/RewardedVideoAd"; import { sdkConfig } from "./sdkConfig"; /** SDK 流程跟踪(默认关,sdkConfig.enableSdkTraceLog) */ const sdkTrace = (...args: unknown[]): void => { if (!sdkConfig.enableSdkTraceLog) return; const parts: string[] = []; for (let i = 0; i < args.length; i++) { const a = args[i]; if (a !== null && typeof a === 'object') continue; parts.push(String(a)); } if (parts.length > 0) console.log(parts.join(' ')); }; import GEMgr from "./GEMgr"; import { InterstitialAd } from "./functions/InterstitialAd"; import { VivoAd } from "./functions/VivoAd"; import TTRecording from "./functions/TTRecording"; import { Share } from "./functions/Share"; import Vibrate from "./functions/VibrateMgr"; import { TTRank } from "./functions/TTRank"; import { Singleton } from "../../mx/tools/Singleton"; import { createMembershipSet, membershipAdd, membershipHas, type MembershipSet, } from "../../mx/tools/MembershipSet"; import { ConstNumType, ItemNumType, StatisticsType, StatusType, TaskType, OtherDataType } from "../game/GameData"; import { GEvent } from "../../mx/module/event/GEvent"; import { MemoryDowngrade } from "../game/MemoryDowngrade"; import { PerfReport } from "../game/PerfReport"; import { UpdatePipeline } from "../../mx/module/boot/UpdatePipeline"; import { RankDataManager } from "../manager/RankDataManager"; import { MiniGamePf } from "../../mx/tools/MiniGameHostInfo"; /** 30r SDK 实例类型(实现见 Sdk30rWechat.ts,仅微信包加载) */ type Sdk30rInstance = { track(event: string, msg: unknown): void; onTutorialFinish(): void; onRegister?(): void; setOpenId(uid: string): void; getInitResult(): { inited: boolean }; }; /** 订阅消息场景(对应 platformConf.subscribeTmpl) */ type SubscribeTmplScene = 'offlineEarnings' | 'vigourRecovery' | 'versionUpdate'; /** 宿主启动 / onShow 参数(侧边栏、桌面场景) */ type PfLaunchRes = { from?: unknown; launch_from?: unknown; location?: unknown; scene?: unknown; query?: unknown; }; /** game.js 抢先缓存的 onShow */ type PfShowStore = { __pfShowCaptured?: boolean; __pfLastShow?: PfLaunchRes | null; }; /** * SDK管理器 */ export class SDKManager extends Singleton { /** * 抖音头条录屏相关 */ private Recording: TTRecording = null; /** * 分享相关 */ Share: Share = new Share(); /** * 震动 */ Vibrate = Vibrate; /** * 当前平台 */ pf = null; /** * 平台获取的查询参数 */ query = ""; showAd = true; upTestData = false; levelS: number[] = []; serverTime: number = 0; timer: number | null = null; sdk30r: Sdk30rInstance | null = null; adCount = 0; /** 推荐流直玩启动 */ private _feedDirectPlayLaunch = false; /** feed_game_channel:1 复访,2 获客 */ private _feedGameChannel = 0; /** feed_game_scene:0 获客,1 离线收益,2 体力,3 重要事件 */ private _feedGameScene = -1; /** feed_game_content_id */ private _feedGameContentId = ""; /** 是否已真实进入(feedEnter) */ private _feedActuallyEntered = false; /** reportScene(7001) 幂等 */ private _feedReportSceneDone = false; private _feedReportSceneInFlight = false; /** 预启动进关延后扣体力 */ private _feedPendingVigourDeduct = false; /** 最近一次 onShow 是否侧边栏进入(快手 from / 抖音 launch_from / B 站 021036) */ private _fromSidebar = false; /** 最近一次 onShow 是否 B 站桌面快捷方式(scene=10002) */ private _fromDesk = false; /** 分享最小停留(微信无回调) */ private _shareTimeLimit = 0; /** 分享结果回调 */ private _shareResultCb: Function | null = null; /** 添加桌面检测回调 */ private _addDeskCb: Function | undefined; /** 添加桌面操作回调 */ private _addShortcutCb: Function | undefined; /** vivo installShortcut 已成功,complete 不再当成取消 */ private _vivoShortcutOk = false; /** 侧边栏跳转回调 */ private _enterSidebarCb: Function | undefined; /** 侧边栏可用性回调 */ private _sidebarCheckCb: Function | undefined; /** 内存警告业务回调 */ private _memWarnCb: Function | null = null; /** getUserInfo Promise resolve */ private _userInfoResolve: ((v: unknown) => void) | null = null; /** 本会话已调起过的订阅场景(版本更新用存档按版本去重) */ private _subscribePrompted: MembershipSet = createMembershipSet(); private _feedStatusHandler: ((res: { type?: string }) => void) | null = null; private _feedTouchFallbackBound: (() => void) | null = null; /** 本进程是否已拉起过推荐流订阅面板(避免进大厅反复弹) */ private _feedSubscribePrompted = false; /** * 初始化 */ async init() { if (EDITOR || DEBUG || sys.isBrowser) { this.upTestData = true; return null; } this.pf = MiniGamePf.api(); if (!this.pf) { return null; } if (!MiniGamePf.isKs()) { this.initUpdate(); } this.capturePfShow(); if (typeof this.pf.onShow === 'function') { this.pf.onShow(this.onPfShow.bind(this)); } else { console.warn('[SDKManager] 宿主无 onShow'); } if (typeof this.pf.onHide === 'function') { this.pf.onHide(this.onPfHide.bind(this)); } else { console.warn('[SDKManager] 宿主无 onHide'); } this._applyLaunchOptions(this._readLatestLaunch()); this.setupFeedDirectPlay(); this.serverTime = new Date().getTime(); RewardedVideoAd.init(sdkConfig.config.platformConf.video); InterstitialAd.init(sdkConfig.config.platformConf.interstitialAd); this.Share.init(sdkConfig.config.platformConf.shareData); this.openWXShareMenu() if (sys.platform == sys.Platform.BYTEDANCE_MINI_GAME) { this.Recording = TTRecording.Ins; this.Recording.init(sdkConfig.config.platformConf); } if (MiniGamePf.isMy()) { console.warn('[SDKManager] 支付宝不走 ZYSDK,云存档与登录不可用,使用本地存档'); this._bindMemoryWarning(); return null; } ZYSDK.ZYSDK.initSdk(sdkConfig.config.pointData.gid) let serverData = null; let uid: any = "123456"; while (serverData == null) { gg.ui.showToast("获取游戏存档..."); uid = await ZYSDK.ZYSDK.getUserId(); sdkTrace("whileUid", uid); if (uid == null || uid == 1 || uid == "") await ZYSDK.ZYSDK.initSdk(sdkConfig.config.pointData.gid); serverData = await this.getServerData(); } let fun = () => { let str = serverData; if (serverData.length > 350) { str = serverData.substring(0, 350); } gg.sdk.reportDY("inLevel", `存档异常-${str}`); } if (serverData == "") { gg.sdk.reportDY("inLevel", `新玩家-${uid}`); } else if (serverData == undefined) { gg.sdk.reportDY("inLevel", `存档异常-undefined`); } else { try { let sd = JSON.parse(serverData); if (!Object.hasOwnProperty.call(sd, "uid")) { fun(); } } catch (error) { fun(); } } let v = ZYSDK.ZYSDK.getCustomValue(); if (v == 0) this.upTestData = false; else if (v == 100) this.upTestData = true; else { this.upTestData = Math.random() <= v / 100; } let lvs = await ZYSDK.ZYSDK.getLevel(); if (lvs && lvs.length > 0) { this.levelS = lvs; } // this.pf.login({ // force: true, // success(res) { // console.log(`login 调用成功${res.code} ${res.anonymousCode}`); // }, // fail(res) { // console.log(`login 调用失败`); // }, // }); if (this.timer != null) { window.clearInterval(this.timer); this.timer = null; } if (sdkConfig.serverTimeCheckInterval > 0) { await this.updateServerTime(); this.timer = window.setInterval(() => { this.updateServerTime(); }, sdkConfig.serverTimeCheckInterval); } this.checkTime((curentTime) => { if (curentTime) { console.error("时间异常"); gg.ui.showToast("时间异常,请检查时间设置"); gg.sdk.reportDY("inLevel", `时间异常-本地时间${curentTime}`); } }); this._bindMemoryWarning(); return serverData } /** 监听宿主内存警告。支付宝用 my.onMemoryWarning + my.getSystemInfoSync */ private _bindMemoryWarning(): void { this.onMemoryWarning(this._onMemoryWarnReport); } /** 内存警告:记机型并降级 */ private _onMemoryWarnReport = (): void => { try { const res = this.pf.getSystemInfoSync(); const brand = res.brand || res.system || ''; const model = res.model || ''; this.pfLog(`内存不足, 手机型号为 ${brand} ${model}`); MemoryDowngrade.onMemoryWarning(); PerfReport.reportMemWarn(MemoryDowngrade.warnCountInPhase, MemoryDowngrade.level); } catch (error) { sdkTrace(`获取系统信息失败`); MemoryDowngrade.onMemoryWarning(); PerfReport.reportMemWarn(MemoryDowngrade.warnCountInPhase, MemoryDowngrade.level); } }; /** 30r / DataNexus 仅微信小游戏;源码在 tools/wechat-only/,微信构建前由 tools 扩展复制进 assets */ initSdk30r() { if (!WECHAT || MiniGamePf.isKs() || MiniGamePf.isBl() || DEV || EDITOR || sys.isBrowser) return; const dnCfg = sdkConfig.config.dnSdk30r; if (!dnCfg?.user_action_set_id || !dnCfg?.secret_key) return; try { // 模块由 tools/wechat-only 在微信构建时复制进来;编辑期不存在,故用 ts-ignore // @ts-ignore - Sdk30rWechat 仅微信构建期存在 const { createSdk30r } = require('./Sdk30rWechat'); this.sdk30r = createSdk30r({ user_action_set_id: dnCfg.user_action_set_id, secret_key: dnCfg.secret_key, appid: sdkConfig.config.platformConf.appid, }); } catch (error) { console.error('initSdk30r error', String(error)); return; } const initResult = this.sdk30r?.getInitResult(); if (!initResult?.inited) { console.log('30r sdk 初始化失败', initResult); } else { sdkTrace('30r sdk 初始化成功'); } } checkTime(callback: Function = null) { if (!gg.sdk.pf || DEV || DEBUG || sys.isBrowser) { if (callback) { callback(); } return; } ZYSDK.ZYSDK.getTimestamp((res) => { if (res && res.ts) { let curentTime = new Date().getTime(); let servertime = res.ts * 1000; sdkTrace("servertime:", servertime); sdkTrace("curentTime:", curentTime); if (Math.abs(servertime - curentTime) > 1000 * 60 * 30) { callback(curentTime); } else { if (callback) { callback(); } } } }, () => { console.error("获取时间失败"); }); } /**获取服务器时间 */ getServerTime() { // console.log("本机时间毫秒数(UTC):", new Date().getTime()); // console.log("本机时间格式化(UTC):", new Date().toUTCString()); // console.log("本机时间格式化(本地):", new Date().toLocaleString()); // console.log("服务器时间毫秒数:", this.serverTime); // console.log("服务器时间格式化(UTC):", new Date(this.serverTime).toUTCString()); // console.log("服务器时间格式化(本地):", new Date(this.serverTime).toLocaleString()); if (DEV || DEBUG || sys.isBrowser || sdkConfig.serverTimeCheckInterval == 0) { let time = new Date().getTime(); //console.log("走了本地时间:", time, "serverTimeCheckInterval:", sdkConfig.serverTimeCheckInterval); return time; } return this.serverTime; } /**更新服务器时间 */ updateServerTime() { return new Promise((resolve, reject) => { if (!sdkConfig.serverTimeCheckInterval) { this.serverTime = Date.now(); resolve(null); return; } ZYSDK.ZYSDK.getTimestamp((res) => { if (res && res.ts) { let num = res.ts * 1000; this.serverTime = num; resolve(null); } }, () => { resolve(null); }); }); } initUpdate() { if (typeof this.pf?.getUpdateManager !== 'function') { console.warn('[SDKManager] 宿主无 getUpdateManager'); return; } UpdatePipeline.bind( this.pf.getUpdateManager(), (opts) => this.pf.showModal(opts), (opts) => this.pf.showToast(opts), ); } /** 重启小游戏。有 Sync 用 Sync;支付宝是 my.restartMiniProgram */ restartMiniProgramSync() { const pf = this.pf; if (!pf) return; if (typeof pf.restartMiniProgramSync === 'function') { pf.restartMiniProgramSync(); return; } if (typeof pf.restartMiniProgram === 'function') { pf.restartMiniProgram({}); return; } console.warn('[SDKManager] 宿主无重启接口'); } getServerData() { if (EDITOR || DEBUG || sys.platform == sys.Platform.DESKTOP_BROWSER || sys.platform == sys.Platform.MOBILE_BROWSER) { return new Promise((resolve, reject) => { resolve(""); }) } return new Promise((resolve, reject) => { ZYSDK.ZYSDK.getGameData((data) => { resolve(data); }, async () => { resolve(null); }) }) } /** * 当前平台进入前台 * @param res */ onPfShow(res) { sdkTrace("onPfShow", res); this.query = res.query; this._show_time = new Date().getTime(); RewardedVideoAd.onAppShow(); GEvent.Ins.emit(GEvent.PLATFORM_SHOW, res); if (this._onShare) { let t = new Date().getTime() - this._share_time; this._onShare(t); this._onShare = null; } this._applyLaunchOptions(res); } private _show_time = 0; /** * 当前平台进入后台 */ onPfHide() { let c = new Date().getTime(); let t = c - this._show_time; t = Math.floor(t / 1000); sdkTrace("onPfHide,在线时长:" + t); GEvent.Ins.emit(GEvent.PLATFORM_HIDE); } /** * 显示视频广告 * @param callback 回调 */ showVideoAd(callback: Function) { sdkTrace('显示视频广告'); if (gg.data.getItemNum(ItemNumType.skipAd) > 0) { gg.data.subItemNum(ItemNumType.skipAd, 1); callback(true, "skipAd"); } else if (EDITOR || !this.showAd) { gg.data.addDayTaskProgressData(TaskType.videoAd, 1) callback(true, "editor"); } else if (sys.isBrowser) { gg.data.addDayTaskProgressData(TaskType.videoAd, 1) callback(true, window.location.href); } else { gg.audio.pause(); game.pause(); RewardedVideoAd.show((res) => { game.resume(); gg.audio.resume(); if (res) { this.adCount++; callback(true); gg.data.addStatistics(StatisticsType.TodayAdCount, 1); gg.data.addDayTaskProgressData(TaskType.videoAd, 1) ZYSDK.ZYSDK.reportVideo(true); } else { callback(false); ZYSDK.ZYSDK.reportVideo(false); } }); } } /** * 上次显示插屏广告时间戳 */ private _interAdTimeStamp = 0; /** * 显示插屏广告 * @param onAdClose * @param onFail */ showInterstitialAd(onAdClose?: Function, onFail?: Function, useAdLevel = false) { if (EDITOR) { onAdClose && onAdClose(true, "editor"); } else if (sys.isBrowser) { onAdClose && onAdClose(true, window.location.href); } else if (VivoAd.isVivo()) { this._showVivoNative(onFail); } else { if (!useAdLevel || ZYSDK.ZYSDK.getAdLevel() == 1) { //判断是否间隔x秒 let curTime = new Date().getTime(); if (curTime - this._interAdTimeStamp < ZYSDK.ZYSDK.getAdInterval() * 1000) { onFail && onFail(); return; } this._interAdTimeStamp = curTime; InterstitialAd.showInterstitialAd(onAdClose, onFail, () => { this.reportDY("inLevel", `插屏-段位${RankDataManager.getPlayerRankId()}`); }); } } } /** * vivo 结算原生模板。与插屏共用冷却,不走 ZYSDK 广告等级。 * @param onFail 冷却中或未展示 */ private _showVivoNative(onFail?: Function): void { const curTime = new Date().getTime(); if (curTime - this._interAdTimeStamp < ZYSDK.ZYSDK.getAdInterval() * 1000) { onFail?.(); return; } if (VivoAd.showNative()) this._interAdTimeStamp = curTime; else onFail?.(); } /** * vivo 退出浮层。其他渠道立即回调。 * @param onDone 关闭、失败或未展示后继续退出 */ showVivoExitAd(onDone: () => void): void { VivoAd.showExit(onDone); } /** * 设置排行榜 * @param value 分数 * @param dataType 数据类型 0 数字类型 1 字符串类型 * @param priority 权重 * @param zoneId 排行榜分区 */ setImRankData(value: number | string, dataType = 0, priority = 0, zoneId = "default") { if (EDITOR) { sdkTrace('平台无法写入排行榜数据') } else if (sys.isBrowser) { sdkTrace('平台无法写入排行榜数据') } else { TTRank.setImRankData(value, dataType, priority, zoneId) } } /** * 获取排行榜 * @param rankTitle 排行榜标题 * @param suffix 分数后缀补充文案 为空或不填,一般枚举类型不需要填后缀 * @param rankType 排行榜类型 可选值有:day、week、month、month、all * @param zoneId 排行榜分区 * @param dataType 数据类型: 0 数字类型 1 字符串类型 * @param relationType 排行榜类型 可选值有:default 好友榜、总榜都展示 friend 仅展示好友榜 all 仅展示总榜 */ getImRankList(rankTitle: string = "排行榜", suffix = "", rankType = "all", zoneId = "default", dataType = 0, relationType = "default") { if (EDITOR) { sdkTrace('平台无法获取排行榜数据') } else if (sys.isBrowser) { sdkTrace('平台无法获取排行榜数据') } else { TTRank.getImRankList(rankTitle, suffix, rankType, zoneId, dataType, relationType) } } /** * 运营 / 关卡行为打点(进 Gravity userAction) */ reportDY(event, msg) { if (sdkConfig.enableConsolePfLog) { console.log("%c打点==", "color:green", msg); } if (MiniGamePf.canReport()) { ZYSDK.ZYSDK.reportUserAction(msg); void GEMgr.GEReportEvent(msg); } } /** * 性能辅助打点(非运营):PF- 前缀,不进 Gravity,避免污染广告/关卡统计 */ reportPerf(msg: string): void { if (!msg.startsWith('PF-')) { console.log('[reportPerf] 非法格式,须 PF- 开头', msg); return; } if (/-AD-/.test(msg) || msg.includes('广告')) { console.error('[reportPerf] 拒绝:疑似运营广告口径', msg); return; } if (sdkConfig.enableConsolePfLog) { sdkTrace('%c性能打点==', 'color:#00bcd4', msg); } try { this.pfLog?.(msg); } catch { /* ignore */ } if (MiniGamePf.canReport()) { ZYSDK.ZYSDK.reportUserAction(msg); } } /** * 上报30r打点 * @param event * @param msg */ report30r(event, msg) { if (this.sdk30r) { this.sdk30r.track(event, msg); } } /**30r-完成新手引导 */ on30rTutorialFinish() { if (this.sdk30r) { this.sdk30r.onTutorialFinish(); } } /**30r-获取关卡模式 */ get30rStageType() { return "level"; } /**30r-获取关卡id(与现有章节打点口径保持一致) */ get30rLevelId() { return gg?.game?.CurentBattle?.getReportDYLevelId?.() ?? gg?.game?.CurentSelectLevelId ?? 0; } /**30r-广告次数(当前项目仅维护是否看过广告,按 0/1 上报) */ get30rAdCount() { return this.adCount; } /** * 进入侧边栏(快手 / 抖音 / B 站均为 navigateToScene({ scene: 'sidebar' })) * @param callback 跳转结果 */ enterSidebar(callback?: Function) { const host = this._pfHost(); if (typeof host?.navigateToScene === 'function') { this._enterSidebarCb = callback; host.navigateToScene({ scene: "sidebar", success: this._onNavSceneOk, fail: this._onNavSceneFail, }); return; } callback?.(false); } /** 最近一次启动/回前台是否侧边栏 */ isFromSidebar(): boolean { return this._fromSidebar; } /** 最近一次启动/回前台是否 B 站桌面快捷方式 */ isFromDesk(): boolean { return this._fromDesk; } /** * 尽早监听宿主 onShow,避免 B 站侧边栏热返回丢场景值。 * game.js 模板也会抢先写入 globalThis.__pfLastShow。 */ capturePfShow(): void { if (!MiniGamePf.isBl()) return; const host = MiniGamePf.api(); if (!host?.onShow) return; const store = globalThis as PfShowStore; if (!store.__pfShowCaptured) { store.__pfShowCaptured = true; try { store.__pfLastShow = host.getEnterOptionsSync?.() ?? host.getLaunchOptionsSync?.() ?? null; } catch { store.__pfLastShow = null; } host.onShow(this._onEarlyPfShow); } this._applyLaunchOptions(this._readLatestLaunch()); } /** * 当前宿主是否支持侧边栏。 * 快手:checkSliderBarIsAvailable 在未备案/开发者工具常返回 available=false 或不回调,不能用来藏按钮;入口由 showSidebar 控制,点击再走 navigateToScene。 * 抖音/B 站:checkScene → isExist。其余:有 navigateToScene 则显示。 * @param callback true 则展示入口 */ checkSidebar(callback?: Function) { if (EDITOR || DEBUG || sys.isBrowser) { callback?.(true); return; } if (this._fromSidebar) { callback?.(true); return; } if (MiniGamePf.isKs()) { callback?.(true); return; } const host = this._pfHost(); if (!host) { callback?.(false); return; } if (typeof host.checkScene === 'function') { this._callSidebarCheck(host.checkScene.bind(host), callback, { scene: 'sidebar' }); return; } callback?.(typeof host.navigateToScene === 'function'); } /** 当前平台宿主;快手兼容微信包优先 window.ks,避免拿到残缺 wx 包装 */ private _pfHost(): Record | null { if (MiniGamePf.isKs()) { const g = globalThis as unknown as { ks?: Record; GameGlobal?: { ks?: Record } }; if (g.ks && typeof g.ks === 'object') return g.ks; if (g.GameGlobal?.ks && typeof g.GameGlobal.ks === 'object') return g.GameGlobal.ks; } return MiniGamePf.api(); } /** * 调用宿主侧边栏检测 * @param fn 宿主检测方法 * @param callback 业务回调 * @param extra 抖音/B 站 checkScene 需带 scene */ private _callSidebarCheck(fn: (opts: object) => void, callback?: Function, extra?: { scene: string }): void { this._sidebarCheckCb = callback; const opts: Record = { success: this._onSidebarCheckOk, fail: this._onSidebarCheckFail, }; if (extra?.scene) opts.scene = extra.scene; try { fn(opts); } catch (err) { this._sidebarCheckCb = undefined; console.warn('[Sidebar] 检测接口异常', String(err)); callback?.(typeof this._pfHost()?.navigateToScene === 'function'); } } /** 快手 available;抖音 / B 站 isExist */ private _parseSidebarCheck(result: { available?: unknown; isAvailable?: unknown; isExist?: unknown } | boolean | null | undefined): boolean { if (result === true) return true; if (!result || typeof result !== 'object') return false; return result.available === true || result.available === 1 || result.isAvailable === true || result.isExist === true; } /** * 验证是否需要显示添加到桌面按钮 * @param callback */ checkNeedShowAddShortcut(callback?: Function) { if (EDITOR || DEBUG || sys.isBrowser) { callback?.(true); return; } if (sdkConfig.config.chanId === 'vivo') { this._queryVivoShortcut(callback); return; } const pf = MiniGamePf.api(); if (!pf?.addShortcut) { callback?.(false); return; } if (MiniGamePf.isKs() && typeof pf.checkShortcut === 'function') { this._addDeskCb = callback; pf.checkShortcut({ success: this._onCheckShortcutOk, fail: this._onCheckShortcutFail, }); return; } callback?.(true); } /** * 分享游戏 * @param timeLimit 分享最小停留时间, 没有回调的平台通过计时判断是否分享成功 * @param cb 有回调的平台,通过回调函数判断是否分享成功 * @param data 可选参数,分享数据(需要知道参数类型) */ share(timeLimit, cb = null, data = null) { this._share_time = new Date().getTime(); this._shareTimeLimit = timeLimit; this._shareResultCb = cb; if (!this.Share) { console.warn('[SDKManager.share] Share 未初始化'); cb?.(false); return; } let desc = "" let url = "" let query = "" let id = ""; if (data && data.desc) desc = data.desc; if (data && data.res) url = data.res; if (data && data.query) query = data.query; if (data && data.shareid) id = data.shareid; if (!id && data && data.ks) id = data.ks; if (!id && data && data.tt) id = data.tt; const pf = MiniGamePf.id(); if (pf === 'wx' || pf === 'bl') { this._onShare = this._onShareReturn; this.Share.share(desc, url, cb, query); return; } if (pf === 'tt' || pf === 'ks') { this.Share.share(desc, url, cb, query, id); return; } console.warn('[SDKManager.share] 当前平台无分享', pf); cb?.(false); } private _share_time = 0; private _onShare = null; /** * 短时间震动 */ vibrateShort() { this.Vibrate.vibrateShort(); } /** * 长时间震动 */ vibrateLong() { this.Vibrate.vibrateLong(); } /** * 开始震动 */ startVibrate() { this.Vibrate.startVibrate(); } /** * 停止震动 */ stopVibrate() { this.Vibrate.stopVibrate(); } /** * 添加到桌面 * @param callback 回调 */ addShortcut(callback?: Function) { if (sdkConfig.config.chanId === 'vivo') { this._installVivoShortcut(callback); return; } const pf = MiniGamePf.api(); if (!pf?.addShortcut) { if (callback) callback(false); return; } this._addShortcutCb = callback; pf.addShortcut({ success: this._onAddShortcutOk, fail: this._onAddShortcutFail, }); } getVideoPath() { return this.Recording?.getVideoPath(); } getVideoTime() { return this.Recording?.getVideoTime(); } getStatus() { return this.Recording?.getStatus(); } /** * 开始录屏 */ startRecord() { this.Recording && this.Recording.startVideo(); } /** * 停止录屏 */ stopRecord() { this.Recording && this.Recording.stopVideo(); } /** * 分享录屏 */ shareRecord(callback = null) { if (this.Recording) this.Recording.shareVideo(callback); else callback(true); } pauseVideo() { this.Recording && this.Recording.pauseVideo(); } resumeVideo() { this.Recording && this.Recording.resumeVideo(); } /** * 微信转发菜单 */ openWXShareMenu() { if (MiniGamePf.id() === 'wx') { window["wx"].showShareMenu({ withShareTicket: true, menus: ['shareAppMessage', 'shareTimeline'], success: () => { }, fail: () => { }, complete: () => { } }); window["wx"].onShareAppMessage(function () { let share1 = { title: sdkConfig.config.platformConf.wxOnShareMessage.title, imageUrlId: sdkConfig.config.platformConf.wxOnShareMessage.imageUrlId, imageUrl: sdkConfig.config.platformConf.wxOnShareMessage.imageUrl } // let share2 = { // title: "看不见下雨了嘛,赶紧回笼呀!", // imageUrlId: 'Y2PHkx0bR0Ou2a25kg+Vsg==', // imageUrl: 'https://mmocgame.qpic.cn/wechatgame/WFTmogtfYrIwyhRVuZHbEs709mroiaaDcYkwBSBXLckR7ibQNPfrXKstvCpyV4bmyn/0' // } // let share3 = { // title: "快救救这只猪", // imageUrlId: 'qTQmv6/8SCixA6raayq2HA==', // imageUrl: 'https://mmocgame.qpic.cn/wechatgame/qqRAxsORaczSj5Z33DB4pneEtpOBstVwzhsk8VPh02OlTGHzPHBxicaffB09ILdgl/0' // } // return Math.random() > 0.5 ? share1 : Math.random() > 0.5 ? share2 : share3; return share1 }); if (gg.data.getStatistics(StatisticsType.yonghuxieyiYS) == 0) { window["wx"].getPrivacySetting({ success: (res) => { sdkTrace("是否需要授权:", res.needAuthorization); sdkTrace("隐私协议名称:", res.privacyContractName); if (res.needAuthorization) { window["wx"].requirePrivacyAuthorize({ success: () => { // 用户同意授权 // runGame() 继续游戏逻辑 sdkTrace("用户同意授权"); gg.data.setStatistics(StatisticsType.yonghuxieyiYS, 2); }, fail: () => { sdkTrace("用户拒绝授权"); gg.data.setStatistics(StatisticsType.yonghuxieyiYS, 1); }, // 用户拒绝授权 complete: () => { } }) } else { } }, fail: () => { // 低版本基础库不支持时的处理 } }); } } } getUserInfo() { return new Promise((resolve) => { if (this._userInfo) { resolve(this._userInfo); return; } if (!this.pf || !this.pf["getUserInfo"]) { resolve(null); return; } this._userInfoResolve = resolve; this.pf["getUserInfo"]({ success: this._onGetUserInfoOk, fail: this._onGetUserInfoFail, }); }); } private _userInfo: any = null; private _getSubscribeTmplId(scene: SubscribeTmplScene): string { const t = sdkConfig.config.platformConf?.subscribeTmpl; if (!t) return ''; switch (scene) { case 'offlineEarnings': return t.offlineEarnings ?? ''; case 'vigourRecovery': return t.vigourRecovery ?? ''; case 'versionUpdate': return t.versionUpdate ?? ''; } } /** 离线收益领取提醒 */ requestSubscribeOfflineEarnings(force = false): void { this._requestSubscribeScene('offlineEarnings', force); } /** 体力恢复提醒 */ requestSubscribeVigourRecovery(force = false): void { this._requestSubscribeScene('vigourRecovery', force); } /** 版本更新提醒(须单独调起,不可与其它模板同批) */ requestSubscribeVersionUpdate(force = false): void { this._requestSubscribeScene('versionUpdate', force); } private _requestSubscribeScene(scene: SubscribeTmplScene, force = false): void { if (DEV || EDITOR || sys.isBrowser) return; const tmplId = this._getSubscribeTmplId(scene); if (!tmplId) { sdkTrace('[SubscribeMessage] skip: 未配置', scene); return; } const version = sdkConfig.config.vsion ?? ''; if (scene === 'versionUpdate') { const prompted = gg.data?.getStringData?.(OtherDataType.SubscribeVersionPrompted) ?? ''; if (!force && prompted === version) return; } else if (!force && membershipHas(this._subscribePrompted, scene)) { return; } const pf = this.pf; if (!pf?.requestSubscribeMessage) { sdkTrace('[SubscribeMessage] skip: 当前平台不支持 requestSubscribeMessage'); return; } if (pf.canIUse && !pf.canIUse('requestSubscribeMessage')) { sdkTrace('[SubscribeMessage] skip: canIUse(requestSubscribeMessage)=false'); return; } if (scene !== 'versionUpdate') { membershipAdd(this._subscribePrompted, scene); } sdkTrace('[SubscribeMessage] request', scene, tmplId); pf.requestSubscribeMessage({ tmplIds: [tmplId], fail: (err: { errMsg?: string; errCode?: number }) => { sdkTrace('[SubscribeMessage] fail', scene, err?.errMsg ?? err); }, complete: (res: Record) => { sdkTrace('[SubscribeMessage] complete', scene, res); this._notifySubscribeAccept([tmplId], res); if (scene === 'versionUpdate' && version) { gg.data.setStringData(OtherDataType.SubscribeVersionPrompted, version); } }, }); } /** @deprecated 请用 requestSubscribeOfflineEarnings / VigourRecovery / VersionUpdate */ requestSubscribeMessage(_force = false): void { this.requestSubscribeOfflineEarnings(_force); } /** 用户同意订阅后通知 ZYSDK 后台 */ private _notifySubscribeAccept(tmplIds: string[], res: Record): void { if (!res) return; for (const id of tmplIds) { if (res[id] === 'accept') { try { ZYSDK.ZYSDK.subscribe(id); } catch (e) { sdkTrace('[SubscribeMessage] ZYSDK.subscribe error', id, e); } } } } //推荐流复访订阅(只用复访 Content_ID;本项目方案=体力恢复 scene=2) PlatformFeedSubscribe() { sdkTrace('PlatformFeedSubscribe') if (this._feedSubscribePrompted) { sdkTrace('PlatformFeedSubscribe skip: already prompted this session'); return; } let self = this if (this.pf && window["tt"]) { sdkTrace('PlatformFeedSubscribe1') // 1离线 / 2体力 / 3重要事件,本项目方案=2 体力恢复 let sceneId = this.getRevisitFeedScene(); if (this.pf.canIUse("checkFeedSubscribeStatus")) { sdkTrace('PlatformFeedSubscribe2') this.pf.checkFeedSubscribeStatus({ type: "play", scene: sceneId, success(res) { sdkTrace("checkFeedSubscribeStatus:" + res.status) if (!res.status) { let contentIDs = self.resolveFeedContentIds("revisit"); sdkTrace("contentIDs(复访):", contentIDs, "scene:", sceneId) if (!contentIDs.length) { sdkTrace("requestFeedSubscribe skip: missing revisit contentIDs"); return; } self._feedSubscribePrompted = true; self.pf.requestFeedSubscribe({ type: "play", scene: sceneId, contentIDs: contentIDs, success(res) { sdkTrace("requestFeedSubscribe:" + res.success) }, fail(res) { sdkTrace(res.errMsg) }, }) } else { self._feedSubscribePrompted = true; } }, fail(res) { sdkTrace(res.errMsg) }, }) } } } /** 是否推荐流直玩启动 */ isFeedDirectPlayLaunch(): boolean { return this._feedDirectPlayLaunch; } /** 推荐流预启动(直玩启动但尚未 feedEnter) */ isFeedPreboot(): boolean { return this._feedDirectPlayLaunch && !this._feedActuallyEntered; } /** 是否获客直玩 */ isFeedAcquireLaunch(): boolean { return this.resolveFeedCapability() === "acquire"; } /** 是否复访直玩 */ isFeedRevisitLaunch(): boolean { return this.resolveFeedCapability() === "revisit"; } /** 获客应直接进可玩场景 */ shouldFeedEnterPlayScene(): boolean { return this._feedDirectPlayLaunch && this.isFeedAcquireLaunch(); } /** 首屏可交互时上报 reportScene(7001),内部幂等 */ notifyFeedGameInteractive() { if (!this._feedDirectPlayLaunch) { return; } sdkTrace( "SDK 推荐流场景可交互", this.resolveFeedCapability(), this._feedGameContentId || "(none)", "scene", this._feedGameScene, "channel", this._feedGameChannel ); this.PlatformReportScene(); } /** 复访 scene:本项目方案=2(体力恢复);仅当启动 query 显式给出 1|2|3 时用平台值 */ private getRevisitFeedScene(): number { return this._feedGameScene === 1 || this._feedGameScene === 2 || this._feedGameScene === 3 ? this._feedGameScene : 2; } private uniqContentIds(list: Array): string[] { const ids: string[] = []; for (const raw of list) { if (typeof raw !== 'string') { continue; } const v = raw.trim(); if (v && !ids.includes(v)) { ids.push(v); } } return ids; } private getConfiguredRevisitContentIds(): string[] { return this.uniqContentIds(sdkConfig.config.platformConf?.contentIDs || []); } private getConfiguredAcquireContentIds(): string[] { return this.uniqContentIds(sdkConfig.config.platformConf?.acquireContentIDs || []); } private toFeedFiniteNumber(raw: unknown): number { if (raw === "" || raw === undefined || raw === null) { return NaN; } const n = Number(raw); return Number.isFinite(n) ? n : NaN; } private isConfiguredRevisitContentId(contentId: string): boolean { return !!contentId && this.getConfiguredRevisitContentIds().includes(contentId); } private isConfiguredAcquireContentId(contentId: string): boolean { return !!contentId && this.getConfiguredAcquireContentIds().includes(contentId); } /** 获客:channel=2 / scene=0 / 获客 Content_ID;复访:channel=1 / scene=1|2|3 / 复访 Content_ID */ private resolveFeedCapability(): "acquire" | "revisit" | "unknown" { if (!this._feedDirectPlayLaunch) { return "unknown"; } if (this._feedGameChannel === 2 || this._feedGameScene === 0 || this.isConfiguredAcquireContentId(this._feedGameContentId)) { return "acquire"; } if ( this._feedGameChannel === 1 || this._feedGameScene === 1 || this._feedGameScene === 2 || this._feedGameScene === 3 || this.isConfiguredRevisitContentId(this._feedGameContentId) ) { return "revisit"; } return "unknown"; } private resolveFeedContentIds(kind: "revisit" | "acquire"): string[] { const configured = kind === "revisit" ? this.getConfiguredRevisitContentIds() : this.getConfiguredAcquireContentIds(); const launchId = this._feedGameContentId; const launchMatches = (kind === "revisit" && this.isConfiguredRevisitContentId(launchId)) || (kind === "acquire" && this.isConfiguredAcquireContentId(launchId)); return this.uniqContentIds(launchMatches ? [launchId, ...configured] : configured); } private setupFeedDirectPlay() { if (sys.platform !== sys.Platform.BYTEDANCE_MINI_GAME || !this.pf) { return; } const launch = this.getLaunchOptionsSync_custom(); const scene = launch?.scene != null ? String(launch.scene) : ""; const query = launch?.query || {}; const contentId = query.feed_game_content_id != null ? String(query.feed_game_content_id) : ""; // 3041=推荐流直玩,9999=直玩测试入口 const isFeedScene = scene.includes("3041") || scene.includes("9999") || !!contentId || query.feed_game_scene != null; if (!isFeedScene) { return; } this._feedDirectPlayLaunch = true; this._feedGameContentId = contentId; const feedScene = this.toFeedFiniteNumber(query.feed_game_scene); this._feedGameScene = Number.isFinite(feedScene) ? feedScene : -1; let channel = this.toFeedFiniteNumber(query.feed_game_channel); if (!Number.isFinite(channel) || channel <= 0) { if (this._feedGameScene === 0 || this.isConfiguredAcquireContentId(contentId)) { channel = 2; } else if ( this._feedGameScene === 1 || this._feedGameScene === 2 || this._feedGameScene === 3 || this.isConfiguredRevisitContentId(contentId) ) { channel = 1; } else { channel = 0; } } this._feedGameChannel = channel; // scene 缺失时补齐:获客=0,复访默认体力恢复=2 if (this._feedGameScene < 0) { if (this.resolveFeedCapability() === "acquire") { this._feedGameScene = 0; } else if (this.resolveFeedCapability() === "revisit") { this._feedGameScene = 2; } } const capability = this.resolveFeedCapability(); sdkTrace( "SDK 推荐流直玩启动", scene, capability, "channel", this._feedGameChannel, "feed_game_scene", this._feedGameScene, "contentId", this._feedGameContentId || "(none)" ); if (this._feedGameContentId) { this.reportDY("inLevel", `推荐流-方案-${capability}-${this._feedGameContentId}`); } this.reportDY("inLevel", this.feedChannelLabel("预启动")); this._feedStatusHandler = (res: { type?: string }) => { const type = res?.type; sdkTrace("SDK onFeedStatusChange", type); if (type === "feedEnter") { this.markFeedActuallyEntered("feedEnter"); } else if (type === "feedExit") { sdkTrace("SDK 推荐流退出回推荐流"); } }; try { if (typeof this.pf.onFeedStatusChange === "function") { this.pf.onFeedStatusChange(this._feedStatusHandler); } else { console.log("SDK onFeedStatusChange 不可用,启用触摸兜底"); this.bindFeedTouchFallback(); } } catch (e) { console.log("SDK 注册 onFeedStatusChange 失败,启用触摸兜底", String(e)); this.bindFeedTouchFallback(); } } private feedChannelLabel(stage: "预启动" | "活跃"): string { const capability = this.resolveFeedCapability(); if (capability === "revisit" || this._feedGameChannel === 1) { return stage === "预启动" ? "推荐流-预启动-复访用户" : "推荐流-复访用户"; } if (capability === "acquire" || this._feedGameChannel === 2) { return stage === "预启动" ? "推荐流-预启动-获客用户" : "推荐流-获客用户"; } return stage === "预启动" ? "推荐流-预启动" : "推荐流-活跃用户"; } private markFeedActuallyEntered(source: string) { if (this._feedActuallyEntered) { return; } this._feedActuallyEntered = true; this.unbindFeedTouchFallback(); sdkTrace("SDK 推荐流真实进入", source, this.resolveFeedCapability(), this._feedGameChannel); this.reportDY("inLevel", this.feedChannelLabel("活跃")); if (this._feedPendingVigourDeduct) { this._feedPendingVigourDeduct = false; gg.data.subVigour(gg.data.project.vigourCost); } if (this.isFeedRevisitLaunch()) { this.storeFeedData(); } } /** 预启动进关未扣体力,等 feedEnter 再扣 */ markFeedPendingVigourDeduct() { this._feedPendingVigourDeduct = true; } private bindFeedTouchFallback() { if (this._feedTouchFallbackBound || this._feedActuallyEntered) { return; } this._feedTouchFallbackBound = () => { this.markFeedActuallyEntered("touchFallback"); }; try { if (typeof this.pf?.onTouchEnd === "function") { this.pf.onTouchEnd(this._feedTouchFallbackBound); } else if (typeof document !== "undefined") { document.addEventListener("touchend", this._feedTouchFallbackBound, { once: true, capture: true }); document.addEventListener("click", this._feedTouchFallbackBound, { once: true, capture: true }); } } catch (e) { console.log("SDK 绑定推荐流触摸兜底失败", String(e)); } } private unbindFeedTouchFallback() { if (!this._feedTouchFallbackBound) { return; } try { if (typeof this.pf?.offTouchEnd === "function") { this.pf.offTouchEnd(this._feedTouchFallbackBound); } if (typeof document !== "undefined") { document.removeEventListener("touchend", this._feedTouchFallbackBound, true); document.removeEventListener("click", this._feedTouchFallbackBound, true); } } catch (_) { /* ignore */ } this._feedTouchFallbackBound = null; } /** 推荐流上报场景(sceneId 7001,幂等;失败可重试) */ PlatformReportScene() { if (this._feedReportSceneDone || this._feedReportSceneInFlight) { return; } if (this.pf && this.pf.reportScene) { this._feedReportSceneInFlight = true; const self = this; this.pf.reportScene({ sceneId: 7001, costTime: 2000, success(res) { self._feedReportSceneInFlight = false; self._feedReportSceneDone = true; sdkTrace("ReportScene_success:", res); }, fail(res) { self._feedReportSceneInFlight = false; console.log("ReportScene_fail:"); }, }); } } /** * 体力恢复场景就绪时刻(毫秒时间戳)。 * 官方托管:体力恢复到满值时判定就绪;已满=立刻就绪(rightValue=now), * 不可 skip——否则平台侧无就绪数据,测试模式报「不存在相关就绪数据」。 */ private calcVigourFullAtMs(): number { try { const current = gg.data.getVigour(); const maxVigour = gg.data.table.getConst(ConstNumType.MaxVigour); const recoverSec = gg.data.table.getConst(ConstNumType.VigourRecoverSec); // 配表异常兜底:仍上报,避免平台永远查不到就绪数据 if (!(maxVigour > 0) || !(recoverSec > 0)) { sdkTrace('calcVigourFullAtMs fallback: invalid recover config', maxVigour, recoverSec); return Date.now(); } if (current >= maxVigour) { return Date.now(); } const need = maxVigour - current; const nextSec = gg.data.project.getAddVigourTime(); const remainSec = Math.max(0, nextSec) + Math.max(0, need - 1) * recoverSec; return Date.now() + remainSec * 1000; } catch (e) { sdkTrace('calcVigourFullAtMs fail', String(e)); return Date.now(); } } storeFeedData() { if (this._feedDirectPlayLaunch && this.isFeedAcquireLaunch()) { sdkTrace("storeFeedData skip: acquire feed"); return; } if (this.isFeedPreboot()) { sdkTrace("storeFeedData skip: feed preboot"); return; } if (this.pf && this.pf.storeFeedData) { const contentID = this.resolveFeedContentIds("revisit")[0]; if (!contentID) { sdkTrace('storeFeedData skip: missing revisit contentIDs'); return; } const scene = this.getRevisitFeedScene(); // 本项目方案=2 体力恢复:就绪=体力回满;已满则 rightValue=now(立刻就绪) const fullAtMs = this.calcVigourFullAtMs(); let params = { scene, // 1,离线收益 2,体力恢复 3,重要事件 contentID, leftValue: 'timeStampMs', operator: '>=', rightValue: String(fullAtMs), status: 1, success(res) { sdkTrace('storeFeedData success', res) }, fail(res) { sdkTrace(res.errMsg || res) }, } sdkTrace("使用参数:" + JSON.stringify(params)); this.pf.storeFeedData(params); } } getLaunchOptionsSync_custom() { if (this.pf && this.pf.getLaunchOptionsSync) { let obj = this.pf.getLaunchOptionsSync(); let str = JSON.stringify(obj); sdkTrace("getLaunchOptionsSync_custom: 场景值", str) ///obj.scene return obj } else { return null } } /**平台记录日志 */ pfLog(msg: string) { if (this.pf && this.pf.getLogManager) { this.pf.getLogManager().log(msg); } else { sdkTrace(msg); } } /**平台记录调试日志 */ pfDebug(msg: string) { if (this.pf && this.pf.getLogManager) { this.pf.getLogManager().debug(msg); } else { console.debug(msg); } } ttWorker(callback) { // if (this.pf && this.pf.createWorker) { // let worker = this.pf.createWorker({ // scriptPath: "worker.js", // success: (res) => { // console.log("createWorker success", res); // callback(res); // }, // fail: (res) => { // console.log("createWorker fail", res); // }, // }); // } } /** 设置屏幕常亮(抖音 tt / 微信 wx 等小游戏) */ setKeepScreenOn(keep: boolean): void { if (EDITOR || DEBUG || sys.isBrowser) return; const api = this.pf?.setKeepScreenOn; if (!api) return; api.call(this.pf, { keepScreenOn: keep, fail: (err: { errMsg?: string }) => { sdkTrace('setKeepScreenOn fail', keep, err?.errMsg ?? err); }, }); } /**平台触发垃圾回收 */ pfTriggerGC() { if (this.pf && this.pf.triggerGC) { this.pf.triggerGC(); } } /** 平台内存警告(微信 / 抖音 / 快手 / 快应用 / 支付宝 my.onMemoryWarning) */ onMemoryWarning(callback: Function = null) { this._memWarnCb = callback; let bound = false; if (this.pf?.onMemoryWarning) { this.pf.onMemoryWarning(this._onMemWarn); bound = true; } const ks = window['ks']; if (!bound && ks?.onMemoryWarning && ks !== this.pf) { ks.onMemoryWarning(this._onMemWarn); bound = true; } const tt = window['tt']; if (!bound && tt?.onMemoryWarning && tt !== this.pf) { tt.onMemoryWarning(this._onMemWarn); bound = true; } const qg = window['qg']; if (!bound && qg?.onMemoryWarning && qg !== this.pf) { qg.onMemoryWarning(this._onMemWarn); bound = true; } const wx = window['wx']; if (!bound && wx?.onMemoryWarning && wx !== this.pf) { wx.onMemoryWarning(this._onMemWarn); } } /** 排行榜授权时缓存的用户信息 */ private _rankUserInfo: Record | null = null; /** * 排行榜拉头像昵称。 * 新基础库 scope.userInfo 已不支持 authorize(必 fail),优先 getUserProfile 直接拉真实资料 * (须在点击同步链内调用才有手势权限);无该 API 的平台退回 authorize → getUserInfo 旧链。 */ pullRankUserInfo(callback?: (info: { userInfo?: Record } | null) => void): void { const api = MiniGamePf.api(); if (!api) { callback?.(null); return; } const done = (info: { userInfo?: Record } | null) => { if (info?.userInfo) this._rankUserInfo = info.userInfo; callback?.(info ?? null); }; const readViaGetUserInfo = () => { if (this._rankUserInfo) { done({ userInfo: this._rankUserInfo }); return; } if (typeof api.getUserInfo !== 'function') { done(null); return; } api.getUserInfo({ success: (res: { userInfo?: Record }) => done(res ?? null), fail: () => done(null), }); }; if (typeof api.getUserProfile === 'function') { if (this._rankUserInfo) { console.warn('[RankDebug] pullRankUserInfo 走 getUserProfile:命中缓存直接返回'); done({ userInfo: this._rankUserInfo }); return; } console.warn('[RankDebug] pullRankUserInfo 走 getUserProfile 分支'); api.getUserProfile({ desc: '用于排行榜昵称头像展示', success: (res: { userInfo?: Record }) => { console.warn('[RankDebug] getUserProfile 成功'); done(res ?? null); }, fail: (err?: { errMsg?: string }) => { console.log(`[RankDebug] getUserProfile 失败 err=${err?.errMsg ?? String(err ?? '')}`); readViaGetUserInfo(); }, }); return; } if (typeof api.authorize === 'function') { console.warn('[RankDebug] pullRankUserInfo 走 authorize→getUserInfo 分支'); api.authorize({ scope: 'scope.userInfo', success: () => { console.warn('[RankDebug] authorize(scope.userInfo) 成功'); readViaGetUserInfo(); }, fail: (err?: { errMsg?: string }) => { console.log(`[RankDebug] authorize(scope.userInfo) 失败 err=${err?.errMsg ?? String(err ?? '')}`); done(null); }, }); return; } console.warn('[RankDebug] pullRankUserInfo 无 authorize/getUserProfile,直接 getUserInfo'); readViaGetUserInfo(); } /** 授权定位(scope.userLocation) */ authorizeUserLocation(callback?: (ok: boolean) => void): void { this._authorizeScope('scope.userLocation', callback); } private _authorizeScope(scope: string, callback?: (ok: boolean) => void): void { if (!this.pf?.authorize) { callback?.(false); return; } this.pf.authorize({ scope, success: () => callback?.(true), fail: () => callback?.(false), }); } /** 读取授权设置 authSetting */ getSetting(callback?: (setting: { authSetting?: Record } | null) => void): void { if (!this.pf?.getSetting) { callback?.(null); return; } this.pf.getSetting({ success: (res: { authSetting?: Record }) => callback?.(res ?? null), fail: () => callback?.(null), }); } /** 打开系统授权设置页 */ openSetting(callback?: (ok: boolean) => void): void { if (!this.pf?.openSetting) { callback?.(false); return; } this.pf.openSetting({ success: () => callback?.(true), fail: () => callback?.(false), }); } /** 冷/热启动是否侧边栏入口:快手 from、抖音 launch_from、B 站 scene=021036 */ private _isSidebarLaunch(res: PfLaunchRes | null | undefined): boolean { if (!res) return false; const from = String(res.from ?? ''); if (from === 'sidebar_new' || from === 'sidebar_miniprogram') return true; if (res.launch_from === 'homepage' && res.location === 'sidebar_card') return true; if (res.launch_from === 'sidebar_miniprogram' || res.launch_from === 'sidebar_new') return true; if (String(res.scene ?? '') === '021036') return true; return false; } /** B 站桌面快捷方式 scene=10002 */ private _isDeskLaunch(res: PfLaunchRes | null | undefined): boolean { return MiniGamePf.isBl() && String(res?.scene ?? '') === '10002'; } /** 读取 game.js 抢先缓存或宿主最新启动参数 */ private _readLatestLaunch(): PfLaunchRes | null { const store = globalThis as PfShowStore; if (store.__pfLastShow) return store.__pfLastShow; const host = this._pfHost() ?? MiniGamePf.api(); try { if (typeof host?.getEnterOptionsSync === 'function') { return host.getEnterOptionsSync() ?? null; } } catch { /* 宿主未就绪 */ } try { if (typeof host?.getLaunchOptionsSync === 'function') { return host.getLaunchOptionsSync() ?? null; } } catch { /* 宿主未就绪 */ } return null; } /** 更新侧边栏/桌面资格。B 站跟最新 scene;其它平台侧边栏标记粘滞。 */ private _applyLaunchOptions(res: PfLaunchRes | null | undefined): void { const sidebar = this._isSidebarLaunch(res); if (!MiniGamePf.isBl()) { if (!sidebar) return; this._fromSidebar = true; if (sdkConfig.isInitGameData) { gg.data.setStatus(StatusType.DayGiftCanGet, 1); } return; } this._fromSidebar = sidebar; this._fromDesk = this._isDeskLaunch(res); if (!sdkConfig.isInitGameData) return; if (this._fromSidebar) { gg.data.setStatus(StatusType.DayGiftCanGet, 1); return; } if (gg.data.getStatus(StatusType.DayGiftGetFinish) === 0) { gg.data.setStatus(StatusType.DayGiftCanGet, 0); } } /** game.js / 启动阶段 onShow:缓存场景并更新侧边栏/桌面资格 */ private _onEarlyPfShow = (res: PfLaunchRes): void => { (globalThis as PfShowStore).__pfLastShow = res; this._applyLaunchOptions(res); }; /** 跳转侧边栏成功 */ private _onNavSceneOk = (): void => { sdkTrace("navigate to scene success"); const cb = this._enterSidebarCb; this._enterSidebarCb = undefined; cb?.(true); }; /** 跳转侧边栏失败 */ private _onNavSceneFail = (res: { code?: number; msg?: string }): void => { sdkTrace("navigate to scene fail: ", res); if (res?.code === -20042) console.warn('[Sidebar] 游戏未备案'); if (res?.code === -20041) console.warn('[Sidebar] 唤起侧边栏超过每日频控'); if (res?.code === -10005) console.warn('[Sidebar] 客户端版本过低'); const cb = this._enterSidebarCb; this._enterSidebarCb = undefined; cb?.(false); }; /** 侧边栏检测成功 */ private _onSidebarCheckOk = (result: { available?: unknown; isAvailable?: unknown; isExist?: unknown } | boolean): void => { const ok = this._parseSidebarCheck(result); sdkTrace('sidebar check success', ok, result); const cb = this._sidebarCheckCb; this._sidebarCheckCb = undefined; cb?.(ok); }; /** 检测 fail 不等于明确不可用;有 navigateToScene 仍展示入口 */ private _onSidebarCheckFail = (res: unknown): void => { console.warn('[Sidebar] 检测 fail'); const cb = this._sidebarCheckCb; this._sidebarCheckCb = undefined; cb?.(typeof this._pfHost()?.navigateToScene === 'function'); }; /** 未添加桌面则展示入口 */ private _onCheckShortcutOk = (res: { installed?: boolean }): void => { const cb = this._addDeskCb; this._addDeskCb = undefined; cb?.(!res?.installed); }; /** 检测失败仍展示入口(含 -10005 / apk info is invalid) */ private _onCheckShortcutFail = (err: { code?: number; msg?: string }): void => { sdkTrace('checkShortcut fail', err?.code, err?.msg); const cb = this._addDeskCb; this._addDeskCb = undefined; cb?.(true); }; /** 微信分享回前台计时判定 */ private _onShareReturn = (time: number): void => { sdkTrace("本次分享时间:" + time); const cb = this._shareResultCb; const limit = this._shareTimeLimit; this._shareResultCb = null; this._onShare = null; cb?.(time >= limit); }; /** * vivo:未创建过桌面图标才显示按钮。 * @param callback 是否显示 */ private _queryVivoShortcut(callback?: Function): void { const pf = MiniGamePf.api(); if (typeof pf?.hasShortcutInstalled !== 'function') { console.warn('[SDKManager] vivo 无 hasShortcutInstalled'); callback?.(false); return; } this._addDeskCb = callback; pf.hasShortcutInstalled({ success: this._onVivoHasShortcut, fail: this._onVivoHasShortcutFail, }); } /** vivo hasShortcutInstalled 成功。true 表示已创建 */ private _onVivoHasShortcut = (status: unknown): void => { const cb = this._addDeskCb; this._addDeskCb = undefined; if (typeof status !== 'boolean') { console.warn('[SDKManager] hasShortcutInstalled 返回值异常', status); cb?.(false); return; } cb?.(!status); }; /** vivo 查询桌面图标失败 */ private _onVivoHasShortcutFail = (err: unknown): void => { console.warn('[SDKManager] hasShortcutInstalled 失败', err); const cb = this._addDeskCb; this._addDeskCb = undefined; cb?.(false); }; /** * vivo 创建桌面图标。两次调用间隔须大于 120 秒。 * @param callback 是否创建成功 */ private _installVivoShortcut(callback?: Function): void { const pf = MiniGamePf.api(); if (typeof pf?.installShortcut !== 'function') { console.warn('[SDKManager] vivo 无 installShortcut'); callback?.(false); return; } this._vivoShortcutOk = false; this._addShortcutCb = callback; pf.installShortcut({ success: this._onVivoShortcutOk, fail: this._onAddShortcutFail, complete: this._onVivoShortcutComplete, }); } /** vivo 创建桌面图标成功 */ private _onVivoShortcutOk = (): void => { this._vivoShortcutOk = true; this._onAddShortcutOk(); }; /** vivo complete:成功之后忽略;单独触发视为用户取消 */ private _onVivoShortcutComplete = (): void => { if (this._vivoShortcutOk) { this._vivoShortcutOk = false; return; } this._onAddShortcutFail('cancel'); }; /** 添加桌面成功 */ private _onAddShortcutOk = (): void => { sdkTrace("添加成功"); const cb = this._addShortcutCb; this._addShortcutCb = undefined; cb?.(true); }; /** 添加桌面失败 */ private _onAddShortcutFail = (res: unknown): void => { sdkTrace("添加失败", res); const cb = this._addShortcutCb; this._addShortcutCb = undefined; cb?.(false); }; /** getUserInfo 成功 */ private _onGetUserInfoOk = (res: { userInfo?: unknown }): void => { sdkTrace(`getUserInfo 调用成功`, res?.userInfo); this._userInfo = res?.userInfo; const resolve = this._userInfoResolve; this._userInfoResolve = null; resolve?.(res?.userInfo ?? null); }; /** getUserInfo 失败 */ private _onGetUserInfoFail = (res: { errMsg?: string }): void => { sdkTrace(`getUserInfo 调用失败`, res?.errMsg); const resolve = this._userInfoResolve; this._userInfoResolve = null; resolve?.(null); }; /** 内存警告(参数可能是 number 或 {level}) */ private _onMemWarn = (res: number | { level?: number }): void => { const level = typeof res === 'number' ? res : Number(res?.level ?? 0); sdkTrace("onMemoryWarning", level); this.pfTriggerGC(); if (this._memWarnCb && level >= 5) this._memWarnCb(); }; }