gameLaunch.ts 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. export type DemoGamePreset = 'classic' | 'score-o'
  2. export type BusinessLaunchSource = 'demo' | 'competition' | 'direct-event' | 'custom'
  3. export interface GameConfigLaunchRequest {
  4. configUrl: string
  5. configLabel: string
  6. configChecksumSha256?: string | null
  7. releaseId?: string | null
  8. routeCode?: string | null
  9. }
  10. export interface BusinessLaunchContext {
  11. source: BusinessLaunchSource
  12. competitionId?: string | null
  13. eventId?: string | null
  14. launchRequestId?: string | null
  15. participantId?: string | null
  16. sessionId?: string | null
  17. sessionToken?: string | null
  18. sessionTokenExpiresAt?: string | null
  19. realtimeEndpoint?: string | null
  20. realtimeToken?: string | null
  21. }
  22. export interface GameLaunchEnvelope {
  23. config: GameConfigLaunchRequest
  24. business: BusinessLaunchContext | null
  25. }
  26. export interface MapPageLaunchOptions {
  27. launchId?: string
  28. recoverSession?: string
  29. preset?: string
  30. configUrl?: string
  31. configLabel?: string
  32. configChecksumSha256?: string
  33. releaseId?: string
  34. routeCode?: string
  35. launchSource?: string
  36. competitionId?: string
  37. eventId?: string
  38. launchRequestId?: string
  39. participantId?: string
  40. sessionId?: string
  41. sessionToken?: string
  42. sessionTokenExpiresAt?: string
  43. realtimeEndpoint?: string
  44. realtimeToken?: string
  45. }
  46. type PendingGameLaunchStore = Record<string, GameLaunchEnvelope>
  47. const PENDING_GAME_LAUNCH_STORAGE_KEY = 'cmr.pendingGameLaunch.v1'
  48. const CLASSIC_REMOTE_GAME_CONFIG_URL = 'https://oss-mbh5.colormaprun.com/gotomars/event/classic-sequential.json'
  49. const SCORE_O_REMOTE_GAME_CONFIG_URL = 'https://oss-mbh5.colormaprun.com/gotomars/event/score-o.json'
  50. function normalizeOptionalString(value: unknown): string | null {
  51. if (typeof value !== 'string') {
  52. return null
  53. }
  54. const normalized = decodeURIComponent(value).trim()
  55. return normalized ? normalized : null
  56. }
  57. function resolveDemoPreset(value: string | null): DemoGamePreset {
  58. return value === 'score-o' ? 'score-o' : 'classic'
  59. }
  60. function resolveBusinessLaunchSource(value: string | null): BusinessLaunchSource {
  61. if (value === 'competition' || value === 'direct-event' || value === 'custom') {
  62. return value
  63. }
  64. return 'demo'
  65. }
  66. function buildDemoConfig(preset: DemoGamePreset): GameConfigLaunchRequest {
  67. if (preset === 'score-o') {
  68. return {
  69. configUrl: SCORE_O_REMOTE_GAME_CONFIG_URL,
  70. configLabel: '积分赛配置',
  71. }
  72. }
  73. return {
  74. configUrl: CLASSIC_REMOTE_GAME_CONFIG_URL,
  75. configLabel: '顺序赛配置',
  76. }
  77. }
  78. function hasBusinessFields(context: Omit<BusinessLaunchContext, 'source'>): boolean {
  79. return Object.values(context).some((value) => typeof value === 'string' && value.length > 0)
  80. }
  81. function buildBusinessLaunchContext(options?: MapPageLaunchOptions | null): BusinessLaunchContext | null {
  82. if (!options) {
  83. return null
  84. }
  85. const context = {
  86. competitionId: normalizeOptionalString(options.competitionId),
  87. eventId: normalizeOptionalString(options.eventId),
  88. launchRequestId: normalizeOptionalString(options.launchRequestId),
  89. participantId: normalizeOptionalString(options.participantId),
  90. sessionId: normalizeOptionalString(options.sessionId),
  91. sessionToken: normalizeOptionalString(options.sessionToken),
  92. sessionTokenExpiresAt: normalizeOptionalString(options.sessionTokenExpiresAt),
  93. realtimeEndpoint: normalizeOptionalString(options.realtimeEndpoint),
  94. realtimeToken: normalizeOptionalString(options.realtimeToken),
  95. }
  96. const launchSource = normalizeOptionalString(options.launchSource)
  97. if (!hasBusinessFields(context) && launchSource === null) {
  98. return null
  99. }
  100. return {
  101. source: resolveBusinessLaunchSource(launchSource),
  102. ...context,
  103. }
  104. }
  105. function loadPendingGameLaunchStore(): PendingGameLaunchStore {
  106. try {
  107. const stored = wx.getStorageSync(PENDING_GAME_LAUNCH_STORAGE_KEY)
  108. if (!stored || typeof stored !== 'object') {
  109. return {}
  110. }
  111. return stored as PendingGameLaunchStore
  112. } catch {
  113. return {}
  114. }
  115. }
  116. function savePendingGameLaunchStore(store: PendingGameLaunchStore): void {
  117. try {
  118. wx.setStorageSync(PENDING_GAME_LAUNCH_STORAGE_KEY, store)
  119. } catch {}
  120. }
  121. export function getDemoGameLaunchEnvelope(preset: DemoGamePreset = 'classic'): GameLaunchEnvelope {
  122. return {
  123. config: buildDemoConfig(preset),
  124. business: {
  125. source: 'demo',
  126. },
  127. }
  128. }
  129. export function stashPendingGameLaunchEnvelope(envelope: GameLaunchEnvelope): string {
  130. const launchId = `launch_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`
  131. const store = loadPendingGameLaunchStore()
  132. store[launchId] = envelope
  133. savePendingGameLaunchStore(store)
  134. return launchId
  135. }
  136. export function consumePendingGameLaunchEnvelope(launchId: string): GameLaunchEnvelope | null {
  137. const normalizedLaunchId = normalizeOptionalString(launchId)
  138. if (!normalizedLaunchId) {
  139. return null
  140. }
  141. const store = loadPendingGameLaunchStore()
  142. const envelope = store[normalizedLaunchId] || null
  143. if (!envelope) {
  144. return null
  145. }
  146. delete store[normalizedLaunchId]
  147. savePendingGameLaunchStore(store)
  148. return envelope
  149. }
  150. export function buildMapPageUrlWithLaunchId(launchId: string): string {
  151. return `/pages/map/map?launchId=${encodeURIComponent(launchId)}`
  152. }
  153. export function prepareMapPageUrlForLaunch(envelope: GameLaunchEnvelope): string {
  154. return buildMapPageUrlWithLaunchId(stashPendingGameLaunchEnvelope(envelope))
  155. }
  156. export function prepareMapPageUrlForRecovery(envelope: GameLaunchEnvelope): string {
  157. return `${buildMapPageUrlWithLaunchId(stashPendingGameLaunchEnvelope(envelope))}&recoverSession=1`
  158. }
  159. export function getBackendSessionContextFromLaunchEnvelope(envelope: GameLaunchEnvelope | null | undefined): { sessionId: string; sessionToken: string } | null {
  160. if (!envelope || !envelope.business || !envelope.business.sessionId || !envelope.business.sessionToken) {
  161. return null
  162. }
  163. return {
  164. sessionId: envelope.business.sessionId,
  165. sessionToken: envelope.business.sessionToken,
  166. }
  167. }
  168. export function resolveGameLaunchEnvelope(options?: MapPageLaunchOptions | null): GameLaunchEnvelope {
  169. const launchId = normalizeOptionalString(options ? options.launchId : undefined)
  170. if (launchId) {
  171. const pendingEnvelope = consumePendingGameLaunchEnvelope(launchId)
  172. if (pendingEnvelope) {
  173. return pendingEnvelope
  174. }
  175. }
  176. const configUrl = normalizeOptionalString(options ? options.configUrl : undefined)
  177. if (configUrl) {
  178. return {
  179. config: {
  180. configUrl,
  181. configLabel: normalizeOptionalString(options ? options.configLabel : undefined) || '线上配置',
  182. configChecksumSha256: normalizeOptionalString(options ? options.configChecksumSha256 : undefined),
  183. releaseId: normalizeOptionalString(options ? options.releaseId : undefined),
  184. routeCode: normalizeOptionalString(options ? options.routeCode : undefined),
  185. },
  186. business: buildBusinessLaunchContext(options),
  187. }
  188. }
  189. const preset = resolveDemoPreset(normalizeOptionalString(options ? options.preset : undefined))
  190. return getDemoGameLaunchEnvelope(preset)
  191. }