Repository navigation
fix(telemetry): register React and ReactFire versions at import time - #835
Conversation
tyler-reitz
left a comment
There was a problem hiding this comment.
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.
|
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 |
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.
ReactFire registered the React and ReactFire versions with Firebase only when
FirebaseAppProviderinitialized a new app fromfirebaseConfig. Apps that passedfirebaseApp, reused an initialized app, or called hooks without the provider reported no React or ReactFire version.src/telemetry.tsaddsregisterReactFireVersion, which registers both versions withregisterVersionand runs at import time.FirebaseAppProvider(ahead of its early returns) anduseObservablealso call it as a fallback.Firebase's heartbeat records the registered versions once per UTC day, at the first
_getProvidercall (the firstgetFirestore(),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.registerReactFireVersionchecks Firebase's internal_componentsmap 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 bundledfirebase/appcannot 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.