diff --git a/.changeset/spotty-ghosts-kneel.md b/.changeset/spotty-ghosts-kneel.md new file mode 100644 index 00000000000..0db91b7bf19 --- /dev/null +++ b/.changeset/spotty-ghosts-kneel.md @@ -0,0 +1,5 @@ +--- +"@firebase/firestore": patch +--- + +Clean up leaked WebChannel instances when the Firestore instance is terminated. diff --git a/packages/firestore/src/platform/browser/webchannel_connection.ts b/packages/firestore/src/platform/browser/webchannel_connection.ts index 9a69164457e..56f57aa9595 100644 --- a/packages/firestore/src/platform/browser/webchannel_connection.ts +++ b/packages/firestore/src/platform/browser/webchannel_connection.ts @@ -59,6 +59,9 @@ export class WebChannelConnection extends RestConnection { private readonly useFetchStreams: boolean; private readonly longPollingOptions: ExperimentalLongPollingOptions; + /** A collection of open WebChannel instances */ + private openWebChannels: WebChannel[] = []; + constructor(info: DatabaseInfo) { super(info); this.forceLongPolling = info.forceLongPolling; @@ -239,6 +242,7 @@ export class WebChannelConnection extends RestConnection { request ); const channel = webchannelTransport.createWebChannel(url, request); + this.addOpenWebChannel(channel); // WebChannel supports sending the first message with the handshake - saving // a network round trip. However, it will have to call send in the same @@ -321,6 +325,7 @@ export class WebChannelConnection extends RestConnection { `RPC '${rpcName}' stream ${streamId} transport closed` ); streamBridge.callOnClose(); + this.removeOpenWebChannel(channel); } }); @@ -427,4 +432,32 @@ export class WebChannelConnection extends RestConnection { }, 0); return streamBridge; } + + /** + * Closes and cleans up any resources associated with the connection. + */ + terminate(): void { + // If the Firestore instance is terminated, we will explicitly + // close any remaining open WebChannel instances. + this.openWebChannels.forEach(webChannel => webChannel.close()); + this.openWebChannels = []; + } + + /** + * Add a WebChannel instance to the collection of open instances. + * @param webChannel + */ + addOpenWebChannel(webChannel: WebChannel): void { + this.openWebChannels.push(webChannel); + } + + /** + * Remove a WebChannel instance from the collection of open instances. + * @param webChannel + */ + removeOpenWebChannel(webChannel: WebChannel): void { + this.openWebChannels = this.openWebChannels.filter( + instance => instance === webChannel + ); + } }