From bb133314cd02381a5df94a4b36f9fb05353c3a3e Mon Sep 17 00:00:00 2001 From: zhx Date: Thu, 11 Jun 2026 19:47:33 +0800 Subject: [PATCH] =?UTF-8?q?=E9=AA=B0=E5=AD=90=E5=92=8C=E7=8C=9C=E6=8B=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/api.js | 29 ++++++++++ game/rock/index.html | 123 +++++++++++++++++++++++++++++++++++++++++- game/touzi/index.html | 106 +++++++++++++++++++++++++++++++++++- 3 files changed, 254 insertions(+), 4 deletions(-) diff --git a/common/api.js b/common/api.js index a063199..17a6900 100644 --- a/common/api.js +++ b/common/api.js @@ -785,9 +785,38 @@ var default_api = 'https://api.global-interaction.com/'; }, }; + function analyticsEventID(prefix) { + var random = ''; + if (window.crypto && window.crypto.getRandomValues) { + var bytes = new Uint32Array(2); + window.crypto.getRandomValues(bytes); + random = bytes[0].toString(36) + bytes[1].toString(36); + } else { + random = Math.random().toString(36).slice(2); + } + return ( + String(prefix || 'self_game') + + '_' + + Date.now().toString(36) + + '_' + + random + ); + } + + function reportSelfGameEvent(payload) { + var body = Object.assign({}, payload || {}); + body.event_id = body.event_id || analyticsEventID(body.event_name); + body.occurred_at_ms = body.occurred_at_ms || Date.now(); + return request('/api/v1/games/self/report-events', { + method: 'POST', + body: body, + }); + } + var gameAPI = { dice: diceGameAPI, rps: rpsGameAPI, + reportEvent: reportSelfGameEvent, }; window.HyAppAPI = { diff --git a/game/rock/index.html b/game/rock/index.html index eeb01fc..676bacf 100644 --- a/game/rock/index.html +++ b/game/rock/index.html @@ -1556,6 +1556,7 @@ var frame = document.querySelector('.rps-frame'); var timers = []; var pollTimer = 0; + var pageStartedAt = performance.now(); var transparentPixel = 'data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%221%22 height=%221%22%2F%3E'; var gestures = { @@ -1595,6 +1596,26 @@ serverTimeMS: 0, }; + function trackSelfGameEvent(name, extra) { + var api = + window.HyAppAPI && + window.HyAppAPI.game && + window.HyAppAPI.game.reportEvent; + if (typeof api !== 'function') return; + var payload = Object.assign( + { + event_name: name, + game_id: GAME_ID, + page: 'game/rock', + h5_version: 'rock-h5-20260611', + entry_source: currentRoomID() ? 'room' : 'direct', + success: true, + }, + extra || {} + ); + api(payload).catch(function () {}); + } + function updateScale() { var scale = Math.min( window.innerWidth / DESIGN_WIDTH, @@ -2263,12 +2284,24 @@ resetToLobby(); return; } + trackSelfGameEvent('reveal_enter', { + match_id: matchID, + gesture: state.selectedGesture, + }); + var rollStartedAt = performance.now(); rpsAPI() .roll(matchID, { game_id: GAME_ID, gesture: state.selectedGesture, }) .then(function (data) { + trackSelfGameEvent('roll_api', { + match_id: matchID, + gesture: state.selectedGesture, + duration_ms: Math.round( + performance.now() - rollStartedAt + ), + }); var nextMatch = matchFromData(data); if (roundID !== state.roundID) return; state.match = nextMatch || match; @@ -2276,6 +2309,15 @@ showBattle(roundID, state.match); }) .catch(function (error) { + trackSelfGameEvent('roll_api', { + match_id: matchID, + gesture: state.selectedGesture, + duration_ms: Math.round( + performance.now() - rollStartedAt + ), + success: false, + error_code: error && error.message, + }); showMessage(error.message); resetToLobby(); }); @@ -2329,6 +2371,11 @@ stake_coin: state.selectedStake, gesture: state.selectedGesture, }); + trackSelfGameEvent('match_click', { + stake_coin: state.selectedStake, + gesture: state.selectedGesture, + mode: matchRequestMode(), + }); var payload = { game_id: GAME_ID, room_id: currentRoomID(), @@ -2348,7 +2395,30 @@ } else { request = rpsAPI().match(payload); } - request.then(handleMatchData).catch(function (error) { + var matchStartedAt = performance.now(); + request + .then(function (data) { + var match = matchFromData(data); + trackSelfGameEvent('match_success', { + match_id: match && (match.match_id || match.matchId), + stake_coin: state.selectedStake, + gesture: state.selectedGesture, + duration_ms: Math.round( + performance.now() - matchStartedAt + ), + }); + handleMatchData(data); + }) + .catch(function (error) { + trackSelfGameEvent('match_api', { + stake_coin: state.selectedStake, + gesture: state.selectedGesture, + duration_ms: Math.round( + performance.now() - matchStartedAt + ), + success: false, + error_code: error && error.message, + }); state.busy = false; showMessage(error.message); setScreen('lobby'); @@ -2440,6 +2510,12 @@ if (match) renderMatch(match); renderVictory(result, match); setScreen('victory'); + trackSelfGameEvent('settlement_show', { + match_id: match && (match.match_id || match.matchId), + result: result, + gesture: state.selectedGesture, + stake_coin: state.selectedStake, + }); postBridge('gameRPSSettled', { game_code: GAME_CODE, game_id: GAME_ID, @@ -2471,7 +2547,18 @@ } return rpsAPI() .cancel(matchID) - .catch(function () {}); + .then(function () { + trackSelfGameEvent('match_cancel', { + match_id: matchID, + }); + }) + .catch(function () { + trackSelfGameEvent('cancel_api', { + match_id: matchID, + success: false, + error_code: 'cancel_failed', + }); + }); } function openRecharge() { @@ -2544,9 +2631,15 @@ renderStakeOptions(state.config); return Promise.resolve(); } + var configStartedAt = performance.now(); return rpsAPI() .config(GAME_ID) .then(function (data) { + trackSelfGameEvent('config_load_success', { + duration_ms: Math.round( + performance.now() - configStartedAt + ), + }); state.config = (data && (data.config || data.rps_config)) || data || @@ -2554,6 +2647,13 @@ renderStakeOptions(state.config); }) .catch(function () { + trackSelfGameEvent('config_load_fail', { + duration_ms: Math.round( + performance.now() - configStartedAt + ), + success: false, + error_code: 'config_failed', + }); renderStakeOptions(state.config); }); } @@ -2591,6 +2691,9 @@ state.selectedGesture = target.getAttribute('data-gesture') || 'rock'; renderSelection(); + trackSelfGameEvent('gesture_select', { + gesture: state.selectedGesture, + }); return; } if (action === 'stake') { @@ -2609,6 +2712,9 @@ ); } ); + trackSelfGameEvent('stake_select', { + stake_coin: state.selectedStake, + }); return; } if (action === 'start') { @@ -2627,11 +2733,21 @@ if (action === 'rematch') { cancelMatch().then(function () { resetToLobby(); + trackSelfGameEvent('play_again', { + source: 'rematch', + stake_coin: state.selectedStake, + gesture: state.selectedGesture, + }); startMatch(); }); return; } if (action === 'play-again') { + trackSelfGameEvent('play_again', { + source: 'settlement', + stake_coin: state.selectedStake, + gesture: state.selectedGesture, + }); resetToLobby(); } }); @@ -2669,6 +2785,9 @@ updateScale(); initActions(); + trackSelfGameEvent('page_open', { + duration_ms: Math.round(performance.now() - pageStartedAt), + }); window.addEventListener('beforeunload', function () { cancelMatch(); }); diff --git a/game/touzi/index.html b/game/touzi/index.html index b41fde3..074c69b 100644 --- a/game/touzi/index.html +++ b/game/touzi/index.html @@ -2434,6 +2434,7 @@ var timers = []; var pollTimer = 0; var roundSequence = 0; + var pageStartedAt = performance.now(); var rollEffectPlayers = { self: null, opponent: null, @@ -2450,6 +2451,26 @@ serverTimeMS: 0, }; + function trackSelfGameEvent(name, extra) { + var api = + window.HyAppAPI && + window.HyAppAPI.game && + window.HyAppAPI.game.reportEvent; + if (typeof api !== 'function') return; + var payload = Object.assign( + { + event_name: name, + game_id: 'dice', + page: 'game/touzi', + h5_version: 'dice-h5-20260611', + entry_source: currentRoomID() ? 'room' : 'direct', + success: true, + }, + extra || {} + ); + api(payload).catch(function () {}); + } + function updateScale() { var scale = Math.min( window.innerWidth / DESIGN_WIDTH, @@ -3266,6 +3287,9 @@ setPhaseText(i18nText('diceMatch.rolling', 'Rolling...')); frame.classList.remove('has-scores'); playRollEffects(); + trackSelfGameEvent('reveal_enter', { + match_id: matchID, + }); postBridge('gameDiceRolling', { game_code: 'touzi', match_id: matchID, @@ -3273,9 +3297,18 @@ var rollPromise = game .roll(matchID) .then(function (data) { + trackSelfGameEvent('roll_api', { + match_id: matchID, + success: true, + }); return matchFromData(data); }) .catch(function () { + trackSelfGameEvent('roll_api', { + match_id: matchID, + success: false, + error_code: 'roll_failed', + }); return game.getMatch(matchID).then(function (data) { return matchFromData(data); }); @@ -3370,6 +3403,10 @@ frame.classList.add('is-victory'); renderVictory(match); refreshBalance(); + trackSelfGameEvent('settlement_show', { + match_id: match.match_id, + result: match.result, + }); postBridge('gameDiceSettled', { game_code: 'touzi', match_id: match.match_id, @@ -3450,13 +3487,35 @@ game_code: 'touzi', stake: stake, }); + trackSelfGameEvent('match_click', { + stake_coin: stake, + }); + var matchStartedAt = performance.now(); game.match({ game_id: 'dice', room_id: currentRoomID(), stake_coin: stake, }) - .then(handleMatchData) + .then(function (data) { + var match = matchFromData(data); + trackSelfGameEvent('match_success', { + match_id: match && match.match_id, + stake_coin: stake, + duration_ms: Math.round( + performance.now() - matchStartedAt + ), + }); + handleMatchData(data); + }) .catch(function (error) { + trackSelfGameEvent('match_api', { + stake_coin: stake, + duration_ms: Math.round( + performance.now() - matchStartedAt + ), + success: false, + error_code: error && error.message, + }); showMessage(error.message); resetToLobby(true); }); @@ -3473,7 +3532,20 @@ ) { return Promise.resolve(); } - return game.cancel(match.match_id).catch(function () {}); + return game + .cancel(match.match_id) + .then(function () { + trackSelfGameEvent('match_cancel', { + match_id: match.match_id, + }); + }) + .catch(function () { + trackSelfGameEvent('cancel_api', { + match_id: match.match_id, + success: false, + error_code: 'cancel_failed', + }); + }); } function resetToLobby(keepStake) { @@ -3537,6 +3609,7 @@ true, i18nText('diceMatch.loading', 'Loading') ); + var configStartedAt = performance.now(); Promise.all([ api() .user.me() @@ -3555,7 +3628,22 @@ }), diceAPI() .config('dice') + .then(function (data) { + trackSelfGameEvent('config_load_success', { + duration_ms: Math.round( + performance.now() - configStartedAt + ), + }); + return data; + }) .catch(function () { + trackSelfGameEvent('config_load_fail', { + duration_ms: Math.round( + performance.now() - configStartedAt + ), + success: false, + error_code: 'config_failed', + }); return null; }), ]).then(function (results) { @@ -3589,6 +3677,9 @@ if (action === 'stake') { if (state.busy) return; selectStake(target); + trackSelfGameEvent('stake_select', { + stake_coin: state.selectedStake, + }); return; } if (action === 'match') { @@ -3601,6 +3692,10 @@ postBridge('gameRematch', { game_code: 'touzi', }); + trackSelfGameEvent('play_again', { + source: 'rematch', + stake_coin: state.selectedStake, + }); startMatch(); }); return; @@ -3611,6 +3706,10 @@ postBridge('gamePlayAgain', { game_code: 'touzi', }); + trackSelfGameEvent('play_again', { + source: 'settlement', + stake_coin: state.selectedStake, + }); }); } }); @@ -3621,6 +3720,9 @@ updateScale(); initActions(); + trackSelfGameEvent('page_open', { + duration_ms: Math.round(performance.now() - pageStartedAt), + }); window.addEventListener( 'hyapp:i18n-ready', refreshLocalizedRuntimeText