完善联调标准化与诊断链路
This commit is contained in:
@@ -1108,6 +1108,7 @@ export class MapEngine {
|
||||
configAppId: string
|
||||
configSchemaVersion: string
|
||||
configVersion: string
|
||||
playfieldKind: string
|
||||
controlScoreOverrides: Record<string, number>
|
||||
controlContentOverrides: Record<string, GameControlDisplayContentOverride>
|
||||
defaultControlContentOverride: GameControlDisplayContentOverride | null
|
||||
@@ -1417,6 +1418,7 @@ export class MapEngine {
|
||||
this.configAppId = ''
|
||||
this.configSchemaVersion = '1'
|
||||
this.configVersion = ''
|
||||
this.playfieldKind = ''
|
||||
this.controlScoreOverrides = {}
|
||||
this.controlContentOverrides = {}
|
||||
this.defaultControlContentOverride = null
|
||||
@@ -1721,6 +1723,8 @@ export class MapEngine {
|
||||
{ label: '比赛名称', value: title || '--' },
|
||||
{ label: '配置版本', value: this.configVersion || '--' },
|
||||
{ label: 'Schema版本', value: this.configSchemaVersion || '--' },
|
||||
{ label: '场地类型', value: this.playfieldKind || '--' },
|
||||
{ label: '模式编码', value: this.gameMode || '--' },
|
||||
{ label: '活动ID', value: this.configAppId || '--' },
|
||||
{ label: '动画等级', value: formatAnimationLevelText(this.state.animationLevel) },
|
||||
{ label: '地图', value: this.state.mapName || '--' },
|
||||
@@ -3423,8 +3427,8 @@ export class MapEngine {
|
||||
this.courseOverlayVisible = true
|
||||
const gameModeText = this.gameMode === 'score-o' ? '积分赛' : '顺序打点'
|
||||
const defaultStatusText = this.currentGpsPoint
|
||||
? `${gameModeText}已开始 (${this.buildVersion})`
|
||||
: `${gameModeText}已开始,GPS定位启动中 (${this.buildVersion})`
|
||||
? `已进入${gameModeText},请先打开始点 (${this.buildVersion})`
|
||||
: `已进入${gameModeText},GPS定位启动中,请先打开始点 (${this.buildVersion})`
|
||||
this.commitGameResult(gameResult, defaultStatusText)
|
||||
}
|
||||
|
||||
@@ -3683,6 +3687,15 @@ export class MapEngine {
|
||||
this.mockSimulatorDebugLogger.disconnect()
|
||||
}
|
||||
|
||||
handleEmitMockDebugLog(
|
||||
scope: string,
|
||||
level: 'info' | 'warn' | 'error',
|
||||
message: string,
|
||||
payload?: Record<string, unknown>,
|
||||
): void {
|
||||
this.mockSimulatorDebugLogger.log(scope, level, message, payload)
|
||||
}
|
||||
|
||||
handleSetGameMode(nextMode: 'classic-sequential' | 'score-o'): void {
|
||||
if (this.gameMode === nextMode) {
|
||||
return
|
||||
@@ -3882,6 +3895,7 @@ export class MapEngine {
|
||||
this.configAppId = config.configAppId
|
||||
this.configSchemaVersion = config.configSchemaVersion
|
||||
this.configVersion = config.configVersion
|
||||
this.playfieldKind = config.playfieldKind
|
||||
this.controlScoreOverrides = config.controlScoreOverrides
|
||||
this.controlContentOverrides = config.controlContentOverrides
|
||||
this.defaultControlContentOverride = config.defaultControlContentOverride
|
||||
|
||||
@@ -114,7 +114,7 @@ function getGuidanceEffects(
|
||||
|
||||
function buildPunchHintText(definition: GameDefinition, state: GameSessionState, currentTarget: GameControl | null): string {
|
||||
if (state.status === 'idle') {
|
||||
return '点击开始后先打开始点'
|
||||
return '先打开始点即可正式开始比赛'
|
||||
}
|
||||
|
||||
if (state.status === 'finished') {
|
||||
|
||||
@@ -271,7 +271,7 @@ function buildPunchHintText(
|
||||
focusedTarget: GameControl | null,
|
||||
): string {
|
||||
if (state.status === 'idle') {
|
||||
return '点击开始后先打开始点'
|
||||
return '先打开始点即可正式开始比赛'
|
||||
}
|
||||
|
||||
if (state.status === 'finished') {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { getEventPlay, launchEvent, type BackendCourseVariantSummary, type Backe
|
||||
import { adaptBackendLaunchResultToEnvelope } from '../../utils/backendLaunchAdapter'
|
||||
import { formatBackendPlayActionText, formatBackendPlayStatusText } from '../../utils/backendPlayCopy'
|
||||
import { prepareMapPageUrlForLaunch } from '../../utils/gameLaunch'
|
||||
import { reportBackendClientLog } from '../../utils/backendClientLogs'
|
||||
import { HeartRateController } from '../../engine/sensor/heartRateController'
|
||||
|
||||
const PREFERRED_HEART_RATE_DEVICE_STORAGE_KEY = 'cmr.preferredHeartRateDevice'
|
||||
@@ -290,12 +291,32 @@ Page({
|
||||
result.play.assignmentMode,
|
||||
result.play.courseVariants,
|
||||
)
|
||||
const assignmentMode = result.play.assignmentMode ? result.play.assignmentMode : null
|
||||
const logVariantId = assignmentMode === 'manual' && selectedVariantId ? selectedVariantId : null
|
||||
const selectableVariants = buildSelectableVariants(
|
||||
selectedVariantId,
|
||||
result.play.assignmentMode,
|
||||
result.play.courseVariants,
|
||||
)
|
||||
const selectedVariant = selectableVariants.find((item) => item.id === selectedVariantId) || null
|
||||
reportBackendClientLog({
|
||||
level: 'info',
|
||||
category: 'event-prepare',
|
||||
message: 'prepare play loaded',
|
||||
eventId: result.event.id || this.data.eventId || '',
|
||||
releaseId: result.resolvedRelease && result.resolvedRelease.releaseId
|
||||
? result.resolvedRelease.releaseId
|
||||
: '',
|
||||
manifestUrl: result.resolvedRelease && result.resolvedRelease.manifestUrl
|
||||
? result.resolvedRelease.manifestUrl
|
||||
: '',
|
||||
details: {
|
||||
pageEventId: this.data.eventId || '',
|
||||
resultEventId: result.event.id || '',
|
||||
selectedVariantId: logVariantId,
|
||||
assignmentMode,
|
||||
},
|
||||
})
|
||||
this.setData({
|
||||
loading: false,
|
||||
titleText: `${result.event.displayName} / 开始前准备`,
|
||||
@@ -586,6 +607,22 @@ Page({
|
||||
})
|
||||
|
||||
try {
|
||||
const assignmentMode = this.data.assignmentMode ? this.data.assignmentMode : null
|
||||
const selectedVariantId = assignmentMode === 'manual' && this.data.selectedVariantId
|
||||
? this.data.selectedVariantId
|
||||
: null
|
||||
reportBackendClientLog({
|
||||
level: 'info',
|
||||
category: 'event-prepare',
|
||||
message: 'launch requested',
|
||||
eventId: this.data.eventId || '',
|
||||
details: {
|
||||
pageEventId: this.data.eventId || '',
|
||||
selectedVariantId,
|
||||
assignmentMode,
|
||||
phase: 'launch-requested',
|
||||
},
|
||||
})
|
||||
const app = getApp<IAppOption>()
|
||||
if (app.globalData) {
|
||||
const pendingDeviceName = prepareHeartRateController && prepareHeartRateController.currentDeviceName
|
||||
@@ -608,6 +645,32 @@ Page({
|
||||
clientType: 'wechat',
|
||||
deviceKey: 'mini-dev-device-001',
|
||||
})
|
||||
reportBackendClientLog({
|
||||
level: 'info',
|
||||
category: 'event-prepare',
|
||||
message: 'launch response received',
|
||||
eventId: result.launch.business && result.launch.business.eventId ? result.launch.business.eventId : this.data.eventId || '',
|
||||
releaseId: result.launch.config && result.launch.config.releaseId ? result.launch.config.releaseId : '',
|
||||
sessionId: result.launch.business && result.launch.business.sessionId ? result.launch.business.sessionId : '',
|
||||
manifestUrl: result.launch.resolvedRelease && result.launch.resolvedRelease.manifestUrl
|
||||
? result.launch.resolvedRelease.manifestUrl
|
||||
: '',
|
||||
details: {
|
||||
pageEventId: this.data.eventId || '',
|
||||
launchEventId: result.launch.business && result.launch.business.eventId ? result.launch.business.eventId : '',
|
||||
launchSessionId: result.launch.business && result.launch.business.sessionId ? result.launch.business.sessionId : '',
|
||||
configUrl: result.launch.config && result.launch.config.configUrl ? result.launch.config.configUrl : '',
|
||||
releaseId: result.launch.config && result.launch.config.releaseId ? result.launch.config.releaseId : '',
|
||||
resolvedReleaseId: result.launch.resolvedRelease && result.launch.resolvedRelease.releaseId
|
||||
? result.launch.resolvedRelease.releaseId
|
||||
: '',
|
||||
resolvedManifestUrl: result.launch.resolvedRelease && result.launch.resolvedRelease.manifestUrl
|
||||
? result.launch.resolvedRelease.manifestUrl
|
||||
: '',
|
||||
launchVariantId: result.launch.variant && result.launch.variant.id ? result.launch.variant.id : null,
|
||||
phase: 'launch-response',
|
||||
},
|
||||
})
|
||||
const envelope = adaptBackendLaunchResultToEnvelope(result)
|
||||
wx.navigateTo({
|
||||
url: prepareMapPageUrlForLaunch(envelope),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { loadBackendAuthTokens, loadBackendBaseUrl } from '../../utils/backendAuth'
|
||||
import { getEventPlay, type BackendEventPlayResult } from '../../utils/backendApi'
|
||||
import { formatBackendPlayActionText, formatBackendPlayStatusText } from '../../utils/backendPlayCopy'
|
||||
import { reportBackendClientLog } from '../../utils/backendClientLogs'
|
||||
|
||||
type EventPageData = {
|
||||
eventId: string
|
||||
@@ -130,6 +131,26 @@ Page({
|
||||
},
|
||||
|
||||
applyEventPlay(result: BackendEventPlayResult) {
|
||||
const assignmentMode = result.play.assignmentMode ? result.play.assignmentMode : null
|
||||
reportBackendClientLog({
|
||||
level: 'info',
|
||||
category: 'event-play',
|
||||
message: 'event play loaded',
|
||||
eventId: result.event.id || this.data.eventId || '',
|
||||
releaseId: result.resolvedRelease && result.resolvedRelease.releaseId
|
||||
? result.resolvedRelease.releaseId
|
||||
: '',
|
||||
manifestUrl: result.resolvedRelease && result.resolvedRelease.manifestUrl
|
||||
? result.resolvedRelease.manifestUrl
|
||||
: '',
|
||||
details: {
|
||||
pageEventId: this.data.eventId || '',
|
||||
resultEventId: result.event.id || '',
|
||||
primaryAction: result.play.primaryAction || '',
|
||||
assignmentMode,
|
||||
variantCount: result.play.courseVariants ? result.play.courseVariants.length : 0,
|
||||
},
|
||||
})
|
||||
this.setData({
|
||||
loading: false,
|
||||
titleText: result.event.displayName,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { clearBackendAuthTokens, loadBackendAuthTokens, loadBackendBaseUrl } from '../../utils/backendAuth'
|
||||
import { getEntryHome, type BackendCardResult, type BackendEntryHomeResult } from '../../utils/backendApi'
|
||||
import { reportBackendClientLog } from '../../utils/backendClientLogs'
|
||||
import { setGlobalMockDebugBridgeEnabled } from '../../utils/globalMockDebugBridge'
|
||||
|
||||
const DEFAULT_CHANNEL_CODE = 'mini-demo'
|
||||
const DEFAULT_CHANNEL_TYPE = 'wechat_mini'
|
||||
@@ -100,6 +102,18 @@ Page({
|
||||
},
|
||||
|
||||
applyEntryHomeResult(result: BackendEntryHomeResult) {
|
||||
reportBackendClientLog({
|
||||
level: 'info',
|
||||
category: 'entry-home',
|
||||
message: 'entry home loaded',
|
||||
details: {
|
||||
ongoingSessionId: result.ongoingSession && result.ongoingSession.id ? result.ongoingSession.id : '',
|
||||
ongoingEventId: result.ongoingSession && result.ongoingSession.eventId ? result.ongoingSession.eventId : '',
|
||||
recentSessionId: result.recentSession && result.recentSession.id ? result.recentSession.id : '',
|
||||
recentEventId: result.recentSession && result.recentSession.eventId ? result.recentSession.eventId : '',
|
||||
cardEventIds: (result.cards || []).map((item) => (item.event && item.event.id ? item.event.id : '')),
|
||||
},
|
||||
})
|
||||
this.setData({
|
||||
loading: false,
|
||||
statusText: '首页加载完成',
|
||||
@@ -141,6 +155,7 @@ Page({
|
||||
|
||||
handleLogout() {
|
||||
clearBackendAuthTokens()
|
||||
setGlobalMockDebugBridgeEnabled(false)
|
||||
const app = getApp<IAppOption>()
|
||||
if (app.globalData) {
|
||||
app.globalData.backendAuthTokens = null
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { clearBackendAuthTokens, saveBackendAuthTokens, saveBackendBaseUrl } from '../../utils/backendAuth'
|
||||
import { loginWechatMini } from '../../utils/backendApi'
|
||||
import { setGlobalMockDebugBridgeEnabled } from '../../utils/globalMockDebugBridge'
|
||||
|
||||
const DEFAULT_BACKEND_BASE_URL = 'https://api.gotomars.xyz'
|
||||
const DEFAULT_DEVICE_KEY = 'mini-dev-device-001'
|
||||
@@ -116,6 +117,7 @@ Page({
|
||||
|
||||
handleClearLoginState() {
|
||||
clearBackendAuthTokens()
|
||||
setGlobalMockDebugBridgeEnabled(false)
|
||||
const app = getApp<IAppOption>()
|
||||
if (app.globalData) {
|
||||
app.globalData.backendAuthTokens = null
|
||||
|
||||
@@ -16,6 +16,13 @@ import {
|
||||
import { finishSession, startSession, type BackendSessionFinishSummaryPayload } from '../../utils/backendApi'
|
||||
import { loadBackendBaseUrl } from '../../utils/backendAuth'
|
||||
import { loadRemoteMapConfig, type RemoteMapConfig } from '../../utils/remoteMapConfig'
|
||||
import {
|
||||
persistStoredMockDebugLogBridgeUrl,
|
||||
setGlobalMockDebugBridgeChannelId,
|
||||
setGlobalMockDebugBridgeEnabled,
|
||||
setGlobalMockDebugBridgeUrl,
|
||||
} from '../../utils/globalMockDebugBridge'
|
||||
import { reportBackendClientLog } from '../../utils/backendClientLogs'
|
||||
import { type H5ExperienceFallbackPayload, type H5ExperienceRequest } from '../../game/experience/h5Experience'
|
||||
import { type TrackColorPreset } from '../../game/presentation/trackStyleConfig'
|
||||
import { type GpsMarkerColorPreset } from '../../game/presentation/gpsMarkerStyleConfig'
|
||||
@@ -146,6 +153,7 @@ type MapPageData = MapEngineViewState & {
|
||||
showLeftButtonGroup: boolean
|
||||
showRightButtonGroups: boolean
|
||||
showBottomDebugButton: boolean
|
||||
showStartEntryButton: boolean
|
||||
}
|
||||
|
||||
function getGlobalTelemetryProfile(): PlayerTelemetryProfile | null {
|
||||
@@ -184,6 +192,7 @@ let systemSettingsLockLifetimeActive = false
|
||||
let syncedBackendSessionStartId = ''
|
||||
let syncedBackendSessionFinishId = ''
|
||||
let shouldAutoRestoreRecoverySnapshot = false
|
||||
let shouldAutoStartSessionOnEnter = false
|
||||
let redirectedToResultPage = false
|
||||
let pendingHeartRateSwitchDeviceName: string | null = null
|
||||
const DEBUG_MOCK_CHANNEL_ID_STORAGE_KEY = 'cmr.debug.mockChannelId.v1'
|
||||
@@ -828,6 +837,52 @@ function buildRuntimeSummaryRows(envelope: GameLaunchEnvelope): MapEngineGameInf
|
||||
return rows
|
||||
}
|
||||
|
||||
function buildLaunchConfigSummaryRows(envelope: GameLaunchEnvelope): MapEngineGameInfoRow[] {
|
||||
const rows: MapEngineGameInfoRow[] = []
|
||||
rows.push({ label: '配置标签', value: envelope.config.configLabel || '--' })
|
||||
rows.push({ label: '配置URL', value: envelope.config.configUrl || '--' })
|
||||
rows.push({ label: '配置Release', value: envelope.config.releaseId || '--' })
|
||||
rows.push({
|
||||
label: 'Launch Event',
|
||||
value: envelope.business && envelope.business.eventId
|
||||
? envelope.business.eventId
|
||||
: '--',
|
||||
})
|
||||
rows.push({
|
||||
label: 'Resolved Manifest',
|
||||
value: envelope.resolvedRelease && envelope.resolvedRelease.manifestUrl
|
||||
? envelope.resolvedRelease.manifestUrl
|
||||
: '--',
|
||||
})
|
||||
rows.push({
|
||||
label: 'Resolved Release',
|
||||
value: envelope.resolvedRelease && envelope.resolvedRelease.releaseId
|
||||
? envelope.resolvedRelease.releaseId
|
||||
: '--',
|
||||
})
|
||||
return rows
|
||||
}
|
||||
|
||||
function emitSimulatorLaunchDiagnostic(
|
||||
stage: string,
|
||||
payload: Record<string, unknown>,
|
||||
) {
|
||||
reportBackendClientLog({
|
||||
level: 'info',
|
||||
category: 'launch-diagnostic',
|
||||
message: stage,
|
||||
eventId: typeof payload.launchEventId === 'string' ? payload.launchEventId : '',
|
||||
releaseId: typeof payload.configReleaseId === 'string'
|
||||
? payload.configReleaseId
|
||||
: (typeof payload.resolvedReleaseId === 'string' ? payload.resolvedReleaseId : ''),
|
||||
sessionId: typeof payload.launchSessionId === 'string' ? payload.launchSessionId : '',
|
||||
manifestUrl: typeof payload.resolvedManifestUrl === 'string'
|
||||
? payload.resolvedManifestUrl
|
||||
: (typeof payload.configUrl === 'string' ? payload.configUrl : ''),
|
||||
details: payload,
|
||||
})
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {
|
||||
showDebugPanel: false,
|
||||
@@ -967,6 +1022,7 @@ Page({
|
||||
centerScaleRulerMajorMarks: [],
|
||||
compassTicks: buildCompassTicks(),
|
||||
compassLabels: buildCompassLabels(),
|
||||
showStartEntryButton: true,
|
||||
...buildSideButtonVisibility('shown'),
|
||||
...buildSideButtonState({
|
||||
sideButtonMode: 'shown',
|
||||
@@ -989,10 +1045,15 @@ Page({
|
||||
syncedBackendSessionFinishId = ''
|
||||
redirectedToResultPage = false
|
||||
shouldAutoRestoreRecoverySnapshot = options && options.recoverSession === '1'
|
||||
currentGameLaunchEnvelope = resolveGameLaunchEnvelope(options)
|
||||
if (!hasExplicitLaunchOptions(options)) {
|
||||
const recoverySnapshot = loadSessionRecoverySnapshot()
|
||||
if (recoverySnapshot) {
|
||||
shouldAutoStartSessionOnEnter = !!(options && options.autoStartOnEnter === '1')
|
||||
const recoverySnapshot = loadSessionRecoverySnapshot()
|
||||
if (shouldAutoRestoreRecoverySnapshot && recoverySnapshot) {
|
||||
// Recovery should trust the persisted session envelope first so it can
|
||||
// survive launchId stash misses and still reconstruct the original round.
|
||||
currentGameLaunchEnvelope = recoverySnapshot.launchEnvelope
|
||||
} else {
|
||||
currentGameLaunchEnvelope = resolveGameLaunchEnvelope(options)
|
||||
if (!hasExplicitLaunchOptions(options) && recoverySnapshot) {
|
||||
currentGameLaunchEnvelope = recoverySnapshot.launchEnvelope
|
||||
}
|
||||
}
|
||||
@@ -1005,6 +1066,9 @@ Page({
|
||||
const statusBarHeight = systemInfo.statusBarHeight || 0
|
||||
const menuButtonRect = wx.getMenuButtonBoundingClientRect()
|
||||
const menuButtonBottom = menuButtonRect && typeof menuButtonRect.bottom === 'number' ? menuButtonRect.bottom : statusBarHeight
|
||||
this.setData({
|
||||
showStartEntryButton: !shouldAutoStartSessionOnEnter,
|
||||
})
|
||||
|
||||
if (mapEngine) {
|
||||
mapEngine.destroy()
|
||||
@@ -1514,11 +1578,27 @@ Page({
|
||||
systemSettingsLockLifetimeActive = false
|
||||
currentGameLaunchEnvelope = getDemoGameLaunchEnvelope()
|
||||
shouldAutoRestoreRecoverySnapshot = false
|
||||
shouldAutoStartSessionOnEnter = false
|
||||
redirectedToResultPage = false
|
||||
stageCanvasAttached = false
|
||||
},
|
||||
|
||||
loadGameLaunchEnvelope(envelope: GameLaunchEnvelope) {
|
||||
emitSimulatorLaunchDiagnostic('loadGameLaunchEnvelope', {
|
||||
launchEventId: envelope.business && envelope.business.eventId ? envelope.business.eventId : '',
|
||||
launchSessionId: envelope.business && envelope.business.sessionId ? envelope.business.sessionId : '',
|
||||
configUrl: envelope.config.configUrl || '',
|
||||
configReleaseId: envelope.config.releaseId || '',
|
||||
resolvedManifestUrl: envelope.resolvedRelease && envelope.resolvedRelease.manifestUrl
|
||||
? envelope.resolvedRelease.manifestUrl
|
||||
: '',
|
||||
resolvedReleaseId: envelope.resolvedRelease && envelope.resolvedRelease.releaseId
|
||||
? envelope.resolvedRelease.releaseId
|
||||
: '',
|
||||
launchVariantId: envelope.variant && envelope.variant.variantId ? envelope.variant.variantId : null,
|
||||
launchVariantRouteCode: envelope.variant && envelope.variant.routeCode ? envelope.variant.routeCode : null,
|
||||
runtimeCourseVariantId: envelope.runtime && envelope.runtime.courseVariantId ? envelope.runtime.courseVariantId : null,
|
||||
})
|
||||
this.loadMapConfigFromRemote(
|
||||
envelope.config.configUrl,
|
||||
envelope.config.configLabel,
|
||||
@@ -1621,10 +1701,49 @@ Page({
|
||||
reportAbandonedRecoverySnapshot(snapshot: SessionRecoverySnapshot) {
|
||||
const sessionContext = getBackendSessionContextFromLaunchEnvelope(snapshot.launchEnvelope)
|
||||
if (!sessionContext) {
|
||||
reportBackendClientLog({
|
||||
level: 'warn',
|
||||
category: 'session-recovery',
|
||||
message: 'abandon recovery without valid session context',
|
||||
eventId: snapshot.launchEnvelope.business && snapshot.launchEnvelope.business.eventId
|
||||
? snapshot.launchEnvelope.business.eventId
|
||||
: '',
|
||||
releaseId: snapshot.launchEnvelope.config && snapshot.launchEnvelope.config.releaseId
|
||||
? snapshot.launchEnvelope.config.releaseId
|
||||
: '',
|
||||
manifestUrl: snapshot.launchEnvelope.resolvedRelease && snapshot.launchEnvelope.resolvedRelease.manifestUrl
|
||||
? snapshot.launchEnvelope.resolvedRelease.manifestUrl
|
||||
: snapshot.launchEnvelope.config && snapshot.launchEnvelope.config.configUrl
|
||||
? snapshot.launchEnvelope.config.configUrl
|
||||
: '',
|
||||
details: {
|
||||
phase: 'abandon-no-session',
|
||||
},
|
||||
})
|
||||
clearSessionRecoverySnapshot()
|
||||
return
|
||||
}
|
||||
|
||||
reportBackendClientLog({
|
||||
level: 'info',
|
||||
category: 'session-recovery',
|
||||
message: 'abandon recovery requested',
|
||||
eventId: snapshot.launchEnvelope.business && snapshot.launchEnvelope.business.eventId
|
||||
? snapshot.launchEnvelope.business.eventId
|
||||
: '',
|
||||
releaseId: snapshot.launchEnvelope.config && snapshot.launchEnvelope.config.releaseId
|
||||
? snapshot.launchEnvelope.config.releaseId
|
||||
: '',
|
||||
sessionId: sessionContext.sessionId,
|
||||
manifestUrl: snapshot.launchEnvelope.resolvedRelease && snapshot.launchEnvelope.resolvedRelease.manifestUrl
|
||||
? snapshot.launchEnvelope.resolvedRelease.manifestUrl
|
||||
: snapshot.launchEnvelope.config && snapshot.launchEnvelope.config.configUrl
|
||||
? snapshot.launchEnvelope.config.configUrl
|
||||
: '',
|
||||
details: {
|
||||
phase: 'abandon-requested',
|
||||
},
|
||||
})
|
||||
finishSession({
|
||||
baseUrl: getCurrentBackendBaseUrl(),
|
||||
sessionId: sessionContext.sessionId,
|
||||
@@ -1634,6 +1753,26 @@ Page({
|
||||
})
|
||||
.then(() => {
|
||||
syncedBackendSessionFinishId = sessionContext.sessionId
|
||||
reportBackendClientLog({
|
||||
level: 'info',
|
||||
category: 'session-recovery',
|
||||
message: 'abandon recovery synced as cancelled',
|
||||
eventId: snapshot.launchEnvelope.business && snapshot.launchEnvelope.business.eventId
|
||||
? snapshot.launchEnvelope.business.eventId
|
||||
: '',
|
||||
releaseId: snapshot.launchEnvelope.config && snapshot.launchEnvelope.config.releaseId
|
||||
? snapshot.launchEnvelope.config.releaseId
|
||||
: '',
|
||||
sessionId: sessionContext.sessionId,
|
||||
manifestUrl: snapshot.launchEnvelope.resolvedRelease && snapshot.launchEnvelope.resolvedRelease.manifestUrl
|
||||
? snapshot.launchEnvelope.resolvedRelease.manifestUrl
|
||||
: snapshot.launchEnvelope.config && snapshot.launchEnvelope.config.configUrl
|
||||
? snapshot.launchEnvelope.config.configUrl
|
||||
: '',
|
||||
details: {
|
||||
phase: 'abandon-finished',
|
||||
},
|
||||
})
|
||||
clearSessionRecoverySnapshot()
|
||||
wx.showToast({
|
||||
title: '已放弃上次对局',
|
||||
@@ -1642,6 +1781,27 @@ Page({
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
reportBackendClientLog({
|
||||
level: 'warn',
|
||||
category: 'session-recovery',
|
||||
message: 'abandon recovery finish(cancelled) failed',
|
||||
eventId: snapshot.launchEnvelope.business && snapshot.launchEnvelope.business.eventId
|
||||
? snapshot.launchEnvelope.business.eventId
|
||||
: '',
|
||||
releaseId: snapshot.launchEnvelope.config && snapshot.launchEnvelope.config.releaseId
|
||||
? snapshot.launchEnvelope.config.releaseId
|
||||
: '',
|
||||
sessionId: sessionContext.sessionId,
|
||||
manifestUrl: snapshot.launchEnvelope.resolvedRelease && snapshot.launchEnvelope.resolvedRelease.manifestUrl
|
||||
? snapshot.launchEnvelope.resolvedRelease.manifestUrl
|
||||
: snapshot.launchEnvelope.config && snapshot.launchEnvelope.config.configUrl
|
||||
? snapshot.launchEnvelope.config.configUrl
|
||||
: '',
|
||||
details: {
|
||||
phase: 'abandon-failed',
|
||||
message: error && error.message ? error.message : '未知错误',
|
||||
},
|
||||
})
|
||||
clearSessionRecoverySnapshot()
|
||||
const message = error && error.message ? error.message : '未知错误'
|
||||
this.setData({
|
||||
@@ -1712,6 +1872,28 @@ Page({
|
||||
this.applyRuntimeSystemSettings(true)
|
||||
const restored = mapEngine ? mapEngine.restoreSessionRecoveryRuntimeSnapshot(snapshot.runtime) : false
|
||||
if (!restored) {
|
||||
reportBackendClientLog({
|
||||
level: 'warn',
|
||||
category: 'session-recovery',
|
||||
message: 'recovery restore failed',
|
||||
eventId: snapshot.launchEnvelope.business && snapshot.launchEnvelope.business.eventId
|
||||
? snapshot.launchEnvelope.business.eventId
|
||||
: '',
|
||||
releaseId: snapshot.launchEnvelope.config && snapshot.launchEnvelope.config.releaseId
|
||||
? snapshot.launchEnvelope.config.releaseId
|
||||
: '',
|
||||
sessionId: snapshot.launchEnvelope.business && snapshot.launchEnvelope.business.sessionId
|
||||
? snapshot.launchEnvelope.business.sessionId
|
||||
: '',
|
||||
manifestUrl: snapshot.launchEnvelope.resolvedRelease && snapshot.launchEnvelope.resolvedRelease.manifestUrl
|
||||
? snapshot.launchEnvelope.resolvedRelease.manifestUrl
|
||||
: snapshot.launchEnvelope.config && snapshot.launchEnvelope.config.configUrl
|
||||
? snapshot.launchEnvelope.config.configUrl
|
||||
: '',
|
||||
details: {
|
||||
phase: 'restore-failed',
|
||||
},
|
||||
})
|
||||
clearSessionRecoverySnapshot()
|
||||
wx.showToast({
|
||||
title: '恢复失败,已回到初始状态',
|
||||
@@ -1726,11 +1908,34 @@ Page({
|
||||
showDebugPanel: false,
|
||||
showGameInfoPanel: false,
|
||||
showSystemSettingsPanel: false,
|
||||
showStartEntryButton: false,
|
||||
})
|
||||
const sessionContext = getCurrentBackendSessionContext()
|
||||
if (sessionContext) {
|
||||
syncedBackendSessionStartId = sessionContext.sessionId
|
||||
}
|
||||
reportBackendClientLog({
|
||||
level: 'info',
|
||||
category: 'session-recovery',
|
||||
message: 'recovery restored',
|
||||
eventId: snapshot.launchEnvelope.business && snapshot.launchEnvelope.business.eventId
|
||||
? snapshot.launchEnvelope.business.eventId
|
||||
: '',
|
||||
releaseId: snapshot.launchEnvelope.config && snapshot.launchEnvelope.config.releaseId
|
||||
? snapshot.launchEnvelope.config.releaseId
|
||||
: '',
|
||||
sessionId: snapshot.launchEnvelope.business && snapshot.launchEnvelope.business.sessionId
|
||||
? snapshot.launchEnvelope.business.sessionId
|
||||
: '',
|
||||
manifestUrl: snapshot.launchEnvelope.resolvedRelease && snapshot.launchEnvelope.resolvedRelease.manifestUrl
|
||||
? snapshot.launchEnvelope.resolvedRelease.manifestUrl
|
||||
: snapshot.launchEnvelope.config && snapshot.launchEnvelope.config.configUrl
|
||||
? snapshot.launchEnvelope.config.configUrl
|
||||
: '',
|
||||
details: {
|
||||
phase: 'restored',
|
||||
},
|
||||
})
|
||||
this.syncSessionRecoveryLifecycle('running')
|
||||
return true
|
||||
},
|
||||
@@ -1752,24 +1957,77 @@ Page({
|
||||
maybePromptSessionRecoveryRestore(config: RemoteMapConfig) {
|
||||
const snapshot = loadSessionRecoverySnapshot()
|
||||
if (!snapshot || !mapEngine) {
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
if (
|
||||
snapshot.launchEnvelope.config.configUrl !== currentGameLaunchEnvelope.config.configUrl
|
||||
|| snapshot.configAppId !== config.configAppId
|
||||
|| snapshot.configVersion !== config.configVersion
|
||||
) {
|
||||
reportBackendClientLog({
|
||||
level: 'warn',
|
||||
category: 'session-recovery',
|
||||
message: 'recovery snapshot dropped due to config mismatch',
|
||||
eventId: snapshot.launchEnvelope.business && snapshot.launchEnvelope.business.eventId
|
||||
? snapshot.launchEnvelope.business.eventId
|
||||
: '',
|
||||
releaseId: snapshot.launchEnvelope.config && snapshot.launchEnvelope.config.releaseId
|
||||
? snapshot.launchEnvelope.config.releaseId
|
||||
: '',
|
||||
sessionId: snapshot.launchEnvelope.business && snapshot.launchEnvelope.business.sessionId
|
||||
? snapshot.launchEnvelope.business.sessionId
|
||||
: '',
|
||||
manifestUrl: snapshot.launchEnvelope.resolvedRelease && snapshot.launchEnvelope.resolvedRelease.manifestUrl
|
||||
? snapshot.launchEnvelope.resolvedRelease.manifestUrl
|
||||
: snapshot.launchEnvelope.config && snapshot.launchEnvelope.config.configUrl
|
||||
? snapshot.launchEnvelope.config.configUrl
|
||||
: '',
|
||||
details: {
|
||||
phase: 'config-mismatch',
|
||||
currentConfigUrl: currentGameLaunchEnvelope.config.configUrl,
|
||||
snapshotConfigUrl: snapshot.launchEnvelope.config.configUrl,
|
||||
currentConfigAppId: config.configAppId,
|
||||
snapshotConfigAppId: snapshot.configAppId,
|
||||
},
|
||||
})
|
||||
clearSessionRecoverySnapshot()
|
||||
return
|
||||
this.setData({
|
||||
statusText: '检测到旧局恢复记录,但当前配置源已变化,已回到初始状态',
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
if (shouldAutoRestoreRecoverySnapshot) {
|
||||
shouldAutoRestoreRecoverySnapshot = false
|
||||
reportBackendClientLog({
|
||||
level: 'info',
|
||||
category: 'session-recovery',
|
||||
message: 'auto recovery requested',
|
||||
eventId: snapshot.launchEnvelope.business && snapshot.launchEnvelope.business.eventId
|
||||
? snapshot.launchEnvelope.business.eventId
|
||||
: '',
|
||||
releaseId: snapshot.launchEnvelope.config && snapshot.launchEnvelope.config.releaseId
|
||||
? snapshot.launchEnvelope.config.releaseId
|
||||
: '',
|
||||
sessionId: snapshot.launchEnvelope.business && snapshot.launchEnvelope.business.sessionId
|
||||
? snapshot.launchEnvelope.business.sessionId
|
||||
: '',
|
||||
manifestUrl: snapshot.launchEnvelope.resolvedRelease && snapshot.launchEnvelope.resolvedRelease.manifestUrl
|
||||
? snapshot.launchEnvelope.resolvedRelease.manifestUrl
|
||||
: snapshot.launchEnvelope.config && snapshot.launchEnvelope.config.configUrl
|
||||
? snapshot.launchEnvelope.config.configUrl
|
||||
: '',
|
||||
details: {
|
||||
phase: 'auto-restore',
|
||||
},
|
||||
})
|
||||
this.restoreRecoverySnapshot(snapshot)
|
||||
return
|
||||
return true
|
||||
}
|
||||
|
||||
this.setData({
|
||||
showStartEntryButton: true,
|
||||
})
|
||||
wx.showModal({
|
||||
title: '恢复对局',
|
||||
content: '检测到上次有未正常结束的对局,是否继续恢复?',
|
||||
@@ -1784,6 +2042,21 @@ Page({
|
||||
this.restoreRecoverySnapshot(snapshot)
|
||||
},
|
||||
})
|
||||
return true
|
||||
},
|
||||
|
||||
maybeAutoStartSessionOnEnter() {
|
||||
if (!shouldAutoStartSessionOnEnter || !mapEngine) {
|
||||
return
|
||||
}
|
||||
|
||||
shouldAutoStartSessionOnEnter = false
|
||||
systemSettingsLockLifetimeActive = true
|
||||
this.applyRuntimeSystemSettings(true)
|
||||
this.setData({
|
||||
showStartEntryButton: false,
|
||||
})
|
||||
mapEngine.handleStartGame()
|
||||
},
|
||||
|
||||
compileCurrentRuntimeProfile(lockLifetimeActive = isSystemSettingsLockLifetimeActive()) {
|
||||
@@ -1913,20 +2186,76 @@ Page({
|
||||
return
|
||||
}
|
||||
|
||||
emitSimulatorLaunchDiagnostic('loadRemoteMapConfig:resolved', {
|
||||
launchEventId: currentGameLaunchEnvelope.business && currentGameLaunchEnvelope.business.eventId
|
||||
? currentGameLaunchEnvelope.business.eventId
|
||||
: '',
|
||||
configUrl,
|
||||
configVersion: config.configVersion || '',
|
||||
schemaVersion: config.configSchemaVersion || '',
|
||||
playfieldKind: config.playfieldKind || '',
|
||||
gameMode: config.gameMode || '',
|
||||
configTitle: config.configTitle || '',
|
||||
})
|
||||
|
||||
currentEngine.applyRemoteMapConfig(config)
|
||||
this.applyConfiguredSystemSettings(config)
|
||||
this.applyCompiledRuntimeProfiles(true, {
|
||||
const compiledProfile = this.applyCompiledRuntimeProfiles(true, {
|
||||
includeMap: true,
|
||||
includeGame: true,
|
||||
includePresentation: true,
|
||||
})
|
||||
this.maybePromptSessionRecoveryRestore(config)
|
||||
if (compiledProfile) {
|
||||
reportBackendClientLog({
|
||||
level: 'info',
|
||||
category: 'runtime-compiler',
|
||||
message: 'compiled runtime profile applied',
|
||||
eventId: currentGameLaunchEnvelope.business && currentGameLaunchEnvelope.business.eventId
|
||||
? currentGameLaunchEnvelope.business.eventId
|
||||
: '',
|
||||
releaseId: currentGameLaunchEnvelope.config && currentGameLaunchEnvelope.config.releaseId
|
||||
? currentGameLaunchEnvelope.config.releaseId
|
||||
: '',
|
||||
sessionId: currentGameLaunchEnvelope.business && currentGameLaunchEnvelope.business.sessionId
|
||||
? currentGameLaunchEnvelope.business.sessionId
|
||||
: '',
|
||||
manifestUrl: currentGameLaunchEnvelope.resolvedRelease && currentGameLaunchEnvelope.resolvedRelease.manifestUrl
|
||||
? currentGameLaunchEnvelope.resolvedRelease.manifestUrl
|
||||
: currentGameLaunchEnvelope.config && currentGameLaunchEnvelope.config.configUrl
|
||||
? currentGameLaunchEnvelope.config.configUrl
|
||||
: '',
|
||||
details: {
|
||||
phase: 'compiled-runtime-applied',
|
||||
schemaVersion: config.configSchemaVersion || '',
|
||||
playfield: {
|
||||
kind: config.playfieldKind || '',
|
||||
},
|
||||
game: {
|
||||
mode: config.gameMode || '',
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
const recoveryHandled = this.maybePromptSessionRecoveryRestore(config)
|
||||
if (!recoveryHandled) {
|
||||
this.maybeAutoStartSessionOnEnter()
|
||||
} else {
|
||||
shouldAutoStartSessionOnEnter = false
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
if (mapEngine !== currentEngine) {
|
||||
return
|
||||
}
|
||||
|
||||
emitSimulatorLaunchDiagnostic('loadRemoteMapConfig:error', {
|
||||
launchEventId: currentGameLaunchEnvelope.business && currentGameLaunchEnvelope.business.eventId
|
||||
? currentGameLaunchEnvelope.business.eventId
|
||||
: '',
|
||||
configUrl,
|
||||
message: error && error.message ? error.message : '未知错误',
|
||||
})
|
||||
|
||||
const rawErrorMessage = error && error.message ? error.message : '未知错误'
|
||||
const errorMessage = rawErrorMessage.indexOf('404') >= 0
|
||||
? `release manifest 不存在或未发布 (${configLabel})`
|
||||
@@ -2115,6 +2444,10 @@ Page({
|
||||
})
|
||||
persistMockChannelId(channelId)
|
||||
persistMockAutoConnectEnabled(true)
|
||||
setGlobalMockDebugBridgeChannelId(channelId)
|
||||
setGlobalMockDebugBridgeUrl(this.data.mockDebugLogBridgeUrlDraft)
|
||||
persistStoredMockDebugLogBridgeUrl(this.data.mockDebugLogBridgeUrlDraft)
|
||||
setGlobalMockDebugBridgeEnabled(true)
|
||||
mapEngine.handleSetMockChannelId(channelId)
|
||||
mapEngine.handleSetMockLocationBridgeUrl(this.data.mockBridgeUrlDraft)
|
||||
mapEngine.handleSetMockHeartRateBridgeUrl(this.data.mockHeartRateBridgeUrlDraft)
|
||||
@@ -2144,6 +2477,7 @@ Page({
|
||||
mockChannelIdDraft: channelId,
|
||||
})
|
||||
persistMockChannelId(channelId)
|
||||
setGlobalMockDebugBridgeChannelId(channelId)
|
||||
if (mapEngine) {
|
||||
mapEngine.handleSetMockChannelId(channelId)
|
||||
}
|
||||
@@ -2199,12 +2533,17 @@ Page({
|
||||
},
|
||||
|
||||
handleSaveMockDebugLogBridgeUrl() {
|
||||
persistStoredMockDebugLogBridgeUrl(this.data.mockDebugLogBridgeUrlDraft)
|
||||
setGlobalMockDebugBridgeUrl(this.data.mockDebugLogBridgeUrlDraft)
|
||||
if (mapEngine) {
|
||||
mapEngine.handleSetMockDebugLogBridgeUrl(this.data.mockDebugLogBridgeUrlDraft)
|
||||
}
|
||||
},
|
||||
|
||||
handleConnectMockDebugLogBridge() {
|
||||
setGlobalMockDebugBridgeChannelId((this.data.mockChannelIdDraft || '').trim() || 'default')
|
||||
setGlobalMockDebugBridgeUrl(this.data.mockDebugLogBridgeUrlDraft)
|
||||
setGlobalMockDebugBridgeEnabled(true)
|
||||
if (mapEngine) {
|
||||
mapEngine.handleConnectMockDebugLogBridge()
|
||||
}
|
||||
@@ -2212,6 +2551,7 @@ Page({
|
||||
|
||||
handleDisconnectMockDebugLogBridge() {
|
||||
persistMockAutoConnectEnabled(false)
|
||||
setGlobalMockDebugBridgeEnabled(false)
|
||||
if (mapEngine) {
|
||||
mapEngine.handleDisconnectMockDebugLogBridge()
|
||||
}
|
||||
@@ -2358,8 +2698,12 @@ Page({
|
||||
|
||||
handleStartGame() {
|
||||
if (mapEngine) {
|
||||
shouldAutoStartSessionOnEnter = false
|
||||
systemSettingsLockLifetimeActive = true
|
||||
this.applyRuntimeSystemSettings(true)
|
||||
this.setData({
|
||||
showStartEntryButton: false,
|
||||
})
|
||||
mapEngine.handleStartGame()
|
||||
}
|
||||
},
|
||||
@@ -2443,6 +2787,7 @@ Page({
|
||||
const snapshot = mapEngine.getGameInfoSnapshot()
|
||||
const localRows = snapshot.localRows.concat([
|
||||
...buildRuntimeSummaryRows(currentGameLaunchEnvelope),
|
||||
...buildLaunchConfigSummaryRows(currentGameLaunchEnvelope),
|
||||
{ label: '比例尺开关', value: this.data.showCenterScaleRuler ? '开启' : '关闭' },
|
||||
{ label: '比例尺锚点', value: this.data.centerScaleRulerAnchorMode === 'compass-center' ? '指北针圆心' : '屏幕中心' },
|
||||
{ label: '按钮习惯', value: this.data.sideButtonPlacement === 'right' ? '右手' : '左手' },
|
||||
@@ -2471,7 +2816,9 @@ Page({
|
||||
resultSceneSubtitle: snapshot.subtitle,
|
||||
resultSceneHeroLabel: snapshot.heroLabel,
|
||||
resultSceneHeroValue: snapshot.heroValue,
|
||||
resultSceneRows: snapshot.rows.concat(buildRuntimeSummaryRows(currentGameLaunchEnvelope)),
|
||||
resultSceneRows: snapshot.rows
|
||||
.concat(buildRuntimeSummaryRows(currentGameLaunchEnvelope))
|
||||
.concat(buildLaunchConfigSummaryRows(currentGameLaunchEnvelope)),
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@
|
||||
<cover-view class="map-content-entry__text">{{pendingContentEntryText}}</cover-view>
|
||||
</cover-view>
|
||||
|
||||
<cover-view class="screen-button-layer screen-button-layer--start-left" wx:if="{{!showDebugPanel && !showGameInfoPanel && !showResultScene && !showSystemSettingsPanel && showBottomDebugButton && gameSessionStatus !== 'running'}}" bindtap="handleStartGame">
|
||||
<cover-view class="screen-button-layer screen-button-layer--start-left" wx:if="{{!showDebugPanel && !showGameInfoPanel && !showResultScene && !showSystemSettingsPanel && showBottomDebugButton && showStartEntryButton && gameSessionStatus !== 'running'}}" bindtap="handleStartGame">
|
||||
<cover-view class="screen-button-layer__text screen-button-layer__text--start">开始</cover-view>
|
||||
</cover-view>
|
||||
|
||||
|
||||
@@ -237,6 +237,20 @@ export interface BackendSessionResultView {
|
||||
}
|
||||
}
|
||||
|
||||
export interface BackendClientLogInput {
|
||||
source: string
|
||||
level: 'debug' | 'info' | 'warn' | 'error'
|
||||
category: string
|
||||
message: string
|
||||
eventId?: string
|
||||
releaseId?: string
|
||||
sessionId?: string
|
||||
manifestUrl?: string
|
||||
route?: string
|
||||
occurredAt?: string
|
||||
details?: Record<string, unknown>
|
||||
}
|
||||
|
||||
type BackendEnvelope<T> = {
|
||||
data: T
|
||||
}
|
||||
@@ -428,3 +442,15 @@ export function getMyResults(input: {
|
||||
authToken: input.accessToken,
|
||||
})
|
||||
}
|
||||
|
||||
export function postClientLog(input: {
|
||||
baseUrl: string
|
||||
payload: BackendClientLogInput
|
||||
}): Promise<void> {
|
||||
return requestBackend<void>({
|
||||
method: 'POST',
|
||||
baseUrl: input.baseUrl,
|
||||
path: '/dev/client-logs',
|
||||
body: input.payload as unknown as Record<string, unknown>,
|
||||
})
|
||||
}
|
||||
|
||||
90
miniprogram/utils/backendClientLogs.ts
Normal file
90
miniprogram/utils/backendClientLogs.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { loadBackendBaseUrl } from './backendAuth'
|
||||
import { postClientLog, type BackendClientLogInput } from './backendApi'
|
||||
|
||||
type ClientLogLevel = BackendClientLogInput['level']
|
||||
|
||||
type ClientLogEntry = {
|
||||
level: ClientLogLevel
|
||||
category: string
|
||||
message: string
|
||||
eventId?: string
|
||||
releaseId?: string
|
||||
sessionId?: string
|
||||
manifestUrl?: string
|
||||
route?: string
|
||||
details?: Record<string, unknown>
|
||||
}
|
||||
|
||||
const CLIENT_LOG_SOURCE = 'wechat-mini'
|
||||
const MAX_PENDING_CLIENT_LOGS = 100
|
||||
|
||||
const pendingClientLogs: BackendClientLogInput[] = []
|
||||
let clientLogFlushInProgress = false
|
||||
let clientLogSequence = 0
|
||||
|
||||
function getCurrentRoute(): string {
|
||||
const pages = getCurrentPages()
|
||||
if (!pages.length) {
|
||||
return ''
|
||||
}
|
||||
const current = pages[pages.length - 1]
|
||||
return current && current.route ? current.route : ''
|
||||
}
|
||||
|
||||
function enqueueClientLog(payload: BackendClientLogInput) {
|
||||
pendingClientLogs.push(payload)
|
||||
if (pendingClientLogs.length > MAX_PENDING_CLIENT_LOGS) {
|
||||
pendingClientLogs.shift()
|
||||
}
|
||||
}
|
||||
|
||||
function flushNextClientLog() {
|
||||
if (clientLogFlushInProgress || !pendingClientLogs.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const baseUrl = loadBackendBaseUrl()
|
||||
if (!baseUrl) {
|
||||
pendingClientLogs.length = 0
|
||||
return
|
||||
}
|
||||
|
||||
const payload = pendingClientLogs.shift()
|
||||
if (!payload) {
|
||||
return
|
||||
}
|
||||
|
||||
clientLogFlushInProgress = true
|
||||
postClientLog({
|
||||
baseUrl,
|
||||
payload,
|
||||
}).catch(() => {
|
||||
// 联调日志不打断主流程,失败时静默丢弃。
|
||||
}).finally(() => {
|
||||
clientLogFlushInProgress = false
|
||||
if (pendingClientLogs.length) {
|
||||
flushNextClientLog()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function reportBackendClientLog(entry: ClientLogEntry) {
|
||||
clientLogSequence += 1
|
||||
const details = entry.details ? { ...entry.details } : {}
|
||||
details.seq = clientLogSequence
|
||||
const payload: BackendClientLogInput = {
|
||||
source: CLIENT_LOG_SOURCE,
|
||||
level: entry.level,
|
||||
category: entry.category,
|
||||
message: entry.message,
|
||||
eventId: entry.eventId || '',
|
||||
releaseId: entry.releaseId || '',
|
||||
sessionId: entry.sessionId || '',
|
||||
manifestUrl: entry.manifestUrl || '',
|
||||
route: entry.route || getCurrentRoute(),
|
||||
occurredAt: new Date().toISOString(),
|
||||
details,
|
||||
}
|
||||
enqueueClientLog(payload)
|
||||
flushNextClientLog()
|
||||
}
|
||||
@@ -21,6 +21,18 @@ export function adaptBackendLaunchResultToEnvelope(result: BackendLaunchResult):
|
||||
sessionToken: result.launch.business.sessionToken,
|
||||
sessionTokenExpiresAt: result.launch.business.sessionTokenExpiresAt,
|
||||
},
|
||||
resolvedRelease: result.launch.resolvedRelease
|
||||
? {
|
||||
launchMode: result.launch.resolvedRelease.launchMode || null,
|
||||
source: result.launch.resolvedRelease.source || null,
|
||||
eventId: result.launch.resolvedRelease.eventId || null,
|
||||
releaseId: result.launch.resolvedRelease.releaseId || null,
|
||||
configLabel: result.launch.resolvedRelease.configLabel || null,
|
||||
manifestUrl: result.launch.resolvedRelease.manifestUrl || null,
|
||||
manifestChecksumSha256: result.launch.resolvedRelease.manifestChecksumSha256 || null,
|
||||
routeCode: result.launch.resolvedRelease.routeCode || null,
|
||||
}
|
||||
: null,
|
||||
variant: result.launch.variant
|
||||
? {
|
||||
variantId: result.launch.variant.id,
|
||||
|
||||
@@ -9,6 +9,17 @@ export interface GameConfigLaunchRequest {
|
||||
routeCode?: string | null
|
||||
}
|
||||
|
||||
export interface GameResolvedReleaseLaunchContext {
|
||||
launchMode?: string | null
|
||||
source?: string | null
|
||||
eventId?: string | null
|
||||
releaseId?: string | null
|
||||
configLabel?: string | null
|
||||
manifestUrl?: string | null
|
||||
manifestChecksumSha256?: string | null
|
||||
routeCode?: string | null
|
||||
}
|
||||
|
||||
export interface BusinessLaunchContext {
|
||||
source: BusinessLaunchSource
|
||||
competitionId?: string | null
|
||||
@@ -56,6 +67,7 @@ export interface GameContentBundleLaunchContext {
|
||||
export interface GameLaunchEnvelope {
|
||||
config: GameConfigLaunchRequest
|
||||
business: BusinessLaunchContext | null
|
||||
resolvedRelease?: GameResolvedReleaseLaunchContext | null
|
||||
variant?: GameVariantLaunchContext | null
|
||||
runtime?: GameRuntimeLaunchContext | null
|
||||
presentation?: GamePresentationLaunchContext | null
|
||||
@@ -65,6 +77,7 @@ export interface GameLaunchEnvelope {
|
||||
export interface MapPageLaunchOptions {
|
||||
launchId?: string
|
||||
recoverSession?: string
|
||||
autoStartOnEnter?: string
|
||||
preset?: string
|
||||
configUrl?: string
|
||||
configLabel?: string
|
||||
@@ -292,6 +305,7 @@ export function getDemoGameLaunchEnvelope(preset: DemoGamePreset = 'classic'): G
|
||||
business: {
|
||||
source: 'demo',
|
||||
},
|
||||
resolvedRelease: null,
|
||||
variant: null,
|
||||
runtime: null,
|
||||
presentation: null,
|
||||
@@ -324,12 +338,24 @@ export function consumePendingGameLaunchEnvelope(launchId: string): GameLaunchEn
|
||||
return envelope
|
||||
}
|
||||
|
||||
export function buildMapPageUrlWithLaunchId(launchId: string): string {
|
||||
return `/pages/map/map?launchId=${encodeURIComponent(launchId)}`
|
||||
export function buildMapPageUrlWithLaunchId(launchId: string, extraQuery?: Record<string, string>): string {
|
||||
const queryParts = [`launchId=${encodeURIComponent(launchId)}`]
|
||||
if (extraQuery) {
|
||||
Object.keys(extraQuery).forEach((key) => {
|
||||
const value = extraQuery[key]
|
||||
if (typeof value === 'string' && value) {
|
||||
queryParts.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
return `/pages/map/map?${queryParts.join('&')}`
|
||||
}
|
||||
|
||||
export function prepareMapPageUrlForLaunch(envelope: GameLaunchEnvelope): string {
|
||||
return buildMapPageUrlWithLaunchId(stashPendingGameLaunchEnvelope(envelope))
|
||||
return buildMapPageUrlWithLaunchId(
|
||||
stashPendingGameLaunchEnvelope(envelope),
|
||||
{ autoStartOnEnter: '1' },
|
||||
)
|
||||
}
|
||||
|
||||
export function prepareMapPageUrlForRecovery(envelope: GameLaunchEnvelope): string {
|
||||
@@ -367,6 +393,7 @@ export function resolveGameLaunchEnvelope(options?: MapPageLaunchOptions | null)
|
||||
routeCode: normalizeOptionalString(options ? options.routeCode : undefined),
|
||||
},
|
||||
business: buildBusinessLaunchContext(options),
|
||||
resolvedRelease: null,
|
||||
variant: buildVariantLaunchContext(options),
|
||||
runtime: buildRuntimeLaunchContext(options),
|
||||
presentation: buildPresentationLaunchContext(options),
|
||||
|
||||
88
miniprogram/utils/globalMockDebugBridge.ts
Normal file
88
miniprogram/utils/globalMockDebugBridge.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { MockSimulatorDebugLogger, type MockSimulatorDebugLogLevel } from '../engine/debug/mockSimulatorDebugLogger'
|
||||
|
||||
const DEBUG_MOCK_CHANNEL_ID_STORAGE_KEY = 'cmr.debug.mockChannelId.v1'
|
||||
const DEBUG_MOCK_AUTO_CONNECT_STORAGE_KEY = 'cmr.debug.autoConnectMockSources.v1'
|
||||
const DEBUG_MOCK_LOG_URL_STORAGE_KEY = 'cmr.debug.logBridgeUrl.v1'
|
||||
const DEFAULT_DEBUG_LOG_URL = 'wss://gs.gotomars.xyz/debug-log'
|
||||
|
||||
let globalMockDebugLogger: MockSimulatorDebugLogger | null = null
|
||||
|
||||
function ensureLogger(): MockSimulatorDebugLogger {
|
||||
if (!globalMockDebugLogger) {
|
||||
globalMockDebugLogger = new MockSimulatorDebugLogger()
|
||||
}
|
||||
return globalMockDebugLogger
|
||||
}
|
||||
|
||||
export function loadStoredMockChannelIdForGlobalDebug(): string {
|
||||
try {
|
||||
const value = wx.getStorageSync(DEBUG_MOCK_CHANNEL_ID_STORAGE_KEY)
|
||||
if (typeof value === 'string' && value.trim().length > 0) {
|
||||
return value.trim()
|
||||
}
|
||||
} catch (_error) {
|
||||
// Ignore storage read failures and fall back to default.
|
||||
}
|
||||
return 'default'
|
||||
}
|
||||
|
||||
export function loadMockAutoConnectEnabledForGlobalDebug(): boolean {
|
||||
try {
|
||||
return wx.getStorageSync(DEBUG_MOCK_AUTO_CONNECT_STORAGE_KEY) === true
|
||||
} catch (_error) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function loadStoredMockDebugLogBridgeUrl(): string {
|
||||
try {
|
||||
const value = wx.getStorageSync(DEBUG_MOCK_LOG_URL_STORAGE_KEY)
|
||||
if (typeof value === 'string' && value.trim().length > 0) {
|
||||
return value.trim()
|
||||
}
|
||||
} catch (_error) {
|
||||
// Ignore storage read failures and fall back to default.
|
||||
}
|
||||
return DEFAULT_DEBUG_LOG_URL
|
||||
}
|
||||
|
||||
export function persistStoredMockDebugLogBridgeUrl(url: string) {
|
||||
try {
|
||||
wx.setStorageSync(DEBUG_MOCK_LOG_URL_STORAGE_KEY, url)
|
||||
} catch (_error) {
|
||||
// Ignore storage write failures.
|
||||
}
|
||||
}
|
||||
|
||||
export function syncGlobalMockDebugBridgeFromStorage(): void {
|
||||
const logger = ensureLogger()
|
||||
logger.setChannelId(loadStoredMockChannelIdForGlobalDebug())
|
||||
logger.setUrl(loadStoredMockDebugLogBridgeUrl())
|
||||
logger.setEnabled(loadMockAutoConnectEnabledForGlobalDebug())
|
||||
}
|
||||
|
||||
export function setGlobalMockDebugBridgeChannelId(channelId: string): void {
|
||||
const logger = ensureLogger()
|
||||
logger.setChannelId(channelId)
|
||||
}
|
||||
|
||||
export function setGlobalMockDebugBridgeEnabled(enabled: boolean): void {
|
||||
const logger = ensureLogger()
|
||||
logger.setEnabled(enabled)
|
||||
}
|
||||
|
||||
export function setGlobalMockDebugBridgeUrl(url: string): void {
|
||||
const logger = ensureLogger()
|
||||
logger.setUrl(url)
|
||||
}
|
||||
|
||||
export function emitGlobalMockDebugLog(
|
||||
scope: string,
|
||||
level: MockSimulatorDebugLogLevel,
|
||||
message: string,
|
||||
payload?: Record<string, unknown>,
|
||||
): void {
|
||||
const logger = ensureLogger()
|
||||
logger.log(scope, level, message, payload)
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ export interface RemoteMapConfig {
|
||||
configAppId: string
|
||||
configSchemaVersion: string
|
||||
configVersion: string
|
||||
playfieldKind: string
|
||||
tileSource: string
|
||||
minZoom: number
|
||||
maxZoom: number
|
||||
@@ -122,6 +123,7 @@ interface ParsedGameConfig {
|
||||
appId: string
|
||||
schemaVersion: string
|
||||
version: string
|
||||
playfieldKind: string
|
||||
mapRoot: string
|
||||
mapMeta: string
|
||||
course: string | null
|
||||
@@ -1754,6 +1756,7 @@ function parseGameConfigFromJson(text: string, gameConfigUrl: string): ParsedGam
|
||||
appId: rawApp && typeof rawApp.id === 'string' ? rawApp.id : '',
|
||||
schemaVersion: typeof parsed.schemaVersion === 'string' ? parsed.schemaVersion : '1',
|
||||
version: typeof parsed.version === 'string' ? parsed.version : '',
|
||||
playfieldKind: rawPlayfield && typeof rawPlayfield.kind === 'string' ? rawPlayfield.kind : '',
|
||||
mapRoot,
|
||||
mapMeta,
|
||||
course: rawPlayfieldSource && typeof rawPlayfieldSource.url === 'string'
|
||||
@@ -1855,6 +1858,7 @@ function parseGameConfigFromYaml(text: string, gameConfigUrl: string): ParsedGam
|
||||
appId: '',
|
||||
schemaVersion: '1',
|
||||
version: '',
|
||||
playfieldKind: typeof config.playfieldkind === 'string' ? config.playfieldkind : '',
|
||||
mapRoot,
|
||||
mapMeta,
|
||||
course: typeof config.course === 'string' ? config.course : null,
|
||||
@@ -2157,6 +2161,7 @@ export async function loadRemoteMapConfig(gameConfigUrl: string): Promise<Remote
|
||||
configAppId: gameConfig.appId || '',
|
||||
configSchemaVersion: gameConfig.schemaVersion || '1',
|
||||
configVersion: gameConfig.version || '',
|
||||
playfieldKind: gameConfig.playfieldKind || '',
|
||||
tileSource: resolveUrl(mapRootUrl, mapMeta.tilePathTemplate),
|
||||
minZoom: mapMeta.minZoom,
|
||||
maxZoom: mapMeta.maxZoom,
|
||||
|
||||
Reference in New Issue
Block a user