@sentry/react-native
Version:
Official Sentry SDK for react-native
249 lines • 14.1 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { captureFeedback, getCurrentScope, lastEventId, logger } from '@sentry/core';
import * as React from 'react';
import { Image, Keyboard, Text, TextInput, TouchableOpacity, TouchableWithoutFeedback, View } from 'react-native';
import { isWeb, notWeb } from '../utils/environment';
import { NATIVE } from '../wrapper';
import { sentryLogo } from './branding';
import { defaultConfiguration } from './defaults';
import defaultStyles from './FeedbackWidget.styles';
import { lazyLoadFeedbackIntegration } from './lazy';
import { base64ToUint8Array, feedbackAlertDialog, isValidEmail } from './utils';
/**
* @beta
* Implements a feedback form screen that sends feedback to Sentry using Sentry.captureFeedback.
*/
export class FeedbackWidget extends React.Component {
constructor(props) {
var _a, _b, _c, _d, _e, _f, _g, _h;
super(props);
this._didSubmitForm = false;
this.handleFeedbackSubmit = () => {
const { name, email, description } = this.state;
const { onSubmitSuccess, onSubmitError, onFormSubmitted } = this.props;
const text = this.props;
const trimmedName = name === null || name === void 0 ? void 0 : name.trim();
const trimmedEmail = email === null || email === void 0 ? void 0 : email.trim();
const trimmedDescription = description === null || description === void 0 ? void 0 : description.trim();
if ((this.props.isNameRequired && !trimmedName) || (this.props.isEmailRequired && !trimmedEmail) || !trimmedDescription) {
feedbackAlertDialog(text.errorTitle, text.formError);
return;
}
if (this.props.shouldValidateEmail && (this.props.isEmailRequired || trimmedEmail.length > 0) && !isValidEmail(trimmedEmail)) {
feedbackAlertDialog(text.errorTitle, text.emailError);
return;
}
const attachments = this.state.filename && this.state.attachment
? [
{
filename: this.state.filename,
data: this.state.attachment,
},
]
: undefined;
const eventId = lastEventId();
const userFeedback = {
message: trimmedDescription,
name: trimmedName,
email: trimmedEmail,
associatedEventId: eventId,
};
try {
if (!onFormSubmitted) {
this.setState({ isVisible: false });
}
captureFeedback(userFeedback, attachments ? { attachments } : undefined);
onSubmitSuccess({ name: trimmedName, email: trimmedEmail, message: trimmedDescription, attachments: attachments });
feedbackAlertDialog(text.successMessageText, '');
onFormSubmitted();
this._didSubmitForm = true;
}
catch (error) {
const errorString = `Feedback form submission failed: ${error}`;
onSubmitError(new Error(errorString));
feedbackAlertDialog(text.errorTitle, text.genericError);
logger.error(`Feedback form submission failed: ${error}`);
}
};
this.onScreenshotButtonPress = () => __awaiter(this, void 0, void 0, function* () {
if (!this.state.filename && !this.state.attachment) {
const imagePickerConfiguration = this.props;
if (imagePickerConfiguration.imagePicker) {
const launchImageLibrary = imagePickerConfiguration.imagePicker.launchImageLibraryAsync
// expo-image-picker library is available
? () => imagePickerConfiguration.imagePicker.launchImageLibraryAsync({ mediaTypes: ['images'], base64: isWeb() })
// react-native-image-picker library is available
: imagePickerConfiguration.imagePicker.launchImageLibrary
? () => imagePickerConfiguration.imagePicker.launchImageLibrary({ mediaType: 'photo', includeBase64: isWeb() })
: null;
if (!launchImageLibrary) {
logger.warn('No compatible image picker library found. Please provide a valid image picker library.');
if (__DEV__) {
feedbackAlertDialog('Development note', 'No compatible image picker library found. Please provide a compatible version of `expo-image-picker` or `react-native-image-picker`.');
}
return;
}
const result = yield launchImageLibrary();
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) {
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;
NATIVE.getDataFromUri(imageUri).then((data) => {
if (data != null) {
this.setState({ filename, attachment: data, attachmentUri: imageUri });
}
else {
logger.error('Failed to read image data from uri:', imageUri);
}
}).catch((error) => {
logger.error('Failed to read image data from uri:', imageUri, 'error: ', error);
});
}
}
}
else {
// Defaulting to the onAddScreenshot callback
const { onAddScreenshot } = Object.assign(Object.assign({}, defaultConfiguration), this.props);
onAddScreenshot((uri) => {
NATIVE.getDataFromUri(uri).then((data) => {
if (data != null) {
this.setState({ filename: 'feedback_screenshot', attachment: data, attachmentUri: uri });
}
else {
logger.error('Failed to read image data from uri:', uri);
}
})
.catch((error) => {
logger.error('Failed to read image data from uri:', uri, 'error: ', error);
});
});
}
}
else {
this.setState({ filename: undefined, attachment: undefined, attachmentUri: undefined });
}
});
this._saveFormState = () => {
FeedbackWidget._savedState = Object.assign({}, this.state);
};
this._clearFormState = () => {
FeedbackWidget._savedState = {
name: '',
email: '',
description: '',
filename: undefined,
attachment: undefined,
attachmentUri: undefined,
};
};
const currentUser = {
useSentryUser: {
email: ((_b = (_a = this.props) === null || _a === void 0 ? void 0 : _a.useSentryUser) === null || _b === void 0 ? void 0 : _b.email) || ((_d = (_c = getCurrentScope()) === null || _c === void 0 ? void 0 : _c.getUser()) === null || _d === void 0 ? void 0 : _d.email) || '',
name: ((_f = (_e = this.props) === null || _e === void 0 ? void 0 : _e.useSentryUser) === null || _f === void 0 ? void 0 : _f.name) || ((_h = (_g = getCurrentScope()) === null || _g === void 0 ? void 0 : _g.getUser()) === null || _h === void 0 ? void 0 : _h.name) || '',
}
};
this.state = {
isVisible: true,
name: FeedbackWidget._savedState.name || currentUser.useSentryUser.name,
email: FeedbackWidget._savedState.email || currentUser.useSentryUser.email,
description: FeedbackWidget._savedState.description || '',
filename: FeedbackWidget._savedState.filename || undefined,
attachment: FeedbackWidget._savedState.attachment || undefined,
attachmentUri: FeedbackWidget._savedState.attachmentUri || undefined,
};
lazyLoadFeedbackIntegration();
}
/**
* Save the state before unmounting the component.
*/
componentWillUnmount() {
if (this._didSubmitForm) {
this._clearFormState();
this._didSubmitForm = false;
}
else {
this._saveFormState();
}
}
/**
* Renders the feedback form screen.
*/
render() {
const { name, email, description } = this.state;
const { onFormClose } = this.props;
const config = this.props;
const imagePickerConfiguration = this.props;
const text = this.props;
const styles = Object.assign(Object.assign({}, defaultStyles), this.props.styles);
const onCancel = () => {
if (onFormClose) {
onFormClose();
}
else {
this.setState({ isVisible: false });
}
};
if (!this.state.isVisible) {
return null;
}
return (React.createElement(TouchableWithoutFeedback, { onPress: notWeb() ? Keyboard.dismiss : undefined },
React.createElement(View, { style: styles.container },
React.createElement(View, { style: styles.titleContainer },
React.createElement(Text, { style: styles.title }, text.formTitle),
config.showBranding && (React.createElement(Image, { source: { uri: sentryLogo }, style: styles.sentryLogo, testID: 'sentry-logo' }))),
config.showName && (React.createElement(React.Fragment, null,
React.createElement(Text, { style: styles.label },
text.nameLabel,
config.isNameRequired && ` ${text.isRequiredLabel}`),
React.createElement(TextInput, { style: styles.input, placeholder: text.namePlaceholder, value: name, onChangeText: (value) => this.setState({ name: value }) }))),
config.showEmail && (React.createElement(React.Fragment, null,
React.createElement(Text, { style: styles.label },
text.emailLabel,
config.isEmailRequired && ` ${text.isRequiredLabel}`),
React.createElement(TextInput, { style: styles.input, placeholder: text.emailPlaceholder, keyboardType: 'email-address', value: email, onChangeText: (value) => this.setState({ email: value }) }))),
React.createElement(Text, { style: styles.label },
text.messageLabel,
` ${text.isRequiredLabel}`),
React.createElement(TextInput, { style: [styles.input, styles.textArea], placeholder: text.messagePlaceholder, value: description, onChangeText: (value) => this.setState({ description: value }), multiline: true }),
(config.enableScreenshot || imagePickerConfiguration.imagePicker) && (React.createElement(View, { style: styles.screenshotContainer },
this.state.attachmentUri && (React.createElement(Image, { source: { uri: this.state.attachmentUri }, style: styles.screenshotThumbnail })),
React.createElement(TouchableOpacity, { style: styles.screenshotButton, onPress: this.onScreenshotButtonPress },
React.createElement(Text, { style: styles.screenshotText }, !this.state.filename && !this.state.attachment
? text.addScreenshotButtonLabel
: text.removeScreenshotButtonLabel)))),
React.createElement(TouchableOpacity, { style: styles.submitButton, onPress: this.handleFeedbackSubmit },
React.createElement(Text, { style: styles.submitText }, text.submitButtonLabel)),
React.createElement(TouchableOpacity, { style: styles.cancelButton, onPress: onCancel },
React.createElement(Text, { style: styles.cancelText }, text.cancelButtonLabel)))));
}
}
FeedbackWidget.defaultProps = Object.assign({}, defaultConfiguration);
FeedbackWidget._savedState = {
name: '',
email: '',
description: '',
filename: undefined,
attachment: undefined,
attachmentUri: undefined,
};
//# sourceMappingURL=FeedbackWidget.js.map