Skip to content

Commit ac9df7b

Browse files
authored
Added reentrancy gates when websocket is created/dropped (#1431)
If the browser extension is not installed there's a potential timing issue that causes the machine to go into a create/close WS loop - now there's a singular Promise while waiting for port creation - dropped connections are now throttled
1 parent f96df6c commit ac9df7b

File tree

1 file changed

+76
-50
lines changed

1 file changed

+76
-50
lines changed

ts/packages/shell/src/main/browserIpc.ts

Lines changed: 76 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ export class BrowserAgentIpc {
1515
private static instance: BrowserAgentIpc;
1616
public onMessageReceived: ((message: WebSocketMessageV2) => void) | null;
1717
private webSocket: any;
18+
private reconnectionPending: boolean = false;
19+
private webSocketPromise: Promise<WebSocket | undefined> | null = null;
1820

1921
private constructor() {
2022
this.webSocket = null;
@@ -30,65 +32,86 @@ export class BrowserAgentIpc {
3032
};
3133

3234
public async ensureWebsocketConnected() {
33-
return new Promise<WebSocket | undefined>(async (resolve) => {
34-
if (this.webSocket) {
35-
if (this.webSocket.readyState === WebSocket.OPEN) {
36-
resolve(this.webSocket);
35+
// if there's a pending websocket promise, return it
36+
if (this.webSocketPromise) {
37+
return this.webSocketPromise;
38+
}
39+
40+
//create a new promise to establish the websocket connection
41+
this.webSocketPromise = new Promise<WebSocket | undefined>(
42+
async (resolve) => {
43+
if (this.webSocket) {
44+
if (this.webSocket.readyState === WebSocket.OPEN) {
45+
resolve(this.webSocket);
46+
return;
47+
}
48+
try {
49+
this.webSocket.close();
50+
this.webSocket = undefined;
51+
} catch {}
52+
}
53+
54+
this.webSocket = await createWebSocket(
55+
"browser",
56+
"client",
57+
"inlineBrowser",
58+
);
59+
if (!this.webSocket) {
60+
resolve(undefined);
3761
return;
3862
}
39-
try {
40-
this.webSocket.close();
41-
this.webSocket = undefined;
42-
} catch {}
43-
}
4463

45-
this.webSocket = await createWebSocket(
46-
"browser",
47-
"client",
48-
"inlineBrowser",
49-
);
50-
if (!this.webSocket) {
51-
resolve(undefined);
52-
return;
53-
}
64+
this.webSocket.binaryType = "blob";
65+
keepWebSocketAlive(this.webSocket, "browser");
66+
67+
this.webSocket.onmessage = async (event: any) => {
68+
const text =
69+
typeof event.data === "string"
70+
? event.data
71+
: await (event.data as Blob).text();
72+
try {
73+
const data = JSON.parse(text) as WebSocketMessageV2;
74+
75+
let schema = data.method?.split("/")[0];
76+
schema = schema || "browser";
77+
78+
if (
79+
(schema == "browser" ||
80+
schema == "webAgent" ||
81+
schema.startsWith("browser.")) &&
82+
this.onMessageReceived
83+
) {
84+
debugBrowserIPC("Browser -> Dispatcher", data);
85+
this.onMessageReceived(data);
86+
}
87+
} catch {}
88+
};
89+
90+
this.webSocket.onclose = () => {
91+
console.log("websocket connection closed");
92+
this.webSocket = undefined;
93+
this.reconnectWebSocket();
94+
};
5495

55-
this.webSocket.binaryType = "blob";
56-
keepWebSocketAlive(this.webSocket, "browser");
57-
58-
this.webSocket.onmessage = async (event: any) => {
59-
const text =
60-
typeof event.data === "string"
61-
? event.data
62-
: await (event.data as Blob).text();
63-
try {
64-
const data = JSON.parse(text) as WebSocketMessageV2;
65-
66-
let schema = data.method?.split("/")[0];
67-
schema = schema || "browser";
68-
69-
if (
70-
(schema == "browser" ||
71-
schema == "webAgent" ||
72-
schema.startsWith("browser.")) &&
73-
this.onMessageReceived
74-
) {
75-
debugBrowserIPC("Browser -> Dispatcher", data);
76-
this.onMessageReceived(data);
77-
}
78-
} catch {}
79-
};
96+
this.webSocketPromise = null;
8097

81-
this.webSocket.onclose = () => {
82-
console.log("websocket connection closed");
83-
this.webSocket = undefined;
84-
this.reconnectWebSocket();
85-
};
98+
resolve(this.webSocket);
99+
},
100+
);
86101

87-
resolve(this.webSocket);
88-
});
102+
return this.webSocketPromise;
89103
}
90104

91105
private reconnectWebSocket() {
106+
// if there is a reconnection pending just return
107+
if (this.reconnectionPending) {
108+
return;
109+
}
110+
111+
// indicate a reconnection attempt is pending
112+
this.reconnectionPending = true;
113+
114+
// attempt reconnection every 5 seconds
92115
const connectionCheckIntervalId = setInterval(async () => {
93116
if (
94117
this.webSocket &&
@@ -100,6 +123,9 @@ export class BrowserAgentIpc {
100123
console.log("Retrying connection");
101124
await this.ensureWebsocketConnected();
102125
}
126+
127+
// reconnection was either successful or attempted
128+
this.reconnectionPending = false;
103129
}, 5 * 1000);
104130
}
105131

0 commit comments

Comments
 (0)