import { context } from '@/common/context'; import { invokeNative, subscribe } from '@jolibox/native-bridge'; import { Deferred, EventType } from '@jolibox/common'; import { createRecommendModal, IGame, IRecommendationButton, RecommendModalOnCloseParams } from '@jolibox/ui'; import { innerFetch as fetch } from '../network'; import { track } from '../report'; enum RecommendGuideType { Hide = 'Hide', View = 'View', Click = 'Click' } let exitRecommendationsCache: { code: string; data: { gameListInfo: { games: IGame[]; }; title: string; buttons: IRecommendationButton[]; }; } | null = null; /** * Fetches exit recommendations data from the API */ async function fetchExitRecommendations() { if (exitRecommendationsCache) { return exitRecommendationsCache; } const host = context.testMode ? `https://stg-game.jolibox.com` : `https://game.jolibox.com`; const url = `${host}/api/user-retention/exit-recommendations?objectId=${context.mpId}&from=GAME_DETAIL`; const { response: { data } } = await fetch<{ code: string; data: { gameListInfo: { games: IGame[]; }; title: string; buttons: IRecommendationButton[]; }; }>(url); exitRecommendationsCache = data; return data; } //prefetch fetchExitRecommendations(); const openGameSchema = (game: IGame) => { const { data } = invokeNative('envSync'); // Parse the original URL to preserve its structure const url = new URL(data.schema); const originalSearch = new URLSearchParams(url.search); // Set or replace gameId and joliSource parameters originalSearch.set('gameId', game.gameId); originalSearch.set( 'joliSource', context.encodeJoliSourceQuery({ __mpType: 'game', __orientation: game.orientation ?? 'VERTICAL' }) ); const host = `https://${game.gameId}.content.jolibox.com/`; // Construct the final schema URL const schema = `${host}index.html?${originalSearch.toString()}`; invokeNative('openSchemaSync', { schema }); }; let isOpenRetentionSchema = false; export async function openRetentionSchema() { const quitResultDeffer = new Deferred(); // 小程序不走挽留逻辑 if (context.mpType !== 'game') { quitResultDeffer.resolve(false); return quitResultDeffer.promise; } if (isOpenRetentionSchema) { quitResultDeffer.resolve(true); return quitResultDeffer.promise; } const data = await fetchExitRecommendations(); if (data.code !== 'SUCCESS') { quitResultDeffer.resolve(false); return quitResultDeffer.promise; } const { gameListInfo, title, buttons } = data.data; isOpenRetentionSchema = true; const modalContentForReport = { domain: 'GAME', buttons: data.data.buttons.map((btn) => `${btn.type}-${btn.text}`).join(','), list: gameListInfo.games.map((game) => game.gameId).join(',') }; track('RecommendedGuide', { eventType: EventType.View, ...modalContentForReport }); const modal = createRecommendModal({ games: gameListInfo.games, title, buttons, onClose: (params: RecommendModalOnCloseParams) => { isOpenRetentionSchema = false; switch (params.type) { case 'quit': track('RecommendedGuide', { eventType: EventType.Click, type: 'QUIT', domain: 'GAME' }); quitResultDeffer.resolve(false); break; case 'dismiss': track('RecommendedGuide', { eventType: EventType.Hide, ...modalContentForReport }); quitResultDeffer.resolve(true); modal.destroy(); break; case 'navigate': // TODO: 跳转游戏 if (params.data?.game) { const game = params.data.game; openGameSchema(game); track('GameItem', { eventType: EventType.Click, gameId: game.gameId, layerName: 'RecommendedGuide', domain: 'GAME' }); setTimeout(() => { quitResultDeffer.resolve(false); }, 0); } else { quitResultDeffer.resolve(true); } modal.destroy(); break; default: // 关闭弹框,留在当前游戏 track('RecommendedGuide', { eventType: EventType.Hide, ...modalContentForReport }); quitResultDeffer.resolve(true); break; } } }); // // 异步 return quitResultDeffer.promise; }