From c87d204f28ebf455f96e072424d3f02720cc18f5 Mon Sep 17 00:00:00 2001 From: Joywayer Date: Sun, 28 Jun 2026 16:44:43 +0800 Subject: [PATCH] =?UTF-8?q?feat(framework):=20config=20=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E7=BC=96=E6=8E=92=20resolveBootstrap(=E8=BA=AB=E4=BB=BD?= =?UTF-8?q?=E5=90=88=E5=B9=B6+=E6=9C=8D=E5=8A=A1=E5=99=A8=E5=86=B3?= =?UTF-8?q?=E7=AD=96+=E9=99=8D=E7=BA=A7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../assets/framework/config/bootstrap.ts | 61 +++++++++++++++++++ .../framework-tests/config/bootstrap.test.ts | 60 ++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 cocoscreator_projects/YouleNexus/assets/framework/config/bootstrap.ts create mode 100644 cocoscreator_projects/framework-tests/config/bootstrap.test.ts diff --git a/cocoscreator_projects/YouleNexus/assets/framework/config/bootstrap.ts b/cocoscreator_projects/YouleNexus/assets/framework/config/bootstrap.ts new file mode 100644 index 0000000..2c2b8b7 --- /dev/null +++ b/cocoscreator_projects/YouleNexus/assets/framework/config/bootstrap.ts @@ -0,0 +1,61 @@ +import type { ChannelIdentity } from './identity.ts'; +import { resolveIdentity } from './identity.ts'; +import { BUILD_IDENTITY } from './sources/defaults.ts'; +import { queryStringSource } from './sources/query-string.ts'; +import { nativeSettingsSource, type NativeHost } from './sources/native-settings.ts'; +import { resolveActiveProfile, DEFAULT_GAMESERVER, type DebugProfile } from './profiles.ts'; +import { resolveServers, type ConfigFetcher, type RemoteConfig, ConfigFetchError } from './remote-config.ts'; + +export interface BootstrapOptions { + win: NativeHost; // 原生注入宿主(≈ window) + search: string; // location.search + fetcher: ConfigFetcher; // 远程配置抓取 + isNative: boolean; // 环境判定(URL 含 index.html → true,由调用方算) + fallbackServers?: string[]; // 远程失败/无地址时降级 + cacheBust?: () => string; // 防缓存串注入(默认 Date.now) +} + +export interface BootstrapResult { + identity: ChannelIdentity; + servers: string[]; + rawConfig: RemoteConfig | null; + profile: DebugProfile; +} + +/** 启动编排:解析身份 + 决定服务器,产出可喂 NetClient 的结果。 */ +export async function resolveBootstrap(opts: BootstrapOptions): Promise { + const profile = resolveActiveProfile(opts.search); + const runtime = opts.isNative ? nativeSettingsSource(opts.win) : queryStringSource(opts.search); + const identity = resolveIdentity([ + () => BUILD_IDENTITY, + runtime, + () => profile.identity ?? {}, + queryStringSource(opts.search), // 显式 URL 覆盖(最高) + ]); + + // 服务器决策:profile 显式 server 直连捷径 + if (profile.server) { + return { identity, servers: [profile.server], rawConfig: null, profile }; + } + + // 否则远程配置 + const base = profile.gameserver ?? DEFAULT_GAMESERVER; + const bust = opts.cacheBust ? opts.cacheBust() : String(Date.now()); + const url = base + '?' + bust; // 防缓存,等价原 min_timestamp/ifast_random + + let config: RemoteConfig; + try { + config = await opts.fetcher.fetch(url); + } catch { + if (opts.fallbackServers && opts.fallbackServers.length) { + return { identity, servers: opts.fallbackServers, rawConfig: null, profile }; + } + throw new ConfigFetchError(`远程配置抓取失败: ${url}`); + } + + let servers = resolveServers(config, identity); + if (!servers.length && opts.fallbackServers && opts.fallbackServers.length) { + servers = opts.fallbackServers; + } + return { identity, servers, rawConfig: config, profile }; +} diff --git a/cocoscreator_projects/framework-tests/config/bootstrap.test.ts b/cocoscreator_projects/framework-tests/config/bootstrap.test.ts new file mode 100644 index 0000000..127acfa --- /dev/null +++ b/cocoscreator_projects/framework-tests/config/bootstrap.test.ts @@ -0,0 +1,60 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { resolveBootstrap } from '../../YouleNexus/assets/framework/config/bootstrap.ts'; +import { BUILD_IDENTITY } from '../../YouleNexus/assets/framework/config/sources/defaults.ts'; +import type { ConfigFetcher, RemoteConfig } from '../../YouleNexus/assets/framework/config/remote-config.ts'; + +function fetcherReturning(config: RemoteConfig): ConfigFetcher & { calls: string[] } { + const calls: string[] = []; + return { calls, fetch: async (url: string) => { calls.push(url); return config; } }; +} +const throwingFetcher: ConfigFetcher = { + fetch: async () => { throw new Error('net down'); }, +}; + +const REMOTE: RemoteConfig = { data: { player_server_tcp: '5.5.5.5:5000' } }; + +test('profile=local:用 profile.server 直连,不抓远程', async () => { + const fetcher = fetcherReturning(REMOTE); + const r = await resolveBootstrap({ win: {}, search: '?profile=local', fetcher, isNative: false }); + assert.deepEqual(r.servers, ['ws://127.0.0.1:3088']); + assert.equal(r.rawConfig, null); + assert.equal(fetcher.calls.length, 0); +}); + +test('prod + H5:抓远程配置产出 servers,identity 来自 defaults', async () => { + const fetcher = fetcherReturning(REMOTE); + const r = await resolveBootstrap({ win: {}, search: '', fetcher, isNative: false }); + assert.deepEqual(r.servers, ['ws://5.5.5.5:5000']); + assert.equal(r.identity.agentid, BUILD_IDENTITY.agentid); + assert.equal(r.identity.gameid, BUILD_IDENTITY.gameid); + assert.equal(fetcher.calls.length, 1); +}); + +test('H5 显式 URL query 覆盖身份(最高优先级)', async () => { + const fetcher = fetcherReturning(REMOTE); + const r = await resolveBootstrap({ win: {}, search: '?agentid=999&channelid=C9', fetcher, isNative: false }); + assert.equal(r.identity.agentid, '999'); + assert.equal(r.identity.channelid, 'C9'); +}); + +test('原生模式:identity 来自 window.settings', async () => { + const fetcher = fetcherReturning(REMOTE); + const win = { settings: { getothername: () => 'NA', getchannelName: () => 'NC', getmarketname: () => 3 } }; + const r = await resolveBootstrap({ win, search: '', fetcher, isNative: true }); + assert.equal(r.identity.agentid, 'NA'); + assert.equal(r.identity.channelid, 'NC'); + assert.equal(r.identity.marketid, 3); +}); + +test('远程失败 + 有 fallbackServers → 降级使用', async () => { + const r = await resolveBootstrap({ win: {}, search: '', fetcher: throwingFetcher, isNative: false, fallbackServers: ['ws://fb:1'] }); + assert.deepEqual(r.servers, ['ws://fb:1']); +}); + +test('远程失败 + 无 fallback → 抛 ConfigFetchError', async () => { + await assert.rejects( + resolveBootstrap({ win: {}, search: '', fetcher: throwingFetcher, isNative: false }), + /远程配置抓取失败/, + ); +});