380 lines
11 KiB
TypeScript
380 lines
11 KiB
TypeScript
export type DemoGamePreset = 'classic' | 'score-o'
|
|
export type BusinessLaunchSource = 'demo' | 'competition' | 'direct-event' | 'custom'
|
|
|
|
export interface GameConfigLaunchRequest {
|
|
configUrl: string
|
|
configLabel: string
|
|
configChecksumSha256?: string | null
|
|
releaseId?: string | null
|
|
routeCode?: string | null
|
|
}
|
|
|
|
export interface BusinessLaunchContext {
|
|
source: BusinessLaunchSource
|
|
competitionId?: string | null
|
|
eventId?: string | null
|
|
launchRequestId?: string | null
|
|
participantId?: string | null
|
|
sessionId?: string | null
|
|
sessionToken?: string | null
|
|
sessionTokenExpiresAt?: string | null
|
|
realtimeEndpoint?: string | null
|
|
realtimeToken?: string | null
|
|
}
|
|
|
|
export interface GameVariantLaunchContext {
|
|
variantId?: string | null
|
|
variantName?: string | null
|
|
routeCode?: string | null
|
|
assignmentMode?: string | null
|
|
}
|
|
|
|
export interface GameRuntimeLaunchContext {
|
|
runtimeBindingId?: string | null
|
|
placeId?: string | null
|
|
placeName?: string | null
|
|
mapId?: string | null
|
|
mapName?: string | null
|
|
tileReleaseId?: string | null
|
|
courseSetId?: string | null
|
|
courseVariantId?: string | null
|
|
routeCode?: string | null
|
|
}
|
|
|
|
export interface GamePresentationLaunchContext {
|
|
presentationId?: string | null
|
|
templateKey?: string | null
|
|
version?: string | null
|
|
}
|
|
|
|
export interface GameContentBundleLaunchContext {
|
|
bundleId?: string | null
|
|
bundleType?: string | null
|
|
version?: string | null
|
|
}
|
|
|
|
export interface GameLaunchEnvelope {
|
|
config: GameConfigLaunchRequest
|
|
business: BusinessLaunchContext | null
|
|
variant?: GameVariantLaunchContext | null
|
|
runtime?: GameRuntimeLaunchContext | null
|
|
presentation?: GamePresentationLaunchContext | null
|
|
contentBundle?: GameContentBundleLaunchContext | null
|
|
}
|
|
|
|
export interface MapPageLaunchOptions {
|
|
launchId?: string
|
|
recoverSession?: string
|
|
preset?: string
|
|
configUrl?: string
|
|
configLabel?: string
|
|
configChecksumSha256?: string
|
|
releaseId?: string
|
|
routeCode?: string
|
|
launchSource?: string
|
|
competitionId?: string
|
|
eventId?: string
|
|
launchRequestId?: string
|
|
participantId?: string
|
|
sessionId?: string
|
|
sessionToken?: string
|
|
sessionTokenExpiresAt?: string
|
|
realtimeEndpoint?: string
|
|
realtimeToken?: string
|
|
variantId?: string
|
|
variantName?: string
|
|
assignmentMode?: string
|
|
runtimeBindingId?: string
|
|
placeId?: string
|
|
placeName?: string
|
|
mapId?: string
|
|
mapName?: string
|
|
tileReleaseId?: string
|
|
courseSetId?: string
|
|
courseVariantId?: string
|
|
presentationId?: string
|
|
presentationTemplateKey?: string
|
|
presentationVersion?: string
|
|
contentBundleId?: string
|
|
contentBundleType?: string
|
|
contentBundleVersion?: string
|
|
}
|
|
|
|
type PendingGameLaunchStore = Record<string, GameLaunchEnvelope>
|
|
|
|
const PENDING_GAME_LAUNCH_STORAGE_KEY = 'cmr.pendingGameLaunch.v1'
|
|
const CLASSIC_REMOTE_GAME_CONFIG_URL = 'https://oss-mbh5.colormaprun.com/gotomars/event/classic-sequential.json'
|
|
const SCORE_O_REMOTE_GAME_CONFIG_URL = 'https://oss-mbh5.colormaprun.com/gotomars/event/score-o.json'
|
|
|
|
function normalizeOptionalString(value: unknown): string | null {
|
|
if (typeof value !== 'string') {
|
|
return null
|
|
}
|
|
|
|
const normalized = decodeURIComponent(value).trim()
|
|
return normalized ? normalized : null
|
|
}
|
|
|
|
function resolveDemoPreset(value: string | null): DemoGamePreset {
|
|
return value === 'score-o' ? 'score-o' : 'classic'
|
|
}
|
|
|
|
function resolveBusinessLaunchSource(value: string | null): BusinessLaunchSource {
|
|
if (value === 'competition' || value === 'direct-event' || value === 'custom') {
|
|
return value
|
|
}
|
|
|
|
return 'demo'
|
|
}
|
|
|
|
function buildDemoConfig(preset: DemoGamePreset): GameConfigLaunchRequest {
|
|
if (preset === 'score-o') {
|
|
return {
|
|
configUrl: SCORE_O_REMOTE_GAME_CONFIG_URL,
|
|
configLabel: '积分赛配置',
|
|
}
|
|
}
|
|
|
|
return {
|
|
configUrl: CLASSIC_REMOTE_GAME_CONFIG_URL,
|
|
configLabel: '顺序赛配置',
|
|
}
|
|
}
|
|
|
|
function hasBusinessFields(context: Omit<BusinessLaunchContext, 'source'>): boolean {
|
|
return Object.values(context).some((value) => typeof value === 'string' && value.length > 0)
|
|
}
|
|
|
|
function buildBusinessLaunchContext(options?: MapPageLaunchOptions | null): BusinessLaunchContext | null {
|
|
if (!options) {
|
|
return null
|
|
}
|
|
|
|
const context = {
|
|
competitionId: normalizeOptionalString(options.competitionId),
|
|
eventId: normalizeOptionalString(options.eventId),
|
|
launchRequestId: normalizeOptionalString(options.launchRequestId),
|
|
participantId: normalizeOptionalString(options.participantId),
|
|
sessionId: normalizeOptionalString(options.sessionId),
|
|
sessionToken: normalizeOptionalString(options.sessionToken),
|
|
sessionTokenExpiresAt: normalizeOptionalString(options.sessionTokenExpiresAt),
|
|
realtimeEndpoint: normalizeOptionalString(options.realtimeEndpoint),
|
|
realtimeToken: normalizeOptionalString(options.realtimeToken),
|
|
}
|
|
|
|
const launchSource = normalizeOptionalString(options.launchSource)
|
|
if (!hasBusinessFields(context) && launchSource === null) {
|
|
return null
|
|
}
|
|
|
|
return {
|
|
source: resolveBusinessLaunchSource(launchSource),
|
|
...context,
|
|
}
|
|
}
|
|
|
|
function buildVariantLaunchContext(options?: MapPageLaunchOptions | null): GameVariantLaunchContext | null {
|
|
if (!options) {
|
|
return null
|
|
}
|
|
|
|
const variantId = normalizeOptionalString(options.variantId)
|
|
const variantName = normalizeOptionalString(options.variantName)
|
|
const routeCode = normalizeOptionalString(options.routeCode)
|
|
const assignmentMode = normalizeOptionalString(options.assignmentMode)
|
|
|
|
if (!variantId && !variantName && !routeCode && !assignmentMode) {
|
|
return null
|
|
}
|
|
|
|
return {
|
|
variantId,
|
|
variantName,
|
|
routeCode,
|
|
assignmentMode,
|
|
}
|
|
}
|
|
|
|
function buildRuntimeLaunchContext(options?: MapPageLaunchOptions | null): GameRuntimeLaunchContext | null {
|
|
if (!options) {
|
|
return null
|
|
}
|
|
|
|
const runtimeBindingId = normalizeOptionalString(options.runtimeBindingId)
|
|
const placeId = normalizeOptionalString(options.placeId)
|
|
const placeName = normalizeOptionalString(options.placeName)
|
|
const mapId = normalizeOptionalString(options.mapId)
|
|
const mapName = normalizeOptionalString(options.mapName)
|
|
const tileReleaseId = normalizeOptionalString(options.tileReleaseId)
|
|
const courseSetId = normalizeOptionalString(options.courseSetId)
|
|
const courseVariantId = normalizeOptionalString(options.courseVariantId)
|
|
const routeCode = normalizeOptionalString(options.routeCode)
|
|
|
|
if (!runtimeBindingId && !placeId && !placeName && !mapId && !mapName && !tileReleaseId && !courseSetId && !courseVariantId && !routeCode) {
|
|
return null
|
|
}
|
|
|
|
return {
|
|
runtimeBindingId,
|
|
placeId,
|
|
placeName,
|
|
mapId,
|
|
mapName,
|
|
tileReleaseId,
|
|
courseSetId,
|
|
courseVariantId,
|
|
routeCode,
|
|
}
|
|
}
|
|
|
|
function buildPresentationLaunchContext(options?: MapPageLaunchOptions | null): GamePresentationLaunchContext | null {
|
|
if (!options) {
|
|
return null
|
|
}
|
|
|
|
const presentationId = normalizeOptionalString(options.presentationId)
|
|
const templateKey = normalizeOptionalString(options.presentationTemplateKey)
|
|
const version = normalizeOptionalString(options.presentationVersion)
|
|
|
|
if (!presentationId && !templateKey && !version) {
|
|
return null
|
|
}
|
|
|
|
return {
|
|
presentationId,
|
|
templateKey,
|
|
version,
|
|
}
|
|
}
|
|
|
|
function buildContentBundleLaunchContext(options?: MapPageLaunchOptions | null): GameContentBundleLaunchContext | null {
|
|
if (!options) {
|
|
return null
|
|
}
|
|
|
|
const bundleId = normalizeOptionalString(options.contentBundleId)
|
|
const bundleType = normalizeOptionalString(options.contentBundleType)
|
|
const version = normalizeOptionalString(options.contentBundleVersion)
|
|
|
|
if (!bundleId && !bundleType && !version) {
|
|
return null
|
|
}
|
|
|
|
return {
|
|
bundleId,
|
|
bundleType,
|
|
version,
|
|
}
|
|
}
|
|
|
|
function loadPendingGameLaunchStore(): PendingGameLaunchStore {
|
|
try {
|
|
const stored = wx.getStorageSync(PENDING_GAME_LAUNCH_STORAGE_KEY)
|
|
if (!stored || typeof stored !== 'object') {
|
|
return {}
|
|
}
|
|
|
|
return stored as PendingGameLaunchStore
|
|
} catch {
|
|
return {}
|
|
}
|
|
}
|
|
|
|
function savePendingGameLaunchStore(store: PendingGameLaunchStore): void {
|
|
try {
|
|
wx.setStorageSync(PENDING_GAME_LAUNCH_STORAGE_KEY, store)
|
|
} catch {}
|
|
}
|
|
|
|
export function getDemoGameLaunchEnvelope(preset: DemoGamePreset = 'classic'): GameLaunchEnvelope {
|
|
return {
|
|
config: buildDemoConfig(preset),
|
|
business: {
|
|
source: 'demo',
|
|
},
|
|
variant: null,
|
|
runtime: null,
|
|
presentation: null,
|
|
contentBundle: null,
|
|
}
|
|
}
|
|
|
|
export function stashPendingGameLaunchEnvelope(envelope: GameLaunchEnvelope): string {
|
|
const launchId = `launch_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`
|
|
const store = loadPendingGameLaunchStore()
|
|
store[launchId] = envelope
|
|
savePendingGameLaunchStore(store)
|
|
return launchId
|
|
}
|
|
|
|
export function consumePendingGameLaunchEnvelope(launchId: string): GameLaunchEnvelope | null {
|
|
const normalizedLaunchId = normalizeOptionalString(launchId)
|
|
if (!normalizedLaunchId) {
|
|
return null
|
|
}
|
|
|
|
const store = loadPendingGameLaunchStore()
|
|
const envelope = store[normalizedLaunchId] || null
|
|
if (!envelope) {
|
|
return null
|
|
}
|
|
|
|
delete store[normalizedLaunchId]
|
|
savePendingGameLaunchStore(store)
|
|
return envelope
|
|
}
|
|
|
|
export function buildMapPageUrlWithLaunchId(launchId: string): string {
|
|
return `/pages/map/map?launchId=${encodeURIComponent(launchId)}`
|
|
}
|
|
|
|
export function prepareMapPageUrlForLaunch(envelope: GameLaunchEnvelope): string {
|
|
return buildMapPageUrlWithLaunchId(stashPendingGameLaunchEnvelope(envelope))
|
|
}
|
|
|
|
export function prepareMapPageUrlForRecovery(envelope: GameLaunchEnvelope): string {
|
|
return `${buildMapPageUrlWithLaunchId(stashPendingGameLaunchEnvelope(envelope))}&recoverSession=1`
|
|
}
|
|
|
|
export function getBackendSessionContextFromLaunchEnvelope(envelope: GameLaunchEnvelope | null | undefined): { sessionId: string; sessionToken: string } | null {
|
|
if (!envelope || !envelope.business || !envelope.business.sessionId || !envelope.business.sessionToken) {
|
|
return null
|
|
}
|
|
|
|
return {
|
|
sessionId: envelope.business.sessionId,
|
|
sessionToken: envelope.business.sessionToken,
|
|
}
|
|
}
|
|
|
|
export function resolveGameLaunchEnvelope(options?: MapPageLaunchOptions | null): GameLaunchEnvelope {
|
|
const launchId = normalizeOptionalString(options ? options.launchId : undefined)
|
|
if (launchId) {
|
|
const pendingEnvelope = consumePendingGameLaunchEnvelope(launchId)
|
|
if (pendingEnvelope) {
|
|
return pendingEnvelope
|
|
}
|
|
}
|
|
|
|
const configUrl = normalizeOptionalString(options ? options.configUrl : undefined)
|
|
if (configUrl) {
|
|
return {
|
|
config: {
|
|
configUrl,
|
|
configLabel: normalizeOptionalString(options ? options.configLabel : undefined) || '线上配置',
|
|
configChecksumSha256: normalizeOptionalString(options ? options.configChecksumSha256 : undefined),
|
|
releaseId: normalizeOptionalString(options ? options.releaseId : undefined),
|
|
routeCode: normalizeOptionalString(options ? options.routeCode : undefined),
|
|
},
|
|
business: buildBusinessLaunchContext(options),
|
|
variant: buildVariantLaunchContext(options),
|
|
runtime: buildRuntimeLaunchContext(options),
|
|
presentation: buildPresentationLaunchContext(options),
|
|
contentBundle: buildContentBundleLaunchContext(options),
|
|
}
|
|
}
|
|
|
|
const preset = resolveDemoPreset(normalizeOptionalString(options ? options.preset : undefined))
|
|
return getDemoGameLaunchEnvelope(preset)
|
|
}
|