diff --git a/packages/core/src/js/feedback/FeedbackWidget.tsx b/packages/core/src/js/feedback/FeedbackWidget.tsx index 7448d9d8b4..95b6e295fb 100644 --- a/packages/core/src/js/feedback/FeedbackWidget.tsx +++ b/packages/core/src/js/feedback/FeedbackWidget.tsx @@ -26,9 +26,7 @@ import { base64ToUint8Array, feedbackAlertDialog, isValidEmail } from './utils' * Implements a feedback form screen that sends feedback to Sentry using Sentry.captureFeedback. */ export class FeedbackWidget extends React.Component { - public static defaultProps: Partial = { - ...defaultConfiguration - } + public static defaultProps = defaultConfiguration; private static _savedState: Omit = { name: '', @@ -67,7 +65,7 @@ export class FeedbackWidget extends React.Component void = () => { const { name, email, description } = this.state; const { onSubmitSuccess, onSubmitError, onFormSubmitted } = this.props; - const text: FeedbackTextConfiguration = this.props; + const text = this.props; const trimmedName = name?.trim(); const trimmedEmail = email?.trim(); @@ -119,14 +117,14 @@ export class FeedbackWidget extends React.Component void = async () => { if (!this.state.filename && !this.state.attachment) { - const imagePickerConfiguration: ImagePickerConfiguration = this.props; - if (imagePickerConfiguration.imagePicker) { - const launchImageLibrary = imagePickerConfiguration.imagePicker.launchImageLibraryAsync + const { imagePicker } = this.props; + if (imagePicker) { + const launchImageLibrary = imagePicker.launchImageLibraryAsync // expo-image-picker library is available - ? () => imagePickerConfiguration.imagePicker.launchImageLibraryAsync({ mediaTypes: ['images'], base64: isWeb() }) + ? () => imagePicker.launchImageLibraryAsync?.({ mediaTypes: ['images'], base64: isWeb() }) // react-native-image-picker library is available - : imagePickerConfiguration.imagePicker.launchImageLibrary - ? () => imagePickerConfiguration.imagePicker.launchImageLibrary({ mediaType: 'photo', includeBase64: isWeb() }) + : imagePicker.launchImageLibrary + ? () => imagePicker.launchImageLibrary?.({ mediaType: 'photo', includeBase64: isWeb() }) : null; if (!launchImageLibrary) { logger.warn('No compatible image picker library found. Please provide a valid image picker library.'); @@ -140,22 +138,22 @@ export class FeedbackWidget extends React.Component 0) { + if (result?.assets && result.assets.length > 0) { if (isWeb()) { - const filename = result.assets[0].fileName; - const imageUri = result.assets[0].uri; - const base64 = result.assets[0].base64; - const data = base64ToUint8Array(base64); - if (data != null) { + const filename = result.assets[0]?.fileName; + const imageUri = result.assets[0]?.uri; + const base64 = result.assets[0]?.base64; + const data = base64 ? base64ToUint8Array(base64) : undefined; + if (data) { this.setState({ filename, attachment: data, attachmentUri: imageUri }); } else { logger.error('Failed to read image data on the web'); } } else { - const filename = result.assets[0].fileName; - const imageUri = result.assets[0].uri; - getDataFromUri(imageUri).then((data) => { - if (data != null) { + const filename = result.assets[0]?.fileName; + const imageUri = result.assets[0]?.uri; + imageUri && getDataFromUri(imageUri).then((data) => { + if (data) { this.setState({ filename, attachment: data, attachmentUri: imageUri }); } else { logger.error('Failed to read image data from uri:', imageUri); diff --git a/packages/core/src/js/feedback/FeedbackWidget.types.ts b/packages/core/src/js/feedback/FeedbackWidget.types.ts index af08c2ffc3..adb300784b 100644 --- a/packages/core/src/js/feedback/FeedbackWidget.types.ts +++ b/packages/core/src/js/feedback/FeedbackWidget.types.ts @@ -21,38 +21,38 @@ export interface FeedbackGeneralConfiguration { * * @default true */ - showBranding?: boolean; + showBranding: boolean; /** * Should the email field be required? */ - isEmailRequired?: boolean; + isEmailRequired: boolean; /** * Should the email field be validated? */ - shouldValidateEmail?: boolean; + shouldValidateEmail: boolean; /** * Should the name field be required? */ - isNameRequired?: boolean; + isNameRequired: boolean; /** * Should the email input field be visible? Note: email will still be collected if set via `Sentry.setUser()` */ - showEmail?: boolean; + showEmail: boolean; /** * Should the name input field be visible? Note: name will still be collected if set via `Sentry.setUser()` */ - showName?: boolean; + showName: boolean; /** * This flag determines whether the "Add Screenshot" button is displayed * @default false */ - enableScreenshot?: boolean; + enableScreenshot: boolean; /** * Fill in email/name input fields with Sentry user context if it exists. @@ -71,32 +71,32 @@ export interface FeedbackTextConfiguration { /** * The label for the Feedback form cancel button that closes dialog */ - cancelButtonLabel?: string; + cancelButtonLabel: string; /** * The label for the Feedback form submit button that sends feedback */ - submitButtonLabel?: string; + submitButtonLabel: string; /** * The title of the Feedback form */ - formTitle?: string; + formTitle: string; /** * Label for the email input */ - emailLabel?: string; + emailLabel: string; /** * Placeholder text for Feedback email input */ - emailPlaceholder?: string; + emailPlaceholder: string; /** * Label for the message input */ - messageLabel?: string; + messageLabel: string; /** * Placeholder text for Feedback message input @@ -106,52 +106,52 @@ export interface FeedbackTextConfiguration { /** * Label for the name input */ - nameLabel?: string; + nameLabel: string; /** * Message after feedback was sent successfully */ - successMessageText?: string; + successMessageText: string; /** * Placeholder text for Feedback name input */ - namePlaceholder?: string; + namePlaceholder: string; /** * Text which indicates that a field is required */ - isRequiredLabel?: string; + isRequiredLabel: string; /** * The label for the button that adds a screenshot and renders the image editor */ - addScreenshotButtonLabel?: string; + addScreenshotButtonLabel: string; /** * The label for the button that removes a screenshot and hides the image editor */ - removeScreenshotButtonLabel?: string; + removeScreenshotButtonLabel: string; /** * The title of the error dialog */ - errorTitle?: string; + errorTitle: string; /** * The error message when the form is invalid */ - formError?: string; + formError: string; /** * The error message when the email is invalid */ - emailError?: string; + emailError: string; /** * Message when there is a generic error */ - genericError?: string; + genericError: string; } /** @@ -161,34 +161,34 @@ export interface FeedbackCallbacks { /** * Callback when form is opened */ - onFormOpen?: () => void; + onFormOpen: () => void; /** * Callback when form is closed and not submitted */ - onFormClose?: () => void; + onFormClose: () => void; /** * Callback when a screenshot is added */ - onAddScreenshot?: (addScreenshot: (uri: string) => void) => void; + onAddScreenshot: (addScreenshot: (uri: string) => void) => void; /** * Callback when feedback is successfully submitted * * After this you'll see a SuccessMessage on the screen for a moment. */ - onSubmitSuccess?: (data: FeedbackFormData) => void; + onSubmitSuccess: (data: FeedbackFormData) => void; /** * Callback when feedback is unsuccessfully submitted */ - onSubmitError?: (error: Error) => void; + onSubmitError: (error: Error) => void; /** * Callback when the feedback form is submitted successfully, and the SuccessMessage is complete, or dismissed */ - onFormSubmitted?: () => void; + onFormSubmitted: () => void; } /** diff --git a/packages/core/src/js/feedback/FeedbackWidgetManager.tsx b/packages/core/src/js/feedback/FeedbackWidgetManager.tsx index 856298382e..4f380842e3 100644 --- a/packages/core/src/js/feedback/FeedbackWidgetManager.tsx +++ b/packages/core/src/js/feedback/FeedbackWidgetManager.tsx @@ -15,6 +15,10 @@ const PULL_DOWN_CLOSE_THRESHOLD = 200; const SLIDE_ANIMATION_DURATION = 200; const BACKGROUND_ANIMATION_DURATION = 200; +const NOOP_SET_VISIBILITY = (): void => { + // No-op +}; + class FeedbackWidgetManager { private static _isVisible = false; private static _setVisibility: (visible: boolean) => void; @@ -28,22 +32,24 @@ class FeedbackWidgetManager { */ public static reset(): void { this._isVisible = false; - this._setVisibility = undefined; + this._setVisibility = NOOP_SET_VISIBILITY; } public static show(): void { - if (this._setVisibility) { + if (this._setVisibility !== NOOP_SET_VISIBILITY) { this._isVisible = true; this._setVisibility(true); } else { // This message should be always shown otherwise it's not possible to use the widget. // eslint-disable-next-line no-console - console.warn('[Sentry] FeedbackWidget requires `Sentry.wrap(RootComponent)` to be called before `showFeedbackWidget()`.'); + console.warn( + '[Sentry] FeedbackWidget requires `Sentry.wrap(RootComponent)` to be called before `showFeedbackWidget()`.', + ); } } public static hide(): void { - if (this._setVisibility) { + if (this._setVisibility !== NOOP_SET_VISIBILITY) { this._isVisible = false; this._setVisibility(false); } else { diff --git a/packages/core/src/js/feedback/defaults.ts b/packages/core/src/js/feedback/defaults.ts index 90d9534874..3152c769fb 100644 --- a/packages/core/src/js/feedback/defaults.ts +++ b/packages/core/src/js/feedback/defaults.ts @@ -19,7 +19,7 @@ const ADD_SCREENSHOT_LABEL = 'Add a screenshot'; const REMOVE_SCREENSHOT_LABEL = 'Remove screenshot'; const GENERIC_ERROR_TEXT = 'Unable to send feedback due to an unexpected error.'; -export const defaultConfiguration: Partial = { +export const defaultConfiguration: FeedbackWidgetProps = { // FeedbackCallbacks onFormOpen: () => { // Does nothing by default diff --git a/packages/core/src/js/feedback/integration.ts b/packages/core/src/js/feedback/integration.ts index 96280cf967..ad2bd0675b 100644 --- a/packages/core/src/js/feedback/integration.ts +++ b/packages/core/src/js/feedback/integration.ts @@ -8,7 +8,7 @@ type FeedbackIntegration = Integration & { options: Partial; }; -export const feedbackIntegration = (initOptions: FeedbackWidgetProps = {}): FeedbackIntegration => { +export const feedbackIntegration = (initOptions: Partial = {}): FeedbackIntegration => { return { name: MOBILE_FEEDBACK_INTEGRATION_NAME, options: initOptions, diff --git a/packages/core/src/js/feedback/utils.ts b/packages/core/src/js/feedback/utils.ts index 9c2826981d..05b8b471f1 100644 --- a/packages/core/src/js/feedback/utils.ts +++ b/packages/core/src/js/feedback/utils.ts @@ -15,7 +15,7 @@ declare global { */ export function isModalSupported(): boolean { const { major, minor } = ReactNativeLibraries.ReactNativeVersion?.version || {}; - return !(isFabricEnabled() && major === 0 && minor < 71); + return !(isFabricEnabled() && major === 0 && minor && minor < 71); } export const isValidEmail = (email: string): boolean => { diff --git a/packages/core/src/js/integrations/debugsymbolicator.ts b/packages/core/src/js/integrations/debugsymbolicator.ts index 3ff5bc5169..ed1c2901d3 100644 --- a/packages/core/src/js/integrations/debugsymbolicator.ts +++ b/packages/core/src/js/integrations/debugsymbolicator.ts @@ -131,7 +131,7 @@ async function convertReactNativeFramesToSentryFrames(frames: ReactNative.StackF * @param event Event * @param frames StackFrame[] */ -function replaceExceptionFramesInException(exception: Exception, frames: SentryStackFrame[]): void { +function replaceExceptionFramesInException(exception: Exception | undefined, frames: SentryStackFrame[]): void { if (exception?.stacktrace) { exception.stacktrace.frames = frames.reverse(); } diff --git a/packages/core/src/js/integrations/debugsymbolicatorutils.ts b/packages/core/src/js/integrations/debugsymbolicatorutils.ts index 2b51171b39..d99f72b261 100644 --- a/packages/core/src/js/integrations/debugsymbolicatorutils.ts +++ b/packages/core/src/js/integrations/debugsymbolicatorutils.ts @@ -20,7 +20,14 @@ export async function fetchSourceContext(frames: SentryStackFrame[]): Promise { }; async function processEvent(event: Event, hint: EventHint, client: ReactNativeClient): Promise { - const hasException = event.exception?.values?.length > 0; + const hasException = event.exception?.values && event.exception.values.length > 0; if (!hasException || client.getOptions().beforeScreenshot?.(event, hint) === false) { return event; } const screenshots: ScreenshotAttachment[] | null = await NATIVE.captureScreenshot(); - if (screenshots?.length > 0) { + if (screenshots && screenshots.length > 0) { hint.attachments = [...screenshots, ...(hint?.attachments || [])]; } diff --git a/packages/core/src/js/integrations/spotlight.ts b/packages/core/src/js/integrations/spotlight.ts index 50191aa132..90af07ccca 100644 --- a/packages/core/src/js/integrations/spotlight.ts +++ b/packages/core/src/js/integrations/spotlight.ts @@ -83,17 +83,23 @@ function sendEnvelopesToSidecar(client: Client, sidecarUrl: string): void { }); } +const DEFAULT_SIDECAR_URL = 'http://localhost:8969/stream'; + /** * Gets the default Spotlight sidecar URL. */ export function getDefaultSidecarUrl(): string { try { - const { url } = ReactNativeLibraries.Devtools?.getDevServer(); + const { url } = ReactNativeLibraries.Devtools?.getDevServer() ?? {}; + if (!url) { + return DEFAULT_SIDECAR_URL; + } + return `http://${getHostnameFromString(url)}:8969/stream`; } catch (_oO) { // We can't load devserver URL } - return 'http://localhost:8969/stream'; + return DEFAULT_SIDECAR_URL; } /** diff --git a/packages/core/src/js/integrations/viewhierarchy.ts b/packages/core/src/js/integrations/viewhierarchy.ts index d4d4530b15..c2c56fa4fc 100644 --- a/packages/core/src/js/integrations/viewhierarchy.ts +++ b/packages/core/src/js/integrations/viewhierarchy.ts @@ -21,7 +21,7 @@ export const viewHierarchyIntegration = (): Integration => { }; async function processEvent(event: Event, hint: EventHint): Promise { - const hasException = event.exception?.values?.length > 0; + const hasException = event.exception?.values && event.exception.values.length > 0; if (!hasException) { return event; } diff --git a/packages/core/src/js/profiling/convertHermesProfile.ts b/packages/core/src/js/profiling/convertHermesProfile.ts index 6db66dd6d5..241c6fd249 100644 --- a/packages/core/src/js/profiling/convertHermesProfile.ts +++ b/packages/core/src/js/profiling/convertHermesProfile.ts @@ -82,11 +82,21 @@ export function mapSamples( hermesStacks: Set; jsThreads: Set; } { + const samples: ThreadCpuSample[] = []; const jsThreads = new Set(); const hermesStacks = new Set(); - const start = Number(hermesSamples[0].ts); - const samples: ThreadCpuSample[] = []; + const firstSample = hermesSamples[0]; + if (!firstSample) { + logger.warn('[Profiling] No samples found in profile.'); + return { + samples, + hermesStacks, + jsThreads, + }; + } + + const start = Number(firstSample.ts); for (const hermesSample of hermesSamples) { jsThreads.add(hermesSample.tid); hermesStacks.add(hermesSample.sf); @@ -130,8 +140,12 @@ function mapFrames(hermesStackFrames: Record { - const hasException = event.exception?.values?.length > 0; + const hasException = event.exception?.values && event.exception.values.length > 0; if (!hasException) { // Event is not an error, will not capture replay return event; diff --git a/packages/core/src/js/scopeSync.ts b/packages/core/src/js/scopeSync.ts index bc9f20c597..bc69ffe86d 100644 --- a/packages/core/src/js/scopeSync.ts +++ b/packages/core/src/js/scopeSync.ts @@ -1,4 +1,5 @@ import type { Breadcrumb, Scope } from '@sentry/core'; +import { logger } from '@sentry/react'; import { DEFAULT_BREADCRUMB_LEVEL } from './breadcrumb'; import { fillTyped } from './utils/fill'; @@ -60,7 +61,11 @@ export function enableSyncToNative(scope: Scope): void { original.call(scope, mergedBreadcrumb, maxBreadcrumbs); const finalBreadcrumb = scope.getLastBreadcrumb(); - NATIVE.addBreadcrumb(finalBreadcrumb); + if (finalBreadcrumb) { + NATIVE.addBreadcrumb(finalBreadcrumb); + } else { + logger.warn('[ScopeSync] Last created breadcrumb is undefined. Skipping sync to native.'); + } return scope; }); diff --git a/packages/core/src/js/sdk.tsx b/packages/core/src/js/sdk.tsx index dd6a176322..c50f1c35f1 100644 --- a/packages/core/src/js/sdk.tsx +++ b/packages/core/src/js/sdk.tsx @@ -63,7 +63,7 @@ export function init(passedOptions: ReactNativeOptions): void { enableSyncToNative(getIsolationScope()); } - const getURLFromDSN = (dsn: string | null): string | undefined => { + const getURLFromDSN = (dsn: string | undefined): string | undefined => { if (!dsn) { return undefined; } diff --git a/packages/core/src/js/touchevents.tsx b/packages/core/src/js/touchevents.tsx index 293391a834..2280597264 100644 --- a/packages/core/src/js/touchevents.tsx +++ b/packages/core/src/js/touchevents.tsx @@ -121,8 +121,12 @@ class TouchEventBoundary extends React.Component { const level = 'info' as SeverityLevel; const root = touchPath[0]; - const detail = label ? label : `${root.name}${root.file ? ` (${root.file})` : ''}`; + if (!root) { + logger.warn('[TouchEvents] No root component found in touch path.'); + return; + } + const detail = label ? label : `${root.name}${root.file ? ` (${root.file})` : ''}`; const crumb = { category: this.props.breadcrumbCategory, data: { path: touchPath }, diff --git a/packages/core/src/js/tracing/integrations/appStart.ts b/packages/core/src/js/tracing/integrations/appStart.ts index acd5096bc4..aa2ce1c70e 100644 --- a/packages/core/src/js/tracing/integrations/appStart.ts +++ b/packages/core/src/js/tracing/integrations/appStart.ts @@ -211,6 +211,13 @@ export const appStartIntegration = ({ }; async function captureStandaloneAppStart(): Promise { + if (!_client) { + // If client is not set, SDK was not initialized, logger is thus disabled + // eslint-disable-next-line no-console + console.warn('[AppStart] Could not capture App Start, missing client, call `Sentry.init` first.'); + return; + } + if (!standalone) { logger.debug( '[AppStart] App start tracking is enabled. App start will be added to the first transaction as a child span.', diff --git a/packages/core/src/js/tracing/integrations/nativeFrames.ts b/packages/core/src/js/tracing/integrations/nativeFrames.ts index ceec914b88..e5dd20f98a 100644 --- a/packages/core/src/js/tracing/integrations/nativeFrames.ts +++ b/packages/core/src/js/tracing/integrations/nativeFrames.ts @@ -60,7 +60,7 @@ export const createNativeFramesIntegrations = (enable: boolean | undefined): Int export const nativeFramesIntegration = (): Integration => { /** The native frames at the finish time of the most recent span. */ let _lastChildSpanEndFrames: NativeFramesResponseWithTimestamp | null = null; - const _spanToNativeFramesAtStartMap: AsyncExpiringMap = new AsyncExpiringMap({ + const _spanToNativeFramesAtStartMap: AsyncExpiringMap = new AsyncExpiringMap({ ttl: START_FRAMES_TIMEOUT_MS, }); const _spanToNativeFramesAtEndMap: AsyncExpiringMap = diff --git a/packages/core/src/js/tracing/integrations/timeToDisplayIntegration.ts b/packages/core/src/js/tracing/integrations/timeToDisplayIntegration.ts index 6f48b4b877..8b5d0ffc66 100644 --- a/packages/core/src/js/tracing/integrations/timeToDisplayIntegration.ts +++ b/packages/core/src/js/tracing/integrations/timeToDisplayIntegration.ts @@ -30,7 +30,7 @@ export const timeToDisplayIntegration = (): Integration => { return event; } - const rootSpanId = event.contexts.trace.span_id; + const rootSpanId = event.contexts?.trace?.span_id; if (!rootSpanId) { logger.warn(`[${INTEGRATION_NAME}] No root span id found in transaction.`); return event; @@ -64,7 +64,9 @@ export const timeToDisplayIntegration = (): Integration => { if (ttfdSpan?.start_timestamp && ttfdSpan?.timestamp) { const durationMs = (ttfdSpan.timestamp - ttfdSpan.start_timestamp) * 1000; if (isDeadlineExceeded(durationMs)) { - event.measurements['time_to_full_display'] = event.measurements['time_to_initial_display']; + if (event.measurements['time_to_initial_display']) { + event.measurements['time_to_full_display'] = event.measurements['time_to_initial_display']; + } } else { event.measurements['time_to_full_display'] = { value: durationMs, @@ -100,6 +102,8 @@ async function addTimeToInitialDisplay({ }): Promise { const ttidEndTimestampSeconds = await NATIVE.popTimeToDisplayFor(`ttid-${rootSpanId}`); + event.spans = event.spans || []; + let ttidSpan: SpanJSON | undefined = event.spans?.find(span => span.op === UI_LOAD_INITIAL_DISPLAY); if (ttidSpan && (ttidSpan.status === undefined || ttidSpan.status === 'ok') && !ttidEndTimestampSeconds) { @@ -204,11 +208,13 @@ async function addTimeToFullDisplay({ return undefined; } + event.spans = event.spans || []; + let ttfdSpan = event.spans?.find(span => span.op === UI_LOAD_FULL_DISPLAY); let ttfdAdjustedEndTimestampSeconds = ttfdEndTimestampSeconds; - const ttfdIsBeforeTtid = ttidSpan?.timestamp && ttfdEndTimestampSeconds < ttidSpan.timestamp; - if (ttfdIsBeforeTtid) { + const ttfdIsBeforeTtid = ttidSpan.timestamp && ttfdEndTimestampSeconds < ttidSpan.timestamp; + if (ttfdIsBeforeTtid && ttidSpan.timestamp) { ttfdAdjustedEndTimestampSeconds = ttidSpan.timestamp; } diff --git a/packages/core/src/js/tracing/onSpanEndUtils.ts b/packages/core/src/js/tracing/onSpanEndUtils.ts index 4ae3c027f6..cffa13a852 100644 --- a/packages/core/src/js/tracing/onSpanEndUtils.ts +++ b/packages/core/src/js/tracing/onSpanEndUtils.ts @@ -44,7 +44,7 @@ export const adjustTransactionDuration = (client: Client, span: Span, maxDuratio }); }; -export const ignoreEmptyBackNavigation = (client: Client | undefined, span: Span): void => { +export const ignoreEmptyBackNavigation = (client: Client | undefined, span: Span | undefined): void => { if (!client) { logger.warn('Could not hook on spanEnd event because client is not defined.'); return; diff --git a/packages/core/src/js/tracing/reactnavigation.ts b/packages/core/src/js/tracing/reactnavigation.ts index 8528466d11..feebb3b9f7 100644 --- a/packages/core/src/js/tracing/reactnavigation.ts +++ b/packages/core/src/js/tracing/reactnavigation.ts @@ -220,6 +220,7 @@ export const reactNavigationIntegration = ({ const navigationActionType = useDispatchedActionData ? event?.data.action.type : undefined; if ( useDispatchedActionData && + navigationActionType && [ // Process common actions 'PRELOAD', @@ -252,12 +253,12 @@ export const reactNavigationIntegration = ({ ignoreEmptyBackNavigation(getClient(), latestNavigationSpan); } - if (enableTimeToInitialDisplay) { - NATIVE.setActiveSpanId(latestNavigationSpan?.spanContext().spanId); + if (enableTimeToInitialDisplay && latestNavigationSpan) { + NATIVE.setActiveSpanId(latestNavigationSpan.spanContext().spanId); navigationProcessingSpan = startInactiveSpan({ op: 'navigation.processing', name: 'Navigation dispatch to navigation cancelled or screen mounted', - startTime: latestNavigationSpan && spanToJSON(latestNavigationSpan).start_timestamp, + startTime: spanToJSON(latestNavigationSpan).start_timestamp, }); navigationProcessingSpan.setAttribute( SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, diff --git a/packages/core/src/js/tracing/span.ts b/packages/core/src/js/tracing/span.ts index c4b89b8d74..4e3a70ff66 100644 --- a/packages/core/src/js/tracing/span.ts +++ b/packages/core/src/js/tracing/span.ts @@ -127,7 +127,7 @@ export function getDefaultIdleNavigationSpanOptions(): StartSpanOptions { * Checks if the span is a Sentry User Interaction span. */ export function isSentryInteractionSpan(span: Span): boolean { - return [SPAN_ORIGIN_AUTO_INTERACTION, SPAN_ORIGIN_MANUAL_INTERACTION].includes(spanToJSON(span).origin); + return [SPAN_ORIGIN_AUTO_INTERACTION, SPAN_ORIGIN_MANUAL_INTERACTION].includes(spanToJSON(span).origin || ''); } export const SCOPE_SPAN_FIELD = '_sentrySpan'; diff --git a/packages/core/src/js/tracing/timeToDisplayFallback.ts b/packages/core/src/js/tracing/timeToDisplayFallback.ts index cd71b21df0..e854d43477 100644 --- a/packages/core/src/js/tracing/timeToDisplayFallback.ts +++ b/packages/core/src/js/tracing/timeToDisplayFallback.ts @@ -13,6 +13,6 @@ export const addTimeToInitialDisplayFallback = ( spanIdToTimeToInitialDisplayFallback.set(spanId, timestampSeconds); }; -export const getTimeToInitialDisplayFallback = async (spanId: string): Promise => { +export const getTimeToInitialDisplayFallback = async (spanId: string): Promise => { return spanIdToTimeToInitialDisplayFallback.get(spanId); }; diff --git a/packages/core/src/js/transports/encodePolyfill.ts b/packages/core/src/js/transports/encodePolyfill.ts index 102d1886fb..e9244d562a 100644 --- a/packages/core/src/js/transports/encodePolyfill.ts +++ b/packages/core/src/js/transports/encodePolyfill.ts @@ -1,16 +1,12 @@ import { getMainCarrier, SDK_VERSION } from '@sentry/core'; -import type { RN_GLOBAL_OBJ } from '../utils/worldwide'; import { utf8ToBytes } from '../vendor'; export const useEncodePolyfill = (): void => { - const globalCarriers = getMainCarrier().__SENTRY__; - - if (!globalCarriers) { - (globalCarriers as Partial<(typeof RN_GLOBAL_OBJ)['__SENTRY__']>) = {}; - } - - globalCarriers[SDK_VERSION].encodePolyfill = encodePolyfill; + // Based on https://github.com/getsentry/sentry-javascript/blob/f0fc41f6166857cd97a695f5cc9a18caf6a0bf43/packages/core/src/carrier.ts#L49 + const carrier = getMainCarrier(); + const __SENTRY__ = (carrier.__SENTRY__ = carrier.__SENTRY__ || {}); + (__SENTRY__[SDK_VERSION] = __SENTRY__[SDK_VERSION] || {}).encodePolyfill = encodePolyfill; }; export const encodePolyfill = (text: string): Uint8Array => { diff --git a/packages/core/src/js/utils/AsyncExpiringMap.ts b/packages/core/src/js/utils/AsyncExpiringMap.ts index 657286ed26..bba14e01f1 100644 --- a/packages/core/src/js/utils/AsyncExpiringMap.ts +++ b/packages/core/src/js/utils/AsyncExpiringMap.ts @@ -7,7 +7,7 @@ export class AsyncExpiringMap { private _ttl: number; private _cleanupIntervalMs: number; private _map: Map | null }>; - private _cleanupInterval: ReturnType; + private _cleanupInterval: ReturnType | undefined; public constructor({ cleanupInterval = 5_000, @@ -30,7 +30,7 @@ export class AsyncExpiringMap { this.startCleanup(); } - if (typeof promise !== 'object' || !('then' in promise)) { + if (typeof promise !== 'object' || !promise || !('then' in promise)) { this._map.set(key, { value: promise, expiresAt: Date.now() + this._ttl, promise: null }); return; } @@ -143,7 +143,9 @@ export class AsyncExpiringMap { * Clear all entries. */ public clear(): void { - clearInterval(this._cleanupInterval); + if (this._cleanupInterval) { + clearInterval(this._cleanupInterval); + } this._map.clear(); } @@ -151,7 +153,9 @@ export class AsyncExpiringMap { * Stop the cleanup interval. */ public stopCleanup(): void { - clearInterval(this._cleanupInterval); + if (this._cleanupInterval) { + clearInterval(this._cleanupInterval); + } } /** diff --git a/packages/core/src/js/vendor/base64-js/fromByteArray.ts b/packages/core/src/js/vendor/base64-js/fromByteArray.ts index 51c046b0a4..11c771f1d5 100644 --- a/packages/core/src/js/vendor/base64-js/fromByteArray.ts +++ b/packages/core/src/js/vendor/base64-js/fromByteArray.ts @@ -28,10 +28,12 @@ const lookup: string[] = []; const code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; for (let i = 0, len = code.length; i < len; ++i) { + // @ts-expect-error lookup[i] = code[i]; } function tripletToBase64(num: number): string { + // @ts-expect-error return lookup[(num >> 18) & 0x3f] + lookup[(num >> 12) & 0x3f] + lookup[(num >> 6) & 0x3f] + lookup[num & 0x3f]; } @@ -39,6 +41,7 @@ function encodeChunk(uint8: Uint8Array | number[], start: number, end: number): let tmp; const output = []; for (let i = start; i < end; i += 3) { + // @ts-expect-error tmp = ((uint8[i] << 16) & 0xff0000) + ((uint8[i + 1] << 8) & 0xff00) + (uint8[i + 2] & 0xff); output.push(tripletToBase64(tmp)); } @@ -63,9 +66,12 @@ export function base64StringFromByteArray(uint8: Uint8Array | number[]): string // pad the end with zeros, but make sure to not forget the extra bytes if (extraBytes === 1) { tmp = uint8[len - 1]; + // @ts-expect-error parts.push(`${lookup[tmp >> 2] + lookup[(tmp << 4) & 0x3f]}==`); } else if (extraBytes === 2) { + // @ts-expect-error tmp = (uint8[len - 2] << 8) + uint8[len - 1]; + // @ts-expect-error parts.push(`${lookup[tmp >> 10] + lookup[(tmp >> 4) & 0x3f] + lookup[(tmp << 2) & 0x3f]}=`); } diff --git a/packages/core/src/js/wrapper.ts b/packages/core/src/js/wrapper.ts index 31152c02b2..289af29923 100644 --- a/packages/core/src/js/wrapper.ts +++ b/packages/core/src/js/wrapper.ts @@ -729,7 +729,7 @@ export const NATIVE: SentryNativeWrapper = { return RNSentry.popTimeToDisplayFor(key); } catch (error) { logger.error('Error:', error); - return null; + return Promise.resolve(null); } }, diff --git a/packages/core/tsconfig.build.json b/packages/core/tsconfig.build.json index b5b1127a11..90d9d08e63 100644 --- a/packages/core/tsconfig.build.json +++ b/packages/core/tsconfig.build.json @@ -26,7 +26,6 @@ "module": "es6", "skipLibCheck": true, "strictBindCallApply": true, - "strictNullChecks": false, "importHelpers": false } }