diff --git a/cocoscreator_projects/YouleNexus/assets/framework/config/bootstrap.ts b/cocoscreator_projects/YouleNexus/assets/framework/config/bootstrap.ts index 2c2b8b7..d39b8e6 100644 --- a/cocoscreator_projects/YouleNexus/assets/framework/config/bootstrap.ts +++ b/cocoscreator_projects/YouleNexus/assets/framework/config/bootstrap.ts @@ -3,10 +3,12 @@ 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 { resolveActiveProfile, PROFILES, DEFAULT_GAMESERVER, type DebugProfile } from './profiles.ts'; import { resolveServers, type ConfigFetcher, type RemoteConfig, ConfigFetchError } from './remote-config.ts'; +import type { RuntimeMode } from './runtime-mode.ts'; export interface BootstrapOptions { + mode: RuntimeMode; // 运行模式(由 resolveRuntimeMode 得到) win: NativeHost; // 原生注入宿主(≈ window) search: string; // location.search fetcher: ConfigFetcher; // 远程配置抓取 @@ -16,39 +18,50 @@ export interface BootstrapOptions { } export interface BootstrapResult { + mode: RuntimeMode; + isDebugger: boolean; // = (mode === 'debug') identity: ChannelIdentity; servers: string[]; rawConfig: RemoteConfig | null; profile: DebugProfile; } -/** 启动编排:解析身份 + 决定服务器,产出可喂 NetClient 的结果。 */ +/** 启动编排:按运行模式解析身份 + 决定服务器,产出可喂 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 覆盖(最高) - ]); + const isDebug = opts.mode === 'debug'; - // 服务器决策:profile 显式 server 直连捷径 + // 模式决定 profile 与身份来源:release 强制 prod,且身份只走原生(绝不读 URL)。 + const profile = isDebug ? resolveActiveProfile(opts.search) : PROFILES.prod; + const identity = isDebug + ? resolveIdentity([ + () => BUILD_IDENTITY, + opts.isNative ? nativeSettingsSource(opts.win) : queryStringSource(opts.search), + () => profile.identity ?? {}, + queryStringSource(opts.search), // 显式 URL 覆盖(最高) + ]) + : resolveIdentity([ + () => BUILD_IDENTITY, + nativeSettingsSource(opts.win), // release:身份只取原生,URL 一律无效 + ]); + + const resultBase = { mode: opts.mode, isDebugger: isDebug, identity, profile }; + + // 服务器决策:profile 显式 server 直连捷径(仅 debug 的 local/lab 等档位会有) if (profile.server) { - return { identity, servers: [profile.server], rawConfig: null, profile }; + return { ...resultBase, servers: [profile.server], rawConfig: null }; } // 否则远程配置 - const base = profile.gameserver ?? DEFAULT_GAMESERVER; + const gameserver = profile.gameserver ?? DEFAULT_GAMESERVER; const bust = opts.cacheBust ? opts.cacheBust() : String(Date.now()); - const url = base + '?' + bust; // 防缓存,等价原 min_timestamp/ifast_random + const url = gameserver + '?' + 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 }; + return { ...resultBase, servers: opts.fallbackServers, rawConfig: null }; } throw new ConfigFetchError(`远程配置抓取失败: ${url}`); } @@ -57,5 +70,5 @@ export async function resolveBootstrap(opts: BootstrapOptions): Promise { +test('debug + profile=local:用 profile.server 直连,不抓远程', async () => { const fetcher = fetcherReturning(REMOTE); - const r = await resolveBootstrap({ win: {}, search: '?profile=local', fetcher, isNative: false }); + const r = await resolveBootstrap({ mode: 'debug', 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 () => { +test('debug + prod + H5:抓远程配置产出 servers,identity 来自 defaults', async () => { const fetcher = fetcherReturning(REMOTE); - const r = await resolveBootstrap({ win: {}, search: '', fetcher, isNative: false }); + const r = await resolveBootstrap({ mode: 'debug', 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 () => { +test('debug + H5 显式 URL query 覆盖身份(最高优先级)', async () => { const fetcher = fetcherReturning(REMOTE); - const r = await resolveBootstrap({ win: {}, search: '?agentid=999&channelid=C9', fetcher, isNative: false }); + const r = await resolveBootstrap({ mode: 'debug', 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 () => { +test('debug + 原生模式: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 }); + const r = await resolveBootstrap({ mode: 'debug', 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'] }); +test('debug + 远程失败 + 有 fallbackServers → 降级使用', async () => { + const r = await resolveBootstrap({ mode: 'debug', win: {}, search: '', fetcher: throwingFetcher, isNative: false, fallbackServers: ['ws://fb:1'] }); assert.deepEqual(r.servers, ['ws://fb:1']); }); -test('远程失败 + 无 fallback → 抛 ConfigFetchError', async () => { +test('debug + 远程失败 + 无 fallback → 抛 ConfigFetchError', async () => { await assert.rejects( - resolveBootstrap({ win: {}, search: '', fetcher: throwingFetcher, isNative: false }), + resolveBootstrap({ mode: 'debug', win: {}, search: '', fetcher: throwingFetcher, isNative: false }), /远程配置抓取失败/, ); }); + +test('debug 结果含 mode/isDebugger', async () => { + const fetcher = fetcherReturning(REMOTE); + const r = await resolveBootstrap({ mode: 'debug', win: {}, search: '', fetcher, isNative: false }); + assert.equal(r.mode, 'debug'); + assert.equal(r.isDebugger, true); +}); + +test('release:忽略所有 URL 覆盖,强制 prod,identity 不被 URL 改', async () => { + const fetcher = fetcherReturning(REMOTE); + const r = await resolveBootstrap({ mode: 'release', win: {}, search: '?profile=local&agentid=999', fetcher, isNative: false }); + assert.equal(r.profile.name, 'prod'); + assert.deepEqual(r.servers, ['ws://5.5.5.5:5000']); // 走远程,不是 local 直连 + assert.equal(r.identity.agentid, BUILD_IDENTITY.agentid); // 999 无效 + assert.equal(r.isDebugger, false); + assert.equal(r.mode, 'release'); + assert.equal(fetcher.calls.length, 1); +}); + +test('release:原生身份仍生效(URL 的 999 无效)', async () => { + const fetcher = fetcherReturning(REMOTE); + const win = { settings: { getothername: () => 'NA', getchannelName: () => 'NC', getmarketname: () => 3 } }; + const r = await resolveBootstrap({ mode: 'release', win, search: '?agentid=999', fetcher, isNative: true }); + assert.equal(r.identity.agentid, 'NA'); + assert.equal(r.identity.channelid, 'NC'); +});