fix(reconnect): auto rejoin on socket connect and widen reconnection triggers for mobile foreground resume

- Attempt reconnection on 'disconnected' | 'failed' | 'closed' states (BaseWebRTC)
  - Relax gating: rejoin when roomId exists and any of isPeerDisconnected, isSocketDisconnected, or socketId changed
  - Auto re-join room on socket 'connect' if lastJoinedSocketId differs or not in room; send initiator-online for initiators
  - Track lastJoinedSocketId after successful join and reset isInRoom when socketId changes to bypass early-return
  - Update flows to document mobile background/foreground reconnection and socketId-based rejoin
This commit is contained in:
david_bai
2025-11-21 20:23:47 +08:00
parent 415adfe638
commit 18f6703c6b
2 changed files with 60 additions and 5 deletions
+7 -1
View File
@@ -634,9 +634,15 @@ async requestWakeLock(): Promise<void> {
**网络切换适应**
- **连接检测**:监听 `connectionstatechange` 事件检测网络质量变化
- **自动重连**`iceConnectionState: 'disconnected'` 时触发重连流程
- **自动重连**`connectionState: 'disconnected' | 'failed' | 'closed'`触发重连流程(统一走 attemptReconnection
- **状态恢复**:重连成功后恢复房间状态和传输进度
**移动端后台/前台切换补充策略**
- **socket 连接恢复自动入房**`socket.on('connect')` 时,若已持有 `roomId` 且(`lastJoinedSocketId !== socket.id``!isInRoom`),则强制重新 `joinRoom(roomId, isInitiator, isInitiator)`;发送端会自动广播 `initiator-online`,接收端回复 `recipient-ready`
- **身份追踪**:成功 `joinRoom` 后记录 `lastJoinedSocketId = socket.id`,用以检测“后台恢复时 socketId 更换”的情形。
- **门槛放宽**`attemptReconnection` 只要满足“`roomId` 存在,且满足任一:P2P 断开 / socket 断开 / socketId 改变”,即可发起重连;不再强依赖“socket 与 P2P 同时断开”。
### 重连调试要点
**关键日志点**
+53 -4
View File
@@ -66,6 +66,8 @@ export default class BaseWebRTC {
protected wakeLockManager: WakeLockManager;
// Graceful disconnect tracking
protected gracefullyDisconnectedPeers: Set<string>;
// Track last socket.id used to successfully join a room
protected lastJoinedSocketId: string | null;
constructor(config: WebRTCConfig) {
this.iceServers = config.iceServers;
@@ -94,6 +96,7 @@ export default class BaseWebRTC {
this.isPeerDisconnected = false;
this.reconnectionInProgress = false;
this.wakeLockManager = new WakeLockManager();
this.lastJoinedSocketId = null;
}
// region Logging and Error Handling
protected log(
@@ -113,9 +116,41 @@ export default class BaseWebRTC {
// endregion
// Sets up event listeners for the signaling server to handle various signaling messages (connection, ICE candidates, offer, answer, etc.).
setupCommonSocketListeners() {
this.socket.on("connect", () => {
this.socket.on("connect", async () => {
this.peerId = this.socket.id; // Save own ID
this.isSocketDisconnected = false;
this.log("log", `Connected to signaling server, peerId: ${this.peerId}`);
// Auto re-join if we previously joined a room but socket.id changed
const hasRoom = !!this.roomId;
const currentSocketId = this.socket.id ?? null;
const socketIdChanged =
this.lastJoinedSocketId !== null &&
this.lastJoinedSocketId !== currentSocketId;
if (hasRoom && (socketIdChanged || !this.isInRoom)) {
// Ensure joinRoom does not early-return
if (socketIdChanged) this.isInRoom = false;
if (!this.reconnectionInProgress) {
this.reconnectionInProgress = true;
try {
const sendInitiatorOnline = this.isInitiator;
await this.joinRoom(
this.roomId as string,
this.isInitiator,
sendInitiatorOnline
);
// Reset flags after successful auto rejoin
this.isSocketDisconnected = false;
this.isPeerDisconnected = false;
} catch (error) {
this.fireError("Auto rejoin on socket connect failed", { error });
} finally {
this.reconnectionInProgress = false;
}
}
}
});
this.socket.on("error", (error) => {
@@ -149,17 +184,25 @@ export default class BaseWebRTC {
}
protected async attemptReconnection(): Promise<void> {
if (this.reconnectionInProgress) return;
if (!this.roomId) return;
if (this.isSocketDisconnected && this.isPeerDisconnected && this.roomId) {
// Start reconnection only after both socket and P2P connections are disconnected
const currentSocketId = this.socket.id ?? null;
const socketIdChanged =
this.lastJoinedSocketId !== null &&
this.lastJoinedSocketId !== currentSocketId;
// Widen condition: if either side disconnected or socketId changed, try to rejoin
if (this.isPeerDisconnected || this.isSocketDisconnected || socketIdChanged) {
this.reconnectionInProgress = true;
if (developmentEnv === "development") {
postLogToBackend(
`Starting reconnection, socket and peer both disconnected. isInitiator:${this.isInitiator}`
`Starting reconnection. socketDisc:${this.isSocketDisconnected}, peerDisc:${this.isPeerDisconnected}, socketIdChanged:${socketIdChanged}, isInitiator:${this.isInitiator}`
);
}
try {
// Ensure joinRoom does not early-return
if (socketIdChanged) this.isInRoom = false;
const sendInitiatorOnline = this.isInitiator;
await this.joinRoom(this.roomId, this.isInitiator, sendInitiatorOnline);
@@ -323,11 +366,15 @@ export default class BaseWebRTC {
failed: async () => {
this.cleanupExistingConnection(peerId);
this.isPeerDisconnected = true;
// Attempt to reconnect as well when failed
this.attemptReconnection();
await this.wakeLockManager.releaseWakeLock();
},
closed: async () => {
this.cleanupExistingConnection(peerId);
this.isPeerDisconnected = true;
// Attempt to reconnect when closed
this.attemptReconnection();
await this.wakeLockManager.releaseWakeLock();
},
// The following must be added to prevent errors
@@ -437,6 +484,8 @@ export default class BaseWebRTC {
if (response.success) {
this.roomId = roomId;
this.isInRoom = true;
// Record the socket.id used for this successful join
this.lastJoinedSocketId = this.socket.id ?? null;
if (sendInitiatorOnline) {
this.socket.emit("initiator-online", {
roomId: this.roomId,