import { invokeNative, onNative, RuntimeLoader } from '@jolibox/native-bridge'; import { context } from '@/common/context'; import { hostEmitter, isBoolean } from '@jolibox/common'; import { taskTracker, track } from '../report'; import { initializeNativeEnv } from './init-env'; import { adEventEmitter } from '@/common/ads'; import { openRetentionSchema } from '../ui/retention'; import { innerFetch } from '../network'; import { Env } from '@jolibox/types'; import { registerLanguageHandler } from '@jolibox/ui'; import { createIframeModal, registerIframeModalToGlobal } from '../ui/modal-iframe'; import { onFCP, onLCP, onTTFB } from 'web-vitals'; import { canIUseNative } from '../utils/can-iuse-native'; interface IBasicMetaConfig { canShowRecommended: boolean; } declare const globalThis: { joliboxJSCore: { doExit: (uuid: string) => void; onDocumentReady: (path: string) => void; env: (params: { onError: (error: Error) => void }) => Env | undefined; doBackPress: (uuid: string) => void; }; }; /** * 全局变量 */ /** * 清除safari jolibox样式 */ let cleanStyles: () => void; let unregisterIframeModal: () => void; /** * 启动时间戳 */ let start_timestamp: number; /** * 是否展示广告 */ let isAdShowing = false; let baskcMeta: IBasicMetaConfig | null = null; /** * 是否全局系统退出 */ let isInterceptSystemExit = false; /** * android全局拦截物理按键 */ let isInterceptBackPress = false; /** * 是否使用退出挽留 */ let disableExitRecommendDialog = false; const updateDisableExitRecommendDialog = (disable?: boolean) => { if (isBoolean(disable)) { disableExitRecommendDialog = disable; } }; RuntimeLoader.onReady(() => { // TODO: merge some env config }); /** * 添加广告展示监听 */ function addShowAdListener() { adEventEmitter.on('isAdShowing', (isShowing) => { isAdShowing = isShowing; if (isBoolean(isAdShowing) && context.mpType === 'game') { invokeNative('updateContainerConfigSync', { displayCapsuleButton: !isAdShowing, webviewId: context.webviewId }); } }); } /** * 添加系统退出拦截监听 */ function addInterceptSystemExitListener() { hostEmitter.on('onInterceptSystemExit', ({ intercept }) => { isInterceptSystemExit = intercept; }); } /** * android 全局拦截物理退出 */ function addBackPressListener() { hostEmitter.on('onInterceptBackPress', ({ intercept }) => { isInterceptBackPress = intercept; }); onNative('onBeforeBackPress', ({ uuid, disableExitRecommendDialog }) => { updateDisableExitRecommendDialog(disableExitRecommendDialog); hostEmitter.emit('onBackPress', {}); globalThis.joliboxJSCore?.doBackPress?.(uuid); }); } /** * The DOMContentLoaded event might be triggered very early, * so the global loaded listener should be executed during the bootstrap process. */ function addWebviewReadyListener() { hostEmitter.on('onDocumentReady', () => { globalThis.joliboxJSCore?.onDocumentReady(window.location.href); track('onDocumentReady', { start_timestamp, timestamp: Date.now() }); }); } function addI18nChangedListener() { onNative('onI18nChanged', ({ language }) => { hostEmitter.emit('onI18nChanged', { i18n: language }); }); } function handleTaskTracker(duration?: number) { // 只有小游戏场景需要上报进度 if (context.mpType === 'game') { taskTracker.start(duration); } } function addGameServiceReadyListener() { onNative('onJoliboxServiceReady', ({ runtimeInfo, loadDuration }) => { runtimeInfo && context.onEnvConfigChanged({ hostUserInfo: runtimeInfo }); hostEmitter.emit('LifecycleEvent.onReady', { ...(context.hostUserInfo ? context.hostUserInfo : { isLogin: false }) }); track('joliboxServiceReady', { start_timestamp, timestamp: Date.now() }); // 任务上报,1.7.3 版本以下在onReady时机开始计算埋点 if (!canIUseNative('onJoliboxHideLoading')) { handleTaskTracker(loadDuration); } registerLanguageHandler(() => context.language); }); onNative('onBeforeExit', ({ uuid, disableExitRecommendDialog }) => { updateDisableExitRecommendDialog(disableExitRecommendDialog); globalThis.joliboxJSCore?.doExit(uuid); }); onNative('onJoliboxHideLoading', ({ duration }) => { handleTaskTracker(duration); }); } function addDoExitLoader() { /** * 退出逻辑 * 1. 如果正在展示广告,则禁止退出 * 2. 如果 * 2. 如果指定退出挽留逻辑,则按照指定逻辑运行 * 3. 如果退出挽留逻辑返回 true,则按照指定逻辑运行 * 4. 否则,按照默认逻辑运行 */ const doActualExit = () => { //埋点上报 track('onBeforeExit', { timestamp: Date.now() }); cleanStyles?.(); unregisterIframeModal?.(); taskTracker.close(Date.now() - start_timestamp); }; const shouldInterceptSystemExit = () => { return isAdShowing || isInterceptSystemExit; }; RuntimeLoader.onDoExit(async () => { if (shouldInterceptSystemExit()) { return true; // Forbid exit on watching ads } if (context.platform === 'ios') { doActualExit(); return false; } // 指定退出挽留逻辑,则按照指定逻辑运行 if (isBoolean(context.shouldInterupt)) { // 不需要打断退出,上报埋点 if (!context.shouldInterupt) { doActualExit(); } return context.shouldInterupt; } if (baskcMeta?.canShowRecommended && !disableExitRecommendDialog) { const stay = await openRetentionSchema(); if (stay) { // 挽留成功,打断退出 return true; } } // 退出,对应上报 //埋点上报 doActualExit(); return false; }); RuntimeLoader.onDoBackPress(async () => { if (shouldInterceptSystemExit()) { return true; // Forbid exit on watching ads } return false; }); } function trackPerformance() { onFCP((metric) => { track('GameFCP', { value: metric.value, rating: metric.rating, navigationType: metric.navigationType }); }); onLCP((metric) => { track('GameLCP', { value: metric.value, rating: metric.rating, navigationType: metric.navigationType }); }); onTTFB((metric) => { track('GameTTFB', { value: metric.value, rating: metric.rating, navigationType: metric.navigationType }); }); } async function fetchMetaConfig() { try { const url = `/api/fe-configs/js-sdk/basic-meta`; const { response: { data } } = await innerFetch(url); if (data) { baskcMeta = data; } } catch (error) { console.error('fetchMetaConfig error:', error); } } export function config(): void { start_timestamp = Date.now(); addGameServiceReadyListener(); addShowAdListener(); addInterceptSystemExitListener(); addBackPressListener(); addDoExitLoader(); addWebviewReadyListener(); addI18nChangedListener(); fetchMetaConfig(); trackPerformance(); unregisterIframeModal = registerIframeModalToGlobal(); cleanStyles = initializeNativeEnv(); }