Skip to content

typescript: Enable strictNullChecks #4760

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: v7
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 19 additions & 21 deletions packages/core/src/js/feedback/FeedbackWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<FeedbackWidgetProps, FeedbackWidgetState> {
public static defaultProps: Partial<FeedbackWidgetProps> = {
...defaultConfiguration
}
public static defaultProps = defaultConfiguration;

private static _savedState: Omit<FeedbackWidgetState, 'isVisible'> = {
name: '',
Expand Down Expand Up @@ -67,14 +65,14 @@ export class FeedbackWidget extends React.Component<FeedbackWidgetProps, Feedbac
public handleFeedbackSubmit: () => 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();
const trimmedDescription = description?.trim();

if ((this.props.isNameRequired && !trimmedName) || (this.props.isEmailRequired && !trimmedEmail) || !trimmedDescription) {
feedbackAlertDialog(text.errorTitle, text.formError);
feedbackAlertDialog(text.errorTitle ?? defaultConfiguration.errorTitle, text.formError ?? defaultConfiguration.formError);
return;
}

Expand Down Expand Up @@ -119,14 +117,14 @@ export class FeedbackWidget extends React.Component<FeedbackWidgetProps, Feedbac

public onScreenshotButtonPress: () => 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.');
Expand All @@ -140,22 +138,22 @@ export class FeedbackWidget extends React.Component<FeedbackWidgetProps, Feedbac
}

const result = await launchImageLibrary();
if (result.assets && result.assets.length > 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);
Expand Down
58 changes: 29 additions & 29 deletions packages/core/src/js/feedback/FeedbackWidget.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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;
}

/**
Expand All @@ -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;
}

/**
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/js/feedback/FeedbackWidgetManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ class FeedbackWidgetManager {
*/
public static reset(): void {
this._isVisible = false;
this._setVisibility = undefined;
this._setVisibility = () => {
// No-op
};
}

public static show(): void {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/js/feedback/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<FeedbackWidgetProps> = {
export const defaultConfiguration: FeedbackWidgetProps = {
// FeedbackCallbacks
onFormOpen: () => {
// Does nothing by default
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/js/feedback/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ type FeedbackIntegration = Integration & {
options: Partial<FeedbackWidgetProps>;
};

export const feedbackIntegration = (initOptions: FeedbackWidgetProps = {}): FeedbackIntegration => {
export const feedbackIntegration = (initOptions: Partial<FeedbackWidgetProps> = {}): FeedbackIntegration => {
return {
name: MOBILE_FEEDBACK_INTEGRATION_NAME,
options: initOptions,
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/js/feedback/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/js/integrations/debugsymbolicator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
19 changes: 16 additions & 3 deletions packages/core/src/js/integrations/debugsymbolicatorutils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,14 @@ export async function fetchSourceContext(frames: SentryStackFrame[]): Promise<Se
return;
}

xhr.open('POST', getSentryMetroSourceContextUrl(), true);
const url = getSentryMetroSourceContextUrl();
if (!url) {
logger.error('Could not fetch source context. No dev server URL found.');
resolve(frames);
return;
}

xhr.open('POST', url, true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify({ stack: frames }));

Expand Down Expand Up @@ -52,8 +59,14 @@ export async function fetchSourceContext(frames: SentryStackFrame[]): Promise<Se
});
}

function getSentryMetroSourceContextUrl(): string {
return `${getDevServer().url}__sentry/context`;
function getSentryMetroSourceContextUrl(): string | undefined {
const devServer = getDevServer();

if (!devServer) {
return undefined;
}

return `${devServer.url}__sentry/context`;
}

/**
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/js/integrations/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ export function getDefaultIntegrations(options: ReactNativeClientOptions): Integ

if (!hasReplayOptions && hasExperimentsReplayOptions) {
// Remove in the next major version (v7)
options.replaysOnErrorSampleRate = options._experiments.replaysOnErrorSampleRate;
options.replaysSessionSampleRate = options._experiments.replaysSessionSampleRate;
options.replaysOnErrorSampleRate = options._experiments?.replaysOnErrorSampleRate;
options.replaysSessionSampleRate = options._experiments?.replaysSessionSampleRate;
}

if ((hasReplayOptions || hasExperimentsReplayOptions) && notWeb()) {
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/js/integrations/screenshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ export const screenshotIntegration = (): Integration => {
};

async function processEvent(event: Event, hint: EventHint, client: ReactNativeClient): Promise<Event> {
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 || [])];
}

Expand Down
Loading
Loading