feat(scripts): LoginFlow 改用真 WS 调本地服务器 (?profile=local)

之前 mode='release' 导致 ?profile=local 不生效,本地服务器永远连不上。
修复:mode='debug' + ?profile=local → resolveActiveProfile 选 local
→ server='ws://127.0.0.1:3088' 直连。

MockTransport 替换为 WSTransport(用浏览器原生 WebSocket API),
NetClient 调用 transport.connect → 真 WS connect 真服务器。

真机端到端实证(07:40:59):
[LoginFlow] bootstrap OK, servers=ws://127.0.0.1:3088 profile=local
[LoginFlow] WS connect to ws://127.0.0.1:3088

待本地服务器真回 player_login 后,链路完整。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-01 07:41:12 +08:00
co-authored by Claude Opus 5
parent ae14622d4b
commit 2fa663a9e5
@@ -12,42 +12,54 @@ const { ccclass } = _decorator;
/** /**
* LoginFlow: 登录场景编排器(真机版,接入 PlatformSession 真实流)。 * LoginFlow: 登录场景编排器(真机版,接入 PlatformSession 真实流)。
* *
* 流程(对应 gameabc 启动流程 step 3): * 调试期间:
* 1. 构造 EventBus + PlatformSession + NetClient(用 mock transport) * - mode='debug' + ?profile=local → 跳过远程配置,直连本地服务器
* 2. 调 resolveBootstrap 拉远程配置(可能因无后端失败 → 进 loginFailed) * - 真实 WebSocket 传输(用浏览器原生 WebSocket API)
* 3. Mock transport 同步触发 open + login 事件 → PlatformSession 落 Store
* 4. login 成功(state=0) → AppStore.phase=loggedIn → director.loadScene('MainMenu')
* *
* 注意:当前 Transport 是 mock sync 触发(真 WS 需要 Cocos WebSocket adapter, * 流程:
* 未实现)。framework/net/cocos-transport.ts 留作生产占位。 * 1. 构造 EventBus + PlatformSession + NetClient(WebSocket transport)
* * 2. bootstrap() 跳过远程,直接拿 local server='ws://127.0.0.1:3088'
* 限制:PlayerInfoView prefab 持久化 __type__ 字段问题未解决(memory 记录), * 3. NetClient.start() → 真 WS connect → server 回 player_login
* 本脚本不依赖 PlayerInfoView 组件,只验证 PlatformSession 链路。 * 4. PlatformSession 落 3 Store → app.phase=loggedIn
* 5. director.loadScene('MainMenu')
*/ */
class MockTransport implements Transport { class WSTransport implements Transport {
private onOpenCb?: () => void; private onOpenCb?: () => void;
private onMessageCb?: (frame: string) => void; private onMessageCb?: (f: string) => void;
private onCloseCb?: () => void; private onCloseCb?: () => void;
connect(_url: string): void { private ws: WebSocket | null = null;
// 模拟:连接后 200ms 同步触发 open + login connect(url: string): void {
setTimeout(() => { console.log('[LoginFlow] WS connect to ' + url);
this.onOpenCb?.(); try {
this.onMessageCb?.(JSON.stringify({ this.ws = new WebSocket(url);
app: 'youle', route: 'agent', rpc: 'player_login', this.ws.onopen = () => {
data: { console.log('[LoginFlow] WS open');
state: 0, playerid: 430511, agentid: 'A', channelid: 'C', this.onOpenCb?.();
nickname: '测试玩家', avatar: '', openid: 'o1', sex: 0, };
bean: 0, roomcard: 3, score: 0, invitecode: null, advanced: 0, this.ws.onmessage = (ev) => {
taskstate: 1, ip: '127.0.0.1', bankpower: 1, bank: 0, this.onMessageCb?.(typeof ev.data === 'string' ? ev.data : '');
sign: null, tel: null, initCard: '3', initBean: '0', bankpwd: 0, };
unionid: 'mock_unionid', this.ws.onerror = (ev) => {
}, console.warn('[LoginFlow] WS error: ' + (ev as any).message);
})); this.onCloseCb?.();
}, 200); };
this.ws.onclose = () => {
console.log('[LoginFlow] WS close');
this.onCloseCb?.();
};
} catch (e) {
console.error('[LoginFlow] WebSocket construct failed: ' + (e as Error).message);
this.onCloseCb?.();
}
}
send(frame: string): void {
this.ws?.send(frame);
}
close(): void {
this.ws?.close();
this.onCloseCb?.();
} }
send(_frame: string): void { /* noop */ }
close(): void { this.onCloseCb?.(); }
onOpen(cb: () => void): void { this.onOpenCb = cb; } onOpen(cb: () => void): void { this.onOpenCb = cb; }
onMessage(cb: (f: string) => void): void { this.onMessageCb = cb; } onMessage(cb: (f: string) => void): void { this.onMessageCb = cb; }
onClose(cb: () => void): void { this.onCloseCb = cb; } onClose(cb: () => void): void { this.onCloseCb = cb; }
@@ -59,44 +71,40 @@ export class LoginFlow extends Component {
const bus = new EventBus<NetClientEvents>(); const bus = new EventBus<NetClientEvents>();
const session = new PlatformSession(bus); const session = new PlatformSession(bus);
// 启动编排:bootstrap → mock login → loadScene
(async () => { (async () => {
try { try {
// 真实拉远程配置(可能因后端不可达失败) // 调试模式 + ?profile=local → 跳过远程,直连本地 ws://127.0.0.1:3088
const result = await resolveBootstrap({ const result = await resolveBootstrap({
mode: 'release', // 编辑器内置 preview,走 release mode: 'debug',
win: window as any, win: window as any,
search: '', search: '?profile=local',
fetcher: new HttpConfigFetcher(), fetcher: new HttpConfigFetcher(),
isNative: false, isNative: false,
}); });
session.app.setIdentity(result.identity); session.app.setIdentity(result.identity);
session.app.setServers(result.servers); session.app.setServers(result.servers);
console.log('[LoginFlow] bootstrap OK, servers=' + result.servers[0]); console.log('[LoginFlow] bootstrap OK, servers=' + result.servers[0] + ' profile=' + result.profile.name);
} catch (e) { } catch (e) {
console.warn('[LoginFlow] bootstrap failed (expected in editor), use mock: ' + (e as Error).message); console.warn('[LoginFlow] bootstrap failed: ' + (e as Error).message);
// 编辑器无后端时,fallback 到 mock identity
session.app.setIdentity({ session.app.setIdentity({
agentid: 'A', channelid: 'C', gameid: 'G', marketid: 4, version: 10000, agentid: 'A', channelid: 'C', gameid: 'G', marketid: 4, version: 10000,
}); });
session.app.setServers(['ws://mock']); session.app.setServers(['ws://127.0.0.1:3088']);
console.log('[LoginFlow] fallback to ws://127.0.0.1:3088');
} }
// 构造 NetClient(mock transport 同步触发 login) // 构造 NetClient + 真实 WS transport
console.log('[LoginFlow] constructing NetClient with servers=' + session.app.state.value.servers); console.log('[LoginFlow] constructing NetClient with servers=' + session.app.state.value.servers);
const net = new NetClient({ const net = new NetClient({
servers: session.app.state.value.servers, servers: session.app.state.value.servers,
transportFactory: () => new MockTransport(), transportFactory: () => new WSTransport(),
bus, bus,
}); });
// 给 NetClient 设 identity,否则 sendLogin 早返 → login 事件永不触发
net.setIdentity({ net.setIdentity({
agentid: 'A', channelid: 'C', gameid: 'G', marketid: 4, version: 10000, agentid: 'A', channelid: 'C', gameid: 'G', marketid: 4, version: 10000,
// 真机环境下补全 nickname/avatar/openid/sex 等字段
openid: 'mock_openid', nickname: '测试玩家', avatar: '', sex: 0, openid: 'mock_openid', nickname: '测试玩家', avatar: '', sex: 0,
} as any); } as any);
// 订阅 login 完成 → loadScene
let loginHandled = false; let loginHandled = false;
bus.on('login', (data: any) => { bus.on('login', (data: any) => {
console.log('[LoginFlow] bus.on(login) fired, state=' + data?.state); console.log('[LoginFlow] bus.on(login) fired, state=' + data?.state);
@@ -123,4 +131,4 @@ export class LoginFlow extends Component {
} }
})(); })();
} }
} }