grafana / grafana/faro-react-native-sdk

config.app name/version/environment dropped: RN SDK's installationId meta wins faro-core's shallow meta merge

Open
#188 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
9
Forks
4
Avg merge
1d 18h
Merged PRs (30d)
9

Description

## Summary

With `@grafana/faro-react-native` 1.3.1 and default mobile metas, `meta.app.name`, `meta.app.version` and `meta.app.environment` from `config.app` are dropped before the first signal is sent. Only `meta.app.installationId` survives.

## Cause

`faro-core` builds the meta object by reducing the metas list with a shallow `Object.assign`:

```js
// @grafana/faro-core/dist/cjs/metas/initialize.js
const getValue = () => items.reduce((acc, item) => Object.assign(acc, isFunction(item) ? item() : item), {});
```

so a later `{ app: {...} }` replaces an earlier one rather than merging into it.

`makeRNConfig` registers `config.app` via core's `registerInitialMetas`, and then the preloaded mobile meta from `loadMobileMetaForInit` contributes `{ app: { installationId } }` later in the list (`sessionAttributes.ts` around lines 286–317). The second entry wins, and the app identity is gone.

## Reproduction

Against real `@grafana/faro-core` 2.11.0, mirroring the meta order `makeRNConfig` produces:

```ts
const faro = initializeFaro({
app: { name: 'my-app', version: '1', environment: 'staging' },
metas: [{ app: { installationId: 'abc-123' } }],
transports: [], instrumentations: [], isolate: true, preventGlobalExposure: true,
});
faro.metas.value.app // => { installationId: 'abc-123' }
```

Observed live: every payload from a production Android device reaches a self-hosted Alloy `faro.receiver` with `session_attr_*`, `view_name`, `page_url`, `sdk_*` and `browser_mobile` fields, and **no `app_name` / `app_version` / `app_environment`**. A web app using `@grafana/faro-web-sdk` against the same receiver emits all three.

## Impact

Any receiver-side routing that keys on `app.name` (the standard way to separate apps and environments on a shared collector) sees no app identity from RN clients at all. On our receiver every mobile signal lands in a quarantine stream.

## Suggested fix

In `makeRNConfig`, merge `preloadedAppMeta` into the returned `app` object instead of appending it as a separate meta item, so there is only ever one `app` entry in the list:

```ts
return {
app: { ...config.app, ...preloadedAppMeta, ...(installationId && { installationId }) },
metas: [...defaultMetas, ...customMetas], // drop appMetasIfPresent
...
}
```

Alternatively, `faro-core` could deep-merge the `app` key specifically, since it is the one meta every SDK contributes to.

## Workaround we're shipping

After `initializeFaro` resolves:

```ts
faro.metas.add({ app: { ...faro.metas.value.app, ...configApp } });
```

Last meta wins the shallow merge, and `installationId` is preserved by the spread.

## Versions

- `@grafana/faro-react-native` 1.3.1
- `@grafana/faro-core` 2.11.0
- React Native 0.81.5, Expo SDK 54, Hermes

Contributor guide

Open the contributing guide

Research direction

Start in makeRNConfig and inspect sessionAttributes.ts around lines 286–317, where loadMobileMetaForInit contributes the installationId metadata. Reproduce the reported initializeFaro metadata order, then verify the resulting app metadata retains config.app name, version, and environment alongside installationId. Confirm the existing workaround behavior and add or run the relevant SDK tests if available.

Written by the indexing model from the issue text.

Assessment

Tech stack
react-native, typescript
Domain
mobile-dev, observability-sre
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.