Skip to content

Commit af4bdd8

Browse files
Breaking: Model the disconnect for hidden tabs explicitly (#72)
This has caused app-level issues because disconnected doesn't let the app understand that a reconnect will automatically happen.
1 parent ce98672 commit af4bdd8

File tree

3 files changed

+39
-20
lines changed

3 files changed

+39
-20
lines changed

lib/device.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,12 @@ export enum ConnectionStatus {
152152
* a reconnection is attempted.
153153
*/
154154
RECONNECTING = "RECONNECTING",
155+
/**
156+
* Paused due to tab visibility. The connection was temporarily suspended
157+
* because the browser tab became hidden. Reconnection will be attempted
158+
* automatically when the tab becomes visible again.
159+
*/
160+
PAUSED = "PAUSED",
155161
}
156162

157163
export interface ConnectOptions {

lib/usb-radio-bridge.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,16 +83,25 @@ class MicrobitRadioBridgeConnectionImpl
8383
const currentStatus = this.status;
8484
if (e.status !== ConnectionStatus.CONNECTED) {
8585
this.setStatus(e.status);
86-
if (this.serialSessionOpen) {
86+
// Don't dispose the serial session when PAUSED - USB is temporarily
87+
// unavailable due to tab visibility, but we'll reconnect when visible.
88+
// Attempting dispose would fail anyway since USB is disconnected.
89+
if (e.status === ConnectionStatus.PAUSED) {
90+
// Clear timestamp so stale values don't trigger reconnection when resuming.
91+
this.serialSession?.clearLastReceivedTimestamp();
92+
} else if (this.serialSessionOpen) {
8793
// If the session is already closed we don't need to dispose.
8894
this.serialSession?.dispose();
8995
}
9096
} else {
9197
this.status = ConnectionStatus.DISCONNECTED;
92-
if (
93-
currentStatus === ConnectionStatus.DISCONNECTED &&
94-
this.serialSessionOpen
95-
) {
98+
// Reconnect the serial session if we were previously disconnected or paused.
99+
// PAUSED means the USB connection was temporarily suspended due to tab
100+
// visibility, and now the tab is visible again so we should reconnect.
101+
const shouldReconnect =
102+
currentStatus === ConnectionStatus.DISCONNECTED ||
103+
currentStatus === ConnectionStatus.PAUSED;
104+
if (shouldReconnect && this.serialSessionOpen) {
96105
this.serialSession?.connect();
97106
}
98107
}
@@ -412,6 +421,11 @@ class RadioBridgeSerialSession {
412421
// Check for connection lost
413422
if (this.connectionCheckIntervalId === undefined) {
414423
this.connectionCheckIntervalId = setInterval(async () => {
424+
// Don't check connection while USB is paused/disconnected.
425+
// The check will resume when USB reconnects.
426+
if (this.delegate.status !== ConnectionStatus.CONNECTED) {
427+
return;
428+
}
415429
if (
416430
this.lastReceivedMessageTimestamp &&
417431
Date.now() - this.lastReceivedMessageTimestamp <= 1_000
@@ -457,6 +471,10 @@ class RadioBridgeSerialSession {
457471
this.lastReceivedMessageTimestamp = undefined;
458472
}
459473

474+
clearLastReceivedTimestamp() {
475+
this.lastReceivedMessageTimestamp = undefined;
476+
}
477+
460478
private async handshake(): Promise<void> {
461479
// There is an issue where we cannot read data out from the micro:bit serial
462480
// buffer until the buffer has been filled.

lib/usb.ts

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -148,15 +148,10 @@ class MicrobitWebUSBConnectionImpl
148148

149149
private flashing: boolean = false;
150150
private disconnectAfterFlash: boolean = false;
151-
private visibilityReconnect: boolean = false;
152151
private visibilityChangeListener = () => {
153152
if (document.visibilityState === "visible") {
154-
if (
155-
this.visibilityReconnect &&
156-
this.status !== ConnectionStatus.CONNECTED
157-
) {
153+
if (this.status === ConnectionStatus.PAUSED) {
158154
this.disconnectAfterFlash = false;
159-
this.visibilityReconnect = false;
160155
if (!this.flashing) {
161156
this.log("Reconnecting visible tab");
162157
this.connect();
@@ -165,10 +160,9 @@ class MicrobitWebUSBConnectionImpl
165160
} else {
166161
if (!this.unloading && this.status === ConnectionStatus.CONNECTED) {
167162
if (!this.flashing) {
168-
this.log("Disconnecting hidden tab");
169-
this.disconnect().then(() => {
170-
this.visibilityReconnect = true;
171-
});
163+
this.log("Pausing connection for hidden tab");
164+
// Transition to PAUSED not DISCONNECTED
165+
this.disconnect(false, ConnectionStatus.PAUSED);
172166
} else {
173167
this.log("Scheduling disconnect of hidden tab for after flash");
174168
this.disconnectAfterFlash = true;
@@ -346,8 +340,7 @@ class MicrobitWebUSBConnectionImpl
346340
if (this.disconnectAfterFlash) {
347341
this.log("Disconnecting after flash due to tab visibility");
348342
this.disconnectAfterFlash = false;
349-
await this.disconnect();
350-
this.visibilityReconnect = true;
343+
await this.disconnect(false, ConnectionStatus.PAUSED);
351344
} else {
352345
if (this.addedListeners.serialdata) {
353346
this.log("Reinstating serial after flash");
@@ -391,7 +384,10 @@ class MicrobitWebUSBConnectionImpl
391384
});
392385
}
393386

394-
async disconnect(quiet?: boolean): Promise<void> {
387+
async disconnect(
388+
quiet?: boolean,
389+
finalStatus: ConnectionStatus = ConnectionStatus.DISCONNECTED,
390+
): Promise<void> {
395391
try {
396392
if (this.connection) {
397393
await this.stopSerialInternal();
@@ -407,7 +403,7 @@ class MicrobitWebUSBConnectionImpl
407403
}
408404
} finally {
409405
this.connection = undefined;
410-
this.setStatus(ConnectionStatus.DISCONNECTED);
406+
this.setStatus(finalStatus);
411407
if (!quiet) {
412408
this.logging.log("Disconnection complete");
413409
this.logging.event({
@@ -420,7 +416,6 @@ class MicrobitWebUSBConnectionImpl
420416

421417
private setStatus(newStatus: ConnectionStatus) {
422418
this.status = newStatus;
423-
this.visibilityReconnect = false;
424419
this.log("USB connection status " + newStatus);
425420
this.dispatchTypedEvent("status", new ConnectionStatusEvent(newStatus));
426421
}

0 commit comments

Comments
 (0)