Skip to content

fix(telemetry): register React and ReactFire versions at import time - #835

Merged
jhuleatt merged 5 commits into
mainfrom
fix/register-version
Oct 28, 2026
Merged

jhuleatt merged 5 commits into
mainfrom
fix/register-version

Conversation

@jhuleatt

@jhuleatt jhuleatt commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

ReactFire registered the React and ReactFire versions with Firebase only when FirebaseAppProvider initialized a new app from firebaseConfig. Apps that passed firebaseApp, reused an initialized app, or called hooks without the provider reported no React or ReactFire version.

src/telemetry.ts adds registerReactFireVersion, which registers both versions with registerVersion and runs at import time. FirebaseAppProvider (ahead of its early returns) and useObservable also call it as a fallback.

Firebase's heartbeat records the registered versions once per UTC day, at the first _getProvider call (the first getFirestore(), getAuth(), and so on). Registering at import time therefore covers apps that create SDK instances at module scope, unless their entry file imports that setup before ReactFire. Those apps still go uncounted, so heartbeat counts are a lower bound.

registerReactFireVersion checks Firebase's internal _components map before registering, so repeated calls do not log "There were multiple attempts to register component" at debug level. If that map is unavailable, a module-level flag prevents repeat calls instead. The function never throws, so a partially mocked or badly bundled firebase/app cannot break importing or rendering ReactFire.

New tests cover each provider path, registration at import time, the debug-log check, and the no-throw guarantee. The PR also regenerates the reference docs, whose line numbers moved.

@jhuleatt
jhuleatt requested a review from tyler-reitz October 27, 2026 14:50

@tyler-reitz tyler-reitz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approving: registering on every provider path is a real gain, and both halves are pinned (moving the provider calls back below the early returns fails two tests; dropping the useObservable calls fails one).

FYI on how far it reaches: the heartbeat snapshots getPlatformInfoString() at the first _getProvider of each UTC day (triggerHeartbeat), and both registration sites run during render. So an app that calls getFirestore(app) at module level, as our emulator docs recommend (docs/use.md), stores that day's heartbeat before ReactFire registers. Checked in headless Chromium against this branch and main (firebase 13.1.0), reading the stored heartbeat from IndexedDB:

first SDK access main this PR
module level no reactfire/ no reactfire/
in render, firebaseApp prop no reactfire/ reactfire/
in render, firebaseConfig reactfire/ reactfire/

A second load the same day does not recover it: module-level first, then in-render in the same profile, still stores none. Covering those apps would mean registering at import time, which cuts against the side-effect-free design, so your call.

Non-blocking: useObservable now calls registerVersion twice per render, and every repeat logs "There were multiple attempts to register component" at debug level (20 calls gave 18 lines under setLogLevel('debug')). A module-level flag would quiet it.

@tyler-reitz

Copy link
Copy Markdown
Contributor

Still approved; both notes are addressed and the new tests go red when the code they cover is removed. One caveat for reading the data: import-time registration only counts an app if reactfire is loaded before its first getFirestore/getAuth call. An entry file with import './firebase' above import App is never counted, so reactfire's heartbeat numbers are a minimum, not an exact count. I don't think it's fixable from inside the library.

Three firebaseApp tests assumed an earlier test's afterEach had cleared
the import-time registration, and the useObservable test assumed it ran
first, so each failed when run alone or in shuffled order. Clear both
version components before every test instead.
registerReactFireVersion runs at import time, so an error from
firebase/app, such as a partial test mock or a CJS/ESM interop bug,
would break importing ReactFire. Catch and drop the error. The
_components lookup moves inside the try because a mocked module can
throw on property access.
@jhuleatt jhuleatt changed the title Telemetry fix fix(telemetry): register React and ReactFire versions at import time Oct 28, 2026
@jhuleatt
jhuleatt merged commit 2179320 into main Oct 28, 2026
17 checks passed
@jhuleatt
jhuleatt deleted the fix/register-version branch October 28, 2026 20:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants