diff --git a/docs/constants/navigation.js b/docs/constants/navigation.js index 6a05553c7adcec..47c871955c2baf 100644 --- a/docs/constants/navigation.js +++ b/docs/constants/navigation.js @@ -646,6 +646,7 @@ export const eas = [ ]), makeGroup('Reference', [ makePage('eas/observe/reference/metrics.mdx'), + makePage('eas/observe/reference/client-id.mdx'), makePage('eas/observe/reference/troubleshooting.mdx'), ]), ]), diff --git a/docs/pages/eas/observe/reference/client-id.mdx b/docs/pages/eas/observe/reference/client-id.mdx new file mode 100644 index 00000000000000..5b36b66a6547d7 --- /dev/null +++ b/docs/pages/eas/observe/reference/client-id.mdx @@ -0,0 +1,58 @@ +--- +title: Client ID +description: Read the EAS client ID that EAS Observe records on every metric and event, and use it to correlate data with other services. +--- + +Every metric, event, and log record that EAS Observe sends carries an **EAS client ID**: a random identifier for one installation of your app. The dashboard and the EAS CLI use it to group data by installation, and `Observe.clientId` exposes the same value to your app. + +Use it to look up an installation's Observe data from another tool, such as your crash reporter, your analytics provider, or your own backend. + +## Read the client ID + +`Observe.clientId` is a string on Android and iOS, and `null` on web: + +```tsx +import { Observe } from 'expo-observe'; + +console.log(Observe.clientId); +// 'f81d4fae-7dec-41d0-a765-00a0c91e6bf6' +``` + +The value is available as soon as `expo-observe` is imported. You do not need to call `configure()` first. + +## Correlate with another service + +Attach the client ID to the data you send elsewhere. Then, when you find a problem in that service, you can query the same installation in EAS Observe. + +The following example sends the client ID alongside a report to your own backend: + +```tsx +import { Observe } from 'expo-observe'; + +async function reportFeedback(message: string) { + await fetch('https://example.com/feedback', { + method: 'POST', + body: JSON.stringify({ + message, + easClientId: Observe.clientId, + }), + }); +} +``` + +Crash reporting and analytics SDKs usually offer a tag, a custom property, or a context field for this. Set it once at startup, then use the value to find the matching installation in EAS Observe. + +To go the other direction, run [`eas observe:metrics`](/eas/observe/eas-cli/#eas-observemetrics) or [`eas observe:events`](/eas/observe/eas-cli/#eas-observeevents) with `--json`. Each sample includes its `easClientId`. + +## How the client ID behaves + +- The ID is generated on the device the first time an EAS client library needs it, and is stored in native preferences. It is not derived from any hardware or account identifier. +- It is stable across app launches, app updates, and EAS Updates. +- It is shared with the other EAS client libraries in your app, such as `expo-updates`. The same installation has one ID across all of them. +- It changes when the app's data is cleared or when the app is reinstalled, although a backup restore (including Android Auto Backup on reinstall) can carry the previous ID over. + +Because the ID identifies an installation rather than a person, treat it as pseudonymous data. If you send it to a third-party service, check that your privacy policy covers that use. + +## Sampling and the client ID + +The [`sampleRate`](/eas/observe/configuration/#sampling) decision is derived from the client ID, which is why an installation stays in-sample or out-of-sample across launches. `Observe.clientId` returns the ID whether or not the installation is in-sample, so you can log it even when the app dispatches no metrics. diff --git a/docs/pages/eas/workflows/syntax.mdx b/docs/pages/eas/workflows/syntax.mdx index 027f0b9a2187e9..b81a684780272b 100644 --- a/docs/pages/eas/workflows/syntax.mdx +++ b/docs/pages/eas/workflows/syntax.mdx @@ -78,6 +78,8 @@ With the `paths` list, you can trigger the workflow only when changes are made t When neither `branches` nor `tags` are provided, `branches` defaults to `['*']` and `tags` defaults to `[]`, which means the workflow triggers on push events to all branches and does not trigger on tag pushes. If only one of the two lists is provided the other defaults to `[]`. +With the [`if:`](#ontriggerif) condition, you can decide whether a workflow run starts. + ```yaml on: # @info # @@ -112,6 +114,8 @@ With the `tags` list, you can trigger the workflow only when those specified tag When neither `branches` nor `tags` are provided, `branches` defaults to `['*']` and `tags` defaults to `[]`, which means the workflow triggers when any branch is deleted and does not trigger on tag deletions. If only one of the two lists is provided the other defaults to `[]`. +With the [`if:`](#ontriggerif) condition, you can decide whether a workflow run starts. + > **info** Workflow files are read from the default branch HEAD at the time of deletion, not from the deleted ref. Pair this trigger with the [`branch-delete`](/eas/workflows/pre-packaged-jobs#branch-delete) job to remove EAS Update branches when GitHub branches are deleted. See the [Clean up update branches example](/eas/workflows/examples/branch-cleanup) for a complete workflow. @@ -156,6 +160,8 @@ The `branches` filter matches the pull request's current base branch, so retarge With the `paths` list, you can trigger the workflow only when changes are made to files matching the specified paths. For example, if you use `paths: ['apps/mobile/**']`, only changes to files in the `apps/mobile` directory trigger the workflow. Supports globs. By default, changes to any path trigger the workflow. +You can add an [`if:`](#ontriggerif) condition to decide whether a workflow run starts. + ```yaml on: # @info # @@ -184,6 +190,8 @@ Runs your workflow when a pull request is labeled with a matching label. With the `labels` list, you can specify which labels, when assigned to your pull request, trigger the workflow. For example, if you use `labels: ['Test']`, only labeling a pull request with the `Test` label triggers the workflow. Defaults to `[]` when not provided, which means no labels trigger the workflow. +To decide whether a workflow run starts, add an [`if:`](#ontriggerif) condition. + You can also provide a list of matching labels directly to `on.pull_request_labeled` for simpler syntax. ```yaml @@ -209,6 +217,33 @@ on: # other labels ``` +### `on.pull_request_comment` + +Runs your workflow when someone creates, edits, or deletes a comment on a pull request. + +> **info** Only open, unmerged pull requests trigger `pull_request_comment` workflow runs. Pull requests opened from a fork of the connected repository do not trigger workflow runs. + +With the `types` list, you can specify which events trigger the workflow. Defaults to `['created']` when not provided. Supported event types: + +- `created` +- `edited` +- `deleted` + +Unlike `on.pull_request`, this trigger has no `branches` or `paths` filter. + +With the [`if:`](#ontriggerif) condition, you can decide whether a workflow run starts. + +```yaml +on: + # @info # + pull_request_comment: + # @end # + types: + - created + - edited + # other event types +``` + ### `on.app_store_connect` Runs your workflow when one of the selected App Store Connect events occurs. @@ -217,6 +252,8 @@ Runs your workflow when one of the selected App Store Connect events occurs. When `on.app_store_connect` is present, you must specify at least one event domain (`app_version`, `build_upload`, `external_beta`, or `beta_feedback`). Within a configured event domain, you can specify which states should trigger your workflow. +Each event domain also accepts an [`if:`](#ontriggerif) condition, so you can decide whether a workflow run starts. + #### `on.app_store_connect.app_version.states` Filters app store app version state change events. Defaults to all supported app version states when not provided. @@ -409,6 +446,36 @@ jobs: echo "Hello, ${{ inputs.name || 'World' }}!" ``` +### `on..if` + +The `if` condition on a trigger decides whether a workflow run starts. + +You can add `if:` under `push`, `ref_delete`, `pull_request`, `pull_request_labeled`, `pull_request_comment`, and each `app_store_connect` event domain (`app_version`, `build_upload`, `external_beta`, or `beta_feedback`). + +The value is a boolean or an expression string. You can write it with or without the `${{ }}` wrapper. + +```yaml +on: + pull_request: + # @info # + if: ${{ !github.event.pull_request.draft }} + # @end # +``` + +The expression must fit in one `${{ }}` block and can be at most 250 characters. + +When the condition evaluates to false, no workflow run starts for that trigger event. A retry of an existing run skips this check. It only applies when a new run is created. + +The expression can use the [`github`](#github), [`app_store_connect`](#app_store_connect), `inputs`, [`workflow`](#workflow), `app`, and `account` contexts. It supports all [context functions](#context-functions) except `success()`, `failure()`, and `hashFiles()`. Those functions need a job or step that has already run, but a trigger's `if` condition evaluates before any job starts. + +```yaml +on: + push: + if: ${{ github.ref_name == 'main' }} + pull_request_comment: + if: ${{ startsWith(github.event.comment.body, '/deploy') }} +``` + ## `jobs` A workflow run is made up of one or more jobs. @@ -825,6 +892,10 @@ type GitHubContext = { merged: boolean | null; // ... Other fields from the GitHub Pull Request webhook payload }; + comment?: { + body: string; + // ... Other fields from the GitHub issue_comment webhook payload + }; changes?: { base?: { ref?: { @@ -839,7 +910,7 @@ type GitHubContext = { }; ``` -> **info** The `event` object contains the full [GitHub webhook payload](https://docs.github.com/en/webhooks/webhook-events-and-payloads). For `pull_request` events, `event.pull_request` includes fields from GitHub's Pull Request webhook payload, such as `github.event.pull_request.title` and `github.event.pull_request.body`. For edited pull request events, `github.event.changes` contains the fields that changed, such as `github.event.changes.base.ref.from` when the base branch changed. The type above lists a few useful fields, but additional fields such as `user`, `labels`, `milestone`, and others are also available. +> **info** The `event` object contains the full [GitHub webhook payload](https://docs.github.com/en/webhooks/webhook-events-and-payloads). For `pull_request` events, `event.pull_request` includes fields from GitHub's Pull Request webhook payload, such as `github.event.pull_request.title` and `github.event.pull_request.body`. For edited pull request events, `github.event.changes` contains the fields that changed, such as `github.event.changes.base.ref.from` when the base branch changed. For `pull_request_comment` events, `event.comment.body` contains the comment text, and `event.pull_request.number` and `event.number` contain the pull request number. The type above lists a few useful fields, but additional fields such as `user`, `labels`, `milestone`, and others are also available. If a workflow run is started from `eas workflow:run`, its `event_name` will be `workflow_dispatch` and all the rest of the properties will be empty. diff --git a/docs/public/_redirects b/docs/public/_redirects index 0edcce930d01e5..696dfacdfd03ee 100644 --- a/docs/public/_redirects +++ b/docs/public/_redirects @@ -582,6 +582,91 @@ /versions/v55.0.0/sdk/ui/jetpack-compose/textinput /versions/v55.0.0/sdk/ui/jetpack-compose/textfield 301 /versions/v55.0.0/sdk/ui/jetpack-compose/textinput/ /versions/v55.0.0/sdk/ui/jetpack-compose/textfield 301 +# AI agents guess /
/introduction as a section's first page; redirect the HTML and .md forms to the real one. +# Keep this block above the first splat rule, or the parser's 100 dynamic-rule cap silently drops the rest of the file. +/agents/introduction /agents 301 +/agents/introduction/ /agents 301 +/agents/introduction.md /agents.md 301 +/develop/introduction /develop/tools 301 +/develop/introduction/ /develop/tools 301 +/develop/introduction.md /develop/tools.md 301 +/review/introduction /review/overview 301 +/review/introduction/ /review/overview 301 +/review/introduction.md /review/overview.md 301 +/deploy/introduction /deploy/build-project 301 +/deploy/introduction/ /deploy/build-project 301 +/deploy/introduction.md /deploy/build-project.md 301 +/monitoring/introduction /monitoring/services 301 +/monitoring/introduction/ /monitoring/services 301 +/monitoring/introduction.md /monitoring/services.md 301 +/debugging/introduction /debugging/errors-and-warnings 301 +/debugging/introduction/ /debugging/errors-and-warnings 301 +/debugging/introduction.md /debugging/errors-and-warnings.md 301 +/guides/introduction /guides/overview 301 +/guides/introduction/ /guides/overview 301 +/guides/introduction.md /guides/overview.md 301 +/workflow/introduction /workflow/overview 301 +/workflow/introduction/ /workflow/overview 301 +/workflow/introduction.md /workflow/overview.md 301 +/modules/introduction /modules/overview 301 +/modules/introduction/ /modules/overview 301 +/modules/introduction.md /modules/overview.md 301 +/push-notifications/introduction /push-notifications/overview 301 +/push-notifications/introduction/ /push-notifications/overview 301 +/push-notifications/introduction.md /push-notifications/overview.md 301 +/regulatory-compliance/introduction /regulatory-compliance/data-and-privacy-protection 301 +/regulatory-compliance/introduction/ /regulatory-compliance/data-and-privacy-protection 301 +/regulatory-compliance/introduction.md /regulatory-compliance/data-and-privacy-protection.md 301 +/linking/introduction /linking/overview 301 +/linking/introduction/ /linking/overview 301 +/linking/introduction.md /linking/overview.md 301 +/bare/introduction /bare/overview 301 +/bare/introduction/ /bare/overview 301 +/bare/introduction.md /bare/overview.md 301 +/brownfield/introduction /brownfield/overview 301 +/brownfield/introduction/ /brownfield/overview 301 +/brownfield/introduction.md /brownfield/overview.md 301 +/troubleshooting/introduction /troubleshooting/overview 301 +/troubleshooting/introduction/ /troubleshooting/overview 301 +/troubleshooting/introduction.md /troubleshooting/overview.md 301 +/eas/introduction /eas 301 +/eas/introduction/ /eas 301 +/eas/introduction.md /eas.md 301 +/eas/metadata/introduction /eas/metadata 301 +/eas/metadata/introduction/ /eas/metadata 301 +/eas/metadata/introduction.md /eas/metadata.md 301 +/accounts/introduction /accounts/account-types 301 +/accounts/introduction/ /accounts/account-types 301 +/accounts/introduction.md /accounts/account-types.md 301 +/billing/introduction /billing/overview 301 +/billing/introduction/ /billing/overview 301 +/billing/introduction.md /billing/overview.md 301 +/app-signing/introduction /app-signing/app-credentials 301 +/app-signing/introduction/ /app-signing/app-credentials 301 +/app-signing/introduction.md /app-signing/app-credentials.md 301 +/custom-builds/introduction /custom-builds/get-started 301 +/custom-builds/introduction/ /custom-builds/get-started 301 +/custom-builds/introduction.md /custom-builds/get-started.md 301 +/additional-resources/introduction /additional-resources 301 +/additional-resources/introduction/ /additional-resources 301 +/additional-resources/introduction.md /additional-resources.md 301 +/versions/introduction /versions/latest 301 +/versions/introduction/ /versions/latest 301 +/versions/introduction.md /versions/latest.md 301 +/technical-specs/introduction /technical-specs/expo-updates-1 301 +/technical-specs/introduction/ /technical-specs/expo-updates-1 301 +/technical-specs/introduction.md /technical-specs/expo-updates-1.md 301 +/more/introduction /more/expo-cli 301 +/more/introduction/ /more/expo-cli 301 +/more/introduction.md /more/expo-cli.md 301 +/archive/introduction /archive 301 +/archive/introduction/ /archive 301 +/archive/introduction.md /archive.md 301 + +# .md siblings of introduction redirects declared elsewhere in this file +/get-started/introduction.md /get-started/create-a-project.md 301 +/submit/introduction.md /deploy/submit-to-app-stores.md 301 + # EAS Build, Submit, Update, and Insights predate the /eas/* URL convention; /eas/build/* /build/:splat 301 /eas/build /build/introduction 301 diff --git a/docs/public/static/data/unversioned/expo-observe.json b/docs/public/static/data/unversioned/expo-observe.json index f564c557525c56..56835eea8cf424 100644 --- a/docs/public/static/data/unversioned/expo-observe.json +++ b/docs/public/static/data/unversioned/expo-observe.json @@ -1 +1 @@ -{"schemaVersion":"2.0","name":"expo-observe","variant":"project","kind":1,"children":[{"name":"ObserveErrorBoundary","variant":"declaration","kind":128,"comment":{"summary":[{"kind":"text","text":"A React error boundary that records render-phase errors as non-fatal "},{"kind":"code","text":"`exception`"},{"kind":"text","text":" log events (with\nthe React component stack) and renders a "},{"kind":"code","text":"`fallback`"},{"kind":"text","text":" in place of the subtree that threw.\n\nRender-phase errors don't reach "},{"kind":"code","text":"`global.ErrorUtils`"},{"kind":"text","text":", so a boundary is the only way to capture them\nwith the component stack. Place one around any subtree, or let "},{"kind":"code","text":"`AppMetricsRoot`"},{"kind":"text","text":" mount one via its\n"},{"kind":"code","text":"`errorBoundaryFallback`"},{"kind":"text","text":" prop."}]},"children":[{"name":"constructor","variant":"declaration","kind":512,"flags":{"isExternal":true},"signatures":[{"name":"ObserveErrorBoundary","variant":"signature","kind":16384,"flags":{"isExternal":true},"parameters":[{"name":"props","variant":"param","kind":32768,"flags":{"isExternal":true},"type":{"type":"reference","name":"AppMetricsErrorBoundaryProps","package":"expo-app-metrics"}}],"type":{"type":"reference","name":"AppMetricsErrorBoundary","package":"expo-app-metrics"},"inheritedFrom":{"type":"reference","name":"React.Component.constructor","package":"@types/react"}},{"name":"ObserveErrorBoundary","variant":"signature","kind":16384,"flags":{"isExternal":true},"parameters":[{"name":"props","variant":"param","kind":32768,"flags":{"isExternal":true},"type":{"type":"reference","name":"AppMetricsErrorBoundaryProps","package":"expo-app-metrics"}},{"name":"context","variant":"param","kind":32768,"flags":{"isExternal":true},"comment":{"summary":[{"kind":"text","text":"value of the parent "},{"kind":"inline-tag","tag":"@link","text":"Context"},{"kind":"text","text":" specified\nin "},{"kind":"code","text":"`contextType`"},{"kind":"text","text":"."}]},"type":{"type":"intrinsic","name":"any"}}],"type":{"type":"reference","name":"AppMetricsErrorBoundary","package":"expo-app-metrics"},"inheritedFrom":{"type":"reference","name":"React.Component.constructor","package":"@types/react"}}],"inheritedFrom":{"type":"reference","name":"React.Component.constructor","package":"@types/react"}},{"name":"state","variant":"declaration","kind":1024,"type":{"type":"reference","target":{"packageName":"expo-app-metrics","packagePath":"src/AppMetricsErrorBoundary.tsx","qualifiedName":"State"},"name":"State","package":"expo-app-metrics"},"defaultValue":"...","overwrites":{"type":"reference","name":"React.Component.state","package":"@types/react"}},{"name":"componentDidCatch","variant":"declaration","kind":2048,"signatures":[{"name":"componentDidCatch","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Catches exceptions generated in descendant components. Unhandled exceptions will cause\nthe entire component tree to unmount."}]},"parameters":[{"name":"error","variant":"param","kind":32768,"type":{"type":"intrinsic","name":"unknown"}},{"name":"errorInfo","variant":"param","kind":32768,"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ErrorInfo"},"name":"ErrorInfo","package":"@types/react","qualifiedName":"React.ErrorInfo"}}],"type":{"type":"intrinsic","name":"void"},"overwrites":{"type":"reference","name":"React.Component.componentDidCatch","package":"@types/react"}}],"overwrites":{"type":"reference","name":"React.Component.componentDidCatch","package":"@types/react"}},{"name":"render","variant":"declaration","kind":2048,"signatures":[{"name":"render","variant":"signature","kind":4096,"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"ReactNode","package":"@types/react","qualifiedName":"React.ReactNode"},"overwrites":{"type":"reference","name":"React.Component.render","package":"@types/react"}}],"overwrites":{"type":"reference","name":"React.Component.render","package":"@types/react"}},{"name":"getDerivedStateFromError","variant":"declaration","kind":2048,"flags":{"isStatic":true},"signatures":[{"name":"getDerivedStateFromError","variant":"signature","kind":4096,"parameters":[{"name":"error","variant":"param","kind":32768,"type":{"type":"intrinsic","name":"unknown"}}],"type":{"type":"reference","target":{"packageName":"expo-app-metrics","packagePath":"src/AppMetricsErrorBoundary.tsx","qualifiedName":"State"},"name":"State","package":"expo-app-metrics"}}]}],"extendedTypes":[{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.Component"},"typeArguments":[{"type":"reference","name":"AppMetricsErrorBoundaryProps","package":"expo-app-metrics"},{"type":"reference","target":{"packageName":"expo-app-metrics","packagePath":"src/AppMetricsErrorBoundary.tsx","qualifiedName":"State"},"name":"State","package":"expo-app-metrics"}],"name":"Component","package":"@types/react","qualifiedName":"React.Component"}]},{"name":"ExpoAppMetricsModuleType","variant":"declaration","kind":256,"children":[{"name":"NetworkRequestObserver","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Class for subscribing to HTTP requests observed by the native networking interceptor.\nConstruct an instance to begin receiving "},{"kind":"code","text":"`requestStarted`"},{"kind":"text","text":"/"},{"kind":"code","text":"`requestCompleted`"},{"kind":"text","text":" events;\nrelease the instance (drop all references) to stop."}]},"type":{"type":"query","queryType":{"type":"reference","target":{"packageName":"expo-app-metrics","packagePath":"src/types.ts","qualifiedName":"NetworkRequestObserver"},"name":"NetworkRequestObserver","package":"expo-app-metrics","preferValues":true}}},{"name":"clearStoredEntries","variant":"declaration","kind":2048,"signatures":[{"name":"clearStoredEntries","variant":"signature","kind":4096,"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"intrinsic","name":"void"}],"name":"Promise","package":"typescript"}}]},{"name":"logEvent","variant":"declaration","kind":2048,"signatures":[{"name":"logEvent","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Records a log event against the current main session. The event is\npersisted locally and dispatched on the next "},{"kind":"code","text":"`dispatchEvents()`"},{"kind":"text","text":" flush as an\nOpenTelemetry log record sent to the "},{"kind":"code","text":"`/v1/logs`"},{"kind":"text","text":" endpoint.\n\nSeverity defaults to "},{"kind":"code","text":"`\"info\"`"},{"kind":"text","text":" when not provided."}]},"parameters":[{"name":"name","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"Event name. Maps to the OpenTelemetry "},{"kind":"code","text":"`event.name`"},{"kind":"text","text":" attribute."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"options","variant":"param","kind":32768,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Optional body, attributes, and severity overrides."}]},"type":{"type":"reference","name":"LogEventOptions","package":"expo-app-metrics"}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"markFirstRender","variant":"declaration","kind":2048,"signatures":[{"name":"markFirstRender","variant":"signature","kind":4096,"type":{"type":"intrinsic","name":"void"}}]},{"name":"markInteractive","variant":"declaration","kind":2048,"signatures":[{"name":"markInteractive","variant":"signature","kind":4096,"parameters":[{"name":"attributes","variant":"param","kind":32768,"flags":{"isOptional":true},"type":{"type":"reference","name":"MetricAttributes","package":"expo-app-metrics"}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"setGlobalAttributes","variant":"declaration","kind":2048,"signatures":[{"name":"setGlobalAttributes","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Sets attributes merged into every subsequent metric and log event.\nPer-record keys win on collision. Pass "},{"kind":"code","text":"`null`"},{"kind":"text","text":", "},{"kind":"code","text":"`undefined`"},{"kind":"text","text":", or an empty\nobject to clear."}],"blockTags":[{"tag":"@example","content":[{"kind":"code","text":"```ts\nAppMetrics.setGlobalAttributes({\n subscription_tier: 'pro',\n experiment_variant: 'B',\n});\n```"}]}]},"parameters":[{"name":"attributes","variant":"param","kind":32768,"flags":{"isOptional":true},"type":{"type":"union","types":[{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Record"},"typeArguments":[{"type":"intrinsic","name":"string"},{"type":"reference","name":"LogAttributeValue","package":"expo-app-metrics"}],"name":"Record","package":"typescript"},{"type":"literal","value":null}]}}],"type":{"type":"intrinsic","name":"void"}}]}]},{"name":"ObserveIntegrationsConfig","variant":"declaration","kind":256,"children":[{"name":"expo-router","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Enables the "},{"kind":"code","text":"`expo-router`"},{"kind":"text","text":" integration, which records navigation metrics\n("},{"kind":"code","text":"`cold_ttr`"},{"kind":"text","text":", "},{"kind":"code","text":"`warm_ttr`"},{"kind":"text","text":", "},{"kind":"code","text":"`tti`"},{"kind":"text","text":") from router state changes.\n\nRequires "},{"kind":"code","text":"`expo-router`"},{"kind":"text","text":" to be installed.\n\nPass an object to filter exported route/query params."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"false"}]}]},"type":{"type":"union","types":[{"type":"intrinsic","name":"boolean"},{"type":"reference","name":"ObserveNavigationIntegrationConfig","package":"expo-observe"}]}},{"name":"react-navigation","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Enables the "},{"kind":"code","text":"`@react-navigation/native`"},{"kind":"text","text":" integration, which records\nnavigation metrics ("},{"kind":"code","text":"`cold_ttr`"},{"kind":"text","text":", "},{"kind":"code","text":"`warm_ttr`"},{"kind":"text","text":", "},{"kind":"code","text":"`tti`"},{"kind":"text","text":").\n\nRequires "},{"kind":"code","text":"`@react-navigation/native`"},{"kind":"text","text":" to be installed and the app tree\nto be wrapped in "},{"kind":"code","text":"``"},{"kind":"text","text":" instead of the stock\n"},{"kind":"code","text":"``"},{"kind":"text","text":".\n\nPass an object to filter exported route/query params."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"false"}]}]},"type":{"type":"union","types":[{"type":"intrinsic","name":"boolean"},{"type":"reference","name":"ObserveNavigationIntegrationConfig","package":"expo-observe"}]}}]},{"name":"ObserveModule","variant":"declaration","kind":256,"children":[{"name":"addListener","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"addListener","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Adds a listener for the given event name."}]},"typeParameters":[{"name":"EventName","variant":"typeParam","kind":131072,"type":{"type":"literal","value":"configure"}}],"parameters":[{"name":"eventName","variant":"param","kind":32768,"type":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true}},{"name":"listener","variant":"param","kind":32768,"type":{"type":"indexedAccess","indexType":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true},"objectType":{"type":"reference","name":"ObserveModuleEvents","package":"expo-observe"}}}],"type":{"type":"reference","target":{"packageName":"expo-modules-core","packagePath":"src/ts-declarations/EventEmitter.ts","qualifiedName":"EventSubscription"},"name":"EventSubscription","package":"expo-modules-core"},"inheritedFrom":{"type":"reference","name":"NativeModule.addListener","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"NativeModule.addListener","package":"expo-modules-core"}},{"name":"configure","variant":"declaration","kind":2048,"signatures":[{"name":"configure","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Configures how observability events are collected and dispatched at runtime, such as\nthe environment label, dispatching behavior, sampling, and integrations."}],"blockTags":[{"tag":"@example","content":[{"kind":"code","text":"```ts\nimport { Observe } from 'expo-observe';\n\nObserve.configure({\n environment: 'production',\n dispatchingEnabled: true,\n});\n```"}]}]},"parameters":[{"name":"config","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"Observability settings to apply."}]},"type":{"type":"reference","name":"ObserveConfig","package":"expo-observe"}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"dispatchEvents","variant":"declaration","kind":2048,"signatures":[{"name":"dispatchEvents","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Dispatches pending events to the server immediately.\n\nEvents are dispatched automatically when the app moves to the background. On Android,\na background worker dispatches events once network connectivity is available. On iOS,\ndispatching happens when the app resigns active state or is about to terminate. Call\nthis method to flush events manually, for example, during testing or to ensure events\nare sent before a specific point."}],"blockTags":[{"tag":"@returns","content":[{"kind":"text","text":"A promise that resolves when the pending events have been dispatched."}]},{"tag":"@example","content":[{"kind":"code","text":"```ts\nimport { Observe } from 'expo-observe';\n\nawait Observe.dispatchEvents();\n```"}]}]},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"intrinsic","name":"void"}],"name":"Promise","package":"typescript"}}]},{"name":"emit","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"emit","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Synchronously calls all the listeners attached to that specific event.\nThe event can include any number of arguments that will be passed to the listeners."}]},"typeParameters":[{"name":"EventName","variant":"typeParam","kind":131072,"type":{"type":"literal","value":"configure"}}],"parameters":[{"name":"eventName","variant":"param","kind":32768,"type":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true}},{"name":"args","variant":"param","kind":32768,"flags":{"isRest":true},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Parameters"},"typeArguments":[{"type":"indexedAccess","indexType":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true},"objectType":{"type":"reference","name":"ObserveModuleEvents","package":"expo-observe"}}],"name":"Parameters","package":"typescript"}}],"type":{"type":"intrinsic","name":"void"},"inheritedFrom":{"type":"reference","name":"NativeModule.emit","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"NativeModule.emit","package":"expo-modules-core"}},{"name":"getIntegrations","variant":"declaration","kind":2048,"signatures":[{"name":"getIntegrations","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Returns the "},{"kind":"code","text":"`integrations`"},{"kind":"text","text":" config from the most recent "},{"kind":"code","text":"`configure(...)`"},{"kind":"text","text":"\ncall, or an empty object if "},{"kind":"code","text":"`configure`"},{"kind":"text","text":" has not run yet."}]},"type":{"type":"reference","name":"ObserveIntegrationsConfig","package":"expo-observe"}}]},{"name":"listenerCount","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"listenerCount","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Returns a number of listeners added to the given event."}]},"typeParameters":[{"name":"EventName","variant":"typeParam","kind":131072,"type":{"type":"literal","value":"configure"}}],"parameters":[{"name":"eventName","variant":"param","kind":32768,"type":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true}}],"type":{"type":"intrinsic","name":"number"},"inheritedFrom":{"type":"reference","name":"NativeModule.listenerCount","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"NativeModule.listenerCount","package":"expo-modules-core"}},{"name":"logEvent","variant":"declaration","kind":2048,"signatures":[{"name":"logEvent","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Records a log event against the current main session. The event is\npersisted locally and dispatched on the next "},{"kind":"code","text":"`dispatchEvents()`"},{"kind":"text","text":" flush.\n\nSeverity defaults to "},{"kind":"code","text":"`\"info\"`"},{"kind":"text","text":" when not provided."}]},"parameters":[{"name":"name","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"Event name."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"options","variant":"param","kind":32768,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Optional body, attributes, and severity overrides."}]},"type":{"type":"reference","name":"LogEventOptions","package":"expo-app-metrics"}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"markFirstRender","variant":"declaration","kind":2048,"signatures":[{"name":"markFirstRender","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Marks the first render of the app. Used to compute the "},{"kind":"code","text":"`cold_ttr`"},{"kind":"text","text":" and\n"},{"kind":"code","text":"`warm_ttr`"},{"kind":"text","text":" metrics."}]},"type":{"type":"intrinsic","name":"void"}}]},{"name":"markInteractive","variant":"declaration","kind":2048,"signatures":[{"name":"markInteractive","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Marks the moment the app becomes interactive. Used to compute the "},{"kind":"code","text":"`tti`"},{"kind":"text","text":"\nmetric. Custom "},{"kind":"code","text":"`routeName`"},{"kind":"text","text":" and "},{"kind":"code","text":"`params`"},{"kind":"text","text":" can be attached via "},{"kind":"code","text":"`attributes`"},{"kind":"text","text":".\n\n> Note: When the "},{"kind":"code","text":"`expo-router`"},{"kind":"text","text":" or "},{"kind":"code","text":"`@react-navigation/native`"},{"kind":"text","text":" integration\n> is active, prefer "},{"kind":"code","text":"`useObserve().markInteractive(...)`"},{"kind":"text","text":" — the hook fills\n> in "},{"kind":"code","text":"`routeName`"},{"kind":"text","text":" from the current route, while this raw call does not."}]},"parameters":[{"name":"attributes","variant":"param","kind":32768,"flags":{"isOptional":true},"type":{"type":"reference","name":"MetricAttributes","package":"expo-app-metrics"}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"registerIntegration","variant":"declaration","kind":2048,"signatures":[{"name":"registerIntegration","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Invokes a callback once when the named integration configuration becomes available."}],"blockTags":[{"tag":"@example","content":[{"kind":"code","text":"```ts\nObserve.registerIntegration('expo-router', config => {\n console.log(config);\n});\n```"}]}]},"typeParameters":[{"name":"K","variant":"typeParam","kind":131072,"type":{"type":"typeOperator","operator":"keyof","target":{"type":"reference","name":"ObserveIntegrationsConfig","package":"expo-observe"}}}],"parameters":[{"name":"name","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"Integration name."}]},"type":{"type":"reference","name":"K","package":"expo-observe","refersToTypeParameter":true}},{"name":"callback","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"Function called with the integration configuration."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"parameters":[{"name":"config","variant":"param","kind":32768,"type":{"type":"indexedAccess","indexType":{"type":"reference","name":"K","package":"expo-observe","refersToTypeParameter":true},"objectType":{"type":"reference","name":"ObserveIntegrationsConfig","package":"expo-observe"}}}],"type":{"type":"intrinsic","name":"void"}}]}}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"removeAllListeners","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"removeAllListeners","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Removes all listeners for the given event name."}]},"parameters":[{"name":"eventName","variant":"param","kind":32768,"type":{"type":"literal","value":"configure"}}],"type":{"type":"intrinsic","name":"void"},"inheritedFrom":{"type":"reference","name":"NativeModule.removeAllListeners","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"NativeModule.removeAllListeners","package":"expo-modules-core"}},{"name":"removeListener","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"removeListener","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Removes a listener for the given event name."}]},"typeParameters":[{"name":"EventName","variant":"typeParam","kind":131072,"type":{"type":"literal","value":"configure"}}],"parameters":[{"name":"eventName","variant":"param","kind":32768,"type":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true}},{"name":"listener","variant":"param","kind":32768,"type":{"type":"indexedAccess","indexType":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true},"objectType":{"type":"reference","name":"ObserveModuleEvents","package":"expo-observe"}}}],"type":{"type":"intrinsic","name":"void"},"inheritedFrom":{"type":"reference","name":"NativeModule.removeListener","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"NativeModule.removeListener","package":"expo-modules-core"}},{"name":"reportError","variant":"declaration","kind":2048,"signatures":[{"name":"reportError","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Reports an error your code caught and handled, recorded as a non-fatal "},{"kind":"code","text":"`exception`"},{"kind":"text","text":" event. Use it\nto keep visibility into failures you recover from, which never reach the automatic global handler\nor an error boundary.\n\nThe thrown value is normalized: an "},{"kind":"code","text":"`Error`"},{"kind":"text","text":"'s "},{"kind":"code","text":"`name`"},{"kind":"text","text":", "},{"kind":"code","text":"`message`"},{"kind":"text","text":", and "},{"kind":"code","text":"`stack`"},{"kind":"text","text":" are captured; any\nother value (a string, a plain object) is stringified as the message."}],"blockTags":[{"tag":"@example","content":[{"kind":"code","text":"```ts\ntry {\n await syncCart();\n} catch (error) {\n Observe.reportError(error);\n}\n```"}]}]},"parameters":[{"name":"error","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"The caught value. An "},{"kind":"code","text":"`Error`"},{"kind":"text","text":" is preferred, but any thrown value is accepted."}]},"type":{"type":"intrinsic","name":"unknown"}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"setBundleDefaults","variant":"declaration","kind":2048,"signatures":[{"name":"setBundleDefaults","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Pushes JS-bundle-derived facts ("},{"kind":"code","text":"`process.env.NODE_ENV`"},{"kind":"text","text":", "},{"kind":"code","text":"`__DEV__`"},{"kind":"text","text":") into native\nstorage. Called automatically once when the package is first imported; should\nnot be called by host apps directly."}],"modifierTags":["@internal"]},"parameters":[{"name":"defaults","variant":"param","kind":32768,"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"children":[{"name":"environment","variant":"declaration","kind":1024,"type":{"type":"intrinsic","name":"string"}},{"name":"isJsDev","variant":"declaration","kind":1024,"type":{"type":"intrinsic","name":"boolean"}}]}}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"setGlobalAttributes","variant":"declaration","kind":2048,"signatures":[{"name":"setGlobalAttributes","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Sets attributes merged into every subsequent metric and log event.\nPer-record keys win on collision. Pass "},{"kind":"code","text":"`null`"},{"kind":"text","text":", "},{"kind":"code","text":"`undefined`"},{"kind":"text","text":", or an empty\nobject to clear."}],"blockTags":[{"tag":"@example","content":[{"kind":"code","text":"```ts\nObserve.setGlobalAttributes({\n subscription_tier: 'pro',\n experiment_variant: 'B',\n});\n```"}]}]},"parameters":[{"name":"attributes","variant":"param","kind":32768,"flags":{"isOptional":true},"type":{"type":"union","types":[{"type":"reference","name":"ObserveAttributes","package":"expo-observe"},{"type":"literal","value":null}]}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"startObserving","variant":"declaration","kind":2048,"flags":{"isOptional":true,"isInherited":true},"signatures":[{"name":"startObserving","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Function that is automatically invoked when the first listener for an event with the given name is added.\nOverride it in a subclass to perform some additional setup once the event started being observed."}]},"typeParameters":[{"name":"EventName","variant":"typeParam","kind":131072,"type":{"type":"literal","value":"configure"}}],"parameters":[{"name":"eventName","variant":"param","kind":32768,"type":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true}}],"type":{"type":"intrinsic","name":"void"},"inheritedFrom":{"type":"reference","name":"NativeModule.startObserving","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"NativeModule.startObserving","package":"expo-modules-core"}},{"name":"stopObserving","variant":"declaration","kind":2048,"flags":{"isOptional":true,"isInherited":true},"signatures":[{"name":"stopObserving","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Function that is automatically invoked when the last listener for an event with the given name is removed.\nOverride it in a subclass to perform some additional cleanup once the event is no longer observed."}]},"typeParameters":[{"name":"EventName","variant":"typeParam","kind":131072,"type":{"type":"literal","value":"configure"}}],"parameters":[{"name":"eventName","variant":"param","kind":32768,"type":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true}}],"type":{"type":"intrinsic","name":"void"},"inheritedFrom":{"type":"reference","name":"NativeModule.stopObserving","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"NativeModule.stopObserving","package":"expo-modules-core"}}],"extendedTypes":[{"type":"reference","target":{"packageName":"expo-modules-core","packagePath":"src/NativeModule.ts","qualifiedName":"NativeModule"},"typeArguments":[{"type":"reference","name":"ObserveModuleEvents","package":"expo-observe"}],"name":"NativeModule","package":"expo-modules-core"}]},{"name":"AppMetricsErrorBoundaryFallbackProps","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Arguments passed to a "},{"kind":"code","text":"`fallback`"},{"kind":"text","text":" render function."}]},"children":[{"name":"error","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"The value the subtree threw. Usually an "},{"kind":"code","text":"`Error`"},{"kind":"text","text":", but any value can be thrown."}]},"type":{"type":"intrinsic","name":"unknown"}},{"name":"resetError","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Clears the caught error and re-renders the children. Use it to offer a \"try again\" action;\nthe children re-mount, so they run from a clean state."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"intrinsic","name":"void"}}]}}}]},{"name":"AppMetricsErrorBoundaryProps","variant":"declaration","kind":2097152,"children":[{"name":"children","variant":"declaration","kind":1024,"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"React.ReactNode","package":"@types/react"}},{"name":"fallback","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Rendered in place of the subtree after an error is caught. Provide one of:\n\n- a React element to render as-is,\n- a function receiving the "},{"kind":"code","text":"`error`"},{"kind":"text","text":" and a "},{"kind":"code","text":"`resetError`"},{"kind":"text","text":" callback (to show details and offer retry),\n- "},{"kind":"code","text":"`null`"},{"kind":"text","text":" to render nothing.\n\nA boundary can't re-throw to reproduce React Native's default crash, so it always renders one of\nthe above; there's no capture-only mode. Errors no boundary catches are still recorded by the\nglobal "},{"kind":"code","text":"`ErrorUtils`"},{"kind":"text","text":" handler."}]},"type":{"type":"union","types":[{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactElement"},"name":"React.ReactElement","package":"@types/react"},{"type":"literal","value":null},{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"parameters":[{"name":"props","variant":"param","kind":32768,"type":{"type":"reference","name":"AppMetricsErrorBoundaryFallbackProps","package":"expo-app-metrics"}}],"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"React.ReactNode","package":"@types/react"}}]}}]}}]},{"name":"AppMetricsRootProps","variant":"declaration","kind":2097152,"children":[{"name":"children","variant":"declaration","kind":1024,"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"React.ReactNode","package":"@types/react"}},{"name":"errorBoundaryFallback","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"When set, the app is wrapped in an "},{"kind":"code","text":"`AppMetricsErrorBoundary`"},{"kind":"text","text":" with this "},{"kind":"code","text":"`fallback`"},{"kind":"text","text":", capturing\nReact render-phase errors at the root. Omit it and no boundary is mounted, so render errors keep\nReact Native's default behavior (they're still recorded by the global "},{"kind":"code","text":"`ErrorUtils`"},{"kind":"text","text":" handler, just\nwithout the component stack). Pass "},{"kind":"code","text":"`null`"},{"kind":"text","text":" to capture but render nothing.\n\nTo place a boundary deeper in the tree, use "},{"kind":"code","text":"`AppMetricsErrorBoundary`"},{"kind":"text","text":" directly."}]},"type":{"type":"indexedAccess","indexType":{"type":"literal","value":"fallback"},"objectType":{"type":"reference","name":"AppMetricsErrorBoundaryProps","package":"expo-app-metrics"}}}]},{"name":"LogAttributeValue","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Value types accepted in a log event's "},{"kind":"code","text":"`attributes`"},{"kind":"text","text":" map. Strings, numbers,\nand booleans are stored as typed primitives; arrays and nested maps preserve\ntheir structure. Other JS values (functions, "},{"kind":"code","text":"`Date`"},{"kind":"text","text":", "},{"kind":"code","text":"`undefined`"},{"kind":"text","text":", etc.) are\nnot supported and may be dropped by downstream consumers."}]},"type":{"type":"union","types":[{"type":"intrinsic","name":"string"},{"type":"intrinsic","name":"number"},{"type":"intrinsic","name":"boolean"},{"type":"array","elementType":{"type":"reference","name":"LogAttributeValue","package":"expo-app-metrics"}},{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"indexSignatures":[{"name":"__index","variant":"signature","kind":8192,"parameters":[{"name":"key","variant":"param","kind":32768,"type":{"type":"intrinsic","name":"string"}}],"type":{"type":"reference","name":"LogAttributeValue","package":"expo-app-metrics"}}]}}]}},{"name":"LogEventOptions","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Optional configuration accepted by "},{"kind":"code","text":"`logEvent`"},{"kind":"text","text":". The event name is passed as\nthe first positional argument since it's required and the only field most\ncallers set."}]},"children":[{"name":"attributes","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Custom attributes attached to the event. Each entry is preserved with its\noriginal value type — see "},{"kind":"code","text":"`LogAttributeValue`"},{"kind":"text","text":" for the supported shapes."}]},"type":{"type":"union","types":[{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Record"},"typeArguments":[{"type":"intrinsic","name":"string"},{"type":"reference","name":"LogAttributeValue","package":"expo-app-metrics"}],"name":"Record","package":"typescript"},{"type":"literal","value":null}]}},{"name":"body","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Optional free-form message describing the event."}]},"type":{"type":"union","types":[{"type":"intrinsic","name":"string"},{"type":"literal","value":null}]}},{"name":"displayName","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Optional human-friendly label for the event. Unlike "},{"kind":"code","text":"`name`"},{"kind":"text","text":" (a stable machine\nidentifier), this is meant for display in dashboards and is not constrained\nto a naming scheme."}]},"type":{"type":"union","types":[{"type":"intrinsic","name":"string"},{"type":"literal","value":null}]}},{"name":"severity","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Severity of the event."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"\"info\""}]}]},"type":{"type":"union","types":[{"type":"reference","name":"LogSeverity","package":"expo-app-metrics"},{"type":"literal","value":null}]}}]},{"name":"LogSeverity","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Severity of a log event, ordered from least to most severe:\n\n- "},{"kind":"code","text":"`\"trace\"`"},{"kind":"text","text":" — Fine-grained tracing, typically only useful while reproducing\n a specific issue.\n- "},{"kind":"code","text":"`\"debug\"`"},{"kind":"text","text":" — Diagnostic detail useful during development; usually filtered\n out in production.\n- "},{"kind":"code","text":"`\"info\"`"},{"kind":"text","text":" — Routine, expected events that record normal app behavior.\n- "},{"kind":"code","text":"`\"warn\"`"},{"kind":"text","text":" — Unexpected but recoverable conditions worth investigating.\n- "},{"kind":"code","text":"`\"error\"`"},{"kind":"text","text":" — An operation failed; the app continues running but is in a\n degraded state.\n- "},{"kind":"code","text":"`\"fatal\"`"},{"kind":"text","text":" — A severe failure, often immediately followed by app termination."}]},"type":{"type":"union","types":[{"type":"literal","value":"trace"},{"type":"literal","value":"debug"},{"type":"literal","value":"info"},{"type":"literal","value":"warn"},{"type":"literal","value":"error"},{"type":"literal","value":"fatal"}]}},{"name":"MetricAttributes","variant":"declaration","kind":2097152,"children":[{"name":"params","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Custom parameters to attach to the metric."}]},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Record"},"typeArguments":[{"type":"intrinsic","name":"string"},{"type":"intrinsic","name":"unknown"}],"name":"Record","package":"typescript"}},{"name":"routeName","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Name of the route associated with the metric. Some metrics populate this\nwith a sensible default when omitted — for example, the TTI metric falls\nback to the initial route name detected from the router."}]},"type":{"type":"union","types":[{"type":"intrinsic","name":"string"},{"type":"literal","value":null}]}}]},{"name":"ObserveAttribute","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Value types accepted as attribute values in "},{"kind":"code","text":"`setGlobalAttributes`"},{"kind":"text","text":" and the\nother Observe APIs. Strings, numbers, and booleans are stored as typed\nprimitives; arrays and nested maps preserve their structure."}]},"type":{"type":"reference","name":"LogAttributeValue","package":"expo-app-metrics"}},{"name":"ObserveAttributes","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"A map of attribute key to value, as accepted by "},{"kind":"code","text":"`setGlobalAttributes`"},{"kind":"text","text":" and\nother Observe APIs that take a free-form attributes payload."}]},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Record"},"typeArguments":[{"type":"intrinsic","name":"string"},{"type":"reference","name":"ObserveAttribute","package":"expo-observe"}],"name":"Record","package":"typescript"}},{"name":"ObserveConfig","variant":"declaration","kind":2097152,"children":[{"name":"dispatchInDebug","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Whether to dispatch metrics that were collected in a debug build of the host app.\n\nWhen "},{"kind":"code","text":"`false`"},{"kind":"text","text":", metrics produced by debug builds are marked as sent without being dispatched.\nWhen "},{"kind":"code","text":"`true`"},{"kind":"text","text":", debug-build metrics are dispatched alongside release-build metrics.\n\nHas no effect on release builds.\n\nIf "},{"kind":"code","text":"`dispatchingEnabled`"},{"kind":"text","text":" is "},{"kind":"code","text":"`false`"},{"kind":"text","text":" or this device is out-of-sample for "},{"kind":"code","text":"`sampleRate`"},{"kind":"text","text":", nothing\nis dispatched regardless of "},{"kind":"code","text":"`dispatchInDebug`"},{"kind":"text","text":"."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"false"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"dispatchingEnabled","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Whether to dispatch observability events to the server.\n\nWhen "},{"kind":"code","text":"`false`"},{"kind":"text","text":", any pending metrics are marked as sent without being dispatched\nand no further metrics are dispatched until this is set back to "},{"kind":"code","text":"`true`"},{"kind":"text","text":"."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"true"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"environment","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The environment for observability events"}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"process.env.NODE_ENV"}]}]},"type":{"type":"intrinsic","name":"string"}},{"name":"errorHandlingEnabled","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Whether to record unhandled JavaScript errors as "},{"kind":"code","text":"`exception`"},{"kind":"text","text":" log events.\n\nWhen "},{"kind":"code","text":"`false`"},{"kind":"text","text":", unhandled errors are no longer recorded. React Native's own handling is\nunaffected either way: the red box in development and fatal termination in production still\nhappen. Errors you report yourself with "},{"kind":"code","text":"`reportError`"},{"kind":"text","text":", and render-phase errors captured by\n"},{"kind":"code","text":"`ObserveErrorBoundary`"},{"kind":"text","text":", are also unaffected.\n\n> Note: The handler is installed when the package is first imported, which is earlier than any\n> "},{"kind":"code","text":"`configure`"},{"kind":"text","text":" call. An error thrown before "},{"kind":"code","text":"`configure`"},{"kind":"text","text":" runs is therefore still recorded."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"true"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"integrations","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Opt in to per-integration behavior. See the [Expo Router](/eas/observe/integrations/expo-router/)\nand [React Navigation](/eas/observe/integrations/react-navigation/) integrations, or\n[integrate your own package](/eas/observe/integrations/third-party/)."}]},"type":{"type":"reference","name":"ObserveIntegrationsConfig","package":"expo-observe"}},{"name":"sampleRate","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Fraction of installations that should dispatch metrics, in "},{"kind":"code","text":"`[0, 1]`"},{"kind":"text","text":". Values outside that range\nare clamped.\n\nThe decision is **deterministic per installation** — a device is either permanently in-sample\nor out-of-sample for a given rate, so the choice is stable across app launches.\n\nInteraction with "},{"kind":"code","text":"`dispatchingEnabled`"},{"kind":"text","text":":\n- If "},{"kind":"code","text":"`dispatchingEnabled`"},{"kind":"text","text":" is "},{"kind":"code","text":"`false`"},{"kind":"text","text":", metrics are never dispatched\n- If "},{"kind":"code","text":"`dispatchingEnabled`"},{"kind":"text","text":" is "},{"kind":"code","text":"`true`"},{"kind":"text","text":" (or unset), metrics are dispatched only when this device\n is in-sample.\n\n> Note: Devices that end up out-of-sample drop pending metrics rather than accumulating them."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"undefined - metrics from all devices are sent"}]}]},"type":{"type":"intrinsic","name":"number"}}]},{"name":"ObserveInteractiveMarkerProps","variant":"declaration","kind":2097152,"children":[{"name":"params","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Custom parameters attached to the TTI metric, forwarded to "},{"kind":"code","text":"`markInteractive`"},{"kind":"text","text":".\nValues can be strings, numbers, booleans, or other JSON-serializable values."}]},"type":{"type":"indexedAccess","indexType":{"type":"literal","value":"params"},"objectType":{"type":"reference","name":"MetricAttributes","package":"expo-app-metrics"}}}]},{"name":"ObserveModuleEvents","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Events emitted by the native "},{"kind":"code","text":"`ExpoObserve`"},{"kind":"text","text":" module."}]},"children":[{"name":"configure","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Fired on every "},{"kind":"code","text":"`configure(...)`"},{"kind":"text","text":" call, carrying the resolved "},{"kind":"code","text":"`integrations`"},{"kind":"text","text":"\nconfig"}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"parameters":[{"name":"payload","variant":"param","kind":32768,"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"children":[{"name":"integrations","variant":"declaration","kind":1024,"type":{"type":"reference","name":"ObserveIntegrationsConfig","package":"expo-observe"}}]}}}],"type":{"type":"intrinsic","name":"void"}}]}}}]},{"name":"ObserveNavigationIntegrationConfig","variant":"declaration","kind":2097152,"children":[{"name":"filteredParams","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Route or query parameter keys to remove from exported navigation metric\n"},{"kind":"code","text":"`routeParams`"},{"kind":"text","text":". When any configured parameter is removed from a metric,\nthe exported resolved URL/path is replaced with "},{"kind":"code","text":"`urlHidden: true`"},{"kind":"text","text":".\nDoes not affect "},{"kind":"code","text":"`routeName`"},{"kind":"text","text":"."}]},"type":{"type":"array","elementType":{"type":"intrinsic","name":"string"}}}]},{"name":"AppMetrics","variant":"declaration","kind":32,"comment":{"summary":[],"blockTags":[{"tag":"@deprecated","content":[{"kind":"text","text":"Use "},{"kind":"code","text":"`Observe`"},{"kind":"text","text":" instead. "},{"kind":"code","text":"`AppMetrics`"},{"kind":"text","text":" is the legacy name of this API from SDK 55."}]}]},"type":{"type":"reference","name":"ExpoAppMetricsModuleType","package":"expo-app-metrics"}},{"name":"Observe","variant":"declaration","kind":32,"flags":{"isConst":true},"type":{"type":"reference","name":"ObserveModule","package":"expo-observe"},"defaultValue":"..."},{"name":"ObserveInteractiveMarker","variant":"declaration","kind":64,"signatures":[{"name":"ObserveInteractiveMarker","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Declarative wrapper around "},{"kind":"code","text":"`useObserve().markInteractive(...)`"},{"kind":"text","text":". Renders nothing\nand calls "},{"kind":"code","text":"`markInteractive`"},{"kind":"text","text":" once when it first mounts, marking the moment the\nscreen becomes interactive (used to compute the "},{"kind":"code","text":"`tti`"},{"kind":"text","text":" metric). Render it once\nthe screen is ready for user interaction — for example, after its initial data\nhas loaded.\n\nBecause "},{"kind":"code","text":"`markInteractive`"},{"kind":"text","text":" is only sent on mount, the marker is fire-once: changing\n"},{"kind":"code","text":"`params`"},{"kind":"text","text":" after the first render has no effect and warns in development. If you need\nto attach attributes that are only known later, call "},{"kind":"code","text":"`useObserve().markInteractive(...)`"},{"kind":"text","text":"\nimperatively instead."}],"blockTags":[{"tag":"@example","content":[{"kind":"code","text":"```tsx\nimport { ObserveInteractiveMarker } from 'expo-observe';\n\nfunction Feed({ items }) {\n if (!items) return ;\n return (\n <>\n \n \n \n );\n}\n```"}]}]},"parameters":[{"name":"props","variant":"param","kind":32768,"type":{"type":"reference","name":"ObserveInteractiveMarkerProps","package":"expo-observe"}}],"type":{"type":"literal","value":null}}]},{"name":"ObserveRoot","variant":"declaration","kind":64,"children":[{"name":"wrap","variant":"declaration","kind":2048,"signatures":[{"name":"wrap","variant":"signature","kind":4096,"typeParameters":[{"name":"P","variant":"typeParam","kind":131072,"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Record"},"typeArguments":[{"type":"intrinsic","name":"string"},{"type":"intrinsic","name":"unknown"}],"name":"Record","package":"typescript"}}],"parameters":[{"name":"Component","variant":"param","kind":32768,"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ComponentType"},"typeArguments":[{"type":"reference","name":"P","package":"expo-observe","refersToTypeParameter":true}],"name":"ComponentType","package":"@types/react","qualifiedName":"React.ComponentType"}}],"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ComponentType"},"typeArguments":[{"type":"reference","name":"P","package":"expo-observe","refersToTypeParameter":true}],"name":"ComponentType","package":"@types/react","qualifiedName":"React.ComponentType"}}]}],"signatures":[{"name":"ObserveRoot","variant":"signature","kind":4096,"parameters":[{"name":"__namedParameters","variant":"param","kind":32768,"type":{"type":"intersection","types":[{"type":"reference","name":"AppMetricsRootProps","package":"expo-app-metrics"},{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"children":[{"name":"children","variant":"declaration","kind":1024,"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"ReactNode","package":"@types/react","qualifiedName":"React.ReactNode"}}]}}]}}],"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"jsx-runtime.d.ts","qualifiedName":"JSX.Element"},"name":"Element","package":"@types/react","qualifiedName":"JSX.Element"}}]},{"name":"useObserve","variant":"declaration","kind":64,"signatures":[{"name":"useObserve","variant":"signature","kind":4096,"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"children":[{"name":"markInteractive","variant":"declaration","kind":1024,"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"parameters":[{"name":"attributes","variant":"param","kind":32768,"flags":{"isOptional":true},"type":{"type":"reference","name":"MetricAttributes","package":"expo-app-metrics"}}],"type":{"type":"intrinsic","name":"void"}}]}},"defaultValue":"..."}]}}}]},{"name":"default","variant":"reference","kind":4194304},{"name":"ObserveErrorBoundaryFallbackProps","variant":"reference","kind":4194304},{"name":"ObserveErrorBoundaryProps","variant":"reference","kind":4194304}],"packageName":"expo-observe"} \ No newline at end of file +{"schemaVersion":"2.0","name":"expo-observe","variant":"project","kind":1,"children":[{"name":"ObserveErrorBoundary","variant":"declaration","kind":128,"comment":{"summary":[{"kind":"text","text":"A React error boundary that records render-phase errors as non-fatal "},{"kind":"code","text":"`exception`"},{"kind":"text","text":" log events (with\nthe React component stack) and renders a "},{"kind":"code","text":"`fallback`"},{"kind":"text","text":" in place of the subtree that threw.\n\nRender-phase errors don't reach "},{"kind":"code","text":"`global.ErrorUtils`"},{"kind":"text","text":", so a boundary is the only way to capture them\nwith the component stack. Place one around any subtree, or let "},{"kind":"code","text":"`AppMetricsRoot`"},{"kind":"text","text":" mount one via its\n"},{"kind":"code","text":"`errorBoundaryFallback`"},{"kind":"text","text":" prop."}]},"children":[{"name":"constructor","variant":"declaration","kind":512,"flags":{"isExternal":true},"signatures":[{"name":"ObserveErrorBoundary","variant":"signature","kind":16384,"flags":{"isExternal":true},"parameters":[{"name":"props","variant":"param","kind":32768,"flags":{"isExternal":true},"type":{"type":"reference","name":"AppMetricsErrorBoundaryProps","package":"expo-app-metrics"}}],"type":{"type":"reference","name":"AppMetricsErrorBoundary","package":"expo-app-metrics"},"inheritedFrom":{"type":"reference","name":"React.Component.constructor","package":"@types/react"}},{"name":"ObserveErrorBoundary","variant":"signature","kind":16384,"flags":{"isExternal":true},"parameters":[{"name":"props","variant":"param","kind":32768,"flags":{"isExternal":true},"type":{"type":"reference","name":"AppMetricsErrorBoundaryProps","package":"expo-app-metrics"}},{"name":"context","variant":"param","kind":32768,"flags":{"isExternal":true},"comment":{"summary":[{"kind":"text","text":"value of the parent "},{"kind":"inline-tag","tag":"@link","text":"Context"},{"kind":"text","text":" specified\nin "},{"kind":"code","text":"`contextType`"},{"kind":"text","text":"."}]},"type":{"type":"intrinsic","name":"any"}}],"type":{"type":"reference","name":"AppMetricsErrorBoundary","package":"expo-app-metrics"},"inheritedFrom":{"type":"reference","name":"React.Component.constructor","package":"@types/react"}}],"inheritedFrom":{"type":"reference","name":"React.Component.constructor","package":"@types/react"}},{"name":"state","variant":"declaration","kind":1024,"type":{"type":"reference","target":{"packageName":"expo-app-metrics","packagePath":"src/AppMetricsErrorBoundary.tsx","qualifiedName":"State"},"name":"State","package":"expo-app-metrics"},"defaultValue":"...","overwrites":{"type":"reference","name":"React.Component.state","package":"@types/react"}},{"name":"componentDidCatch","variant":"declaration","kind":2048,"signatures":[{"name":"componentDidCatch","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Catches exceptions generated in descendant components. Unhandled exceptions will cause\nthe entire component tree to unmount."}]},"parameters":[{"name":"error","variant":"param","kind":32768,"type":{"type":"intrinsic","name":"unknown"}},{"name":"errorInfo","variant":"param","kind":32768,"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ErrorInfo"},"name":"ErrorInfo","package":"@types/react","qualifiedName":"React.ErrorInfo"}}],"type":{"type":"intrinsic","name":"void"},"overwrites":{"type":"reference","name":"React.Component.componentDidCatch","package":"@types/react"}}],"overwrites":{"type":"reference","name":"React.Component.componentDidCatch","package":"@types/react"}},{"name":"render","variant":"declaration","kind":2048,"signatures":[{"name":"render","variant":"signature","kind":4096,"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"ReactNode","package":"@types/react","qualifiedName":"React.ReactNode"},"overwrites":{"type":"reference","name":"React.Component.render","package":"@types/react"}}],"overwrites":{"type":"reference","name":"React.Component.render","package":"@types/react"}},{"name":"getDerivedStateFromError","variant":"declaration","kind":2048,"flags":{"isStatic":true},"signatures":[{"name":"getDerivedStateFromError","variant":"signature","kind":4096,"parameters":[{"name":"error","variant":"param","kind":32768,"type":{"type":"intrinsic","name":"unknown"}}],"type":{"type":"reference","target":{"packageName":"expo-app-metrics","packagePath":"src/AppMetricsErrorBoundary.tsx","qualifiedName":"State"},"name":"State","package":"expo-app-metrics"}}]}],"extendedTypes":[{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.Component"},"typeArguments":[{"type":"reference","name":"AppMetricsErrorBoundaryProps","package":"expo-app-metrics"},{"type":"reference","target":{"packageName":"expo-app-metrics","packagePath":"src/AppMetricsErrorBoundary.tsx","qualifiedName":"State"},"name":"State","package":"expo-app-metrics"}],"name":"Component","package":"@types/react","qualifiedName":"React.Component"}]},{"name":"ExpoAppMetricsModuleType","variant":"declaration","kind":256,"children":[{"name":"NetworkRequestObserver","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Class for subscribing to HTTP requests observed by the native networking interceptor.\nConstruct an instance to begin receiving "},{"kind":"code","text":"`requestStarted`"},{"kind":"text","text":"/"},{"kind":"code","text":"`requestCompleted`"},{"kind":"text","text":" events;\nrelease the instance (drop all references) to stop."}]},"type":{"type":"query","queryType":{"type":"reference","target":{"packageName":"expo-app-metrics","packagePath":"src/types.ts","qualifiedName":"NetworkRequestObserver"},"name":"NetworkRequestObserver","package":"expo-app-metrics","preferValues":true}}},{"name":"clearStoredEntries","variant":"declaration","kind":2048,"signatures":[{"name":"clearStoredEntries","variant":"signature","kind":4096,"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"intrinsic","name":"void"}],"name":"Promise","package":"typescript"}}]},{"name":"logEvent","variant":"declaration","kind":2048,"signatures":[{"name":"logEvent","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Records a log event against the current main session. The event is\npersisted locally and dispatched on the next "},{"kind":"code","text":"`dispatchEvents()`"},{"kind":"text","text":" flush as an\nOpenTelemetry log record sent to the "},{"kind":"code","text":"`/v1/logs`"},{"kind":"text","text":" endpoint.\n\nSeverity defaults to "},{"kind":"code","text":"`\"info\"`"},{"kind":"text","text":" when not provided."}]},"parameters":[{"name":"name","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"Event name. Maps to the OpenTelemetry "},{"kind":"code","text":"`event.name`"},{"kind":"text","text":" attribute."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"options","variant":"param","kind":32768,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Optional body, attributes, and severity overrides."}]},"type":{"type":"reference","name":"LogEventOptions","package":"expo-app-metrics"}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"markFirstRender","variant":"declaration","kind":2048,"signatures":[{"name":"markFirstRender","variant":"signature","kind":4096,"type":{"type":"intrinsic","name":"void"}}]},{"name":"markInteractive","variant":"declaration","kind":2048,"signatures":[{"name":"markInteractive","variant":"signature","kind":4096,"parameters":[{"name":"attributes","variant":"param","kind":32768,"flags":{"isOptional":true},"type":{"type":"reference","name":"MetricAttributes","package":"expo-app-metrics"}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"setGlobalAttributes","variant":"declaration","kind":2048,"signatures":[{"name":"setGlobalAttributes","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Sets attributes merged into every subsequent metric and log event.\nPer-record keys win on collision. Pass "},{"kind":"code","text":"`null`"},{"kind":"text","text":", "},{"kind":"code","text":"`undefined`"},{"kind":"text","text":", or an empty\nobject to clear."}],"blockTags":[{"tag":"@example","content":[{"kind":"code","text":"```ts\nAppMetrics.setGlobalAttributes({\n subscription_tier: 'pro',\n experiment_variant: 'B',\n});\n```"}]}]},"parameters":[{"name":"attributes","variant":"param","kind":32768,"flags":{"isOptional":true},"type":{"type":"union","types":[{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Record"},"typeArguments":[{"type":"intrinsic","name":"string"},{"type":"reference","name":"LogAttributeValue","package":"expo-app-metrics"}],"name":"Record","package":"typescript"},{"type":"literal","value":null}]}}],"type":{"type":"intrinsic","name":"void"}}]}]},{"name":"ObserveIntegrationsConfig","variant":"declaration","kind":256,"children":[{"name":"expo-router","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Enables the "},{"kind":"code","text":"`expo-router`"},{"kind":"text","text":" integration, which records navigation metrics\n("},{"kind":"code","text":"`cold_ttr`"},{"kind":"text","text":", "},{"kind":"code","text":"`warm_ttr`"},{"kind":"text","text":", "},{"kind":"code","text":"`tti`"},{"kind":"text","text":") from router state changes.\n\nRequires "},{"kind":"code","text":"`expo-router`"},{"kind":"text","text":" to be installed.\n\nPass an object to filter exported route/query params."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"false"}]}]},"type":{"type":"union","types":[{"type":"intrinsic","name":"boolean"},{"type":"reference","name":"ObserveNavigationIntegrationConfig","package":"expo-observe"}]}},{"name":"react-navigation","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Enables the "},{"kind":"code","text":"`@react-navigation/native`"},{"kind":"text","text":" integration, which records\nnavigation metrics ("},{"kind":"code","text":"`cold_ttr`"},{"kind":"text","text":", "},{"kind":"code","text":"`warm_ttr`"},{"kind":"text","text":", "},{"kind":"code","text":"`tti`"},{"kind":"text","text":").\n\nRequires "},{"kind":"code","text":"`@react-navigation/native`"},{"kind":"text","text":" to be installed and the app tree\nto be wrapped in "},{"kind":"code","text":"``"},{"kind":"text","text":" instead of the stock\n"},{"kind":"code","text":"``"},{"kind":"text","text":".\n\nPass an object to filter exported route/query params."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"false"}]}]},"type":{"type":"union","types":[{"type":"intrinsic","name":"boolean"},{"type":"reference","name":"ObserveNavigationIntegrationConfig","package":"expo-observe"}]}}]},{"name":"ObserveModule","variant":"declaration","kind":256,"children":[{"name":"clientId","variant":"declaration","kind":1024,"flags":{"isReadonly":true},"comment":{"summary":[{"kind":"text","text":"The EAS client id: a random, pseudonymous identifier for this app installation, shared by all\nEAS client libraries. Observe records it on every metric and log event as the\n"},{"kind":"code","text":"`expo.eas_client.id`"},{"kind":"text","text":" attribute, so use it to correlate Observe data with another service.\n\nThe id is stored in native preferences and is stable across app launches and app updates. It\nchanges when the app's data is cleared or the app is reinstalled, although a backup restore\ncan carry the previous id over. It identifies an installation, not a user or a device.\n\n"},{"kind":"code","text":"`null`"},{"kind":"text","text":" on web, where there is no EAS client id."}],"blockTags":[{"tag":"@example","content":[{"kind":"code","text":"```ts\nimport { Observe } from 'expo-observe';\n\n// Attach the same id to data you send elsewhere to line it up with Observe.\nawait fetch('https://example.com/events', {\n method: 'POST',\n body: JSON.stringify({ easClientId: Observe.clientId }),\n});\n```"}]},{"tag":"@platform","content":[{"kind":"text","text":"android"}]},{"tag":"@platform","content":[{"kind":"text","text":"ios"}]}]},"type":{"type":"union","types":[{"type":"intrinsic","name":"string"},{"type":"literal","value":null}]}},{"name":"addListener","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"addListener","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Adds a listener for the given event name."}]},"typeParameters":[{"name":"EventName","variant":"typeParam","kind":131072,"type":{"type":"literal","value":"configure"}}],"parameters":[{"name":"eventName","variant":"param","kind":32768,"type":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true}},{"name":"listener","variant":"param","kind":32768,"type":{"type":"indexedAccess","indexType":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true},"objectType":{"type":"reference","name":"ObserveModuleEvents","package":"expo-observe"}}}],"type":{"type":"reference","target":{"packageName":"expo-modules-core","packagePath":"src/ts-declarations/EventEmitter.ts","qualifiedName":"EventSubscription"},"name":"EventSubscription","package":"expo-modules-core"},"inheritedFrom":{"type":"reference","name":"NativeModule.addListener","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"NativeModule.addListener","package":"expo-modules-core"}},{"name":"configure","variant":"declaration","kind":2048,"signatures":[{"name":"configure","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Configures how observability events are collected and dispatched at runtime, such as\nthe environment label, dispatching behavior, sampling, and integrations."}],"blockTags":[{"tag":"@example","content":[{"kind":"code","text":"```ts\nimport { Observe } from 'expo-observe';\n\nObserve.configure({\n environment: 'production',\n dispatchingEnabled: true,\n});\n```"}]}]},"parameters":[{"name":"config","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"Observability settings to apply."}]},"type":{"type":"reference","name":"ObserveConfig","package":"expo-observe"}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"dispatchEvents","variant":"declaration","kind":2048,"signatures":[{"name":"dispatchEvents","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Dispatches pending events to the server immediately.\n\nEvents are dispatched automatically when the app moves to the background. On Android,\na background worker dispatches events once network connectivity is available. On iOS,\ndispatching happens when the app resigns active state or is about to terminate. Call\nthis method to flush events manually, for example, during testing or to ensure events\nare sent before a specific point."}],"blockTags":[{"tag":"@returns","content":[{"kind":"text","text":"A promise that resolves when the pending events have been dispatched."}]},{"tag":"@example","content":[{"kind":"code","text":"```ts\nimport { Observe } from 'expo-observe';\n\nawait Observe.dispatchEvents();\n```"}]}]},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"intrinsic","name":"void"}],"name":"Promise","package":"typescript"}}]},{"name":"emit","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"emit","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Synchronously calls all the listeners attached to that specific event.\nThe event can include any number of arguments that will be passed to the listeners."}]},"typeParameters":[{"name":"EventName","variant":"typeParam","kind":131072,"type":{"type":"literal","value":"configure"}}],"parameters":[{"name":"eventName","variant":"param","kind":32768,"type":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true}},{"name":"args","variant":"param","kind":32768,"flags":{"isRest":true},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Parameters"},"typeArguments":[{"type":"indexedAccess","indexType":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true},"objectType":{"type":"reference","name":"ObserveModuleEvents","package":"expo-observe"}}],"name":"Parameters","package":"typescript"}}],"type":{"type":"intrinsic","name":"void"},"inheritedFrom":{"type":"reference","name":"NativeModule.emit","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"NativeModule.emit","package":"expo-modules-core"}},{"name":"getIntegrations","variant":"declaration","kind":2048,"signatures":[{"name":"getIntegrations","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Returns the "},{"kind":"code","text":"`integrations`"},{"kind":"text","text":" config from the most recent "},{"kind":"code","text":"`configure(...)`"},{"kind":"text","text":"\ncall, or an empty object if "},{"kind":"code","text":"`configure`"},{"kind":"text","text":" has not run yet."}]},"type":{"type":"reference","name":"ObserveIntegrationsConfig","package":"expo-observe"}}]},{"name":"listenerCount","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"listenerCount","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Returns a number of listeners added to the given event."}]},"typeParameters":[{"name":"EventName","variant":"typeParam","kind":131072,"type":{"type":"literal","value":"configure"}}],"parameters":[{"name":"eventName","variant":"param","kind":32768,"type":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true}}],"type":{"type":"intrinsic","name":"number"},"inheritedFrom":{"type":"reference","name":"NativeModule.listenerCount","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"NativeModule.listenerCount","package":"expo-modules-core"}},{"name":"logEvent","variant":"declaration","kind":2048,"signatures":[{"name":"logEvent","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Records a log event against the current main session. The event is\npersisted locally and dispatched on the next "},{"kind":"code","text":"`dispatchEvents()`"},{"kind":"text","text":" flush.\n\nSeverity defaults to "},{"kind":"code","text":"`\"info\"`"},{"kind":"text","text":" when not provided."}]},"parameters":[{"name":"name","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"Event name."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"options","variant":"param","kind":32768,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Optional body, attributes, and severity overrides."}]},"type":{"type":"reference","name":"LogEventOptions","package":"expo-app-metrics"}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"markFirstRender","variant":"declaration","kind":2048,"signatures":[{"name":"markFirstRender","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Marks the first render of the app. Used to compute the "},{"kind":"code","text":"`cold_ttr`"},{"kind":"text","text":" and\n"},{"kind":"code","text":"`warm_ttr`"},{"kind":"text","text":" metrics."}]},"type":{"type":"intrinsic","name":"void"}}]},{"name":"markInteractive","variant":"declaration","kind":2048,"signatures":[{"name":"markInteractive","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Marks the moment the app becomes interactive. Used to compute the "},{"kind":"code","text":"`tti`"},{"kind":"text","text":"\nmetric. Custom "},{"kind":"code","text":"`routeName`"},{"kind":"text","text":" and "},{"kind":"code","text":"`params`"},{"kind":"text","text":" can be attached via "},{"kind":"code","text":"`attributes`"},{"kind":"text","text":".\n\n> Note: When the "},{"kind":"code","text":"`expo-router`"},{"kind":"text","text":" or "},{"kind":"code","text":"`@react-navigation/native`"},{"kind":"text","text":" integration\n> is active, prefer "},{"kind":"code","text":"`useObserve().markInteractive(...)`"},{"kind":"text","text":" — the hook fills\n> in "},{"kind":"code","text":"`routeName`"},{"kind":"text","text":" from the current route, while this raw call does not."}]},"parameters":[{"name":"attributes","variant":"param","kind":32768,"flags":{"isOptional":true},"type":{"type":"reference","name":"MetricAttributes","package":"expo-app-metrics"}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"registerIntegration","variant":"declaration","kind":2048,"signatures":[{"name":"registerIntegration","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Invokes a callback once when the named integration configuration becomes available."}],"blockTags":[{"tag":"@example","content":[{"kind":"code","text":"```ts\nObserve.registerIntegration('expo-router', config => {\n console.log(config);\n});\n```"}]}]},"typeParameters":[{"name":"K","variant":"typeParam","kind":131072,"type":{"type":"typeOperator","operator":"keyof","target":{"type":"reference","name":"ObserveIntegrationsConfig","package":"expo-observe"}}}],"parameters":[{"name":"name","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"Integration name."}]},"type":{"type":"reference","name":"K","package":"expo-observe","refersToTypeParameter":true}},{"name":"callback","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"Function called with the integration configuration."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"parameters":[{"name":"config","variant":"param","kind":32768,"type":{"type":"indexedAccess","indexType":{"type":"reference","name":"K","package":"expo-observe","refersToTypeParameter":true},"objectType":{"type":"reference","name":"ObserveIntegrationsConfig","package":"expo-observe"}}}],"type":{"type":"intrinsic","name":"void"}}]}}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"removeAllListeners","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"removeAllListeners","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Removes all listeners for the given event name."}]},"parameters":[{"name":"eventName","variant":"param","kind":32768,"type":{"type":"literal","value":"configure"}}],"type":{"type":"intrinsic","name":"void"},"inheritedFrom":{"type":"reference","name":"NativeModule.removeAllListeners","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"NativeModule.removeAllListeners","package":"expo-modules-core"}},{"name":"removeListener","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"removeListener","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Removes a listener for the given event name."}]},"typeParameters":[{"name":"EventName","variant":"typeParam","kind":131072,"type":{"type":"literal","value":"configure"}}],"parameters":[{"name":"eventName","variant":"param","kind":32768,"type":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true}},{"name":"listener","variant":"param","kind":32768,"type":{"type":"indexedAccess","indexType":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true},"objectType":{"type":"reference","name":"ObserveModuleEvents","package":"expo-observe"}}}],"type":{"type":"intrinsic","name":"void"},"inheritedFrom":{"type":"reference","name":"NativeModule.removeListener","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"NativeModule.removeListener","package":"expo-modules-core"}},{"name":"reportError","variant":"declaration","kind":2048,"signatures":[{"name":"reportError","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Reports an error your code caught and handled, recorded as a non-fatal "},{"kind":"code","text":"`exception`"},{"kind":"text","text":" event. Use it\nto keep visibility into failures you recover from, which never reach the automatic global handler\nor an error boundary.\n\nThe thrown value is normalized: an "},{"kind":"code","text":"`Error`"},{"kind":"text","text":"'s "},{"kind":"code","text":"`name`"},{"kind":"text","text":", "},{"kind":"code","text":"`message`"},{"kind":"text","text":", and "},{"kind":"code","text":"`stack`"},{"kind":"text","text":" are captured; any\nother value (a string, a plain object) is stringified as the message."}],"blockTags":[{"tag":"@example","content":[{"kind":"code","text":"```ts\ntry {\n await syncCart();\n} catch (error) {\n Observe.reportError(error);\n}\n```"}]}]},"parameters":[{"name":"error","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"The caught value. An "},{"kind":"code","text":"`Error`"},{"kind":"text","text":" is preferred, but any thrown value is accepted."}]},"type":{"type":"intrinsic","name":"unknown"}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"setBundleDefaults","variant":"declaration","kind":2048,"signatures":[{"name":"setBundleDefaults","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Pushes JS-bundle-derived facts ("},{"kind":"code","text":"`process.env.NODE_ENV`"},{"kind":"text","text":", "},{"kind":"code","text":"`__DEV__`"},{"kind":"text","text":") into native\nstorage. Called automatically once when the package is first imported; should\nnot be called by host apps directly."}],"modifierTags":["@internal"]},"parameters":[{"name":"defaults","variant":"param","kind":32768,"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"children":[{"name":"environment","variant":"declaration","kind":1024,"type":{"type":"intrinsic","name":"string"}},{"name":"isJsDev","variant":"declaration","kind":1024,"type":{"type":"intrinsic","name":"boolean"}}]}}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"setGlobalAttributes","variant":"declaration","kind":2048,"signatures":[{"name":"setGlobalAttributes","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Sets attributes merged into every subsequent metric and log event.\nPer-record keys win on collision. Pass "},{"kind":"code","text":"`null`"},{"kind":"text","text":", "},{"kind":"code","text":"`undefined`"},{"kind":"text","text":", or an empty\nobject to clear."}],"blockTags":[{"tag":"@example","content":[{"kind":"code","text":"```ts\nObserve.setGlobalAttributes({\n subscription_tier: 'pro',\n experiment_variant: 'B',\n});\n```"}]}]},"parameters":[{"name":"attributes","variant":"param","kind":32768,"flags":{"isOptional":true},"type":{"type":"union","types":[{"type":"reference","name":"ObserveAttributes","package":"expo-observe"},{"type":"literal","value":null}]}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"startObserving","variant":"declaration","kind":2048,"flags":{"isOptional":true,"isInherited":true},"signatures":[{"name":"startObserving","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Function that is automatically invoked when the first listener for an event with the given name is added.\nOverride it in a subclass to perform some additional setup once the event started being observed."}]},"typeParameters":[{"name":"EventName","variant":"typeParam","kind":131072,"type":{"type":"literal","value":"configure"}}],"parameters":[{"name":"eventName","variant":"param","kind":32768,"type":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true}}],"type":{"type":"intrinsic","name":"void"},"inheritedFrom":{"type":"reference","name":"NativeModule.startObserving","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"NativeModule.startObserving","package":"expo-modules-core"}},{"name":"stopObserving","variant":"declaration","kind":2048,"flags":{"isOptional":true,"isInherited":true},"signatures":[{"name":"stopObserving","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Function that is automatically invoked when the last listener for an event with the given name is removed.\nOverride it in a subclass to perform some additional cleanup once the event is no longer observed."}]},"typeParameters":[{"name":"EventName","variant":"typeParam","kind":131072,"type":{"type":"literal","value":"configure"}}],"parameters":[{"name":"eventName","variant":"param","kind":32768,"type":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true}}],"type":{"type":"intrinsic","name":"void"},"inheritedFrom":{"type":"reference","name":"NativeModule.stopObserving","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"NativeModule.stopObserving","package":"expo-modules-core"}}],"extendedTypes":[{"type":"reference","target":{"packageName":"expo-modules-core","packagePath":"src/NativeModule.ts","qualifiedName":"NativeModule"},"typeArguments":[{"type":"reference","name":"ObserveModuleEvents","package":"expo-observe"}],"name":"NativeModule","package":"expo-modules-core"}]},{"name":"AppMetricsErrorBoundaryFallbackProps","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Arguments passed to a "},{"kind":"code","text":"`fallback`"},{"kind":"text","text":" render function."}]},"children":[{"name":"error","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"The value the subtree threw. Usually an "},{"kind":"code","text":"`Error`"},{"kind":"text","text":", but any value can be thrown."}]},"type":{"type":"intrinsic","name":"unknown"}},{"name":"resetError","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Clears the caught error and re-renders the children. Use it to offer a \"try again\" action;\nthe children re-mount, so they run from a clean state."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"intrinsic","name":"void"}}]}}}]},{"name":"AppMetricsErrorBoundaryProps","variant":"declaration","kind":2097152,"children":[{"name":"children","variant":"declaration","kind":1024,"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"React.ReactNode","package":"@types/react"}},{"name":"fallback","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Rendered in place of the subtree after an error is caught. Provide one of:\n\n- a React element to render as-is,\n- a function receiving the "},{"kind":"code","text":"`error`"},{"kind":"text","text":" and a "},{"kind":"code","text":"`resetError`"},{"kind":"text","text":" callback (to show details and offer retry),\n- "},{"kind":"code","text":"`null`"},{"kind":"text","text":" to render nothing.\n\nA boundary can't re-throw to reproduce React Native's default crash, so it always renders one of\nthe above; there's no capture-only mode. Errors no boundary catches are still recorded by the\nglobal "},{"kind":"code","text":"`ErrorUtils`"},{"kind":"text","text":" handler."}]},"type":{"type":"union","types":[{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactElement"},"name":"React.ReactElement","package":"@types/react"},{"type":"literal","value":null},{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"parameters":[{"name":"props","variant":"param","kind":32768,"type":{"type":"reference","name":"AppMetricsErrorBoundaryFallbackProps","package":"expo-app-metrics"}}],"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"React.ReactNode","package":"@types/react"}}]}}]}}]},{"name":"AppMetricsRootProps","variant":"declaration","kind":2097152,"children":[{"name":"children","variant":"declaration","kind":1024,"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"React.ReactNode","package":"@types/react"}},{"name":"errorBoundaryFallback","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"When set, the app is wrapped in an "},{"kind":"code","text":"`AppMetricsErrorBoundary`"},{"kind":"text","text":" with this "},{"kind":"code","text":"`fallback`"},{"kind":"text","text":", capturing\nReact render-phase errors at the root. Omit it and no boundary is mounted, so render errors keep\nReact Native's default behavior (they're still recorded by the global "},{"kind":"code","text":"`ErrorUtils`"},{"kind":"text","text":" handler, just\nwithout the component stack). Pass "},{"kind":"code","text":"`null`"},{"kind":"text","text":" to capture but render nothing.\n\nTo place a boundary deeper in the tree, use "},{"kind":"code","text":"`AppMetricsErrorBoundary`"},{"kind":"text","text":" directly."}]},"type":{"type":"indexedAccess","indexType":{"type":"literal","value":"fallback"},"objectType":{"type":"reference","name":"AppMetricsErrorBoundaryProps","package":"expo-app-metrics"}}}]},{"name":"LogAttributeValue","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Value types accepted in a log event's "},{"kind":"code","text":"`attributes`"},{"kind":"text","text":" map. Strings, numbers,\nand booleans are stored as typed primitives; arrays and nested maps preserve\ntheir structure. Other JS values (functions, "},{"kind":"code","text":"`Date`"},{"kind":"text","text":", "},{"kind":"code","text":"`undefined`"},{"kind":"text","text":", etc.) are\nnot supported and may be dropped by downstream consumers."}]},"type":{"type":"union","types":[{"type":"intrinsic","name":"string"},{"type":"intrinsic","name":"number"},{"type":"intrinsic","name":"boolean"},{"type":"array","elementType":{"type":"reference","name":"LogAttributeValue","package":"expo-app-metrics"}},{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"indexSignatures":[{"name":"__index","variant":"signature","kind":8192,"parameters":[{"name":"key","variant":"param","kind":32768,"type":{"type":"intrinsic","name":"string"}}],"type":{"type":"reference","name":"LogAttributeValue","package":"expo-app-metrics"}}]}}]}},{"name":"LogEventOptions","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Optional configuration accepted by "},{"kind":"code","text":"`logEvent`"},{"kind":"text","text":". The event name is passed as\nthe first positional argument since it's required and the only field most\ncallers set."}]},"children":[{"name":"attributes","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Custom attributes attached to the event. Each entry is preserved with its\noriginal value type — see "},{"kind":"code","text":"`LogAttributeValue`"},{"kind":"text","text":" for the supported shapes."}]},"type":{"type":"union","types":[{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Record"},"typeArguments":[{"type":"intrinsic","name":"string"},{"type":"reference","name":"LogAttributeValue","package":"expo-app-metrics"}],"name":"Record","package":"typescript"},{"type":"literal","value":null}]}},{"name":"body","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Optional free-form message describing the event."}]},"type":{"type":"union","types":[{"type":"intrinsic","name":"string"},{"type":"literal","value":null}]}},{"name":"displayName","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Optional human-friendly label for the event. Unlike "},{"kind":"code","text":"`name`"},{"kind":"text","text":" (a stable machine\nidentifier), this is meant for display in dashboards and is not constrained\nto a naming scheme."}]},"type":{"type":"union","types":[{"type":"intrinsic","name":"string"},{"type":"literal","value":null}]}},{"name":"severity","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Severity of the event."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"\"info\""}]}]},"type":{"type":"union","types":[{"type":"reference","name":"LogSeverity","package":"expo-app-metrics"},{"type":"literal","value":null}]}}]},{"name":"LogSeverity","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Severity of a log event, ordered from least to most severe:\n\n- "},{"kind":"code","text":"`\"trace\"`"},{"kind":"text","text":" — Fine-grained tracing, typically only useful while reproducing\n a specific issue.\n- "},{"kind":"code","text":"`\"debug\"`"},{"kind":"text","text":" — Diagnostic detail useful during development; usually filtered\n out in production.\n- "},{"kind":"code","text":"`\"info\"`"},{"kind":"text","text":" — Routine, expected events that record normal app behavior.\n- "},{"kind":"code","text":"`\"warn\"`"},{"kind":"text","text":" — Unexpected but recoverable conditions worth investigating.\n- "},{"kind":"code","text":"`\"error\"`"},{"kind":"text","text":" — An operation failed; the app continues running but is in a\n degraded state.\n- "},{"kind":"code","text":"`\"fatal\"`"},{"kind":"text","text":" — A severe failure, often immediately followed by app termination."}]},"type":{"type":"union","types":[{"type":"literal","value":"trace"},{"type":"literal","value":"debug"},{"type":"literal","value":"info"},{"type":"literal","value":"warn"},{"type":"literal","value":"error"},{"type":"literal","value":"fatal"}]}},{"name":"MetricAttributes","variant":"declaration","kind":2097152,"children":[{"name":"params","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Custom parameters to attach to the metric."}]},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Record"},"typeArguments":[{"type":"intrinsic","name":"string"},{"type":"intrinsic","name":"unknown"}],"name":"Record","package":"typescript"}},{"name":"routeName","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Name of the route associated with the metric. Some metrics populate this\nwith a sensible default when omitted — for example, the TTI metric falls\nback to the initial route name detected from the router."}]},"type":{"type":"union","types":[{"type":"intrinsic","name":"string"},{"type":"literal","value":null}]}}]},{"name":"ObserveAttribute","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Value types accepted as attribute values in "},{"kind":"code","text":"`setGlobalAttributes`"},{"kind":"text","text":" and the\nother Observe APIs. Strings, numbers, and booleans are stored as typed\nprimitives; arrays and nested maps preserve their structure."}]},"type":{"type":"reference","name":"LogAttributeValue","package":"expo-app-metrics"}},{"name":"ObserveAttributes","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"A map of attribute key to value, as accepted by "},{"kind":"code","text":"`setGlobalAttributes`"},{"kind":"text","text":" and\nother Observe APIs that take a free-form attributes payload."}]},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Record"},"typeArguments":[{"type":"intrinsic","name":"string"},{"type":"reference","name":"ObserveAttribute","package":"expo-observe"}],"name":"Record","package":"typescript"}},{"name":"ObserveConfig","variant":"declaration","kind":2097152,"children":[{"name":"dispatchInDebug","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Whether to dispatch metrics that were collected in a debug build of the host app.\n\nWhen "},{"kind":"code","text":"`false`"},{"kind":"text","text":", metrics produced by debug builds are marked as sent without being dispatched.\nWhen "},{"kind":"code","text":"`true`"},{"kind":"text","text":", debug-build metrics are dispatched alongside release-build metrics.\n\nHas no effect on release builds.\n\nIf "},{"kind":"code","text":"`dispatchingEnabled`"},{"kind":"text","text":" is "},{"kind":"code","text":"`false`"},{"kind":"text","text":" or this device is out-of-sample for "},{"kind":"code","text":"`sampleRate`"},{"kind":"text","text":", nothing\nis dispatched regardless of "},{"kind":"code","text":"`dispatchInDebug`"},{"kind":"text","text":"."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"false"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"dispatchingEnabled","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Whether to dispatch observability events to the server.\n\nWhen "},{"kind":"code","text":"`false`"},{"kind":"text","text":", any pending metrics are marked as sent without being dispatched\nand no further metrics are dispatched until this is set back to "},{"kind":"code","text":"`true`"},{"kind":"text","text":"."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"true"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"environment","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The environment for observability events"}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"process.env.NODE_ENV"}]}]},"type":{"type":"intrinsic","name":"string"}},{"name":"errorHandlingEnabled","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Whether to record unhandled JavaScript errors as "},{"kind":"code","text":"`exception`"},{"kind":"text","text":" log events.\n\nWhen "},{"kind":"code","text":"`false`"},{"kind":"text","text":", unhandled errors are no longer recorded. React Native's own handling is\nunaffected either way: the red box in development and fatal termination in production still\nhappen. Errors you report yourself with "},{"kind":"code","text":"`reportError`"},{"kind":"text","text":", and render-phase errors captured by\n"},{"kind":"code","text":"`ObserveErrorBoundary`"},{"kind":"text","text":", are also unaffected.\n\n> Note: The handler is installed when the package is first imported, which is earlier than any\n> "},{"kind":"code","text":"`configure`"},{"kind":"text","text":" call. An error thrown before "},{"kind":"code","text":"`configure`"},{"kind":"text","text":" runs is therefore still recorded."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"true"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"integrations","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Opt in to per-integration behavior. See the [Expo Router](/eas/observe/integrations/expo-router/)\nand [React Navigation](/eas/observe/integrations/react-navigation/) integrations, or\n[integrate your own package](/eas/observe/integrations/third-party/)."}]},"type":{"type":"reference","name":"ObserveIntegrationsConfig","package":"expo-observe"}},{"name":"sampleRate","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Fraction of installations that should dispatch metrics, in "},{"kind":"code","text":"`[0, 1]`"},{"kind":"text","text":". Values outside that range\nare clamped.\n\nThe decision is **deterministic per installation** — a device is either permanently in-sample\nor out-of-sample for a given rate, so the choice is stable across app launches.\n\nInteraction with "},{"kind":"code","text":"`dispatchingEnabled`"},{"kind":"text","text":":\n- If "},{"kind":"code","text":"`dispatchingEnabled`"},{"kind":"text","text":" is "},{"kind":"code","text":"`false`"},{"kind":"text","text":", metrics are never dispatched\n- If "},{"kind":"code","text":"`dispatchingEnabled`"},{"kind":"text","text":" is "},{"kind":"code","text":"`true`"},{"kind":"text","text":" (or unset), metrics are dispatched only when this device\n is in-sample.\n\n> Note: Devices that end up out-of-sample drop pending metrics rather than accumulating them."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"undefined - metrics from all devices are sent"}]}]},"type":{"type":"intrinsic","name":"number"}}]},{"name":"ObserveInteractiveMarkerProps","variant":"declaration","kind":2097152,"children":[{"name":"params","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Custom parameters attached to the TTI metric, forwarded to "},{"kind":"code","text":"`markInteractive`"},{"kind":"text","text":".\nValues can be strings, numbers, booleans, or other JSON-serializable values."}]},"type":{"type":"indexedAccess","indexType":{"type":"literal","value":"params"},"objectType":{"type":"reference","name":"MetricAttributes","package":"expo-app-metrics"}}}]},{"name":"ObserveModuleEvents","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Events emitted by the native "},{"kind":"code","text":"`ExpoObserve`"},{"kind":"text","text":" module."}]},"children":[{"name":"configure","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Fired on every "},{"kind":"code","text":"`configure(...)`"},{"kind":"text","text":" call, carrying the resolved "},{"kind":"code","text":"`integrations`"},{"kind":"text","text":"\nconfig"}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"parameters":[{"name":"payload","variant":"param","kind":32768,"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"children":[{"name":"integrations","variant":"declaration","kind":1024,"type":{"type":"reference","name":"ObserveIntegrationsConfig","package":"expo-observe"}}]}}}],"type":{"type":"intrinsic","name":"void"}}]}}}]},{"name":"ObserveNavigationIntegrationConfig","variant":"declaration","kind":2097152,"children":[{"name":"filteredParams","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Route or query parameter keys to remove from exported navigation metric\n"},{"kind":"code","text":"`routeParams`"},{"kind":"text","text":". When any configured parameter is removed from a metric,\nthe exported resolved URL/path is replaced with "},{"kind":"code","text":"`urlHidden: true`"},{"kind":"text","text":".\nDoes not affect "},{"kind":"code","text":"`routeName`"},{"kind":"text","text":"."}]},"type":{"type":"array","elementType":{"type":"intrinsic","name":"string"}}}]},{"name":"AppMetrics","variant":"declaration","kind":32,"comment":{"summary":[],"blockTags":[{"tag":"@deprecated","content":[{"kind":"text","text":"Use "},{"kind":"code","text":"`Observe`"},{"kind":"text","text":" instead. "},{"kind":"code","text":"`AppMetrics`"},{"kind":"text","text":" is the legacy name of this API from SDK 55."}]}]},"type":{"type":"reference","name":"ExpoAppMetricsModuleType","package":"expo-app-metrics"}},{"name":"Observe","variant":"declaration","kind":32,"flags":{"isConst":true},"type":{"type":"reference","name":"ObserveModule","package":"expo-observe"},"defaultValue":"..."},{"name":"ObserveInteractiveMarker","variant":"declaration","kind":64,"signatures":[{"name":"ObserveInteractiveMarker","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Declarative wrapper around "},{"kind":"code","text":"`useObserve().markInteractive(...)`"},{"kind":"text","text":". Renders nothing\nand calls "},{"kind":"code","text":"`markInteractive`"},{"kind":"text","text":" once when it first mounts, marking the moment the\nscreen becomes interactive (used to compute the "},{"kind":"code","text":"`tti`"},{"kind":"text","text":" metric). Render it once\nthe screen is ready for user interaction — for example, after its initial data\nhas loaded.\n\nBecause "},{"kind":"code","text":"`markInteractive`"},{"kind":"text","text":" is only sent on mount, the marker is fire-once: changing\n"},{"kind":"code","text":"`params`"},{"kind":"text","text":" after the first render has no effect and warns in development. If you need\nto attach attributes that are only known later, call "},{"kind":"code","text":"`useObserve().markInteractive(...)`"},{"kind":"text","text":"\nimperatively instead."}],"blockTags":[{"tag":"@example","content":[{"kind":"code","text":"```tsx\nimport { ObserveInteractiveMarker } from 'expo-observe';\n\nfunction Feed({ items }) {\n if (!items) return ;\n return (\n <>\n \n \n \n );\n}\n```"}]}]},"parameters":[{"name":"props","variant":"param","kind":32768,"type":{"type":"reference","name":"ObserveInteractiveMarkerProps","package":"expo-observe"}}],"type":{"type":"literal","value":null}}]},{"name":"ObserveRoot","variant":"declaration","kind":64,"children":[{"name":"wrap","variant":"declaration","kind":2048,"signatures":[{"name":"wrap","variant":"signature","kind":4096,"typeParameters":[{"name":"P","variant":"typeParam","kind":131072,"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Record"},"typeArguments":[{"type":"intrinsic","name":"string"},{"type":"intrinsic","name":"unknown"}],"name":"Record","package":"typescript"}}],"parameters":[{"name":"Component","variant":"param","kind":32768,"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ComponentType"},"typeArguments":[{"type":"reference","name":"P","package":"expo-observe","refersToTypeParameter":true}],"name":"ComponentType","package":"@types/react","qualifiedName":"React.ComponentType"}}],"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ComponentType"},"typeArguments":[{"type":"reference","name":"P","package":"expo-observe","refersToTypeParameter":true}],"name":"ComponentType","package":"@types/react","qualifiedName":"React.ComponentType"}}]}],"signatures":[{"name":"ObserveRoot","variant":"signature","kind":4096,"parameters":[{"name":"__namedParameters","variant":"param","kind":32768,"type":{"type":"intersection","types":[{"type":"reference","name":"AppMetricsRootProps","package":"expo-app-metrics"},{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"children":[{"name":"children","variant":"declaration","kind":1024,"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"ReactNode","package":"@types/react","qualifiedName":"React.ReactNode"}}]}}]}}],"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"jsx-runtime.d.ts","qualifiedName":"JSX.Element"},"name":"Element","package":"@types/react","qualifiedName":"JSX.Element"}}]},{"name":"useObserve","variant":"declaration","kind":64,"signatures":[{"name":"useObserve","variant":"signature","kind":4096,"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"children":[{"name":"markInteractive","variant":"declaration","kind":1024,"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"parameters":[{"name":"attributes","variant":"param","kind":32768,"flags":{"isOptional":true},"type":{"type":"reference","name":"MetricAttributes","package":"expo-app-metrics"}}],"type":{"type":"intrinsic","name":"void"}}]}},"defaultValue":"..."}]}}}]},{"name":"default","variant":"reference","kind":4194304},{"name":"ObserveErrorBoundaryFallbackProps","variant":"reference","kind":4194304},{"name":"ObserveErrorBoundaryProps","variant":"reference","kind":4194304}],"packageName":"expo-observe"} \ No newline at end of file diff --git a/docs/ui/components/Dropdown/Item.tsx b/docs/ui/components/Dropdown/Item.tsx index 71146f97fc1872..d520d13bf56066 100644 --- a/docs/ui/components/Dropdown/Item.tsx +++ b/docs/ui/components/Dropdown/Item.tsx @@ -33,7 +33,7 @@ export function Item({ {trigger} -
- - -
- - {children} - -
+ + +
+ + {children} + ); diff --git a/docs/ui/components/Select.tsx b/docs/ui/components/Select.tsx index 93afe98a384f61..a1dd11760707fe 100644 --- a/docs/ui/components/Select.tsx +++ b/docs/ui/components/Select.tsx @@ -63,7 +63,7 @@ export function Select({ /> } className={mergeClasses( - 'min-h-9 transform-none justify-between truncate px-3', + 'min-h-9 justify-between truncate px-3 active:scale-100', !value && 'text-quaternary', size === 'lg' && 'min-h-13', className @@ -81,13 +81,14 @@ export function Select({ - + 'relative z-605 overflow-hidden rounded-xl border border-default bg-overlay shadow-md', + 'max-h-(--radix-select-content-available-height) min-w-(--radix-select-trigger-width)', + 'max-w-[87.5vw] max-md:max-w-[unset]' + )}> + @@ -141,7 +142,7 @@ export function Select({ ))} - + diff --git a/packages/expo-app-metrics/CHANGELOG.md b/packages/expo-app-metrics/CHANGELOG.md index 409d11fb8ed4ea..026b890b306f6a 100644 --- a/packages/expo-app-metrics/CHANGELOG.md +++ b/packages/expo-app-metrics/CHANGELOG.md @@ -15,6 +15,7 @@ ### 🐛 Bug fixes +- [Android] Fixed network request observers emitting duplicate events or continuing after their listeners were removed. ([#49609](https://github.com/expo/expo/pull/49609) by [@behenate](https://github.com/behenate)) - [iOS] Preserve millisecond precision in log event timestamps. ([#49141](https://github.com/expo/expo/pull/49141) by [@Ubax](https://github.com/Ubax)) - [android] Fix `UnsupportedOperationException` and `NoSuchMethodError` on Android 7.x ([#48577](https://github.com/expo/expo/pull/48577) by [@Ubax](https://github.com/Ubax)) - [iOS] Retry the OTA `AppInfo` patch on updates state changes, so a launch where the module registry is created before `expo-updates` has assigned its startup procedure no longer keeps the embedded build's update attribution for the whole session. ([#48899](https://github.com/expo/expo/pull/48899) by [@spsaucier](https://github.com/spsaucier)) diff --git a/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/networkrequests/NetworkRequestInterceptor.kt b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/networkrequests/NetworkRequestInterceptor.kt index 2d27243af53b60..2e39fc8d7d00f9 100644 --- a/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/networkrequests/NetworkRequestInterceptor.kt +++ b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/networkrequests/NetworkRequestInterceptor.kt @@ -23,6 +23,7 @@ import java.net.Proxy import java.util.Date import java.util.UUID import java.util.WeakHashMap +import java.util.concurrent.atomic.AtomicBoolean /** * Sentinel header recognised by `NetworkRequestInterceptor` to skip observation. expo-observe's @@ -509,8 +510,8 @@ private class BodyCloseSignal( private val delegate: ResponseBody, private val onComplete: () -> Unit ) : ResponseBody() { - @Volatile - private var completed: Boolean = false + // EOF and close can race when callers consume and dispose a response on different threads. + private val completed = AtomicBoolean(false) // OkHttp's contract returns the same `BufferedSource` on repeated `source()` calls, so we // cache ours too — wrapping twice would fire the completion callback twice. @@ -538,10 +539,9 @@ private class BodyCloseSignal( override fun source(): BufferedSource = signalingSource private fun fireCompleteOnce() { - if (completed) { + if (!completed.compareAndSet(false, true)) { return } - completed = true onComplete() } } diff --git a/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/networkrequests/NetworkRequestMonitor.kt b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/networkrequests/NetworkRequestMonitor.kt index 406bb8578f6d54..473181548cdd70 100644 --- a/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/networkrequests/NetworkRequestMonitor.kt +++ b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/networkrequests/NetworkRequestMonitor.kt @@ -64,12 +64,21 @@ class NetworkRequestMonitor internal constructor() { return NetworkRequestSummary.from(inWindow) } - /** Adds a delegate. Held weakly - drop the reference to unsubscribe. */ + /** Adds a delegate once. Held weakly - drop the reference to unsubscribe. */ fun addDelegate(delegate: NetworkRequestObserverDelegate) = synchronized(lock) { - delegates.removeAll { it.get() == null } + delegates.removeAll { + val strongRef = it.get() + strongRef === delegate || strongRef == null + } delegates.add(WeakReference(delegate)) } + internal val delegateCount: Int + get() = synchronized(lock) { + delegates.removeAll { it.get() == null } + delegates.size + } + fun removeDelegate(delegate: NetworkRequestObserverDelegate) = synchronized(lock) { delegates.removeAll { val strongRef = it.get() diff --git a/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/networkrequests/NetworkRequestObserver.kt b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/networkrequests/NetworkRequestObserver.kt index d12d2c49ed9fe3..aaab5c392e7182 100644 --- a/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/networkrequests/NetworkRequestObserver.kt +++ b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/networkrequests/NetworkRequestObserver.kt @@ -5,6 +5,7 @@ package expo.modules.appmetrics.networkrequests import expo.modules.appmetrics.utils.TimeUtils import expo.modules.kotlin.AppContext import expo.modules.kotlin.sharedobjects.SharedObject +import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicReference /** Event names emitted by `NetworkRequestObserver`, matching the keys in the JS `NetworkRequestObserverEvents` type. */ @@ -13,28 +14,59 @@ internal const val REQUEST_COMPLETED_EVENT = "requestCompleted" /** * JS-facing `SharedObject` that bridges per-instance JS subscriptions to the singleton - * `NetworkRequestMonitor`. Each JS `new NetworkRequestObserver()` allocates one of these and - * registers it as a delegate; the native instance is released when JS drops the reference, at - * which point `sharedObjectDidRelease` removes the delegate registration. + * `NetworkRequestMonitor`. Each JS `new NetworkRequestObserver()` allocates one of these. It is + * registered as a delegate while it has event listeners and is unregistered after its last + * listener is removed or the shared object is released. * * The class only forwards events — it doesn't store request history. Use * `NetworkRequestMonitor.shared.recent` for that. */ -class NetworkRequestObserver(appContext: AppContext, filter: NetworkRequestFilter? = null) : +class NetworkRequestObserver private constructor( + appContext: AppContext, + filter: NetworkRequestFilter?, + private val monitor: NetworkRequestMonitor // Set for testing, otherwise default singleton +) : SharedObject(appContext), NetworkRequestObserverDelegate { + constructor(appContext: AppContext, filter: NetworkRequestFilter? = null) : + this(appContext, filter, NetworkRequestMonitor.shared) + // The active filter, or null to observe every request. An `AtomicReference` so the read from the // monitor's fan-out (`shouldObserveRequest`) and the swap from `setFilter` are atomic: a // `setFilter` call never leaves a request observed under a half-applied filter. private val filter = AtomicReference(filter) + private val observing = AtomicBoolean(false) + private val listenerLock = Any() + private val listenedEvents = mutableSetOf() + + override fun onStartListeningToEvent(eventName: String) { + if (eventName != REQUEST_STARTED_EVENT && eventName != REQUEST_COMPLETED_EVENT) { + return + } + synchronized(listenerLock) { + listenedEvents.add(eventName) + if (observing.compareAndSet(false, true)) { + monitor.addDelegate(this) + } + } + } - init { - NetworkRequestMonitor.shared.addDelegate(this) + override fun onStopListeningToEvent(eventName: String) { + synchronized(listenerLock) { + listenedEvents.remove(eventName) + if (listenedEvents.isEmpty() && observing.compareAndSet(true, false)) { + monitor.removeDelegate(this) + } + } } override fun sharedObjectDidRelease() { - NetworkRequestMonitor.shared.removeDelegate(this) + synchronized(listenerLock) { + listenedEvents.clear() + observing.set(false) + monitor.removeDelegate(this) + } super.sharedObjectDidRelease() } @@ -46,18 +78,32 @@ class NetworkRequestObserver(appContext: AppContext, filter: NetworkRequestFilte } override fun shouldObserveRequest(url: String, method: String): Boolean { - return filter.get()?.matches(url, method) ?: true + return observing.get() && (filter.get()?.matches(url, method) ?: true) } override fun onNetworkRequestStarted(request: NetworkRequestStarted) { - emit(REQUEST_STARTED_EVENT, startedPayload(request)) + if (isListeningTo(REQUEST_STARTED_EVENT)) { + emit(REQUEST_STARTED_EVENT, startedPayload(request)) + } } override fun onNetworkRequestCompleted(request: NetworkRequest) { - emit(REQUEST_COMPLETED_EVENT, completedPayload(request)) + if (isListeningTo(REQUEST_COMPLETED_EVENT)) { + emit(REQUEST_COMPLETED_EVENT, completedPayload(request)) + } + } + + private fun isListeningTo(eventName: String): Boolean = synchronized(listenerLock) { + observing.get() && listenedEvents.contains(eventName) } companion object { + internal fun forTesting( + appContext: AppContext, + monitor: NetworkRequestMonitor, + filter: NetworkRequestFilter? = null + ) = NetworkRequestObserver(appContext, filter, monitor) + /** * Internal so tests can assert the payload shape without going through `emit`, which needs a * live JS runtime. The keys here are part of the public JS contract — additions are safe but diff --git a/packages/expo-app-metrics/android/src/test/java/expo/modules/appmetrics/networkrequests/NetworkRequestMonitorTest.kt b/packages/expo-app-metrics/android/src/test/java/expo/modules/appmetrics/networkrequests/NetworkRequestMonitorTest.kt index b8e625122bcf4e..6adbe5a73403b4 100644 --- a/packages/expo-app-metrics/android/src/test/java/expo/modules/appmetrics/networkrequests/NetworkRequestMonitorTest.kt +++ b/packages/expo-app-metrics/android/src/test/java/expo/modules/appmetrics/networkrequests/NetworkRequestMonitorTest.kt @@ -69,6 +69,19 @@ class NetworkRequestMonitorTest { assertEquals(0, collector.completed.size) } + @Test + fun `adding the same delegate again does not duplicate fan-out`() { + val monitor = NetworkRequestMonitor() + val collector = CollectingDelegate() + monitor.addDelegate(collector) + monitor.addDelegate(collector) + + monitor.record(makeRequest()) + + assertEquals(1, monitor.delegateCount) + assertEquals(1, collector.completed.size) + } + @Test fun `does not fan out events the delegate filters out`() { val monitor = NetworkRequestMonitor() diff --git a/packages/expo-app-metrics/android/src/test/java/expo/modules/appmetrics/networkrequests/NetworkRequestObserverTest.kt b/packages/expo-app-metrics/android/src/test/java/expo/modules/appmetrics/networkrequests/NetworkRequestObserverTest.kt index f872fbad511f76..e29d8132203d4e 100644 --- a/packages/expo-app-metrics/android/src/test/java/expo/modules/appmetrics/networkrequests/NetworkRequestObserverTest.kt +++ b/packages/expo-app-metrics/android/src/test/java/expo/modules/appmetrics/networkrequests/NetworkRequestObserverTest.kt @@ -1,6 +1,9 @@ package expo.modules.appmetrics.networkrequests +import expo.modules.kotlin.AppContext +import io.mockk.mockk import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Assert.assertTrue @@ -14,6 +17,38 @@ import java.util.UUID * see. These tests pin that shape down so renames require a deliberate change. */ class NetworkRequestObserverTest { + @Test + fun `registers only while it has event listeners`() { + val appContext = mockk(relaxed = true) + val monitor = NetworkRequestMonitor() + val observer = NetworkRequestObserver.forTesting(appContext, monitor) + + assertEquals(0, monitor.delegateCount) + + observer.onStartListeningToEvent(REQUEST_STARTED_EVENT) + observer.onStartListeningToEvent(REQUEST_COMPLETED_EVENT) + assertEquals(1, monitor.delegateCount) + + observer.onStopListeningToEvent(REQUEST_STARTED_EVENT) + assertEquals(1, monitor.delegateCount) + + observer.onStopListeningToEvent(REQUEST_COMPLETED_EVENT) + assertEquals(0, monitor.delegateCount) + } + + @Test + fun `release unregisters the observer`() { + val appContext = mockk(relaxed = true) + val monitor = NetworkRequestMonitor() + val observer = NetworkRequestObserver.forTesting(appContext, monitor) + observer.onStartListeningToEvent(REQUEST_COMPLETED_EVENT) + + observer.sharedObjectDidRelease() + + assertEquals(0, monitor.delegateCount) + assertFalse(observer.shouldObserveRequest("https://expo.dev", "GET")) + } + @Test fun `startedPayload contains the started-event keys`() { val id = UUID.randomUUID() diff --git a/packages/expo-observe/CHANGELOG.md b/packages/expo-observe/CHANGELOG.md index 5b4f66c00f77b3..3c14df03447640 100644 --- a/packages/expo-observe/CHANGELOG.md +++ b/packages/expo-observe/CHANGELOG.md @@ -10,6 +10,7 @@ - Expose `ObserveErrorBoundary`, a React error boundary that records render-phase errors. ([#47341](https://github.com/expo/expo/pull/47341) by [@tsapeta](https://github.com/tsapeta)) - Add `reportError` to report caught, non-fatal errors from your own `try`/`catch` blocks. ([#47871](https://github.com/expo/expo/pull/47871) by [@tsapeta](https://github.com/tsapeta)) - Add an `errorHandlingEnabled` option to `configure` to opt out of recording unhandled JavaScript errors. ([#48506](https://github.com/expo/expo/pull/48506) by [@tsapeta](https://github.com/tsapeta)) +- Add `Observe.clientId`, the EAS client id recorded on every event, so apps can correlate Observe data with other services. ([#49599](https://github.com/expo/expo/pull/49599) by [@kadikraman](https://github.com/kadikraman)) ### 🐛 Bug fixes diff --git a/packages/expo-observe/android/src/main/java/expo/modules/observe/ObserveModule.kt b/packages/expo-observe/android/src/main/java/expo/modules/observe/ObserveModule.kt index 786bdd10e87ca3..d1623594471225 100644 --- a/packages/expo-observe/android/src/main/java/expo/modules/observe/ObserveModule.kt +++ b/packages/expo-observe/android/src/main/java/expo/modules/observe/ObserveModule.kt @@ -3,6 +3,7 @@ package expo.modules.observe import android.content.Context import android.util.Log import expo.modules.appmetrics.AppMetricsModule +import expo.modules.easclient.EASClientID import expo.modules.interfaces.constants.ConstantsInterface import expo.modules.kotlin.exception.Exceptions import expo.modules.kotlin.functions.Coroutine @@ -58,6 +59,10 @@ class ObserveModule : Module() { observabilityManager.scheduleBackgroundDispatch() } + Constant("clientId") { + EASClientID(context).uuid.toString().lowercase() + } + AsyncFunction("dispatchEvents") Coroutine { -> observabilityManager.dispatchUnsentMetrics() observabilityManager.dispatchUnsentLogs() diff --git a/packages/expo-observe/ios/ObserveModule.swift b/packages/expo-observe/ios/ObserveModule.swift index 5b7a3984a06aa9..ec78eea0a57fac 100644 --- a/packages/expo-observe/ios/ObserveModule.swift +++ b/packages/expo-observe/ios/ObserveModule.swift @@ -1,5 +1,6 @@ // Copyright 2025-present 650 Industries. All rights reserved. +import EASClient import ExpoAppMetrics import ExpoModulesCore @@ -35,6 +36,10 @@ public final class ObserveModule: Module { } } + Constant("clientId") { + EASClientID.uuid().uuidString.lowercased() + } + AsyncFunction("dispatchEvents") { await ObservabilityManager.dispatch() } diff --git a/packages/expo-observe/src/__tests__/module.test.native.ts b/packages/expo-observe/src/__tests__/module.test.native.ts index fc31d8874fd64a..6d380c990ced5c 100644 --- a/packages/expo-observe/src/__tests__/module.test.native.ts +++ b/packages/expo-observe/src/__tests__/module.test.native.ts @@ -2,6 +2,7 @@ export {}; const mockNativeTarget = { + clientId: 'aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee', configure: jest.fn(), setBundleDefaults: jest.fn(), dispatchEvents: jest.fn(() => Promise.resolve()), @@ -487,6 +488,12 @@ describe('module Proxy', () => { expect(warnSpy).toHaveBeenCalled(); }); + it('exposes the native EAS client id as clientId', () => { + const Observe = loadModule(); + expect(Observe.clientId).toBe('aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee'); + expect(warnSpy).not.toHaveBeenCalled(); + }); + it('never throws when the native reportError call throws', () => { const Observe = loadModule(); mockAppMetrics.reportError.mockImplementationOnce(() => { diff --git a/packages/expo-observe/src/__tests__/module.test.web.ts b/packages/expo-observe/src/__tests__/module.test.web.ts new file mode 100644 index 00000000000000..cbd477c3bd0ea6 --- /dev/null +++ b/packages/expo-observe/src/__tests__/module.test.web.ts @@ -0,0 +1,31 @@ +/* eslint-disable @typescript-eslint/no-require-imports */ +export {}; + +jest.mock('expo', () => ({ + NativeModule: class {}, + registerWebModule: (moduleClass: new () => unknown) => new moduleClass(), +})); + +jest.mock('expo-app-metrics', () => ({ + __esModule: true, + default: { + logEvent: jest.fn(), + markFirstRender: jest.fn(), + markInteractive: jest.fn(), + setGlobalAttributes: jest.fn(), + reportError: jest.fn(), + }, +})); + +function loadWebModule() { + // `registerWebModule` returns the singleton instance, but its return type is the class itself, + // so cast to the module interface to read instance members. + return require('../module.web').default as unknown as import('../types').ObserveModule; +} + +describe('web module', () => { + it('reports clientId as null, because there is no EAS client id on web', () => { + const Observe = loadWebModule(); + expect(Observe.clientId).toBeNull(); + }); +}); diff --git a/packages/expo-observe/src/module.web.ts b/packages/expo-observe/src/module.web.ts index 9734a5ecb3d891..50a26e38fea06a 100644 --- a/packages/expo-observe/src/module.web.ts +++ b/packages/expo-observe/src/module.web.ts @@ -11,6 +11,10 @@ import type { } from './types'; class ExpoObserveModule extends NativeModule implements ObserveModule { + get clientId(): string | null { + // The EAS client id is stored in native preferences, which web has no equivalent of. + return null; + } async dispatchEvents() {} configure(config: ObserveConfig): void {} getIntegrations(): ObserveIntegrationsConfig { diff --git a/packages/expo-observe/src/types.ts b/packages/expo-observe/src/types.ts index e7eb1ee8077230..d432828e99925a 100644 --- a/packages/expo-observe/src/types.ts +++ b/packages/expo-observe/src/types.ts @@ -132,6 +132,32 @@ export type ObserveModuleEvents = { }; export declare class ObserveModule extends NativeModule { + /** + * The EAS client id: a random, pseudonymous identifier for this app installation, shared by all + * EAS client libraries. Observe records it on every metric and log event as the + * `expo.eas_client.id` attribute, so use it to correlate Observe data with another service. + * + * The id is stored in native preferences and is stable across app launches and app updates. It + * changes when the app's data is cleared or the app is reinstalled, although a backup restore + * can carry the previous id over. It identifies an installation, not a user or a device. + * + * `null` on web, where there is no EAS client id. + * + * @example + * ```ts + * import { Observe } from 'expo-observe'; + * + * // Attach the same id to data you send elsewhere to line it up with Observe. + * await fetch('https://example.com/events', { + * method: 'POST', + * body: JSON.stringify({ easClientId: Observe.clientId }), + * }); + * ``` + * + * @platform android + * @platform ios + */ + readonly clientId: string | null; /** * Dispatches pending events to the server immediately. * diff --git a/packages/expo-router/CHANGELOG.md b/packages/expo-router/CHANGELOG.md index a23d0e1d7b22fe..4865d6695259df 100644 --- a/packages/expo-router/CHANGELOG.md +++ b/packages/expo-router/CHANGELOG.md @@ -66,6 +66,7 @@ - Hide the splash screen when the built-in `+not-found` screen renders ([#48721](https://github.com/expo/expo/pull/48721) by [@Ubax](https://github.com/Ubax)) - Honor loader `Cache-Control` headers via the platform HTTP cache instead of caching loader data in memory ([#48087](https://github.com/expo/expo/pull/48087) by [@hassankhan](https://github.com/hassankhan)) - Cancel pending loader requests when their routes are removed during navigation ([#48451](https://github.com/expo/expo/pull/48451) by [@hassankhan](https://github.com/hassankhan)) +- Add a public `expo-router/native-stack` subpath so `createNativeStackNavigator` can be imported without a deep `build/` path. ([#49604](https://github.com/expo/expo/pull/49604) by [@alanjhughes](https://github.com/alanjhughes)) ### 🐛 Bug fixes @@ -87,6 +88,8 @@ ### 💡 Others +- Remove the root `options` event, `DocumentTitleOptions`, and the `documentTitle` prop from `expo-router/react-navigation`. ([#49590](https://github.com/expo/expo/pull/49590) by [@Ubax](https://github.com/Ubax)) +- Remove `onStateChange` from `BaseNavigationContainer` and `NavigationContainerProps` in `expo-router/react-navigation` ([#49588](https://github.com/expo/expo/pull/49588) by [@Ubax](https://github.com/Ubax)) - Pass toolbar menu icons straight to react-navigation instead of converting them to react-native-screens icons. (by [@Ubax](https://github.com/Ubax)) ([#49584](https://github.com/expo/expo/pull/49584) by [@Ubax](https://github.com/Ubax)) - Remove module-level mutable navigation state from Expo Router. ([#49403](https://github.com/expo/expo/pull/49403) by [@Ubax](https://github.com/Ubax)) - Remove the dev-only `stack` field from the `__unsafe_action__` event in `expo-router/react-navigation`. ([#49431](https://github.com/expo/expo/pull/49431) by [@Ubax](https://github.com/Ubax)) diff --git a/packages/expo-router/package.json b/packages/expo-router/package.json index bfa94462adbedf..d885495bca3d2a 100644 --- a/packages/expo-router/package.json +++ b/packages/expo-router/package.json @@ -146,6 +146,14 @@ "expo-source": "./src/layouts/TopTabs.tsx", "default": "./build/layouts/TopTabs.js" }, + "./native-stack": { + "types": { + "expo-source": "./src/react-navigation/native-stack/index.tsx", + "default": "./build/react-navigation/native-stack/index.d.ts" + }, + "expo-source": "./src/react-navigation/native-stack/index.tsx", + "default": "./build/react-navigation/native-stack/index.js" + }, "./react-navigation": { "types": { "expo-source": "./src/react-navigation/index.ts", diff --git a/packages/expo-router/src/ExpoRoot.tsx b/packages/expo-router/src/ExpoRoot.tsx index ad779775592807..09ca99e150ba68 100644 --- a/packages/expo-router/src/ExpoRoot.tsx +++ b/packages/expo-router/src/ExpoRoot.tsx @@ -51,10 +51,6 @@ const INITIAL_METRICS = } : undefined; -const documentTitle = { - enabled: false, -}; - /** * @hidden */ @@ -150,7 +146,6 @@ function ContextNavigator({ } - documentTitle={documentTitle} onReady={onNavigationReady}> diff --git a/packages/expo-router/src/fork/NavigationContainer.tsx b/packages/expo-router/src/fork/NavigationContainer.tsx index 393b379c814a0b..1a4c0a0ca2ee58 100644 --- a/packages/expo-router/src/fork/NavigationContainer.tsx +++ b/packages/expo-router/src/fork/NavigationContainer.tsx @@ -4,7 +4,6 @@ import { I18nManager } from 'react-native'; import { RouterConfigContext } from '../global-state/routerConfigContext'; import { BaseNavigationContainer } from '../react-navigation/core/BaseNavigationContainer'; import type { - DocumentTitleOptions, LinkingOptions, LocaleDirection, NavigationContainerProps, @@ -20,7 +19,6 @@ import { import { getPathFromState } from './getPathFromState'; import { getStateFromPath } from './getStateFromPath'; import { useBackButton } from './useBackButton'; -import { useDocumentTitle } from './useDocumentTitle'; import { useLinking } from './useLinking'; import { useThenable } from './useThenable'; import { validatePathConfig } from './validatePathConfig'; @@ -39,7 +37,6 @@ type Props = Omit; fallback?: React.ReactNode; - documentTitle?: DocumentTitleOptions; }; /** @@ -47,13 +44,11 @@ type Props = Omit & { @@ -77,7 +71,6 @@ function NavigationContainerInner({ const refContainer = React.useRef | null>(null); useBackButton(refContainer); - useDocumentTitle(refContainer, documentTitle); const { getInitialState } = useLinking(refContainer, { prefixes: [], diff --git a/packages/expo-router/src/fork/__tests__/useLinking.test.ios.tsx b/packages/expo-router/src/fork/__tests__/useLinking.test.ios.tsx index daf7afa2fb98f7..9043820a659ddb 100644 --- a/packages/expo-router/src/fork/__tests__/useLinking.test.ios.tsx +++ b/packages/expo-router/src/fork/__tests__/useLinking.test.ios.tsx @@ -278,10 +278,7 @@ test('shows fallback then content for an async initial URL', async () => { test('seeds navigation state when a synchronous initial URL is absent', () => { const ref = createNavigationContainerRef(); render( - null }}> + null }}> {null} ); diff --git a/packages/expo-router/src/fork/__tests__/useLinking.test.web.tsx b/packages/expo-router/src/fork/__tests__/useLinking.test.web.tsx index a15c36a2510b4b..a709c225309787 100644 --- a/packages/expo-router/src/fork/__tests__/useLinking.test.web.tsx +++ b/packages/expo-router/src/fork/__tests__/useLinking.test.web.tsx @@ -347,8 +347,6 @@ test('does not add browser history when preloading a stack route', async () => { { ); await waitFor(() => expect(ref.current).not.toBeNull()); + ref.current?.addListener('state', onStateChange); history.push.mockClear(); history.replace.mockClear(); diff --git a/packages/expo-router/src/fork/useDocumentTitle.native.ts b/packages/expo-router/src/fork/useDocumentTitle.native.ts deleted file mode 100644 index ef814b5ea358f5..00000000000000 --- a/packages/expo-router/src/fork/useDocumentTitle.native.ts +++ /dev/null @@ -1,6 +0,0 @@ -/* - * This file is unchanged, except for removing eslint comments - */ -export function useDocumentTitle() { - // Noop for native platforms -} diff --git a/packages/expo-router/src/fork/useDocumentTitle.ts b/packages/expo-router/src/fork/useDocumentTitle.ts deleted file mode 100644 index 0f1e4ef9e32c94..00000000000000 --- a/packages/expo-router/src/fork/useDocumentTitle.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * This file is unchanged, except for fixing imports and removing eslint comments - */ -import * as React from 'react'; - -import type { - DocumentTitleOptions, - NavigationContainerRef, - ParamListBase, -} from '../react-navigation/native'; - -// import type { DocumentTitleOptions } from './types'; - -/** - * Set the document title for the active screen - */ -export function useDocumentTitle( - ref: React.RefObject | null>, - { - enabled = true, - formatter = (options, route) => options?.title ?? route?.name, - }: DocumentTitleOptions = {} -) { - React.useEffect(() => { - if (!enabled) { - return; - } - - const navigation = ref.current; - - if (navigation) { - const title = formatter(navigation.getCurrentOptions(), navigation.getCurrentRoute()); - - document.title = title; - } - - return navigation?.addListener('options', (e) => { - const title = formatter(e.data.options, navigation?.getCurrentRoute()); - - document.title = title; - }); - }); -} diff --git a/packages/expo-router/src/react-navigation/core/BaseNavigationContainer.tsx b/packages/expo-router/src/react-navigation/core/BaseNavigationContainer.tsx index db2d325ea7fe57..7242eb4cef7250 100644 --- a/packages/expo-router/src/react-navigation/core/BaseNavigationContainer.tsx +++ b/packages/expo-router/src/react-navigation/core/BaseNavigationContainer.tsx @@ -1,5 +1,4 @@ 'use client'; -import isEqual from 'fast-deep-equal'; import * as React from 'react'; import { use } from 'react'; @@ -22,7 +21,6 @@ import { CommonActions, type InitialState, type NavigationAction, - type NavigationState, type ParamListBase, type Route, } from '../routers'; @@ -60,14 +58,13 @@ const duplicateNameWarnings: string[] = []; * * @param props.initialState Initial state object for the navigation tree. * @param props.onReady Callback which is called after the navigation tree mounts. - * @param props.onStateChange Callback which is called with the latest navigation state when it changes. * @param props.onUnhandledAction Callback which is called when an action is not handled. TODO(@ubax): restore this callback. https://linear.app/expo/issue/ENG-26123 * @param props.theme Theme object for the UI elements. * @param props.children Child elements to render the content. * @param props.ref Ref object which refers to the navigation object containing helper methods. */ export function BaseNavigationContainer(props: InternalNavigationContainerProps) { - const { ref, initialState, onStateChange, onReady, UNSTABLE_routeNode, theme, children } = props; + const { ref, initialState, onReady, UNSTABLE_routeNode, theme, children } = props; const parent = use(NavigationStateContext); const inheritedRouteInfo = use(RouteInfoContext); const routerConfig = use(RouterConfigContext); @@ -105,9 +102,6 @@ export function BaseNavigationContainer(props: InternalNavigationContainerProps) }); useNavigationTreeReportEvents(report, consumeReportEvents); - const hasNotifiedInitialStateRef = React.useRef(false); - const lastNotifiedStateRef = React.useRef(undefined); - const { listeners, addListener } = useChildListeners(); const dispatch = useLatestCallback((action: NavigationAction) => { @@ -195,37 +189,13 @@ export function BaseNavigationContainer(props: InternalNavigationContainerProps) React.useImperativeHandle(ref, () => navigation, [navigation]); - const lastEmittedOptionsRef = React.useRef< - { options: object; routeKey: string | undefined } | undefined - >(undefined); - - // TODO(@ubax): investigate if there is better way to implemnet this and wether this is really needed, - const onOptionsChange = useLatestCallback((options: object, routeKey?: string) => { - const lastEmittedOptions = lastEmittedOptionsRef.current; - if ( - lastEmittedOptions?.routeKey === routeKey && - lastEmittedOptions !== undefined && - isEqual(lastEmittedOptions.options, options) - ) { - return; - } - - lastEmittedOptionsRef.current = { options, routeKey }; - - emitter.emit({ - type: 'options', - data: { options }, - }); - }); - const builderContext = React.useMemo( () => ({ addListener, handleAction, resetNavigator, - onOptionsChange, }), - [addListener, handleAction, onOptionsChange, resetNavigator] + [addListener, handleAction, resetNavigator] ); const context = React.useMemo( @@ -247,10 +217,8 @@ export function BaseNavigationContainer(props: InternalNavigationContainerProps) } const onReadyRef = React.useRef(onReady); - const onStateChangeRef = React.useRef(onStateChange); React.useEffect(() => { - onStateChangeRef.current = onStateChange; onReadyRef.current = onReady; }); @@ -331,24 +299,8 @@ export function BaseNavigationContainer(props: InternalNavigationContainerProps) }, [getRootState, state]); useClientLayoutEffect(() => { - const hydratedState = getRootState(); - - // TODO(@ubax): invesitagte if there is cleaner way to do it - // If not consider deprecating the prop - const onStateChange = onStateChangeRef.current; - const shouldNotifyStateChange = - hasNotifiedInitialStateRef.current && - lastNotifiedStateRef.current !== hydratedState && - onStateChange !== undefined; - hasNotifiedInitialStateRef.current = true; - lastNotifiedStateRef.current = hydratedState; - emitter.emit({ type: 'state', data: { state } }); - - if (shouldNotifyStateChange) { - onStateChange(hydratedState); - } - }, [getRootState, emitter, state]); + }, [emitter, state]); return ( diff --git a/packages/expo-router/src/react-navigation/core/NavigationBuilderContext.tsx b/packages/expo-router/src/react-navigation/core/NavigationBuilderContext.tsx index bc82bbd500a820..e66e85914580b3 100644 --- a/packages/expo-router/src/react-navigation/core/NavigationBuilderContext.tsx +++ b/packages/expo-router/src/react-navigation/core/NavigationBuilderContext.tsx @@ -24,9 +24,7 @@ export const NavigationBuilderContext = React.createContext<{ handleAction: (action: NavigationAction, originKey?: string) => void; resetNavigator: (stateKey: string, routerType: string | undefined) => void; addListener?: AddListener; - onOptionsChange: (options: object, routeKey?: string) => void; }>({ handleAction: () => undefined, resetNavigator: () => undefined, - onOptionsChange: () => undefined, }); diff --git a/packages/expo-router/src/react-navigation/core/__tests__/BaseNavigationContainer.test.ios.tsx b/packages/expo-router/src/react-navigation/core/__tests__/BaseNavigationContainer.test.ios.tsx index 5365e67648c668..0133728b3b2358 100644 --- a/packages/expo-router/src/react-navigation/core/__tests__/BaseNavigationContainer.test.ios.tsx +++ b/packages/expo-router/src/react-navigation/core/__tests__/BaseNavigationContainer.test.ios.tsx @@ -11,7 +11,6 @@ import { type ParamListBase, type Router, StackRouter, - TabRouter, } from '../../routers'; import { BaseNavigationContainer as RawBaseNavigationContainer } from '../BaseNavigationContainer'; import { Screen } from '../Screen'; @@ -243,10 +242,7 @@ test('handle dispatching with ref', () => { - + {() => null} {() => null} @@ -260,6 +256,7 @@ test('handle dispatching with ref', () => { ); render(element).update(element); + ref.current?.addListener('state', () => onStateChange(ref.current!.getRootState())); act(() => { ref.current?.dispatch({ type: 'REVERSE' }); @@ -680,98 +677,7 @@ test('does not emit state events when a new navigator mounts with complete state expect(onStateChange).not.toHaveBeenCalled(); }); -test('emits option events when options change with tab router', () => { - const TestNavigator = (props: any) => { - const { state, descriptors, NavigationContent } = useNavigationBuilder(TabRouter, props); - - return ( - - {state.routes.map((route) => descriptors[route.key]!.render())} - - ); - }; - - const ref = createNavigationContainerRef(); - - const element = ( - - - - {() => null} - - - {() => null} - - - {() => ( - - - {() => null} - - - {() => null} - - - )} - - - - ); - - type ListenerType = EventListenerCallback; - const listener = jest.fn, Parameters>(); - - render(element).update(element); - ref.current?.addListener('options', listener); - - act(() => { - ref.current?.dispatchSync(CommonActions.navigate('bar')); - }); - - expect(listener).toHaveBeenCalledTimes(1); - expect(listener.mock.calls[0]![0].data.options).toEqual({ y: 2 }); - expect(ref.current?.getCurrentOptions()).toEqual({ y: 2 }); - - act(() => { - ref.current?.dispatchSync(CommonActions.navigate('foo')); - }); - - expect(listener).toHaveBeenCalledTimes(2); - expect(listener.mock.calls[1]![0].data.options).toEqual({ x: 1 }); - - ref.current?.removeListener('options', listener); - - const listener2 = jest.fn, Parameters>(); - - ref.current?.addListener('options', listener2); - - act(() => { - ref.current?.dispatchSync(CommonActions.navigate('baz')); - }); - - expect(listener2).toHaveBeenCalledTimes(1); - expect(listener2.mock.calls[0]![0].data.options).toEqual({ g: 5 }); - expect(ref.current?.getCurrentOptions()).toEqual({ g: 5 }); - - act(() => { - ref.current?.dispatchSync(CommonActions.navigate('quxx')); - }); - - expect(listener2).toHaveBeenCalledTimes(2); - expect(listener2.mock.calls[1]![0].data.options).toEqual({ h: 9 }); - expect(ref.current?.getCurrentOptions()).toEqual({ h: 9 }); -}); - -test('does not emit options from an unfocused nested navigator', () => { +test('ignores options from an unfocused nested navigator', () => { const NoFocusMockRouter = (options: DefaultRouterOptions) => ({ ...MockRouter(options), shouldActionChangeFocus: () => false, @@ -792,7 +698,6 @@ test('does not emit options from an unfocused nested navigator', () => { }); const child = React.createRef(); const ref = createNavigationContainerRef(); - const listener = jest.fn(); render( { ); - ref.current?.addListener('options', listener); act(() => child.current.navigate('fourth')); expect(ref.current?.getCurrentRoute()?.name).toBe('first'); - expect(listener).not.toHaveBeenCalled(); expect(ref.current?.getCurrentOptions()).toEqual({ x: 1 }); }); -test('emits option events when options change with stack router', () => { - const TestNavigator = (props: any) => { - const { state, descriptors, NavigationContent } = useNavigationBuilder(StackRouter, props); - - return ( - - {state.routes.map((route) => descriptors[route.key]!.render())} - - ); - }; - - const ref = createNavigationContainerRef(); - - const element = ( - - - - {() => null} - - - {() => null} - - - {() => ( - - - {() => null} - - - {() => null} - - - )} - - - - ); - - type ListenerType = EventListenerCallback; - const listener = jest.fn, Parameters>(); - - render(element).update(element); - ref.current?.addListener('options', listener); - - act(() => { - ref.current?.dispatchSync(CommonActions.navigate('bar')); - }); - - expect(listener).toHaveBeenCalledTimes(1); - expect(listener.mock.calls[0]![0].data.options).toEqual({ y: 2 }); - expect(ref.current?.getCurrentOptions()).toEqual({ y: 2 }); - - ref.current?.removeListener('options', listener); - - const listener2 = jest.fn, Parameters>(); - - ref.current?.addListener('options', listener2); - - act(() => { - ref.current?.dispatchSync(CommonActions.navigate('baz')); - }); - - expect(listener2).toHaveBeenCalledTimes(1); - expect(listener2.mock.calls[0]![0].data.options).toEqual({ g: 5 }); - expect(ref.current?.getCurrentOptions()).toEqual({ g: 5 }); - - act(() => { - ref.current?.dispatchSync(CommonActions.navigate('quxx')); - }); - - expect(listener2).toHaveBeenCalledTimes(2); - expect(listener2.mock.calls[1]![0].data.options).toEqual({ h: 9 }); - expect(ref.current?.getCurrentOptions()).toEqual({ h: 9 }); -}); - test('throws if there is no navigator rendered', () => { expect.assertions(1); diff --git a/packages/expo-router/src/react-navigation/core/__tests__/__fixtures__/BaseNavigationContainer.tsx b/packages/expo-router/src/react-navigation/core/__tests__/__fixtures__/BaseNavigationContainer.tsx index 962308fc3977b3..f5259f79585647 100644 --- a/packages/expo-router/src/react-navigation/core/__tests__/__fixtures__/BaseNavigationContainer.tsx +++ b/packages/expo-router/src/react-navigation/core/__tests__/__fixtures__/BaseNavigationContainer.tsx @@ -4,14 +4,19 @@ import { nanoid } from 'nanoid/non-secure'; import { RemovalPreventionProvider } from '../../../../global-state/removalPrevention'; import { RouterRegistryProvider } from '../../../../global-state/routerRegistry'; import { RoutingQueueProvider } from '../../../../global-state/routingQueueContext'; +import useLatestCallback from '../../../../utils/useLatestCallback'; import type { NavigationState, ParamListBase, PartialState } from '../../../routers'; import type { NavigationContainerRef } from '../../types'; import { BaseNavigationContainer as BaseNavigationContainerImpl } from '../../BaseNavigationContainer'; import { MockRouterKey } from './MockRouter'; type TestInitialState = NavigationState | PartialState; -type Props = Omit, 'initialState'> & { +type Props = Omit< + React.ComponentProps, + 'initialState' | 'onStateChange' +> & { initialState?: TestInitialState; + onStateChange?: (state: NavigationState | undefined) => void; }; function getInitialState(children: React.ReactNode): NavigationState { @@ -127,8 +132,16 @@ function completeState( } export function BaseNavigationContainer(props: Props) { - const { ref, ...rest } = props; + const { onStateChange, ref, ...rest } = props; const navigationRef = React.useRef | null>(null); + const notifyStateChange = useLatestCallback(() => { + onStateChange?.(navigationRef.current?.getRootState()); + }); + + React.useEffect(() => { + // Subscribe after mount so the container's initial state event is ignored. + return navigationRef.current?.addListener('state', notifyStateChange); + }, []); const setRef = React.useCallback( (navigation: NavigationContainerRef | null) => { diff --git a/packages/expo-router/src/react-navigation/core/types.tsx b/packages/expo-router/src/react-navigation/core/types.tsx index a2801393e03862..7238d0316b9cc2 100644 --- a/packages/expo-router/src/react-navigation/core/types.tsx +++ b/packages/expo-router/src/react-navigation/core/types.tsx @@ -399,10 +399,6 @@ export type NavigationContainerProps = { * Initial state object for the navigation tree. */ initialState?: InitialState; - /** - * Callback which is called with the latest navigation state when it changes. - */ - onStateChange?: (state: Readonly | undefined) => void; /** * Callback which is called after the navigation tree mounts. */ @@ -751,10 +747,6 @@ export type NavigationContainerEventMap = { state: NavigationState | PartialState | undefined; }; }; - /** - * Event that fires when current options changes. - */ - options: { data: { options: object } }; }; export type ParamListRoute = { diff --git a/packages/expo-router/src/react-navigation/core/useDescriptors.tsx b/packages/expo-router/src/react-navigation/core/useDescriptors.tsx index e3d42b7bfeec34..0773b90df6b616 100644 --- a/packages/expo-router/src/react-navigation/core/useDescriptors.tsx +++ b/packages/expo-router/src/react-navigation/core/useDescriptors.tsx @@ -103,7 +103,7 @@ export function useDescriptors< }: Options) { const theme = use(ThemeContext); const [options, setOptions] = React.useState>({}); - const { handleAction, resetNavigator, onOptionsChange } = use(NavigationBuilderContext); + const { handleAction, resetNavigator } = use(NavigationBuilderContext); const context = React.useMemo( () => ({ @@ -111,9 +111,8 @@ export function useDescriptors< handleAction, resetNavigator, addListener, - onOptionsChange, }), - [navigation, handleAction, resetNavigator, addListener, onOptionsChange] + [navigation, handleAction, resetNavigator, addListener] ); const getNavigation = useNavigationCache({ diff --git a/packages/expo-router/src/react-navigation/core/useOptionsGetters.tsx b/packages/expo-router/src/react-navigation/core/useOptionsGetters.tsx index d85f51cab636aa..649d9c0c5785b8 100644 --- a/packages/expo-router/src/react-navigation/core/useOptionsGetters.tsx +++ b/packages/expo-router/src/react-navigation/core/useOptionsGetters.tsx @@ -3,7 +3,6 @@ import * as React from 'react'; import { use } from 'react'; import useLatestCallback from '../../utils/useLatestCallback'; -import { NavigationBuilderContext } from './NavigationBuilderContext'; import { NavigationStateContext } from './NavigationStateContext'; import { useIsRouteFocused } from './useIsFocused'; @@ -18,22 +17,12 @@ export function useOptionsGetters({ key, options }: Options) { {} ); - const { onOptionsChange } = use(NavigationBuilderContext); const { addOptionsGetter: parentAddOptionsGetter } = use(NavigationStateContext); const isFocused = useIsRouteFocused(key); - const optionsChangeListener = React.useCallback(() => { - const hasChildren = Object.keys(optionsGettersFromChildRef.current).length; - - if (isFocused && !hasChildren) { - onOptionsChange(optionsRef.current ?? {}, key); - } - }, [isFocused, key, onOptionsChange]); - React.useEffect(() => { optionsRef.current = options; - optionsChangeListener(); - }, [options, optionsChangeListener]); + }, [options]); const getOptionsFromListener = React.useCallback(() => { for (const key in optionsGettersFromChildRef.current) { @@ -71,15 +60,13 @@ export function useOptionsGetters({ key, options }: Options) { const addOptionsGetter = React.useCallback( (key: string, getter: () => object | undefined | null) => { optionsGettersFromChildRef.current[key] = getter; - optionsChangeListener(); return () => { // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete optionsGettersFromChildRef.current[key]; - optionsChangeListener(); }; }, - [optionsChangeListener] + [] ); return { diff --git a/packages/expo-router/src/react-navigation/native-stack/index.tsx b/packages/expo-router/src/react-navigation/native-stack/index.tsx index 134c97a807ffe5..97d964fa8f34f9 100644 --- a/packages/expo-router/src/react-navigation/native-stack/index.tsx +++ b/packages/expo-router/src/react-navigation/native-stack/index.tsx @@ -1,6 +1,11 @@ /** * Navigators */ +/** + * @deprecated Reserved for libraries that ship a self-contained navigator, which the `Stack` layout + * cannot express. There is no stable replacement yet, so expect this factory to change or be removed + * in a future release. App code should use `Stack` from `expo-router`. + */ export { createNativeStackNavigator } from './navigators/createNativeStackNavigator'; /** diff --git a/packages/expo-router/src/react-navigation/native/types.tsx b/packages/expo-router/src/react-navigation/native/types.tsx index a334203f5ab447..8a3b10f29e2d1f 100644 --- a/packages/expo-router/src/react-navigation/native/types.tsx +++ b/packages/expo-router/src/react-navigation/native/types.tsx @@ -1,7 +1,7 @@ import type { ColorValue } from 'react-native'; import type { getStateFromPath as getExpoStateFromPath } from '../../fork/getStateFromPath'; -import type { getPathFromState as getPathFromStateDefault, PathConfigMap, Route } from '../core'; +import type { getPathFromState as getPathFromStateDefault, PathConfigMap } from '../core'; declare global { // eslint-disable-next-line @typescript-eslint/no-namespace @@ -164,17 +164,6 @@ export type LinkingOptions = { getPathFromState?: typeof getPathFromStateDefault; }; -/** - * @deprecated Will be removed in a future SDK. - */ -export type DocumentTitleOptions = { - enabled?: boolean; - formatter?: ( - options: Record | undefined, - route: Route | undefined - ) => string; -}; - /** * @deprecated Will be removed in a future SDK. */