Skip to content

[Heartbeat Service] More null service protections added to triggerHeartBeat #7789

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Nov 22, 2023
Merged
5 changes: 5 additions & 0 deletions .changeset/fast-moose-approve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@firebase/app': patch
---

More safeguards to ensure that heartbeat objects queried from IndexedDB include a heartbeats field.
10 changes: 9 additions & 1 deletion packages/app/src/heartbeatService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ export class HeartbeatServiceImpl implements HeartbeatService {
const date = getUTCDateString();
if (this._heartbeatsCache?.heartbeats == null) {
this._heartbeatsCache = await this._heartbeatsCachePromise;
// If we failed to construct a heartbeats cache, then return immediately.
if (this._heartbeatsCache?.heartbeats == null) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hsubox76 perhaps this should just check if heartbeatsCache === null?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the root problem is here:

return idbHeartbeatObject || { heartbeats: [] };
or even one function back (readHeartbeatsFromIndexedDB). It's checking to see if whatever comes back from indexedDB is falsy but if it's (as I suspect) an empty object, this will go wrong. We have to make sure that whatever gets returned from this read() function is an object with a heartbeats property which is an array (e.g., matches type HeartbeatsInIndexedDB), otherwise we are lying to typescript and so it can't protect us by catching errors further downstream.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I updated the read() method (below) to ensure the returned object has a heartbeats field.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this check should be needed anymore after the other change but I guess it doesn't hurt to be safe.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I feel like keeping it place just in case.

return;
}
}
// Do not store a heartbeat if one is already stored for this day
// or if a header has already been sent today.
Expand Down Expand Up @@ -236,7 +240,11 @@ export class HeartbeatStorageImpl implements HeartbeatStorage {
return { heartbeats: [] };
} else {
const idbHeartbeatObject = await readHeartbeatsFromIndexedDB(this.app);
return idbHeartbeatObject || { heartbeats: [] };
if (idbHeartbeatObject?.heartbeats) {
return idbHeartbeatObject;
} else {
return { heartbeats: [] };
}
}
}
// overwrite the storage with the provided heartbeats
Expand Down