Skip to content
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

- Add experimental `enableStandaloneAppStartTracing` to send app start as a standalone `app.start` transaction ([#6359](https://github.com/getsentry/sentry-react-native/pull/6359))
- Set `app.vitals.start.screen` on the standalone `app.start` transaction from the current route ([#6369](https://github.com/getsentry/sentry-react-native/pull/6369))
- Add experimental `extendAppStart`/`finishExtendedAppStart`/`getExtendedAppStartSpan` to extend the standalone app start window and instrument post-init work ([#6303](https://github.com/getsentry/sentry-react-native/issues/6303))
Comment thread
antonis marked this conversation as resolved.
Outdated
- Expose top-level `Sentry.setAttribute` and `Sentry.setAttributes` APIs ([#6354](https://github.com/getsentry/sentry-react-native/pull/6354)).
- Add `enableTurboModuleTracking` opt-in experimental option to enable Turbo Module performance tracking in the New Architecture ([#6307](https://github.com/getsentry/sentry-react-native/pull/6307))
- Use the runtime's native `btoa` for envelope base64 encoding when available, to improve `captureEnvelope` performance. Falls back to the bundled JS encoder if `btoa` is missing ([#6351](https://github.com/getsentry/sentry-react-native/pull/6351)).
Expand Down
9 changes: 9 additions & 0 deletions packages/core/etc/sentry-react-native.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,9 @@
// @public
export const expoUpdatesListenerIntegration: () => Integration;

// @public

Check warning on line 308 in packages/core/etc/sentry-react-native.api.md

View check run for this annotation

@sentry/warden / warden: code-review

`finalizeStandaloneAppStart` loses the trimmed end time when `appStartEndData` is unset

When `extendAppStart()` is used in standalone mode without `Sentry.wrap()` or `appLoaded()`, `appStartEndData` is null at finalization time. The guard `if (appStartEndData && trimmedEndMs)` on line 857 of `appStart.ts` then skips the mutation that propagates `trimmedEndMs` into `appStartEndData`. As a result, `attachAppStartToTransactionEvent` falls back to `getBundleStartTimestampMs()` and overwrites `event.timestamp` with the bundle-start time, nullifying the extension and producing an incorrect (shorter) measurement and transaction end — defeating the purpose of the API for users who don't use `wrap()`. Note that the normal `captureStandaloneAppStart` path does self-heal by calling `_setAppStartEndData`, but the extend/finalize path has no equivalent, so it depends solely on `wrap()`/`appLoaded()` having run.
Comment thread
antonis marked this conversation as resolved.
export function extendAppStart(): void;

export { extraErrorDataIntegration }

export { FeatureFlagsIntegration }
Expand Down Expand Up @@ -353,6 +356,9 @@
enableShakeToReport?: boolean;
}) => FeedbackIntegration;

// @public
export function finishExtendedAppStart(): void;

// @public
export function flush(): Promise<boolean>;

Expand Down Expand Up @@ -384,6 +390,9 @@
// @public
export function getDefaultIdleNavigationSpanOptions(): StartSpanOptions;

// @public
export function getExtendedAppStartSpan(): Span;

export { getGlobalScope }

export { getIsolationScope }
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ export {
withScope,
crashedLastRun,
appLoaded,
extendAppStart,
getExtendedAppStartSpan,
finishExtendedAppStart,
pauseAppHangTracking,
resumeAppHangTracking,
} from './sdk';
Expand Down
64 changes: 62 additions & 2 deletions packages/core/src/js/sdk.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* oxlint-disable eslint(complexity) */
import type { Breadcrumb, BreadcrumbHint, Integration, Scope } from '@sentry/core';
import type { Breadcrumb, BreadcrumbHint, Integration, Scope, Span } from '@sentry/core';

import {
debug,
Expand All @@ -25,7 +25,12 @@
import { enableSyncToNative } from './scopeSync';
import { TouchEventBoundary } from './touchevents';
import { ReactNativeProfiler } from './tracing';
import { _appLoaded } from './tracing/integrations/appStart';
import {
_appLoaded,
_extendAppStart,
_finishExtendedAppStart,
_getExtendedAppStartSpan,
Comment thread
antonis marked this conversation as resolved.
} from './tracing/integrations/appStart';
import { useEncodePolyfill } from './transports/encodePolyfill';
import { DEFAULT_BUFFER_SIZE, makeNativeTransportFactory } from './transports/native';
import { getDefaultEnvironment, isExpoGo, isRunningInMetroDevServer, isWeb } from './utils/environment';
Expand Down Expand Up @@ -250,6 +255,61 @@
_appLoaded();
}

/**
* Extends the app start window so work done after initialization (remote config, session restore,
* splash screen dismissal, etc.) is included in the app start measurement. Call
* {@link finishExtendedAppStart} when the app is ready, or attach child spans via
* {@link getExtendedAppStartSpan} to break the extended work down.
*
* Requires standalone app start tracing (`_experiments.enableStandaloneAppStartTracing`). No-ops if
* the app start transaction was already created, if extend was already called, or if called before
* `Sentry.init()`.
*
* @experimental This API is subject to change in future versions.
*
* @example
* ```ts
* Sentry.extendAppStart();
* await initializeRemoteConfig();
* Sentry.finishExtendedAppStart();
* ```
*/
export function extendAppStart(): void {
_extendAppStart();
}

/**
* Returns the extended app start span for attaching child spans, or a no-op span when there is no
* active extension. Only meaningful between {@link extendAppStart} and {@link finishExtendedAppStart}.
*
* @experimental This API is subject to change in future versions.
*
* @example
* ```ts
* Sentry.extendAppStart();
* const parentSpan = Sentry.getExtendedAppStartSpan();
* const child = Sentry.startInactiveSpan({ parentSpan, op: 'app.init', name: 'fetch remote config' });
* await loadRemoteConfig();
* child.end();
* Sentry.finishExtendedAppStart();
* ```
*/
export function getExtendedAppStartSpan(): Span {
return _getExtendedAppStartSpan();
}

/**
* Finishes the app start extension started with {@link extendAppStart}, finalizing the app start
* transaction (its duration is trimmed to the last child span). No-ops if there is no active
* extension. Equivalent to calling `.finish()` on the span returned by {@link getExtendedAppStartSpan}.
*
* @experimental This API is subject to change in future versions.
*/

Check warning on line 307 in packages/core/src/js/sdk.tsx

View check run for this annotation

@sentry/warden / warden: find-bugs

Extended app start end-time (`trimmedEndMs`) discarded when `appStartEndData` is unset

In the standalone extend flow, `finalizeStandaloneAppStart` computes `trimmedEndMs` (the latest finished child span end, floored at the default app-start end) and sets it on the transaction span via `setEndTimeValue`. However, it only persists this value into `appStartEndData.timestampMs` when `appStartEndData` is already defined (`if (appStartEndData && trimmedEndMs)`). When `extendAppStart()` is used without `Sentry.wrap`/`ReactNativeProfiler` and without a manual `appLoaded()` call, `appStartEndData` stays `undefined` (the deferred `captureStandaloneAppStart`, which would otherwise call `_setAppStartEndData`, was cancelled by `extendAppStart`). `attachAppStartToTransactionEvent` then reads `appStartEndData?.timestampMs || getBundleStartTimestampMs()`, falling back to the bundle-start time, and overwrites `event.timestamp` with it. The extended window is silently dropped, producing a too-short `app.vitals.start.value` measurement and a transaction that no longer reflects the extension work.
export function finishExtendedAppStart(): void {
// oxlint-disable-next-line typescript-eslint(no-floating-promises)
_finishExtendedAppStart();
}

/**
* Flushes all pending events in the queue to disk.
* Use this before applying any realtime updates such as code-push or expo updates.
Expand Down
Loading
Loading