Skip to content

Commit 391dc92

Browse files
chore(typescript): Enable strictNullChecks for the SDK source (#4760)
1 parent 261f6a9 commit 391dc92

30 files changed

+176
-101
lines changed

packages/core/src/js/feedback/FeedbackWidget.tsx

+18-20
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,7 @@ import { base64ToUint8Array, feedbackAlertDialog, isValidEmail } from './utils'
2626
* Implements a feedback form screen that sends feedback to Sentry using Sentry.captureFeedback.
2727
*/
2828
export class FeedbackWidget extends React.Component<FeedbackWidgetProps, FeedbackWidgetState> {
29-
public static defaultProps: Partial<FeedbackWidgetProps> = {
30-
...defaultConfiguration
31-
}
29+
public static defaultProps = defaultConfiguration;
3230

3331
private static _savedState: Omit<FeedbackWidgetState, 'isVisible'> = {
3432
name: '',
@@ -67,7 +65,7 @@ export class FeedbackWidget extends React.Component<FeedbackWidgetProps, Feedbac
6765
public handleFeedbackSubmit: () => void = () => {
6866
const { name, email, description } = this.state;
6967
const { onSubmitSuccess, onSubmitError, onFormSubmitted } = this.props;
70-
const text: FeedbackTextConfiguration = this.props;
68+
const text = this.props;
7169

7270
const trimmedName = name?.trim();
7371
const trimmedEmail = email?.trim();
@@ -119,14 +117,14 @@ export class FeedbackWidget extends React.Component<FeedbackWidgetProps, Feedbac
119117

120118
public onScreenshotButtonPress: () => void = async () => {
121119
if (!this.state.filename && !this.state.attachment) {
122-
const imagePickerConfiguration: ImagePickerConfiguration = this.props;
123-
if (imagePickerConfiguration.imagePicker) {
124-
const launchImageLibrary = imagePickerConfiguration.imagePicker.launchImageLibraryAsync
120+
const { imagePicker } = this.props;
121+
if (imagePicker) {
122+
const launchImageLibrary = imagePicker.launchImageLibraryAsync
125123
// expo-image-picker library is available
126-
? () => imagePickerConfiguration.imagePicker.launchImageLibraryAsync({ mediaTypes: ['images'], base64: isWeb() })
124+
? () => imagePicker.launchImageLibraryAsync?.({ mediaTypes: ['images'], base64: isWeb() })
127125
// react-native-image-picker library is available
128-
: imagePickerConfiguration.imagePicker.launchImageLibrary
129-
? () => imagePickerConfiguration.imagePicker.launchImageLibrary({ mediaType: 'photo', includeBase64: isWeb() })
126+
: imagePicker.launchImageLibrary
127+
? () => imagePicker.launchImageLibrary?.({ mediaType: 'photo', includeBase64: isWeb() })
130128
: null;
131129
if (!launchImageLibrary) {
132130
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<FeedbackWidgetProps, Feedbac
140138
}
141139

142140
const result = await launchImageLibrary();
143-
if (result.assets && result.assets.length > 0) {
141+
if (result?.assets && result.assets.length > 0) {
144142
if (isWeb()) {
145-
const filename = result.assets[0].fileName;
146-
const imageUri = result.assets[0].uri;
147-
const base64 = result.assets[0].base64;
148-
const data = base64ToUint8Array(base64);
149-
if (data != null) {
143+
const filename = result.assets[0]?.fileName;
144+
const imageUri = result.assets[0]?.uri;
145+
const base64 = result.assets[0]?.base64;
146+
const data = base64 ? base64ToUint8Array(base64) : undefined;
147+
if (data) {
150148
this.setState({ filename, attachment: data, attachmentUri: imageUri });
151149
} else {
152150
logger.error('Failed to read image data on the web');
153151
}
154152
} else {
155-
const filename = result.assets[0].fileName;
156-
const imageUri = result.assets[0].uri;
157-
getDataFromUri(imageUri).then((data) => {
158-
if (data != null) {
153+
const filename = result.assets[0]?.fileName;
154+
const imageUri = result.assets[0]?.uri;
155+
imageUri && getDataFromUri(imageUri).then((data) => {
156+
if (data) {
159157
this.setState({ filename, attachment: data, attachmentUri: imageUri });
160158
} else {
161159
logger.error('Failed to read image data from uri:', imageUri);

packages/core/src/js/feedback/FeedbackWidget.types.ts

+29-29
Original file line numberDiff line numberDiff line change
@@ -21,38 +21,38 @@ export interface FeedbackGeneralConfiguration {
2121
*
2222
* @default true
2323
*/
24-
showBranding?: boolean;
24+
showBranding: boolean;
2525

2626
/**
2727
* Should the email field be required?
2828
*/
29-
isEmailRequired?: boolean;
29+
isEmailRequired: boolean;
3030

3131
/**
3232
* Should the email field be validated?
3333
*/
34-
shouldValidateEmail?: boolean;
34+
shouldValidateEmail: boolean;
3535

3636
/**
3737
* Should the name field be required?
3838
*/
39-
isNameRequired?: boolean;
39+
isNameRequired: boolean;
4040

4141
/**
4242
* Should the email input field be visible? Note: email will still be collected if set via `Sentry.setUser()`
4343
*/
44-
showEmail?: boolean;
44+
showEmail: boolean;
4545

4646
/**
4747
* Should the name input field be visible? Note: name will still be collected if set via `Sentry.setUser()`
4848
*/
49-
showName?: boolean;
49+
showName: boolean;
5050

5151
/**
5252
* This flag determines whether the "Add Screenshot" button is displayed
5353
* @default false
5454
*/
55-
enableScreenshot?: boolean;
55+
enableScreenshot: boolean;
5656

5757
/**
5858
* Fill in email/name input fields with Sentry user context if it exists.
@@ -71,32 +71,32 @@ export interface FeedbackTextConfiguration {
7171
/**
7272
* The label for the Feedback form cancel button that closes dialog
7373
*/
74-
cancelButtonLabel?: string;
74+
cancelButtonLabel: string;
7575

7676
/**
7777
* The label for the Feedback form submit button that sends feedback
7878
*/
79-
submitButtonLabel?: string;
79+
submitButtonLabel: string;
8080

8181
/**
8282
* The title of the Feedback form
8383
*/
84-
formTitle?: string;
84+
formTitle: string;
8585

8686
/**
8787
* Label for the email input
8888
*/
89-
emailLabel?: string;
89+
emailLabel: string;
9090

9191
/**
9292
* Placeholder text for Feedback email input
9393
*/
94-
emailPlaceholder?: string;
94+
emailPlaceholder: string;
9595

9696
/**
9797
* Label for the message input
9898
*/
99-
messageLabel?: string;
99+
messageLabel: string;
100100

101101
/**
102102
* Placeholder text for Feedback message input
@@ -106,52 +106,52 @@ export interface FeedbackTextConfiguration {
106106
/**
107107
* Label for the name input
108108
*/
109-
nameLabel?: string;
109+
nameLabel: string;
110110

111111
/**
112112
* Message after feedback was sent successfully
113113
*/
114-
successMessageText?: string;
114+
successMessageText: string;
115115

116116
/**
117117
* Placeholder text for Feedback name input
118118
*/
119-
namePlaceholder?: string;
119+
namePlaceholder: string;
120120

121121
/**
122122
* Text which indicates that a field is required
123123
*/
124-
isRequiredLabel?: string;
124+
isRequiredLabel: string;
125125

126126
/**
127127
* The label for the button that adds a screenshot and renders the image editor
128128
*/
129-
addScreenshotButtonLabel?: string;
129+
addScreenshotButtonLabel: string;
130130

131131
/**
132132
* The label for the button that removes a screenshot and hides the image editor
133133
*/
134-
removeScreenshotButtonLabel?: string;
134+
removeScreenshotButtonLabel: string;
135135

136136
/**
137137
* The title of the error dialog
138138
*/
139-
errorTitle?: string;
139+
errorTitle: string;
140140

141141
/**
142142
* The error message when the form is invalid
143143
*/
144-
formError?: string;
144+
formError: string;
145145

146146
/**
147147
* The error message when the email is invalid
148148
*/
149-
emailError?: string;
149+
emailError: string;
150150

151151
/**
152152
* Message when there is a generic error
153153
*/
154-
genericError?: string;
154+
genericError: string;
155155
}
156156

157157
/**
@@ -161,34 +161,34 @@ export interface FeedbackCallbacks {
161161
/**
162162
* Callback when form is opened
163163
*/
164-
onFormOpen?: () => void;
164+
onFormOpen: () => void;
165165

166166
/**
167167
* Callback when form is closed and not submitted
168168
*/
169-
onFormClose?: () => void;
169+
onFormClose: () => void;
170170

171171
/**
172172
* Callback when a screenshot is added
173173
*/
174-
onAddScreenshot?: (addScreenshot: (uri: string) => void) => void;
174+
onAddScreenshot: (addScreenshot: (uri: string) => void) => void;
175175

176176
/**
177177
* Callback when feedback is successfully submitted
178178
*
179179
* After this you'll see a SuccessMessage on the screen for a moment.
180180
*/
181-
onSubmitSuccess?: (data: FeedbackFormData) => void;
181+
onSubmitSuccess: (data: FeedbackFormData) => void;
182182

183183
/**
184184
* Callback when feedback is unsuccessfully submitted
185185
*/
186-
onSubmitError?: (error: Error) => void;
186+
onSubmitError: (error: Error) => void;
187187

188188
/**
189189
* Callback when the feedback form is submitted successfully, and the SuccessMessage is complete, or dismissed
190190
*/
191-
onFormSubmitted?: () => void;
191+
onFormSubmitted: () => void;
192192
}
193193

194194
/**

packages/core/src/js/feedback/FeedbackWidgetManager.tsx

+10-4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ const PULL_DOWN_CLOSE_THRESHOLD = 200;
1515
const SLIDE_ANIMATION_DURATION = 200;
1616
const BACKGROUND_ANIMATION_DURATION = 200;
1717

18+
const NOOP_SET_VISIBILITY = (): void => {
19+
// No-op
20+
};
21+
1822
class FeedbackWidgetManager {
1923
private static _isVisible = false;
2024
private static _setVisibility: (visible: boolean) => void;
@@ -28,22 +32,24 @@ class FeedbackWidgetManager {
2832
*/
2933
public static reset(): void {
3034
this._isVisible = false;
31-
this._setVisibility = undefined;
35+
this._setVisibility = NOOP_SET_VISIBILITY;
3236
}
3337

3438
public static show(): void {
35-
if (this._setVisibility) {
39+
if (this._setVisibility !== NOOP_SET_VISIBILITY) {
3640
this._isVisible = true;
3741
this._setVisibility(true);
3842
} else {
3943
// This message should be always shown otherwise it's not possible to use the widget.
4044
// eslint-disable-next-line no-console
41-
console.warn('[Sentry] FeedbackWidget requires `Sentry.wrap(RootComponent)` to be called before `showFeedbackWidget()`.');
45+
console.warn(
46+
'[Sentry] FeedbackWidget requires `Sentry.wrap(RootComponent)` to be called before `showFeedbackWidget()`.',
47+
);
4248
}
4349
}
4450

4551
public static hide(): void {
46-
if (this._setVisibility) {
52+
if (this._setVisibility !== NOOP_SET_VISIBILITY) {
4753
this._isVisible = false;
4854
this._setVisibility(false);
4955
} else {

packages/core/src/js/feedback/defaults.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const ADD_SCREENSHOT_LABEL = 'Add a screenshot';
1919
const REMOVE_SCREENSHOT_LABEL = 'Remove screenshot';
2020
const GENERIC_ERROR_TEXT = 'Unable to send feedback due to an unexpected error.';
2121

22-
export const defaultConfiguration: Partial<FeedbackWidgetProps> = {
22+
export const defaultConfiguration: FeedbackWidgetProps = {
2323
// FeedbackCallbacks
2424
onFormOpen: () => {
2525
// Does nothing by default

packages/core/src/js/feedback/integration.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ type FeedbackIntegration = Integration & {
88
options: Partial<FeedbackWidgetProps>;
99
};
1010

11-
export const feedbackIntegration = (initOptions: FeedbackWidgetProps = {}): FeedbackIntegration => {
11+
export const feedbackIntegration = (initOptions: Partial<FeedbackWidgetProps> = {}): FeedbackIntegration => {
1212
return {
1313
name: MOBILE_FEEDBACK_INTEGRATION_NAME,
1414
options: initOptions,

packages/core/src/js/feedback/utils.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ declare global {
1515
*/
1616
export function isModalSupported(): boolean {
1717
const { major, minor } = ReactNativeLibraries.ReactNativeVersion?.version || {};
18-
return !(isFabricEnabled() && major === 0 && minor < 71);
18+
return !(isFabricEnabled() && major === 0 && minor && minor < 71);
1919
}
2020

2121
export const isValidEmail = (email: string): boolean => {

packages/core/src/js/integrations/debugsymbolicator.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ async function convertReactNativeFramesToSentryFrames(frames: ReactNative.StackF
131131
* @param event Event
132132
* @param frames StackFrame[]
133133
*/
134-
function replaceExceptionFramesInException(exception: Exception, frames: SentryStackFrame[]): void {
134+
function replaceExceptionFramesInException(exception: Exception | undefined, frames: SentryStackFrame[]): void {
135135
if (exception?.stacktrace) {
136136
exception.stacktrace.frames = frames.reverse();
137137
}

packages/core/src/js/integrations/debugsymbolicatorutils.ts

+16-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,14 @@ export async function fetchSourceContext(frames: SentryStackFrame[]): Promise<Se
2020
return;
2121
}
2222

23-
xhr.open('POST', getSentryMetroSourceContextUrl(), true);
23+
const url = getSentryMetroSourceContextUrl();
24+
if (!url) {
25+
logger.error('Could not fetch source context. No dev server URL found.');
26+
resolve(frames);
27+
return;
28+
}
29+
30+
xhr.open('POST', url, true);
2431
xhr.setRequestHeader('Content-Type', 'application/json');
2532
xhr.send(JSON.stringify({ stack: frames }));
2633

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

55-
function getSentryMetroSourceContextUrl(): string {
56-
return `${getDevServer().url}__sentry/context`;
62+
function getSentryMetroSourceContextUrl(): string | undefined {
63+
const devServer = getDevServer();
64+
65+
if (!devServer) {
66+
return undefined;
67+
}
68+
69+
return `${devServer.url}__sentry/context`;
5770
}
5871

5972
/**

packages/core/src/js/integrations/default.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,8 @@ export function getDefaultIntegrations(options: ReactNativeClientOptions): Integ
142142

143143
if (!hasReplayOptions && hasExperimentsReplayOptions) {
144144
// Remove in the next major version (v7)
145-
options.replaysOnErrorSampleRate = options._experiments.replaysOnErrorSampleRate;
146-
options.replaysSessionSampleRate = options._experiments.replaysSessionSampleRate;
145+
options.replaysOnErrorSampleRate = options._experiments?.replaysOnErrorSampleRate;
146+
options.replaysSessionSampleRate = options._experiments?.replaysSessionSampleRate;
147147
}
148148

149149
if ((hasReplayOptions || hasExperimentsReplayOptions) && notWeb()) {

packages/core/src/js/integrations/screenshot.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ export const screenshotIntegration = (): Integration => {
1818
};
1919

2020
async function processEvent(event: Event, hint: EventHint, client: ReactNativeClient): Promise<Event> {
21-
const hasException = event.exception?.values?.length > 0;
21+
const hasException = event.exception?.values && event.exception.values.length > 0;
2222
if (!hasException || client.getOptions().beforeScreenshot?.(event, hint) === false) {
2323
return event;
2424
}
2525

2626
const screenshots: ScreenshotAttachment[] | null = await NATIVE.captureScreenshot();
27-
if (screenshots?.length > 0) {
27+
if (screenshots && screenshots.length > 0) {
2828
hint.attachments = [...screenshots, ...(hint?.attachments || [])];
2929
}
3030

0 commit comments

Comments
 (0)