You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
AdLike2048/assets/script/sdk/SDKManager.ts

1991 lines
69 KiB

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<string> = createMembershipSet<string>();
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<string>((resolve, reject) => {
resolve("");
})
}
return new Promise<string>((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<string, any> | null {
if (MiniGamePf.isKs()) {
const g = globalThis as unknown as { ks?: Record<string, any>; GameGlobal?: { ks?: Record<string, any> } };
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<string, unknown> = {
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<string, string>) => {
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<string, string>): 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 | undefined | null>): 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<string, unknown> | null = null;
/**
* 排行榜拉头像昵称。
* 新基础库 scope.userInfo 已不支持 authorize(必 fail),优先 getUserProfile 直接拉真实资料
* (须在点击同步链内调用才有手势权限);无该 API 的平台退回 authorize → getUserInfo 旧链。
*/
pullRankUserInfo(callback?: (info: { userInfo?: Record<string, unknown> } | null) => void): void {
const api = MiniGamePf.api();
if (!api) {
callback?.(null);
return;
}
const done = (info: { userInfo?: Record<string, unknown> } | 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<string, unknown> }) => 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<string, unknown> }) => {
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<string, unknown> } | null) => void): void {
if (!this.pf?.getSetting) {
callback?.(null);
return;
}
this.pf.getSetting({
success: (res: { authSetting?: Record<string, unknown> }) => 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();
};
}