Skip to content

feat(core): Add Sentry.appLoaded() API to signal app start end#5940

Open
alwx wants to merge 2 commits intomainfrom
alwx/feat/app-loaded-api
Open

feat(core): Add Sentry.appLoaded() API to signal app start end#5940
alwx wants to merge 2 commits intomainfrom
alwx/feat/app-loaded-api

Conversation

@alwx
Copy link
Copy Markdown
Contributor

@alwx alwx commented Mar 31, 2026

📢 Type of change

  • Bugfix
  • New feature
  • Enhancement
  • Refactoring

📜 Description

Fixes #5935

Adds a public Sentry.appLoaded() function that users can call to explicitly signal when their app is fully ready for user interaction. This provides a more accurate app start end timestamp for apps that do significant async work after the root component mounts.

When appLoaded() is called it:

  • Records the current timestamp as the manual app start end
  • Fetches native frames for frame data attachment
  • Triggers standalone app start capture when in standalone mode
  • Marks the span origin as manual.app.start

Existing behaviour is unchanged when appLoaded() is not called.

Usage

await loadRemoteConfig();
await restoreSession();
SplashScreen.hide();
Sentry.appLoaded();

💚 How did you test it?

Tests were added :)

📝 Checklist

  • I added tests to verify changes
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • All tests passing
  • No breaking changes

@alwx alwx self-assigned this Mar 31, 2026
Adds a public `Sentry.appLoaded()` function that users can call to
explicitly signal when their app is fully ready for user interaction.
This provides a more accurate app start end timestamp for apps that
do significant async work after the root component mounts (e.g.
remote config fetching, session restore, splash screen dismissal).

When appLoaded() is called it:
- Records the current timestamp as the manual app start end
- Fetches native frames for frame data attachment
- Triggers standalone app start capture when in standalone mode

Priority / race condition handling:
- appLoaded() before ReactNativeProfiler.componentDidMount: the auto
  capture in _captureAppStart({ isManual: false }) is skipped entirely
- appLoaded() after componentDidMount: the existing appStartEndData
  timestamp is overwritten with the manual (later) value; since the
  app start spans are attached to the first navigation transaction
  (which hasn't been flushed yet), the correct timestamp is used

Existing behaviour is unchanged when appLoaded() is not called.
The three-tier fallback (wrap -> bundle start -> warn) still applies.

Adds _appLoaded() internal function, _clearAppStartEndData() testing
helper, and five new unit tests covering: manual timestamp, duplicate
call guard, post-auto-capture override, pre-auto-capture guard, and
no-op before Sentry.init().
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 31, 2026

Semver Impact of This PR

None (no version bump detected)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


  • feat(core): Add Sentry.appLoaded() API to signal app start end by alwx in #5940
  • chore(deps): update JavaScript SDK to v10.47.0 by github-actions in #5938
  • chore(core): Deprecate FeedbackButton FAB APIs by antonis in #5933
  • Fix: Disable global prettier by lucas-zimerman in #5937
  • refactor(core): Rename FeedbackWidget to FeedbackForm by antonis in #5931
  • refactor(core): Extract playground modal styles to separate file by antonis in #5927
  • fix(ci): Avoid unnecessary runner allocation by splitting platform matrix into separate jobs by alwx in #5924
  • feat(core): Track shake to report integration usage by antonis in #5929
  • chore(deps): update CLI to v3.3.5 by github-actions in #5925
  • chore: Replace prettier with oxfmt by antonis in #5880
  • chore(deps): bump brace-expansion to ^5.0.5 by antonis in #5920
  • chore(deps): bump path-to-regexp to ^8.4.0 by antonis in #5919
  • chore: Migrate from ESLint to oxlint by antonis in #5867
  • chore(deps): bump yaml to ^2.8.3 by antonis in #5921
  • chore(deps): bump activesupport to >= 7.2.3.1 by antonis in #5922
  • fix(ci): Update validate-pr action to remove draft enforcement by stephanie-anderson in #5923
  • chore(deps): bump actions/checkout from 4 to 6 by dependabot in #5916
  • chore(deps): bump getsentry/craft from 2.25.0 to 2.25.2 by dependabot in #5918
  • chore(deps): bump getsentry/craft/.github/workflows/changelog-preview.yml from 2.25.0 to 2.25.2 by dependabot in #5914
  • chore(deps): bump github/codeql-action from 4.34.1 to 4.35.1 by dependabot in #5917
  • chore(deps): bump dorny/paths-filter from 3.0.2 to 4.0.1 by dependabot in #5915
  • fix: Prevent script injection vulnerability in platform-check action by fix-it-felix-sentry in #5913
  • chore(ios): Upgrade clang-format from v20 to v22 by antonis in #5905
  • chore: Add PR validation workflow by stephanie-anderson in #5906

Plus 8 more


🤖 This preview updates automatically when you update the PR.

@alwx alwx force-pushed the alwx/feat/app-loaded-api branch from b12e8e8 to d7e01e6 Compare March 31, 2026 13:19
@alwx alwx marked this pull request as ready for review March 31, 2026 13:37
@github-actions
Copy link
Copy Markdown
Contributor

Fails
🚫 Pull request is not ready for merge, please add the "ready-to-merge" label to the pull request
Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Generated by 🚫 dangerJS against c5453af

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

const client = getClient();
if (!client) {
debug.warn('[AppStart] appLoaded() was called before Sentry.init(). App start end will not be recorded.');
return;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Early flag set disables app start tracking permanently

Medium Severity

isAppLoadedManuallyInvoked is set to true on line 86 before the getClient() check on line 88. If appLoaded() is called before Sentry.init(), the flag stays true permanently. This silently blocks all subsequent auto-capture in _captureAppStart (which checks this flag and returns early for non-manual calls). The result: calling appLoaded() before init() permanently disables app start tracking for the entire session — not just the manual call, but also the automatic ReactNativeProfiler.componentDidMount path.

Additional Locations (1)
Fix in Cursor Fix in Web

Comment on lines +86 to +92
isAppLoadedManuallyInvoked = true;

const client = getClient();
if (!client) {
debug.warn('[AppStart] appLoaded() was called before Sentry.init(). App start end will not be recorded.');
return;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: The isAppLoadedManuallyInvoked flag is set before client validation. If appLoaded() is called before init(), subsequent automatic app start captures are incorrectly skipped.
Severity: MEDIUM

Suggested Fix

Move the isAppLoadedManuallyInvoked = true; assignment to after the if (!client) check succeeds. This ensures the flag is only set when the manual appLoaded() call can be successfully processed.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: packages/core/src/js/tracing/integrations/appStart.ts#L86-L92

Potential issue: In the `_appLoaded` function, the `isAppLoadedManuallyInvoked` flag is
set to `true` unconditionally before checking if a Sentry client exists. If a user calls
`Sentry.appLoaded()` before `Sentry.init()`, the function logs a warning and returns
early, but the flag remains `true`. This prevents the subsequent automatic app start
capture from running, as it incorrectly assumes a manual capture was successful. As a
result, no app start end timestamp is recorded for the entire session, silently
degrading app start tracking.

Did we get this right? 👍 / 👎 to inform future reviews.

* Records the application start end.
* Used automatically by `Sentry.wrap` and `Sentry.ReactNativeProfiler`.
*/
export function captureAppStart(): Promise<void> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Q: would it make sense to deprecate this method?

});

function makeIntegration(): AppStartIntegrationTest {
const integration = appStartIntegration({ standalone: false }) as AppStartIntegrationTest;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Q: Should we also test with standalone: true?

* Sentry.appLoaded();
* ```
*/
export function appLoaded(): void {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I have no strong opinion on this but wdyt of marking as experimental since it's a new api?

Comment on lines +107 to +117
if (NATIVE.enableNative) {
try {
const endFrames = await NATIVE.fetchNativeFrames();
debug.log('[AppStart] Captured end frames for app start.', endFrames);
_updateAppStartEndFrames(endFrames);
} catch (error) {
debug.log('[AppStart] Failed to capture end frames for app start.', error);
}
}

await client.getIntegrationByName<AppStartIntegration>(INTEGRATION_NAME)?.captureStandaloneAppStart();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: we could extract this part from _captureAppStart and reuse it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A method to notify Sentry that the app is loaded

2 participants