Socket
Socket
Sign inDemoInstall

@sentry-internal/feedback

Package Overview
Dependencies
Maintainers
9
Versions
121
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@sentry-internal/feedback - npm Package Compare versions

Comparing version 0.0.1-alpha.3 to 0.0.1-alpha.4

build/npm/types-ts3.8/constants.d.ts

1491

build/npm/cjs/index.js

@@ -156,3 +156,1494 @@ Object.defineProperty(exports, '__esModule', { value: true });

const DEFAULT_THEME = {
light: {
fontFamily: "'Helvetica Neue', Arial, sans-serif",
fontSize: '14px',
background: '#ffffff',
backgroundHover: '#f6f6f7',
foreground: '#2B2233',
success: '#268d75',
error: '#df3338',
border: '1.5px solid rgba(41, 35, 47, 0.13)',
boxShadow: '0px 4px 24px 0px rgba(43, 34, 51, 0.12)',
},
dark: {
fontFamily: "'Helvetica Neue', Arial, sans-serif",
fontSize: '14px',
background: '#29232f',
backgroundHover: '#352f3b',
foreground: '#EBE6EF',
success: '#2da98c',
error: '#f55459',
border: '1.5px solid rgba(235, 230, 239, 0.15)',
boxShadow: '0px 4px 24px 0px rgba(43, 34, 51, 0.12)',
},
};
const ACTOR_LABEL = 'Report a Bug';
const CANCEL_BUTTON_LABEL = 'Cancel';
const SUBMIT_BUTTON_LABEL = 'Send Bug Report';
const FORM_TITLE = 'Report a Bug';
const EMAIL_PLACEHOLDER = 'your.email@example.org';
const EMAIL_LABEL = 'Email';
const MESSAGE_PLACEHOLDER = "What's the bug? What did you expect?";
const MESSAGE_LABEL = 'Description';
const NAME_PLACEHOLDER = 'Your Name';
const NAME_LABEL = 'Name';
const SUCCESS_MESSAGE_TEXT = 'Thank you for your report!';
/**
* Creates <style> element for widget actor (button that opens the dialog)
*/
function createActorStyles(d) {
const style = d.createElement('style');
style.textContent = `
.widget__actor {
line-height: 25px;
display: flex;
align-items: center;
gap: 8px;
border-radius: 12px;
cursor: pointer;
font-size: 14px;
font-weight: 600;
padding: 12px 16px;
text-decoration: none;
z-index: 9000;
color: var(--fg-color);
background-color: var(--bg-color);
border: var(--border);
box-shadow: var(--box-shadow);
opacity: 1;
transition: opacity 0.1s ease-in-out;
}
.widget__actor:hover {
background-color: var(--bg-hover-color);
}
.widget__actor svg {
width: 16px;
height: 16px;
}
.widget__actor--hidden {
opacity: 0;
pointer-events: none;
visibility: hidden;
}
.widget__actor__text {
}
.feedback-icon path {
fill: var(--fg-color);
}
`;
return style;
}
/**
* Creates <style> element for widget dialog
*/
function createDialogStyles(d) {
const style = d.createElement('style');
style.textContent = `
.dialog {
line-height: 25px;
background-color: rgba(0, 0, 0, 0.05);
border: none;
position: fixed;
inset: 0;
z-index: 10000;
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
opacity: 1;
transition: opacity 0.2s ease-in-out;
}
.dialog:not([open]) {
opacity: 0;
pointer-events: none;
visibility: hidden;
}
.dialog:not([open]) .dialog__content {
transform: translate(0, -16px) scale(0.98);
}
.dialog__content {
position: fixed;
left: var(--left);
right: var(--right);
bottom: var(--bottom);
top: var(--top);
border: var(--border);
padding: 24px;
border-radius: 20px;
background-color: var(--bg-color);
color: var(--fg-color);
width: 320px;
max-width: 100%;
max-height: calc(100% - 2rem);
display: flex;
flex-direction: column;
box-shadow:
0 0 0 1px rgba(0, 0, 0, 0.05),
0 4px 16px rgba(0, 0, 0, 0.2);
transition: transform 0.2s ease-in-out;
transform: translate(0, 0) scale(1);
}
.dialog__header {
font-size: 20px;
font-weight: 600;
padding: 0;
margin: 0;
margin-bottom: 16px;
}
.error {
color: red;
margin-bottom: 16px;
}
.form {
display: grid;
overflow: auto;
flex-direction: column;
gap: 16px;
padding: 0;
}
.form__error-container {
color: var(--error-color);
}
.form__error-container--hidden {
display: none;
}
.form__label {
display: flex;
flex-direction: column;
gap: 4px;
margin: 0px;
}
.form__input {
font-family: inherit;
line-height: inherit;
box-sizing: border-box;
border: var(--border);
border-radius: 6px;
font-size: 14px;
font-weight: 500;
padding: 6px 12px;
}
.form__input:focus {
border-color: rgba(108, 95, 199, 1);
}
.form__input--textarea {
font-family: inherit;
resize: vertical;
}
.btn-group {
display: grid;
gap: 8px;
margin-top: 8px;
}
.btn {
line-height: inherit;
border: var(--border);
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 600;
padding: 6px 16px;
}
.btn[disabled] {
opacity: 0.6;
pointer-events: none;
}
.btn--primary {
background-color: rgba(108, 95, 199, 1);
border-color: rgba(108, 95, 199, 1);
color: #fff;
}
.btn--primary:hover {
background-color: rgba(88, 74, 192, 1);
}
.btn--default {
background-color: transparent;
color: var(--fg-color);
font-weight: 500;
}
.btn--default:hover {
background-color: var(--bg-accent-color);
}
.success-message {
background-color: var(--bg-color);
border: var(--border);
border-radius: 12px;
box-shadow: var(--box-shadow);
font-weight: 600;
color: var(--success-color);
padding: 12px 24px;
line-height: 25px;
display: grid;
align-items: center;
grid-auto-flow: column;
gap: 6px;
cursor: default;
}
.success-icon path {
fill: var(--success-color);
}
`;
return style;
}
/**
* Creates <style> element for widget actor (button that opens the dialog)
*/
function createMainStyles(
d,
colorScheme,
themes,
) {
const style = d.createElement('style');
style.textContent = `
:host {
--bottom: 1rem;
--right: 1rem;
--top: auto;
--left: auto;
--z-index: 100000;
--font-family: ${themes.light.fontFamily};
--font-size: ${themes.light.fontSize};
position: fixed;
left: var(--left);
right: var(--right);
bottom: var(--bottom);
top: var(--top);
z-index: var(--z-index);
font-family: var(--font-family);
font-size: var(--font-size);
--bg-color: ${themes.light.background};
--bg-hover-color: ${themes.light.backgroundHover};
--fg-color: ${themes.light.foreground};
--error-color: ${themes.light.error};
--success-color: ${themes.light.success};
--border: ${themes.light.border};
--box-shadow: ${themes.light.boxShadow};
}
${
colorScheme === 'system'
? `
@media (prefers-color-scheme: dark) {
:host {
--bg-color: ${themes.dark.background};
--bg-hover-color: ${themes.dark.backgroundHover};
--fg-color: ${themes.dark.foreground};
--error-color: ${themes.dark.error};
--success-color: ${themes.dark.success};
--border: ${themes.dark.border};
--box-shadow: ${themes.dark.boxShadow};
--font-family: ${themes.dark.fontFamily};
--font-size: ${themes.dark.fontSize};
}
}
`
: `
:host-context([data-sentry-feedback-colorscheme="dark"]) {
--bg-color: ${themes.dark.background};
--bg-hover-color: ${themes.dark.backgroundHover};
--fg-color: ${themes.dark.foreground};
--error-color: ${themes.dark.error};
--success-color: ${themes.dark.success};
--border: ${themes.dark.border};
--box-shadow: ${themes.dark.boxShadow};
--font-family: ${themes.dark.fontFamily};
--font-size: ${themes.dark.fontSize};
}
`
}`;
return style;
}
/**
*
*/
function createShadowHost({ options }) {
// eslint-disable-next-line no-restricted-globals
const doc = document;
if (!doc.head.attachShadow) {
// Shadow DOM not supported
utils.logger.warn('[Feedback] Browser does not support shadow DOM API');
throw new Error('Browser does not support shadow DOM API.');
}
// Create the host
const host = doc.createElement('div');
host.id = options.id;
// Create the shadow root
const shadow = host.attachShadow({ mode: 'open' });
shadow.appendChild(
createMainStyles(doc, options.colorScheme, { dark: options.themeDark, light: options.themeLight }),
);
shadow.appendChild(createDialogStyles(doc));
return [shadow, host];
}
/**
* Public API to send a Feedback item to Sentry
*/
function sendFeedback(
{ name, email, message, url = utils.getLocationHref() },
{ includeReplay = true } = {},
) {
const hub = core.getCurrentHub();
const client = hub && hub.getClient();
const replay = includeReplay && client ? (client.getIntegrationById('Replay') ) : undefined;
// Prepare session replay
replay && replay.flush();
const replayId = replay && replay.getReplayId();
return sendFeedbackRequest({
feedback: {
name,
email,
message,
url,
replay_id: replayId,
},
});
}
/**
*
*/
async function handleFeedbackSubmit(
dialog,
feedback,
) {
if (!dialog) {
// Not sure when this would happen
return false;
}
const showFetchError = () => {
if (!dialog) {
return;
}
dialog.setSubmitEnabled();
dialog.showError('There was a problem submitting feedback, please wait and try again.');
};
try {
dialog.hideError();
dialog.setSubmitDisabled();
const resp = await sendFeedback(feedback);
if (!resp) {
// Errored... re-enable submit button
showFetchError();
return false;
}
// Success!
return resp;
} catch (e) {
// Errored... re-enable submit button
showFetchError();
return false;
}
}
/**
* Helper function to set a dict of attributes on element (w/ specified namespace)
*/
function setAttributesNS(el, attributes) {
Object.entries(attributes).forEach(([key, val]) => {
el.setAttributeNS(null, key, val);
});
return el;
}
const SIZE = 20;
const XMLNS$1 = 'http://www.w3.org/2000/svg';
/**
* Feedback Icon
*/
function Icon() {
const cENS = (tagName) =>
// eslint-disable-next-line no-restricted-globals
document.createElementNS(XMLNS$1, tagName);
const svg = setAttributesNS(cENS('svg'), {
class: 'feedback-icon',
width: `${SIZE}`,
height: `${SIZE}`,
viewBox: `0 0 ${SIZE} ${SIZE}`,
fill: 'none',
});
const g = setAttributesNS(cENS('g'), {
clipPath: 'url(#clip0_57_80)',
});
const path = setAttributesNS(cENS('path'), {
['fill-rule']: 'evenodd',
['clip-rule']: 'evenodd',
d: 'M15.6622 15H12.3997C12.2129 14.9959 12.031 14.9396 11.8747 14.8375L8.04965 12.2H7.49956V19.1C7.4875 19.3348 7.3888 19.5568 7.22256 19.723C7.05632 19.8892 6.83435 19.9879 6.59956 20H2.04956C1.80193 19.9968 1.56535 19.8969 1.39023 19.7218C1.21511 19.5467 1.1153 19.3101 1.11206 19.0625V12.2H0.949652C0.824431 12.2017 0.700142 12.1783 0.584123 12.1311C0.468104 12.084 0.362708 12.014 0.274155 11.9255C0.185602 11.8369 0.115689 11.7315 0.0685419 11.6155C0.0213952 11.4995 -0.00202913 11.3752 -0.00034808 11.25V3.75C-0.00900498 3.62067 0.0092504 3.49095 0.0532651 3.36904C0.0972798 3.24712 0.166097 3.13566 0.255372 3.04168C0.344646 2.94771 0.452437 2.87327 0.571937 2.82307C0.691437 2.77286 0.82005 2.74798 0.949652 2.75H8.04965L11.8747 0.1625C12.031 0.0603649 12.2129 0.00407221 12.3997 0H15.6622C15.9098 0.00323746 16.1464 0.103049 16.3215 0.278167C16.4966 0.453286 16.5964 0.689866 16.5997 0.9375V3.25269C17.3969 3.42959 18.1345 3.83026 18.7211 4.41679C19.5322 5.22788 19.9878 6.32796 19.9878 7.47502C19.9878 8.62209 19.5322 9.72217 18.7211 10.5333C18.1345 11.1198 17.3969 11.5205 16.5997 11.6974V14.0125C16.6047 14.1393 16.5842 14.2659 16.5395 14.3847C16.4948 14.5035 16.4268 14.6121 16.3394 14.7042C16.252 14.7962 16.147 14.8698 16.0307 14.9206C15.9144 14.9714 15.7891 14.9984 15.6622 15ZM1.89695 10.325H1.88715V4.625H8.33715C8.52423 4.62301 8.70666 4.56654 8.86215 4.4625L12.6872 1.875H14.7247V13.125H12.6872L8.86215 10.4875C8.70666 10.3835 8.52423 10.327 8.33715 10.325H2.20217C2.15205 10.3167 2.10102 10.3125 2.04956 10.3125C1.9981 10.3125 1.94708 10.3167 1.89695 10.325ZM2.98706 12.2V18.1625H5.66206V12.2H2.98706ZM16.5997 9.93612V5.01393C16.6536 5.02355 16.7072 5.03495 16.7605 5.04814C17.1202 5.13709 17.4556 5.30487 17.7425 5.53934C18.0293 5.77381 18.2605 6.06912 18.4192 6.40389C18.578 6.73866 18.6603 7.10452 18.6603 7.47502C18.6603 7.84552 18.578 8.21139 18.4192 8.54616C18.2605 8.88093 18.0293 9.17624 17.7425 9.41071C17.4556 9.64518 17.1202 9.81296 16.7605 9.90191C16.7072 9.91509 16.6536 9.9265 16.5997 9.93612Z',
});
svg.appendChild(g).appendChild(path);
const speakerDefs = cENS('defs');
const speakerClipPathDef = setAttributesNS(cENS('clipPath'), {
id: 'clip0_57_80',
});
const speakerRect = setAttributesNS(cENS('rect'), {
width: `${SIZE}`,
height: `${SIZE}`,
fill: 'white',
});
speakerClipPathDef.appendChild(speakerRect);
speakerDefs.appendChild(speakerClipPathDef);
svg.appendChild(speakerDefs).appendChild(speakerClipPathDef).appendChild(speakerRect);
return {
$el: svg,
};
}
/**
* Helper function to create an element. Could be used as a JSX factory
* (i.e. React-like syntax).
*/
function createElement(
tagName,
attributes,
...children
) {
// eslint-disable-next-line no-restricted-globals
const element = document.createElement(tagName);
if (attributes) {
Object.entries(attributes).forEach(([attribute, attributeValue]) => {
if (attribute === 'className' && typeof attributeValue === 'string') {
// JSX does not allow class as a valid name
element.setAttribute('class', attributeValue);
} else if (typeof attributeValue === 'boolean' && attributeValue) {
element.setAttribute(attribute, '');
} else if (typeof attributeValue === 'string') {
element.setAttribute(attribute, attributeValue);
} else if (attribute.startsWith('on') && typeof attributeValue === 'function') {
element.addEventListener(attribute.substring(2).toLowerCase(), attributeValue);
}
});
}
for (const child of children) {
appendChild(element, child);
}
return element;
}
function appendChild(parent, child) {
if (typeof child === 'undefined' || child === null) {
return;
}
if (Array.isArray(child)) {
for (const value of child) {
appendChild(parent, value);
}
} else if (child === false) ; else if (typeof child === 'string') {
// eslint-disable-next-line no-restricted-globals
parent.appendChild(document.createTextNode(child));
} else if (child instanceof Node) {
parent.appendChild(child);
} else {
// eslint-disable-next-line no-restricted-globals
parent.appendChild(document.createTextNode(String(child)));
}
}
/**
*
*/
function Actor({ options, onClick }) {
function _handleClick(e) {
onClick && onClick(e);
}
const $el = createElement(
'button',
{
type: 'button',
className: 'widget__actor',
ariaLabel: options.buttonLabel,
},
Icon().$el,
createElement(
'span',
{
className: 'widget__actor__text',
},
options.buttonLabel,
),
);
$el.addEventListener('click', _handleClick);
return {
$el,
show: () => {
$el.classList.remove('widget__actor--hidden');
},
hide: () => {
$el.classList.add('widget__actor--hidden');
},
};
}
/**
*
*/
function SubmitButton({ label }) {
const $el = createElement(
'button',
{
type: 'submit',
className: 'btn btn--primary',
disabled: true,
ariaDisabled: 'disabled',
},
label,
);
return {
$el,
setDisabled: () => {
$el.disabled = true;
$el.ariaDisabled = 'disabled';
},
setEnabled: () => {
$el.disabled = false;
$el.ariaDisabled = 'false';
$el.removeAttribute('ariaDisabled');
},
};
}
function retrieveStringValue(formData, key) {
const value = formData.get(key);
if (typeof value === 'string') {
return value.trim();
}
return '';
}
/**
* Creates the form element
*/
function Form({
options: {
showName,
showEmail,
isAnonymous,
nameLabel,
namePlaceholder,
emailLabel,
emailPlaceholder,
messageLabel,
messagePlaceholder,
cancelButtonLabel,
submitButtonLabel,
},
defaultName,
defaultEmail,
onCancel,
onSubmit,
}) {
const {
$el: $submit,
setDisabled: setSubmitDisabled,
setEnabled: setSubmitEnabled,
} = SubmitButton({
label: submitButtonLabel,
});
function handleSubmit(e) {
e.preventDefault();
if (!(e.target instanceof HTMLFormElement)) {
return;
}
try {
if (onSubmit) {
const formData = new FormData(e.target );
const feedback = {
name: retrieveStringValue(formData, 'name'),
email: retrieveStringValue(formData, 'email'),
message: retrieveStringValue(formData, 'message'),
};
onSubmit(feedback);
}
} catch (e2) {
// pass
}
}
const $error = createElement('div', {
className: 'form__error-container form__error-container--hidden',
ariaHidden: 'true',
});
function showError(message) {
$error.textContent = message;
$error.classList.remove('form__error-container--hidden');
$error.setAttribute('ariaHidden', 'false');
}
function hideError() {
$error.textContent = '';
$error.classList.add('form__error-container--hidden');
$error.setAttribute('ariaHidden', 'true');
}
const $name = createElement('input', {
id: 'name',
type: showName ? 'text' : 'hidden',
ariaHidden: showName ? 'false' : 'true',
name: 'name',
className: 'form__input',
placeholder: namePlaceholder,
value: defaultName,
});
const $email = createElement('input', {
id: 'email',
type: showEmail ? 'text' : 'hidden',
ariaHidden: showEmail ? 'false' : 'true',
name: 'email',
className: 'form__input',
placeholder: emailPlaceholder,
value: defaultEmail,
});
const $message = createElement('textarea', {
id: 'message',
autoFocus: 'true',
rows: '5',
name: 'message',
className: 'form__input form__input--textarea',
placeholder: messagePlaceholder,
onKeyup: (e) => {
if (!(e.currentTarget instanceof HTMLTextAreaElement)) {
return;
}
if (e.currentTarget.value) {
setSubmitEnabled();
} else {
setSubmitDisabled();
}
},
});
const $cancel = createElement(
'button',
{
type: 'button',
className: 'btn btn--default',
onClick: (e) => {
onCancel && onCancel(e);
},
},
cancelButtonLabel,
);
const $form = createElement(
'form',
{
className: 'form',
onSubmit: handleSubmit,
},
[
$error,
!isAnonymous &&
showName &&
createElement(
'label',
{
htmlFor: 'name',
className: 'form__label',
},
[nameLabel, $name],
),
!isAnonymous && !showName && $name,
!isAnonymous &&
showEmail &&
createElement(
'label',
{
htmlFor: 'email',
className: 'form__label',
},
[emailLabel, $email],
),
!isAnonymous && !showEmail && $email,
createElement(
'label',
{
htmlFor: 'message',
className: 'form__label',
},
[messageLabel, $message],
),
createElement(
'div',
{
className: 'btn-group',
},
[$submit, $cancel],
),
],
);
return {
$el: $form,
setSubmitDisabled,
setSubmitEnabled,
showError,
hideError,
};
}
/**
* Feedback dialog component that has the form
*/
function Dialog({
defaultName,
defaultEmail,
onClosed,
onCancel,
onSubmit,
options,
}) {
let $el = null;
/**
* Handles when the dialog is clicked. In our case, the dialog is the
* semi-transparent bg behind the form. We want clicks outside of the form to
* hide the form.
*/
function handleDialogClick() {
close();
// Only this should trigger `onClose`, we don't want the `close()` method to
// trigger it, otherwise it can cause cycles.
onClosed && onClosed();
}
/**
* Close the dialog
*/
function close() {
if ($el) {
$el.open = false;
}
}
/**
* Opens the dialog
*/
function open() {
if ($el) {
$el.open = true;
}
}
/**
* Check if dialog is currently opened
*/
function checkIsOpen() {
return ($el && $el.open === true) || false;
}
const {
$el: $form,
setSubmitEnabled,
setSubmitDisabled,
showError,
hideError,
} = Form({
defaultName,
defaultEmail,
options,
onSubmit,
onCancel,
});
$el = createElement(
'dialog',
{
className: 'dialog',
open: true,
onClick: handleDialogClick,
},
createElement(
'div',
{
className: 'dialog__content',
onClick: e => {
// Stop event propagation so clicks on content modal do not propagate to dialog (which will close dialog)
e.stopPropagation();
},
},
createElement('h2', { className: 'dialog__header' }, options.formTitle),
$form,
),
);
return {
$el,
showError,
hideError,
setSubmitDisabled,
setSubmitEnabled,
open,
close,
checkIsOpen,
};
}
const WIDTH = 16;
const HEIGHT = 17;
const XMLNS = 'http://www.w3.org/2000/svg';
/**
* Success Icon (checkmark)
*/
function SuccessIcon() {
const cENS = (tagName) =>
// eslint-disable-next-line no-restricted-globals
document.createElementNS(XMLNS, tagName);
const svg = setAttributesNS(cENS('svg'), {
class: 'success-icon',
width: `${WIDTH}`,
height: `${HEIGHT}`,
viewBox: `0 0 ${WIDTH} ${HEIGHT}`,
fill: 'none',
});
const g = setAttributesNS(cENS('g'), {
clipPath: 'url(#clip0_57_156)',
});
const path2 = setAttributesNS(cENS('path'), {
['fill-rule']: 'evenodd',
['clip-rule']: 'evenodd',
d: 'M3.55544 15.1518C4.87103 16.0308 6.41775 16.5 8 16.5C10.1217 16.5 12.1566 15.6571 13.6569 14.1569C15.1571 12.6566 16 10.6217 16 8.5C16 6.91775 15.5308 5.37103 14.6518 4.05544C13.7727 2.73985 12.5233 1.71447 11.0615 1.10897C9.59966 0.503466 7.99113 0.34504 6.43928 0.653721C4.88743 0.962403 3.46197 1.72433 2.34315 2.84315C1.22433 3.96197 0.462403 5.38743 0.153721 6.93928C-0.15496 8.49113 0.00346625 10.0997 0.608967 11.5615C1.21447 13.0233 2.23985 14.2727 3.55544 15.1518ZM4.40546 3.1204C5.46945 2.40946 6.72036 2.03 8 2.03C9.71595 2.03 11.3616 2.71166 12.575 3.92502C13.7883 5.13838 14.47 6.78405 14.47 8.5C14.47 9.77965 14.0905 11.0306 13.3796 12.0945C12.6687 13.1585 11.6582 13.9878 10.476 14.4775C9.29373 14.9672 7.99283 15.0953 6.73777 14.8457C5.48271 14.596 4.32987 13.9798 3.42502 13.075C2.52018 12.1701 1.90397 11.0173 1.65432 9.76224C1.40468 8.50718 1.5328 7.20628 2.0225 6.02404C2.5122 4.8418 3.34148 3.83133 4.40546 3.1204Z',
});
const path = setAttributesNS(cENS('path'), {
d: 'M6.68775 12.4297C6.78586 12.4745 6.89218 12.4984 7 12.5C7.11275 12.4955 7.22315 12.4664 7.32337 12.4145C7.4236 12.3627 7.51121 12.2894 7.58 12.2L12 5.63999C12.0848 5.47724 12.1071 5.28902 12.0625 5.11098C12.0178 4.93294 11.9095 4.77744 11.7579 4.67392C11.6064 4.57041 11.4221 4.52608 11.24 4.54931C11.0579 4.57254 10.8907 4.66173 10.77 4.79999L6.88 10.57L5.13 8.56999C5.06508 8.49566 4.98613 8.43488 4.89768 8.39111C4.80922 8.34735 4.713 8.32148 4.61453 8.31498C4.51605 8.30847 4.41727 8.32147 4.32382 8.35322C4.23038 8.38497 4.14413 8.43484 4.07 8.49999C3.92511 8.63217 3.83692 8.81523 3.82387 9.01092C3.81083 9.2066 3.87393 9.39976 4 9.54999L6.43 12.24C6.50187 12.3204 6.58964 12.385 6.68775 12.4297Z',
});
svg.appendChild(g).append(path, path2);
const speakerDefs = cENS('defs');
const speakerClipPathDef = setAttributesNS(cENS('clipPath'), {
id: 'clip0_57_156',
});
const speakerRect = setAttributesNS(cENS('rect'), {
width: `${WIDTH}`,
height: `${WIDTH}`,
fill: 'white',
transform: 'translate(0 0.5)',
});
speakerClipPathDef.appendChild(speakerRect);
speakerDefs.appendChild(speakerClipPathDef);
svg.appendChild(speakerDefs).appendChild(speakerClipPathDef).appendChild(speakerRect);
return {
$el: svg,
};
}
/**
* Feedback dialog component that has the form
*/
function SuccessMessage({ message, onRemove }) {
function remove() {
if (!$el) {
return;
}
$el.remove();
onRemove && onRemove();
}
const $el = createElement(
'div',
{
className: 'success-message',
onClick: remove,
},
SuccessIcon().$el,
message,
);
return {
$el,
remove,
};
}
/**
*
*/
function createWidget({ shadow, options, attachTo }) {
let actor;
let dialog;
let isDialogOpen = false;
/**
* Show the success message for 5 seconds
*/
function showSuccessMessage() {
if (!shadow) {
return;
}
try {
const success = SuccessMessage({
message: options.successMessageText,
onRemove: () => {
if (timeoutId) {
clearTimeout(timeoutId);
}
showActor();
},
});
shadow.appendChild(success.$el);
const timeoutId = setTimeout(() => {
if (success) {
success.remove();
}
}, 5000);
} catch (err) {
// TODO: error handling
utils.logger.error(err);
}
}
/**
* Handler for when the feedback form is completed by the user. This will
* create and send the feedback message as an event.
*/
async function _handleFeedbackSubmit(feedback) {
if (!dialog) {
return;
}
const result = await handleFeedbackSubmit(dialog, feedback);
// Error submitting feedback
if (!result) {
if (options.onSubmitError) {
options.onSubmitError();
}
return;
}
// Success
removeDialog();
showSuccessMessage();
if (options.onSubmitSuccess) {
options.onSubmitSuccess();
}
}
/**
* Displays the default actor
*/
function showActor() {
actor && actor.show();
}
/**
* Hides the default actor
*/
function hideActor() {
actor && actor.hide();
}
/**
* Removes the default actor element
*/
function removeActor() {
actor && actor.$el.remove();
}
/**
*
*/
function openDialog() {
try {
if (dialog) {
dialog.open();
isDialogOpen = true;
if (options.onDialogOpen) {
options.onDialogOpen();
}
return;
}
const userKey = !options.isAnonymous && options.useSentryUser;
const scope = core.getCurrentHub().getScope();
const user = scope && scope.getUser();
dialog = Dialog({
defaultName: (userKey && user && user[userKey.name]) || '',
defaultEmail: (userKey && user && user[userKey.email]) || '',
onClosed: () => {
showActor();
isDialogOpen = false;
},
onCancel: () => {
hideDialog();
showActor();
},
onSubmit: _handleFeedbackSubmit,
options,
});
shadow.appendChild(dialog.$el);
// Hides the default actor whenever dialog is opened
hideActor();
if (options.onDialogOpen) {
options.onDialogOpen();
}
} catch (err) {
// TODO: Error handling?
utils.logger.error(err);
}
}
/**
* Hides the dialog
*/
function hideDialog() {
if (dialog) {
dialog.close();
isDialogOpen = false;
if (options.onDialogClose) {
options.onDialogClose();
}
}
}
/**
* Removes the dialog element from DOM
*/
function removeDialog() {
if (dialog) {
hideDialog();
dialog.$el.remove();
dialog = undefined;
}
}
/**
*
*/
function handleActorClick() {
// Open dialog
if (!isDialogOpen) {
openDialog();
}
// Hide actor button
hideActor();
if (options.onActorClick) {
options.onActorClick();
}
}
if (!attachTo) {
actor = Actor({ options, onClick: handleActorClick });
shadow.appendChild(actor.$el);
} else {
attachTo.addEventListener('click', handleActorClick);
}
return {
actor,
dialog,
showActor,
hideActor,
removeActor,
openDialog,
hideDialog,
removeDialog,
};
}
// Electron renderers with nodeIntegration enabled are detected as Node.js so we specifically test for them
function isElectronNodeRenderer() {
return typeof process !== 'undefined' && (process ).type === 'renderer';
}
/**
* Returns true if we are in the browser.
*/
function isBrowser() {
// eslint-disable-next-line no-restricted-globals
return typeof window !== 'undefined' && (!utils.isNodeEnv() || isElectronNodeRenderer());
}
/**
* Feedback integration. When added as an integration to the SDK, it will
* inject a button in the bottom-right corner of the window that opens a
* feedback modal when clicked.
*/
class Feedback {
/**
* @inheritDoc
*/
static __initStatic() {this.id = 'Feedback';}
/**
* @inheritDoc
*/
/**
* Feedback configuration options
*/
/**
* Reference to widget element that is created when autoInject is true
*/
/**
* List of all widgets that are created from the integration
*/
/**
* Reference to the host element where widget is inserted
*/
/**
* Refernce to Shadow DOM root
*/
/**
* Tracks if actor styles have ever been inserted into shadow DOM
*/
constructor({
id = 'sentry-feedback',
// attachTo = null,
autoInject = true,
showEmail = true,
showName = true,
useSentryUser = {
email: 'email',
name: 'username',
},
isAnonymous = false,
isEmailRequired = false,
isNameRequired = false,
themeDark,
themeLight,
colorScheme = 'system',
buttonLabel = ACTOR_LABEL,
cancelButtonLabel = CANCEL_BUTTON_LABEL,
submitButtonLabel = SUBMIT_BUTTON_LABEL,
formTitle = FORM_TITLE,
emailPlaceholder = EMAIL_PLACEHOLDER,
emailLabel = EMAIL_LABEL,
messagePlaceholder = MESSAGE_PLACEHOLDER,
messageLabel = MESSAGE_LABEL,
namePlaceholder = NAME_PLACEHOLDER,
nameLabel = NAME_LABEL,
successMessageText = SUCCESS_MESSAGE_TEXT,
onActorClick,
onDialogClose,
onDialogOpen,
onSubmitError,
onSubmitSuccess,
} = {}) {
// Initializations
this.name = Feedback.id;
// tsc fails if these are not initialized explicitly constructor, e.g. can't call `_initialize()`
this._host = null;
this._shadow = null;
this._widget = null;
this._widgets = new Set();
this._hasInsertedActorStyles = false;
this.options = {
id,
// attachTo,
autoInject,
isAnonymous,
isEmailRequired,
isNameRequired,
showEmail,
showName,
useSentryUser,
colorScheme,
themeDark: Object.assign({}, DEFAULT_THEME.dark, themeDark),
themeLight: Object.assign({}, DEFAULT_THEME.light, themeLight),
buttonLabel,
cancelButtonLabel,
submitButtonLabel,
formTitle,
emailLabel,
emailPlaceholder,
messageLabel,
messagePlaceholder,
nameLabel,
namePlaceholder,
successMessageText,
onActorClick,
onDialogClose,
onDialogOpen,
onSubmitError,
onSubmitSuccess,
};
}
/**
* Setup and initialize replay container
*/
setupOnce() {
if (!isBrowser()) {
return;
}
try {
// TODO: This is only here for hot reloading
if (this._host) {
this.remove();
}
// eslint-disable-next-line no-restricted-globals
const existingFeedback = document.querySelector(`#${this.options.id}`);
if (existingFeedback) {
existingFeedback.remove();
}
// TODO: End hotloading
const { autoInject } = this.options;
if (!autoInject) {
// Nothing to do here
return;
}
this._widget = this._createWidget(this.options);
} catch (err) {
// TODO: error handling
utils.logger.error(err);
}
}
/**
* Adds click listener to attached element to open a feedback dialog
*/
attachTo(el, optionOverrides) {
try {
const options = Object.assign({}, this.options, optionOverrides);
return this._ensureShadowHost(options, ([shadow]) => {
const targetEl =
// eslint-disable-next-line no-restricted-globals
typeof el === 'string' ? document.querySelector(el) : typeof el.addEventListener === 'function' ? el : null;
if (!targetEl) {
utils.logger.error('[Feedback] Unable to attach to target element');
return null;
}
const widget = createWidget({ shadow, options, attachTo: targetEl });
this._widgets.add(widget);
return widget;
});
} catch (err) {
utils.logger.error(err);
return null;
}
}
/**
* Creates a new widget. Accepts partial options to override any options passed to constructor.
*/
createWidget(optionOverrides) {
try {
return this._createWidget(Object.assign({}, this.options, optionOverrides));
} catch (err) {
utils.logger.error(err);
return null;
}
}
/**
* Removes a single widget
*/
removeWidget(widget) {
if (!widget) {
return false;
}
try {
if (this._widgets.has(widget)) {
widget.removeActor();
widget.removeDialog();
this._widgets.delete(widget);
return true;
}
} catch (err) {
utils.logger.error(err);
}
return false;
}
/**
* Removes the Feedback integration (including host, shadow DOM, and all widgets)
*/
remove() {
if (this._host) {
this._host.remove();
}
this._initialize();
}
/**
* Initializes values of protected properties
*/
_initialize() {
this._host = null;
this._shadow = null;
this._widget = null;
this._widgets = new Set();
this._hasInsertedActorStyles = false;
}
/**
* Creates a new widget, after ensuring shadow DOM exists
*/
_createWidget(options) {
return this._ensureShadowHost(options, ([shadow]) => {
const widget = createWidget({ shadow, options });
if (!this._hasInsertedActorStyles && widget.actor) {
// eslint-disable-next-line no-restricted-globals
shadow.appendChild(createActorStyles(document));
this._hasInsertedActorStyles = true;
}
this._widgets.add(widget);
return widget;
});
}
/**
* Ensures that shadow DOM exists and is added to the DOM
*/
_ensureShadowHost(
options,
cb,
) {
let needsAppendHost = false;
// Don't create if it already exists
if (!this._shadow && !this._host) {
const [shadow, host] = createShadowHost({ options });
this._shadow = shadow;
this._host = host;
needsAppendHost = true;
}
if (!this._shadow || !this._host) {
utils.logger.warn('[Feedback] Unable to create host element and/or shadow DOM');
// This shouldn't happen
return null;
}
// set data attribute on host for different themes
this._host.dataset.sentryFeedbackColorscheme = options.colorScheme;
const result = cb([this._shadow, this._host]);
if (needsAppendHost) {
// eslint-disable-next-line no-restricted-globals
document.body.appendChild(this._host);
}
return result;
}
} Feedback.__initStatic();
exports.Feedback = Feedback;
exports.sendFeedbackRequest = sendFeedbackRequest;
//# sourceMappingURL=index.js.map

1494

build/npm/esm/index.js
import { prepareEvent, getCurrentHub } from '@sentry/core';
import { dsnToString } from '@sentry/utils';
import { dsnToString, logger, getLocationHref, isNodeEnv } from '@sentry/utils';

@@ -154,3 +154,1493 @@ /**

export { sendFeedbackRequest };
const DEFAULT_THEME = {
light: {
fontFamily: "'Helvetica Neue', Arial, sans-serif",
fontSize: '14px',
background: '#ffffff',
backgroundHover: '#f6f6f7',
foreground: '#2B2233',
success: '#268d75',
error: '#df3338',
border: '1.5px solid rgba(41, 35, 47, 0.13)',
boxShadow: '0px 4px 24px 0px rgba(43, 34, 51, 0.12)',
},
dark: {
fontFamily: "'Helvetica Neue', Arial, sans-serif",
fontSize: '14px',
background: '#29232f',
backgroundHover: '#352f3b',
foreground: '#EBE6EF',
success: '#2da98c',
error: '#f55459',
border: '1.5px solid rgba(235, 230, 239, 0.15)',
boxShadow: '0px 4px 24px 0px rgba(43, 34, 51, 0.12)',
},
};
const ACTOR_LABEL = 'Report a Bug';
const CANCEL_BUTTON_LABEL = 'Cancel';
const SUBMIT_BUTTON_LABEL = 'Send Bug Report';
const FORM_TITLE = 'Report a Bug';
const EMAIL_PLACEHOLDER = 'your.email@example.org';
const EMAIL_LABEL = 'Email';
const MESSAGE_PLACEHOLDER = "What's the bug? What did you expect?";
const MESSAGE_LABEL = 'Description';
const NAME_PLACEHOLDER = 'Your Name';
const NAME_LABEL = 'Name';
const SUCCESS_MESSAGE_TEXT = 'Thank you for your report!';
/**
* Creates <style> element for widget actor (button that opens the dialog)
*/
function createActorStyles(d) {
const style = d.createElement('style');
style.textContent = `
.widget__actor {
line-height: 25px;
display: flex;
align-items: center;
gap: 8px;
border-radius: 12px;
cursor: pointer;
font-size: 14px;
font-weight: 600;
padding: 12px 16px;
text-decoration: none;
z-index: 9000;
color: var(--fg-color);
background-color: var(--bg-color);
border: var(--border);
box-shadow: var(--box-shadow);
opacity: 1;
transition: opacity 0.1s ease-in-out;
}
.widget__actor:hover {
background-color: var(--bg-hover-color);
}
.widget__actor svg {
width: 16px;
height: 16px;
}
.widget__actor--hidden {
opacity: 0;
pointer-events: none;
visibility: hidden;
}
.widget__actor__text {
}
.feedback-icon path {
fill: var(--fg-color);
}
`;
return style;
}
/**
* Creates <style> element for widget dialog
*/
function createDialogStyles(d) {
const style = d.createElement('style');
style.textContent = `
.dialog {
line-height: 25px;
background-color: rgba(0, 0, 0, 0.05);
border: none;
position: fixed;
inset: 0;
z-index: 10000;
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
opacity: 1;
transition: opacity 0.2s ease-in-out;
}
.dialog:not([open]) {
opacity: 0;
pointer-events: none;
visibility: hidden;
}
.dialog:not([open]) .dialog__content {
transform: translate(0, -16px) scale(0.98);
}
.dialog__content {
position: fixed;
left: var(--left);
right: var(--right);
bottom: var(--bottom);
top: var(--top);
border: var(--border);
padding: 24px;
border-radius: 20px;
background-color: var(--bg-color);
color: var(--fg-color);
width: 320px;
max-width: 100%;
max-height: calc(100% - 2rem);
display: flex;
flex-direction: column;
box-shadow:
0 0 0 1px rgba(0, 0, 0, 0.05),
0 4px 16px rgba(0, 0, 0, 0.2);
transition: transform 0.2s ease-in-out;
transform: translate(0, 0) scale(1);
}
.dialog__header {
font-size: 20px;
font-weight: 600;
padding: 0;
margin: 0;
margin-bottom: 16px;
}
.error {
color: red;
margin-bottom: 16px;
}
.form {
display: grid;
overflow: auto;
flex-direction: column;
gap: 16px;
padding: 0;
}
.form__error-container {
color: var(--error-color);
}
.form__error-container--hidden {
display: none;
}
.form__label {
display: flex;
flex-direction: column;
gap: 4px;
margin: 0px;
}
.form__input {
font-family: inherit;
line-height: inherit;
box-sizing: border-box;
border: var(--border);
border-radius: 6px;
font-size: 14px;
font-weight: 500;
padding: 6px 12px;
}
.form__input:focus {
border-color: rgba(108, 95, 199, 1);
}
.form__input--textarea {
font-family: inherit;
resize: vertical;
}
.btn-group {
display: grid;
gap: 8px;
margin-top: 8px;
}
.btn {
line-height: inherit;
border: var(--border);
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 600;
padding: 6px 16px;
}
.btn[disabled] {
opacity: 0.6;
pointer-events: none;
}
.btn--primary {
background-color: rgba(108, 95, 199, 1);
border-color: rgba(108, 95, 199, 1);
color: #fff;
}
.btn--primary:hover {
background-color: rgba(88, 74, 192, 1);
}
.btn--default {
background-color: transparent;
color: var(--fg-color);
font-weight: 500;
}
.btn--default:hover {
background-color: var(--bg-accent-color);
}
.success-message {
background-color: var(--bg-color);
border: var(--border);
border-radius: 12px;
box-shadow: var(--box-shadow);
font-weight: 600;
color: var(--success-color);
padding: 12px 24px;
line-height: 25px;
display: grid;
align-items: center;
grid-auto-flow: column;
gap: 6px;
cursor: default;
}
.success-icon path {
fill: var(--success-color);
}
`;
return style;
}
/**
* Creates <style> element for widget actor (button that opens the dialog)
*/
function createMainStyles(
d,
colorScheme,
themes,
) {
const style = d.createElement('style');
style.textContent = `
:host {
--bottom: 1rem;
--right: 1rem;
--top: auto;
--left: auto;
--z-index: 100000;
--font-family: ${themes.light.fontFamily};
--font-size: ${themes.light.fontSize};
position: fixed;
left: var(--left);
right: var(--right);
bottom: var(--bottom);
top: var(--top);
z-index: var(--z-index);
font-family: var(--font-family);
font-size: var(--font-size);
--bg-color: ${themes.light.background};
--bg-hover-color: ${themes.light.backgroundHover};
--fg-color: ${themes.light.foreground};
--error-color: ${themes.light.error};
--success-color: ${themes.light.success};
--border: ${themes.light.border};
--box-shadow: ${themes.light.boxShadow};
}
${
colorScheme === 'system'
? `
@media (prefers-color-scheme: dark) {
:host {
--bg-color: ${themes.dark.background};
--bg-hover-color: ${themes.dark.backgroundHover};
--fg-color: ${themes.dark.foreground};
--error-color: ${themes.dark.error};
--success-color: ${themes.dark.success};
--border: ${themes.dark.border};
--box-shadow: ${themes.dark.boxShadow};
--font-family: ${themes.dark.fontFamily};
--font-size: ${themes.dark.fontSize};
}
}
`
: `
:host-context([data-sentry-feedback-colorscheme="dark"]) {
--bg-color: ${themes.dark.background};
--bg-hover-color: ${themes.dark.backgroundHover};
--fg-color: ${themes.dark.foreground};
--error-color: ${themes.dark.error};
--success-color: ${themes.dark.success};
--border: ${themes.dark.border};
--box-shadow: ${themes.dark.boxShadow};
--font-family: ${themes.dark.fontFamily};
--font-size: ${themes.dark.fontSize};
}
`
}`;
return style;
}
/**
*
*/
function createShadowHost({ options }) {
// eslint-disable-next-line no-restricted-globals
const doc = document;
if (!doc.head.attachShadow) {
// Shadow DOM not supported
logger.warn('[Feedback] Browser does not support shadow DOM API');
throw new Error('Browser does not support shadow DOM API.');
}
// Create the host
const host = doc.createElement('div');
host.id = options.id;
// Create the shadow root
const shadow = host.attachShadow({ mode: 'open' });
shadow.appendChild(
createMainStyles(doc, options.colorScheme, { dark: options.themeDark, light: options.themeLight }),
);
shadow.appendChild(createDialogStyles(doc));
return [shadow, host];
}
/**
* Public API to send a Feedback item to Sentry
*/
function sendFeedback(
{ name, email, message, url = getLocationHref() },
{ includeReplay = true } = {},
) {
const hub = getCurrentHub();
const client = hub && hub.getClient();
const replay = includeReplay && client ? (client.getIntegrationById('Replay') ) : undefined;
// Prepare session replay
replay && replay.flush();
const replayId = replay && replay.getReplayId();
return sendFeedbackRequest({
feedback: {
name,
email,
message,
url,
replay_id: replayId,
},
});
}
/**
*
*/
async function handleFeedbackSubmit(
dialog,
feedback,
) {
if (!dialog) {
// Not sure when this would happen
return false;
}
const showFetchError = () => {
if (!dialog) {
return;
}
dialog.setSubmitEnabled();
dialog.showError('There was a problem submitting feedback, please wait and try again.');
};
try {
dialog.hideError();
dialog.setSubmitDisabled();
const resp = await sendFeedback(feedback);
if (!resp) {
// Errored... re-enable submit button
showFetchError();
return false;
}
// Success!
return resp;
} catch (e) {
// Errored... re-enable submit button
showFetchError();
return false;
}
}
/**
* Helper function to set a dict of attributes on element (w/ specified namespace)
*/
function setAttributesNS(el, attributes) {
Object.entries(attributes).forEach(([key, val]) => {
el.setAttributeNS(null, key, val);
});
return el;
}
const SIZE = 20;
const XMLNS$1 = 'http://www.w3.org/2000/svg';
/**
* Feedback Icon
*/
function Icon() {
const cENS = (tagName) =>
// eslint-disable-next-line no-restricted-globals
document.createElementNS(XMLNS$1, tagName);
const svg = setAttributesNS(cENS('svg'), {
class: 'feedback-icon',
width: `${SIZE}`,
height: `${SIZE}`,
viewBox: `0 0 ${SIZE} ${SIZE}`,
fill: 'none',
});
const g = setAttributesNS(cENS('g'), {
clipPath: 'url(#clip0_57_80)',
});
const path = setAttributesNS(cENS('path'), {
['fill-rule']: 'evenodd',
['clip-rule']: 'evenodd',
d: 'M15.6622 15H12.3997C12.2129 14.9959 12.031 14.9396 11.8747 14.8375L8.04965 12.2H7.49956V19.1C7.4875 19.3348 7.3888 19.5568 7.22256 19.723C7.05632 19.8892 6.83435 19.9879 6.59956 20H2.04956C1.80193 19.9968 1.56535 19.8969 1.39023 19.7218C1.21511 19.5467 1.1153 19.3101 1.11206 19.0625V12.2H0.949652C0.824431 12.2017 0.700142 12.1783 0.584123 12.1311C0.468104 12.084 0.362708 12.014 0.274155 11.9255C0.185602 11.8369 0.115689 11.7315 0.0685419 11.6155C0.0213952 11.4995 -0.00202913 11.3752 -0.00034808 11.25V3.75C-0.00900498 3.62067 0.0092504 3.49095 0.0532651 3.36904C0.0972798 3.24712 0.166097 3.13566 0.255372 3.04168C0.344646 2.94771 0.452437 2.87327 0.571937 2.82307C0.691437 2.77286 0.82005 2.74798 0.949652 2.75H8.04965L11.8747 0.1625C12.031 0.0603649 12.2129 0.00407221 12.3997 0H15.6622C15.9098 0.00323746 16.1464 0.103049 16.3215 0.278167C16.4966 0.453286 16.5964 0.689866 16.5997 0.9375V3.25269C17.3969 3.42959 18.1345 3.83026 18.7211 4.41679C19.5322 5.22788 19.9878 6.32796 19.9878 7.47502C19.9878 8.62209 19.5322 9.72217 18.7211 10.5333C18.1345 11.1198 17.3969 11.5205 16.5997 11.6974V14.0125C16.6047 14.1393 16.5842 14.2659 16.5395 14.3847C16.4948 14.5035 16.4268 14.6121 16.3394 14.7042C16.252 14.7962 16.147 14.8698 16.0307 14.9206C15.9144 14.9714 15.7891 14.9984 15.6622 15ZM1.89695 10.325H1.88715V4.625H8.33715C8.52423 4.62301 8.70666 4.56654 8.86215 4.4625L12.6872 1.875H14.7247V13.125H12.6872L8.86215 10.4875C8.70666 10.3835 8.52423 10.327 8.33715 10.325H2.20217C2.15205 10.3167 2.10102 10.3125 2.04956 10.3125C1.9981 10.3125 1.94708 10.3167 1.89695 10.325ZM2.98706 12.2V18.1625H5.66206V12.2H2.98706ZM16.5997 9.93612V5.01393C16.6536 5.02355 16.7072 5.03495 16.7605 5.04814C17.1202 5.13709 17.4556 5.30487 17.7425 5.53934C18.0293 5.77381 18.2605 6.06912 18.4192 6.40389C18.578 6.73866 18.6603 7.10452 18.6603 7.47502C18.6603 7.84552 18.578 8.21139 18.4192 8.54616C18.2605 8.88093 18.0293 9.17624 17.7425 9.41071C17.4556 9.64518 17.1202 9.81296 16.7605 9.90191C16.7072 9.91509 16.6536 9.9265 16.5997 9.93612Z',
});
svg.appendChild(g).appendChild(path);
const speakerDefs = cENS('defs');
const speakerClipPathDef = setAttributesNS(cENS('clipPath'), {
id: 'clip0_57_80',
});
const speakerRect = setAttributesNS(cENS('rect'), {
width: `${SIZE}`,
height: `${SIZE}`,
fill: 'white',
});
speakerClipPathDef.appendChild(speakerRect);
speakerDefs.appendChild(speakerClipPathDef);
svg.appendChild(speakerDefs).appendChild(speakerClipPathDef).appendChild(speakerRect);
return {
$el: svg,
};
}
/**
* Helper function to create an element. Could be used as a JSX factory
* (i.e. React-like syntax).
*/
function createElement(
tagName,
attributes,
...children
) {
// eslint-disable-next-line no-restricted-globals
const element = document.createElement(tagName);
if (attributes) {
Object.entries(attributes).forEach(([attribute, attributeValue]) => {
if (attribute === 'className' && typeof attributeValue === 'string') {
// JSX does not allow class as a valid name
element.setAttribute('class', attributeValue);
} else if (typeof attributeValue === 'boolean' && attributeValue) {
element.setAttribute(attribute, '');
} else if (typeof attributeValue === 'string') {
element.setAttribute(attribute, attributeValue);
} else if (attribute.startsWith('on') && typeof attributeValue === 'function') {
element.addEventListener(attribute.substring(2).toLowerCase(), attributeValue);
}
});
}
for (const child of children) {
appendChild(element, child);
}
return element;
}
function appendChild(parent, child) {
if (typeof child === 'undefined' || child === null) {
return;
}
if (Array.isArray(child)) {
for (const value of child) {
appendChild(parent, value);
}
} else if (child === false) ; else if (typeof child === 'string') {
// eslint-disable-next-line no-restricted-globals
parent.appendChild(document.createTextNode(child));
} else if (child instanceof Node) {
parent.appendChild(child);
} else {
// eslint-disable-next-line no-restricted-globals
parent.appendChild(document.createTextNode(String(child)));
}
}
/**
*
*/
function Actor({ options, onClick }) {
function _handleClick(e) {
onClick && onClick(e);
}
const $el = createElement(
'button',
{
type: 'button',
className: 'widget__actor',
ariaLabel: options.buttonLabel,
},
Icon().$el,
createElement(
'span',
{
className: 'widget__actor__text',
},
options.buttonLabel,
),
);
$el.addEventListener('click', _handleClick);
return {
$el,
show: () => {
$el.classList.remove('widget__actor--hidden');
},
hide: () => {
$el.classList.add('widget__actor--hidden');
},
};
}
/**
*
*/
function SubmitButton({ label }) {
const $el = createElement(
'button',
{
type: 'submit',
className: 'btn btn--primary',
disabled: true,
ariaDisabled: 'disabled',
},
label,
);
return {
$el,
setDisabled: () => {
$el.disabled = true;
$el.ariaDisabled = 'disabled';
},
setEnabled: () => {
$el.disabled = false;
$el.ariaDisabled = 'false';
$el.removeAttribute('ariaDisabled');
},
};
}
function retrieveStringValue(formData, key) {
const value = formData.get(key);
if (typeof value === 'string') {
return value.trim();
}
return '';
}
/**
* Creates the form element
*/
function Form({
options: {
showName,
showEmail,
isAnonymous,
nameLabel,
namePlaceholder,
emailLabel,
emailPlaceholder,
messageLabel,
messagePlaceholder,
cancelButtonLabel,
submitButtonLabel,
},
defaultName,
defaultEmail,
onCancel,
onSubmit,
}) {
const {
$el: $submit,
setDisabled: setSubmitDisabled,
setEnabled: setSubmitEnabled,
} = SubmitButton({
label: submitButtonLabel,
});
function handleSubmit(e) {
e.preventDefault();
if (!(e.target instanceof HTMLFormElement)) {
return;
}
try {
if (onSubmit) {
const formData = new FormData(e.target );
const feedback = {
name: retrieveStringValue(formData, 'name'),
email: retrieveStringValue(formData, 'email'),
message: retrieveStringValue(formData, 'message'),
};
onSubmit(feedback);
}
} catch (e2) {
// pass
}
}
const $error = createElement('div', {
className: 'form__error-container form__error-container--hidden',
ariaHidden: 'true',
});
function showError(message) {
$error.textContent = message;
$error.classList.remove('form__error-container--hidden');
$error.setAttribute('ariaHidden', 'false');
}
function hideError() {
$error.textContent = '';
$error.classList.add('form__error-container--hidden');
$error.setAttribute('ariaHidden', 'true');
}
const $name = createElement('input', {
id: 'name',
type: showName ? 'text' : 'hidden',
ariaHidden: showName ? 'false' : 'true',
name: 'name',
className: 'form__input',
placeholder: namePlaceholder,
value: defaultName,
});
const $email = createElement('input', {
id: 'email',
type: showEmail ? 'text' : 'hidden',
ariaHidden: showEmail ? 'false' : 'true',
name: 'email',
className: 'form__input',
placeholder: emailPlaceholder,
value: defaultEmail,
});
const $message = createElement('textarea', {
id: 'message',
autoFocus: 'true',
rows: '5',
name: 'message',
className: 'form__input form__input--textarea',
placeholder: messagePlaceholder,
onKeyup: (e) => {
if (!(e.currentTarget instanceof HTMLTextAreaElement)) {
return;
}
if (e.currentTarget.value) {
setSubmitEnabled();
} else {
setSubmitDisabled();
}
},
});
const $cancel = createElement(
'button',
{
type: 'button',
className: 'btn btn--default',
onClick: (e) => {
onCancel && onCancel(e);
},
},
cancelButtonLabel,
);
const $form = createElement(
'form',
{
className: 'form',
onSubmit: handleSubmit,
},
[
$error,
!isAnonymous &&
showName &&
createElement(
'label',
{
htmlFor: 'name',
className: 'form__label',
},
[nameLabel, $name],
),
!isAnonymous && !showName && $name,
!isAnonymous &&
showEmail &&
createElement(
'label',
{
htmlFor: 'email',
className: 'form__label',
},
[emailLabel, $email],
),
!isAnonymous && !showEmail && $email,
createElement(
'label',
{
htmlFor: 'message',
className: 'form__label',
},
[messageLabel, $message],
),
createElement(
'div',
{
className: 'btn-group',
},
[$submit, $cancel],
),
],
);
return {
$el: $form,
setSubmitDisabled,
setSubmitEnabled,
showError,
hideError,
};
}
/**
* Feedback dialog component that has the form
*/
function Dialog({
defaultName,
defaultEmail,
onClosed,
onCancel,
onSubmit,
options,
}) {
let $el = null;
/**
* Handles when the dialog is clicked. In our case, the dialog is the
* semi-transparent bg behind the form. We want clicks outside of the form to
* hide the form.
*/
function handleDialogClick() {
close();
// Only this should trigger `onClose`, we don't want the `close()` method to
// trigger it, otherwise it can cause cycles.
onClosed && onClosed();
}
/**
* Close the dialog
*/
function close() {
if ($el) {
$el.open = false;
}
}
/**
* Opens the dialog
*/
function open() {
if ($el) {
$el.open = true;
}
}
/**
* Check if dialog is currently opened
*/
function checkIsOpen() {
return ($el && $el.open === true) || false;
}
const {
$el: $form,
setSubmitEnabled,
setSubmitDisabled,
showError,
hideError,
} = Form({
defaultName,
defaultEmail,
options,
onSubmit,
onCancel,
});
$el = createElement(
'dialog',
{
className: 'dialog',
open: true,
onClick: handleDialogClick,
},
createElement(
'div',
{
className: 'dialog__content',
onClick: e => {
// Stop event propagation so clicks on content modal do not propagate to dialog (which will close dialog)
e.stopPropagation();
},
},
createElement('h2', { className: 'dialog__header' }, options.formTitle),
$form,
),
);
return {
$el,
showError,
hideError,
setSubmitDisabled,
setSubmitEnabled,
open,
close,
checkIsOpen,
};
}
const WIDTH = 16;
const HEIGHT = 17;
const XMLNS = 'http://www.w3.org/2000/svg';
/**
* Success Icon (checkmark)
*/
function SuccessIcon() {
const cENS = (tagName) =>
// eslint-disable-next-line no-restricted-globals
document.createElementNS(XMLNS, tagName);
const svg = setAttributesNS(cENS('svg'), {
class: 'success-icon',
width: `${WIDTH}`,
height: `${HEIGHT}`,
viewBox: `0 0 ${WIDTH} ${HEIGHT}`,
fill: 'none',
});
const g = setAttributesNS(cENS('g'), {
clipPath: 'url(#clip0_57_156)',
});
const path2 = setAttributesNS(cENS('path'), {
['fill-rule']: 'evenodd',
['clip-rule']: 'evenodd',
d: 'M3.55544 15.1518C4.87103 16.0308 6.41775 16.5 8 16.5C10.1217 16.5 12.1566 15.6571 13.6569 14.1569C15.1571 12.6566 16 10.6217 16 8.5C16 6.91775 15.5308 5.37103 14.6518 4.05544C13.7727 2.73985 12.5233 1.71447 11.0615 1.10897C9.59966 0.503466 7.99113 0.34504 6.43928 0.653721C4.88743 0.962403 3.46197 1.72433 2.34315 2.84315C1.22433 3.96197 0.462403 5.38743 0.153721 6.93928C-0.15496 8.49113 0.00346625 10.0997 0.608967 11.5615C1.21447 13.0233 2.23985 14.2727 3.55544 15.1518ZM4.40546 3.1204C5.46945 2.40946 6.72036 2.03 8 2.03C9.71595 2.03 11.3616 2.71166 12.575 3.92502C13.7883 5.13838 14.47 6.78405 14.47 8.5C14.47 9.77965 14.0905 11.0306 13.3796 12.0945C12.6687 13.1585 11.6582 13.9878 10.476 14.4775C9.29373 14.9672 7.99283 15.0953 6.73777 14.8457C5.48271 14.596 4.32987 13.9798 3.42502 13.075C2.52018 12.1701 1.90397 11.0173 1.65432 9.76224C1.40468 8.50718 1.5328 7.20628 2.0225 6.02404C2.5122 4.8418 3.34148 3.83133 4.40546 3.1204Z',
});
const path = setAttributesNS(cENS('path'), {
d: 'M6.68775 12.4297C6.78586 12.4745 6.89218 12.4984 7 12.5C7.11275 12.4955 7.22315 12.4664 7.32337 12.4145C7.4236 12.3627 7.51121 12.2894 7.58 12.2L12 5.63999C12.0848 5.47724 12.1071 5.28902 12.0625 5.11098C12.0178 4.93294 11.9095 4.77744 11.7579 4.67392C11.6064 4.57041 11.4221 4.52608 11.24 4.54931C11.0579 4.57254 10.8907 4.66173 10.77 4.79999L6.88 10.57L5.13 8.56999C5.06508 8.49566 4.98613 8.43488 4.89768 8.39111C4.80922 8.34735 4.713 8.32148 4.61453 8.31498C4.51605 8.30847 4.41727 8.32147 4.32382 8.35322C4.23038 8.38497 4.14413 8.43484 4.07 8.49999C3.92511 8.63217 3.83692 8.81523 3.82387 9.01092C3.81083 9.2066 3.87393 9.39976 4 9.54999L6.43 12.24C6.50187 12.3204 6.58964 12.385 6.68775 12.4297Z',
});
svg.appendChild(g).append(path, path2);
const speakerDefs = cENS('defs');
const speakerClipPathDef = setAttributesNS(cENS('clipPath'), {
id: 'clip0_57_156',
});
const speakerRect = setAttributesNS(cENS('rect'), {
width: `${WIDTH}`,
height: `${WIDTH}`,
fill: 'white',
transform: 'translate(0 0.5)',
});
speakerClipPathDef.appendChild(speakerRect);
speakerDefs.appendChild(speakerClipPathDef);
svg.appendChild(speakerDefs).appendChild(speakerClipPathDef).appendChild(speakerRect);
return {
$el: svg,
};
}
/**
* Feedback dialog component that has the form
*/
function SuccessMessage({ message, onRemove }) {
function remove() {
if (!$el) {
return;
}
$el.remove();
onRemove && onRemove();
}
const $el = createElement(
'div',
{
className: 'success-message',
onClick: remove,
},
SuccessIcon().$el,
message,
);
return {
$el,
remove,
};
}
/**
*
*/
function createWidget({ shadow, options, attachTo }) {
let actor;
let dialog;
let isDialogOpen = false;
/**
* Show the success message for 5 seconds
*/
function showSuccessMessage() {
if (!shadow) {
return;
}
try {
const success = SuccessMessage({
message: options.successMessageText,
onRemove: () => {
if (timeoutId) {
clearTimeout(timeoutId);
}
showActor();
},
});
shadow.appendChild(success.$el);
const timeoutId = setTimeout(() => {
if (success) {
success.remove();
}
}, 5000);
} catch (err) {
// TODO: error handling
logger.error(err);
}
}
/**
* Handler for when the feedback form is completed by the user. This will
* create and send the feedback message as an event.
*/
async function _handleFeedbackSubmit(feedback) {
if (!dialog) {
return;
}
const result = await handleFeedbackSubmit(dialog, feedback);
// Error submitting feedback
if (!result) {
if (options.onSubmitError) {
options.onSubmitError();
}
return;
}
// Success
removeDialog();
showSuccessMessage();
if (options.onSubmitSuccess) {
options.onSubmitSuccess();
}
}
/**
* Displays the default actor
*/
function showActor() {
actor && actor.show();
}
/**
* Hides the default actor
*/
function hideActor() {
actor && actor.hide();
}
/**
* Removes the default actor element
*/
function removeActor() {
actor && actor.$el.remove();
}
/**
*
*/
function openDialog() {
try {
if (dialog) {
dialog.open();
isDialogOpen = true;
if (options.onDialogOpen) {
options.onDialogOpen();
}
return;
}
const userKey = !options.isAnonymous && options.useSentryUser;
const scope = getCurrentHub().getScope();
const user = scope && scope.getUser();
dialog = Dialog({
defaultName: (userKey && user && user[userKey.name]) || '',
defaultEmail: (userKey && user && user[userKey.email]) || '',
onClosed: () => {
showActor();
isDialogOpen = false;
},
onCancel: () => {
hideDialog();
showActor();
},
onSubmit: _handleFeedbackSubmit,
options,
});
shadow.appendChild(dialog.$el);
// Hides the default actor whenever dialog is opened
hideActor();
if (options.onDialogOpen) {
options.onDialogOpen();
}
} catch (err) {
// TODO: Error handling?
logger.error(err);
}
}
/**
* Hides the dialog
*/
function hideDialog() {
if (dialog) {
dialog.close();
isDialogOpen = false;
if (options.onDialogClose) {
options.onDialogClose();
}
}
}
/**
* Removes the dialog element from DOM
*/
function removeDialog() {
if (dialog) {
hideDialog();
dialog.$el.remove();
dialog = undefined;
}
}
/**
*
*/
function handleActorClick() {
// Open dialog
if (!isDialogOpen) {
openDialog();
}
// Hide actor button
hideActor();
if (options.onActorClick) {
options.onActorClick();
}
}
if (!attachTo) {
actor = Actor({ options, onClick: handleActorClick });
shadow.appendChild(actor.$el);
} else {
attachTo.addEventListener('click', handleActorClick);
}
return {
actor,
dialog,
showActor,
hideActor,
removeActor,
openDialog,
hideDialog,
removeDialog,
};
}
// Electron renderers with nodeIntegration enabled are detected as Node.js so we specifically test for them
function isElectronNodeRenderer() {
return typeof process !== 'undefined' && (process ).type === 'renderer';
}
/**
* Returns true if we are in the browser.
*/
function isBrowser() {
// eslint-disable-next-line no-restricted-globals
return typeof window !== 'undefined' && (!isNodeEnv() || isElectronNodeRenderer());
}
/**
* Feedback integration. When added as an integration to the SDK, it will
* inject a button in the bottom-right corner of the window that opens a
* feedback modal when clicked.
*/
class Feedback {
/**
* @inheritDoc
*/
static __initStatic() {this.id = 'Feedback';}
/**
* @inheritDoc
*/
/**
* Feedback configuration options
*/
/**
* Reference to widget element that is created when autoInject is true
*/
/**
* List of all widgets that are created from the integration
*/
/**
* Reference to the host element where widget is inserted
*/
/**
* Refernce to Shadow DOM root
*/
/**
* Tracks if actor styles have ever been inserted into shadow DOM
*/
constructor({
id = 'sentry-feedback',
// attachTo = null,
autoInject = true,
showEmail = true,
showName = true,
useSentryUser = {
email: 'email',
name: 'username',
},
isAnonymous = false,
isEmailRequired = false,
isNameRequired = false,
themeDark,
themeLight,
colorScheme = 'system',
buttonLabel = ACTOR_LABEL,
cancelButtonLabel = CANCEL_BUTTON_LABEL,
submitButtonLabel = SUBMIT_BUTTON_LABEL,
formTitle = FORM_TITLE,
emailPlaceholder = EMAIL_PLACEHOLDER,
emailLabel = EMAIL_LABEL,
messagePlaceholder = MESSAGE_PLACEHOLDER,
messageLabel = MESSAGE_LABEL,
namePlaceholder = NAME_PLACEHOLDER,
nameLabel = NAME_LABEL,
successMessageText = SUCCESS_MESSAGE_TEXT,
onActorClick,
onDialogClose,
onDialogOpen,
onSubmitError,
onSubmitSuccess,
} = {}) {
// Initializations
this.name = Feedback.id;
// tsc fails if these are not initialized explicitly constructor, e.g. can't call `_initialize()`
this._host = null;
this._shadow = null;
this._widget = null;
this._widgets = new Set();
this._hasInsertedActorStyles = false;
this.options = {
id,
// attachTo,
autoInject,
isAnonymous,
isEmailRequired,
isNameRequired,
showEmail,
showName,
useSentryUser,
colorScheme,
themeDark: Object.assign({}, DEFAULT_THEME.dark, themeDark),
themeLight: Object.assign({}, DEFAULT_THEME.light, themeLight),
buttonLabel,
cancelButtonLabel,
submitButtonLabel,
formTitle,
emailLabel,
emailPlaceholder,
messageLabel,
messagePlaceholder,
nameLabel,
namePlaceholder,
successMessageText,
onActorClick,
onDialogClose,
onDialogOpen,
onSubmitError,
onSubmitSuccess,
};
}
/**
* Setup and initialize replay container
*/
setupOnce() {
if (!isBrowser()) {
return;
}
try {
// TODO: This is only here for hot reloading
if (this._host) {
this.remove();
}
// eslint-disable-next-line no-restricted-globals
const existingFeedback = document.querySelector(`#${this.options.id}`);
if (existingFeedback) {
existingFeedback.remove();
}
// TODO: End hotloading
const { autoInject } = this.options;
if (!autoInject) {
// Nothing to do here
return;
}
this._widget = this._createWidget(this.options);
} catch (err) {
// TODO: error handling
logger.error(err);
}
}
/**
* Adds click listener to attached element to open a feedback dialog
*/
attachTo(el, optionOverrides) {
try {
const options = Object.assign({}, this.options, optionOverrides);
return this._ensureShadowHost(options, ([shadow]) => {
const targetEl =
// eslint-disable-next-line no-restricted-globals
typeof el === 'string' ? document.querySelector(el) : typeof el.addEventListener === 'function' ? el : null;
if (!targetEl) {
logger.error('[Feedback] Unable to attach to target element');
return null;
}
const widget = createWidget({ shadow, options, attachTo: targetEl });
this._widgets.add(widget);
return widget;
});
} catch (err) {
logger.error(err);
return null;
}
}
/**
* Creates a new widget. Accepts partial options to override any options passed to constructor.
*/
createWidget(optionOverrides) {
try {
return this._createWidget(Object.assign({}, this.options, optionOverrides));
} catch (err) {
logger.error(err);
return null;
}
}
/**
* Removes a single widget
*/
removeWidget(widget) {
if (!widget) {
return false;
}
try {
if (this._widgets.has(widget)) {
widget.removeActor();
widget.removeDialog();
this._widgets.delete(widget);
return true;
}
} catch (err) {
logger.error(err);
}
return false;
}
/**
* Removes the Feedback integration (including host, shadow DOM, and all widgets)
*/
remove() {
if (this._host) {
this._host.remove();
}
this._initialize();
}
/**
* Initializes values of protected properties
*/
_initialize() {
this._host = null;
this._shadow = null;
this._widget = null;
this._widgets = new Set();
this._hasInsertedActorStyles = false;
}
/**
* Creates a new widget, after ensuring shadow DOM exists
*/
_createWidget(options) {
return this._ensureShadowHost(options, ([shadow]) => {
const widget = createWidget({ shadow, options });
if (!this._hasInsertedActorStyles && widget.actor) {
// eslint-disable-next-line no-restricted-globals
shadow.appendChild(createActorStyles(document));
this._hasInsertedActorStyles = true;
}
this._widgets.add(widget);
return widget;
});
}
/**
* Ensures that shadow DOM exists and is added to the DOM
*/
_ensureShadowHost(
options,
cb,
) {
let needsAppendHost = false;
// Don't create if it already exists
if (!this._shadow && !this._host) {
const [shadow, host] = createShadowHost({ options });
this._shadow = shadow;
this._host = host;
needsAppendHost = true;
}
if (!this._shadow || !this._host) {
logger.warn('[Feedback] Unable to create host element and/or shadow DOM');
// This shouldn't happen
return null;
}
// set data attribute on host for different themes
this._host.dataset.sentryFeedbackColorscheme = options.colorScheme;
const result = cb([this._shadow, this._host]);
if (needsAppendHost) {
// eslint-disable-next-line no-restricted-globals
document.body.appendChild(this._host);
}
return result;
}
} Feedback.__initStatic();
export { Feedback, sendFeedbackRequest };
//# sourceMappingURL=index.js.map
export { sendFeedbackRequest } from './util/sendFeedbackRequest';
export { Feedback } from './integration';
//# sourceMappingURL=index.d.ts.map
import { Event, Primitive } from '@sentry/types';
import { ActorComponent } from '../widget/Actor';
import { DialogComponent } from '../widget/Dialog';
export type SentryTags = {

@@ -27,2 +29,183 @@ [key: string]: Primitive;

}
export interface FeedbackFormData {
message: string;
email?: string;
name?: string;
}
export interface FeedbackConfigurationWithDefaults {
/**
* id to use for the main widget container (this will host the shadow DOM)
*/
id: string;
/**
* Auto-inject default Feedback actor button to the DOM when integration is
* added.
*/
autoInject: boolean;
/**
* If true, will not collect user data (email/name).
*/
isAnonymous: boolean;
/**
* Should the email field be required?
*/
isEmailRequired: boolean;
/**
* Should the name field be required?
*/
isNameRequired: boolean;
/**
* Should the email input field be visible? Note: email will still be collected if set via `Sentry.setUser()`
*/
showEmail: boolean;
/**
* Should the name input field be visible? Note: name will still be collected if set via `Sentry.setUser()`
*/
showName: boolean;
/**
* Fill in email/name input fields with Sentry user context if it exists.
* The value of the email/name keys represent the properties of your user context.
*/
useSentryUser: {
email: string;
name: string;
};
/**
* The colorscheme to use. "system" will choose the scheme based on the user's system settings
*/
colorScheme: 'system' | 'light' | 'dark';
/**
* Light theme customization, will be merged with default theme values.
*/
themeLight: FeedbackTheme;
/**
* Dark theme customization, will be merged with default theme values.
*/
themeDark: FeedbackTheme;
/**
* The label for the Feedback widget button that opens the dialog
*/
buttonLabel: string;
/**
* The label for the Feedback form cancel button that closes dialog
*/
cancelButtonLabel: string;
/**
* The label for the Feedback form submit button that sends feedback
*/
submitButtonLabel: string;
/**
* The title of the Feedback form
*/
formTitle: string;
/**
* Label for the email input
*/
emailLabel: string;
/**
* Placeholder text for Feedback email input
*/
emailPlaceholder: string;
/**
* Label for the message input
*/
messageLabel: string;
/**
* Placeholder text for Feedback message input
*/
messagePlaceholder: string;
/**
* Label for the name input
*/
nameLabel: string;
/**
* Placeholder text for Feedback name input
*/
namePlaceholder: string;
/**
* Message after feedback was sent successfully
*/
successMessageText: string;
/**
* Callback when dialog is closed
*/
onDialogClose?: () => void;
/**
* Callback when dialog is opened
*/
onDialogOpen?: () => void;
/**
* Callback when widget actor is clicked
*/
onActorClick?: () => void;
/**
* Callback when feedback is successfully submitted
*/
onSubmitSuccess?: () => void;
/**
* Callback when feedback is unsuccessfully submitted
*/
onSubmitError?: () => void;
}
export interface FeedbackTheme {
/**
* Font family for widget
*/
fontFamily: string;
/**
* Font size for widget
*/
fontSize: string;
/**
* Background color for actor and dialog
*/
background: string;
/**
* Background color on hover
*/
backgroundHover: string;
/**
* Border styling for actor and dialog
*/
border: string;
/**
* Box shadow for actor and dialog
*/
boxShadow: string;
/**
* Foreground color (i.e. text color)
*/
foreground: string;
/**
* Success color
*/
success: string;
/**
* Error color
*/
error: string;
}
export interface FeedbackThemes {
dark: FeedbackTheme;
light: FeedbackTheme;
}
export interface FeedbackComponent<T extends HTMLElement> {
$el: T;
}
/**
* A widget consists of:
* - actor button [that opens dialog]
* - dialog + feedback form
* - shadow root?
*/
export interface Widget {
actor: ActorComponent | undefined;
dialog: DialogComponent | undefined;
showActor: () => void;
hideActor: () => void;
removeActor: () => void;
openDialog: () => void;
hideDialog: () => void;
removeDialog: () => void;
}
//# sourceMappingURL=index.d.ts.map
export { sendFeedbackRequest } from './util/sendFeedbackRequest';
export { Feedback } from './integration';
//# sourceMappingURL=index.d.ts.map
import type { Event, Primitive } from '@sentry/types';
import type { ActorComponent } from '../widget/Actor';
import type { DialogComponent } from '../widget/Dialog';
export type SentryTags = {

@@ -27,2 +29,183 @@ [key: string]: Primitive;

}
export interface FeedbackFormData {
message: string;
email?: string;
name?: string;
}
export interface FeedbackConfigurationWithDefaults {
/**
* id to use for the main widget container (this will host the shadow DOM)
*/
id: string;
/**
* Auto-inject default Feedback actor button to the DOM when integration is
* added.
*/
autoInject: boolean;
/**
* If true, will not collect user data (email/name).
*/
isAnonymous: boolean;
/**
* Should the email field be required?
*/
isEmailRequired: boolean;
/**
* Should the name field be required?
*/
isNameRequired: boolean;
/**
* Should the email input field be visible? Note: email will still be collected if set via `Sentry.setUser()`
*/
showEmail: boolean;
/**
* Should the name input field be visible? Note: name will still be collected if set via `Sentry.setUser()`
*/
showName: boolean;
/**
* Fill in email/name input fields with Sentry user context if it exists.
* The value of the email/name keys represent the properties of your user context.
*/
useSentryUser: {
email: string;
name: string;
};
/**
* The colorscheme to use. "system" will choose the scheme based on the user's system settings
*/
colorScheme: 'system' | 'light' | 'dark';
/**
* Light theme customization, will be merged with default theme values.
*/
themeLight: FeedbackTheme;
/**
* Dark theme customization, will be merged with default theme values.
*/
themeDark: FeedbackTheme;
/**
* The label for the Feedback widget button that opens the dialog
*/
buttonLabel: string;
/**
* The label for the Feedback form cancel button that closes dialog
*/
cancelButtonLabel: string;
/**
* The label for the Feedback form submit button that sends feedback
*/
submitButtonLabel: string;
/**
* The title of the Feedback form
*/
formTitle: string;
/**
* Label for the email input
*/
emailLabel: string;
/**
* Placeholder text for Feedback email input
*/
emailPlaceholder: string;
/**
* Label for the message input
*/
messageLabel: string;
/**
* Placeholder text for Feedback message input
*/
messagePlaceholder: string;
/**
* Label for the name input
*/
nameLabel: string;
/**
* Placeholder text for Feedback name input
*/
namePlaceholder: string;
/**
* Message after feedback was sent successfully
*/
successMessageText: string;
/**
* Callback when dialog is closed
*/
onDialogClose?: () => void;
/**
* Callback when dialog is opened
*/
onDialogOpen?: () => void;
/**
* Callback when widget actor is clicked
*/
onActorClick?: () => void;
/**
* Callback when feedback is successfully submitted
*/
onSubmitSuccess?: () => void;
/**
* Callback when feedback is unsuccessfully submitted
*/
onSubmitError?: () => void;
}
export interface FeedbackTheme {
/**
* Font family for widget
*/
fontFamily: string;
/**
* Font size for widget
*/
fontSize: string;
/**
* Background color for actor and dialog
*/
background: string;
/**
* Background color on hover
*/
backgroundHover: string;
/**
* Border styling for actor and dialog
*/
border: string;
/**
* Box shadow for actor and dialog
*/
boxShadow: string;
/**
* Foreground color (i.e. text color)
*/
foreground: string;
/**
* Success color
*/
success: string;
/**
* Error color
*/
error: string;
}
export interface FeedbackThemes {
dark: FeedbackTheme;
light: FeedbackTheme;
}
export interface FeedbackComponent<T extends HTMLElement> {
$el: T;
}
/**
* A widget consists of:
* - actor button [that opens dialog]
* - dialog + feedback form
* - shadow root?
*/
export interface Widget {
actor: ActorComponent | undefined;
dialog: DialogComponent | undefined;
showActor: () => void;
hideActor: () => void;
removeActor: () => void;
openDialog: () => void;
hideDialog: () => void;
removeDialog: () => void;
}
//# sourceMappingURL=index.d.ts.map

2

package.json
{
"name": "@sentry-internal/feedback",
"version": "0.0.1-alpha.3",
"version": "0.0.1-alpha.4",
"description": "Sentry SDK integration for user feedback",

@@ -5,0 +5,0 @@ "repository": "git://github.com/getsentry/sentry-javascript.git",

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc