@@ -2,8 +2,8 @@ import { getCurrentHub } from '@sentry/core';
22import type { Integration } from '@sentry/types' ;
33import { isNodeEnv , logger } from '@sentry/utils' ;
44
5- import { sendFeedback } from './sendFeedback' ;
65import type { FeedbackConfigurationWithDefaults , FeedbackFormData } from './types' ;
6+ import { handleFeedbackSubmit } from './util/handleFeedbackSubmit' ;
77import { sendFeedbackRequest } from './util/sendFeedbackRequest' ;
88import { Actor } from './widget/Actor' ;
99import { createActorStyles } from './widget/Actor.css' ;
@@ -43,7 +43,9 @@ const THEME = {
4343} ;
4444
4545/**
46- *
46+ * Feedback integration. When added as an integration to the SDK, it will
47+ * inject a button in the bottom-right corner of the window that opens a
48+ * feedback modal when clicked.
4749 */
4850export class Feedback implements Integration {
4951 /**
@@ -89,6 +91,7 @@ export class Feedback implements Integration {
8991
9092 public constructor ( {
9193 attachTo = null ,
94+ autoInject = true ,
9295 showEmail = true ,
9396 showName = true ,
9497 useSentryUser = {
@@ -124,6 +127,7 @@ export class Feedback implements Integration {
124127
125128 this . options = {
126129 attachTo,
130+ autoInject,
127131 isAnonymous,
128132 isEmailRequired,
129133 isNameRequired,
@@ -159,7 +163,48 @@ export class Feedback implements Integration {
159163 return ;
160164 }
161165
162- this . _injectWidget ( ) ;
166+ try {
167+ // TODO: This is only here for hot reloading
168+ if ( this . _host ) {
169+ this . remove ( ) ;
170+ }
171+ const existingFeedback = document . querySelector ( '#sentry-feedback' ) ;
172+ if ( existingFeedback ) {
173+ existingFeedback . remove ( ) ;
174+ }
175+
176+ // TODO: End hotloading
177+
178+ try {
179+ this . _shadow = this . _createShadowHost ( ) ;
180+ } catch ( err ) {
181+ return ;
182+ }
183+
184+ // Only create widget actor if `attachTo` was not defined
185+ if ( this . options . attachTo ) {
186+ const actorTarget = typeof this . options . attachTo === 'string' ? document . querySelector ( this . options . attachTo ) : typeof this . options . attachTo === 'function' ? this . options . attachTo : null ;
187+
188+ if ( ! actorTarget ) {
189+ logger . warn ( `[Feedback] Unable to find element with selector ${ actorTarget } ` ) ;
190+ return ;
191+ }
192+
193+ actorTarget . addEventListener ( 'click' , this . _handleActorClick ) ;
194+ } else if ( this . options . autoInject ) {
195+ // Only
196+ this . _createWidgetActor ( ) ;
197+ }
198+
199+ if ( ! this . _host ) {
200+ return ;
201+ }
202+
203+ document . body . appendChild ( this . _host ) ;
204+ } catch ( err ) {
205+ // TODO: error handling
206+ console . error ( err ) ;
207+ }
163208 }
164209
165210 /**
@@ -175,43 +220,55 @@ export class Feedback implements Integration {
175220 * Opens the Feedback dialog form
176221 */
177222 public openDialog ( ) : void {
178- if ( this . _dialog ) {
179- this . _dialog . open ( ) ;
180- return ;
181- }
223+ try {
224+ if ( this . _dialog ) {
225+ this . _dialog . open ( ) ;
226+ this . _isDialogOpen = true ;
227+ console . log ( 'dialog already open' )
228+ return ;
229+ }
182230
183- if ( ! this . _shadow ) {
184- this . _shadow = this . _createShadowHost ( ) ;
185- }
231+ try {
232+ this . _shadow = this . _createShadowHost ( ) ;
233+ } catch {
234+ return ;
235+ }
186236
187- // Lazy-load until dialog is opened and only inject styles once
188- if ( ! this . _hasDialogOpened ) {
189- this . _shadow . appendChild ( createDialogStyles ( document , THEME ) ) ;
190- }
237+ console . log ( 'open dialog' , this . _shadow )
238+ // Lazy-load until dialog is opened and only inject styles once
239+ if ( ! this . _hasDialogOpened ) {
240+ this . _shadow . appendChild ( createDialogStyles ( document , THEME ) ) ;
241+ }
191242
192- const userKey = this . options . useSentryUser ;
193- const scope = getCurrentHub ( ) . getScope ( ) ;
194- const user = scope && scope . getUser ( ) ;
195-
196- this . _dialog = Dialog ( {
197- defaultName : ( userKey && user && user [ userKey . name ] ) || '' ,
198- defaultEmail : ( userKey && user && user [ userKey . email ] ) || '' ,
199- onClose : ( ) => {
200- this . showActor ( ) ;
201- } ,
202- onCancel : ( ) => {
203- this . hideDialog ( ) ;
204- this . showActor ( ) ;
205- } ,
206- onSubmit : this . _handleFeedbackSubmit ,
207- options : this . options ,
208- } ) ;
209- this . _shadow . appendChild ( this . _dialog . $el ) ;
210-
211- // Hides the default actor whenever dialog is opened
212- this . _actor && this . _actor . hide ( ) ;
213-
214- this . _hasDialogOpened = true ;
243+ const userKey = this . options . useSentryUser ;
244+ const scope = getCurrentHub ( ) . getScope ( ) ;
245+ const user = scope && scope . getUser ( ) ;
246+
247+ this . _dialog = Dialog ( {
248+ defaultName : ( userKey && user && user [ userKey . name ] ) || '' ,
249+ defaultEmail : ( userKey && user && user [ userKey . email ] ) || '' ,
250+ onClose : ( ) => {
251+ this . showActor ( ) ;
252+ this . _isDialogOpen = false ;
253+ } ,
254+ onCancel : ( ) => {
255+ this . hideDialog ( ) ;
256+ this . showActor ( ) ;
257+ } ,
258+ onSubmit : this . _handleFeedbackSubmit ,
259+ options : this . options ,
260+ } ) ;
261+ this . _shadow . appendChild ( this . _dialog . $el ) ;
262+ console . log ( this . _dialog . $el ) ;
263+
264+ // Hides the default actor whenever dialog is opened
265+ this . _actor && this . _actor . hide ( ) ;
266+
267+ this . _hasDialogOpened = true ;
268+ } catch ( err ) {
269+ // TODO: Error handling?
270+ console . error ( err ) ;
271+ }
215272 }
216273
217274 /**
@@ -220,6 +277,7 @@ export class Feedback implements Integration {
220277 public hideDialog = ( ) : void => {
221278 if ( this . _dialog ) {
222279 this . _dialog . close ( ) ;
280+ this . _isDialogOpen = false ;
223281 }
224282 } ;
225283
@@ -244,57 +302,27 @@ export class Feedback implements Integration {
244302 } ;
245303
246304 /**
247- *
305+ * Creates the host element of widget's shadow DOM. Returns null if not supported.
248306 */
249- protected _injectWidget ( ) : void {
250- // TODO: This is only here for hot reloading
251- if ( this . _host ) {
252- this . remove ( ) ;
253- }
254- const existingFeedback = document . querySelector ( '#sentry-feedback' ) ;
255- if ( existingFeedback ) {
256- existingFeedback . remove ( ) ;
257- }
258-
259- // TODO: End hotloading
260-
261- this . _shadow = this . _createShadowHost ( ) ;
262-
263- // Only create widget actor if `attachTo` was not defined
264- if ( this . options . attachTo === null ) {
265- this . _createWidgetActor ( ) ;
266- } else {
267- const actorTarget = document . querySelector ( this . options . attachTo ) ;
268-
269- if ( ! actorTarget ) {
270- logger . warn ( `[Feedback] Unable to find element with selector ${ actorTarget } ` ) ;
271- return ;
272- }
273-
274- actorTarget . addEventListener ( 'click' , this . _handleActorClick ) ;
307+ protected _createShadowHost ( ) : ShadowRoot {
308+ if ( ! document . head . attachShadow ) {
309+ // Shadow DOM not supported
310+ logger . warn ( '[Feedback] Browser does not support shadow DOM API' )
311+ throw new Error ( 'Browser does not support shadow DOM API.' )
275312 }
276313
277- if ( ! this . _host ) {
278- return ;
314+ // Don't create if it already exists
315+ if ( this . _shadow ) {
316+ return this . _shadow ;
279317 }
280318
281- document . body . appendChild ( this . _host ) ;
282- }
283-
284- /**
285- * Creates the host element of widget's shadow DOM
286- */
287- protected _createShadowHost ( ) : ShadowRoot {
288319 // Create the host
289320 this . _host = document . createElement ( 'div' ) ;
290321 this . _host . id = 'sentry-feedback' ;
291322
292323 // Create the shadow root
293324 const shadow = this . _host . attachShadow ( { mode : 'open' } ) ;
294325
295- // Insert styles for actor
296- shadow . appendChild ( createActorStyles ( document , THEME ) ) ;
297-
298326 return shadow ;
299327 }
300328
@@ -307,12 +335,17 @@ export class Feedback implements Integration {
307335 return ;
308336 }
309337
310- this . _shadow . appendChild ( createActorStyles ( document , THEME ) ) ;
338+ try {
339+ this . _shadow . appendChild ( createActorStyles ( document , THEME ) ) ;
311340
312- // Create Actor component
313- this . _actor = Actor ( { options : this . options , theme : THEME , onClick : this . _handleActorClick } ) ;
341+ // Create Actor component
342+ this . _actor = Actor ( { options : this . options , theme : THEME , onClick : this . _handleActorClick } ) ;
314343
315- this . _shadow . appendChild ( this . _actor . $el ) ;
344+ this . _shadow . appendChild ( this . _actor . $el ) ;
345+ } catch ( err ) {
346+ // TODO: error handling
347+ console . error ( err ) ;
348+ }
316349 }
317350
318351 /**
@@ -323,24 +356,29 @@ export class Feedback implements Integration {
323356 return ;
324357 }
325358
326- const success = SuccessMessage ( {
327- message : this . options . successMessageText ,
328- onRemove : ( ) => {
329- if ( timeoutId ) {
330- clearTimeout ( timeoutId ) ;
359+ try {
360+ const success = SuccessMessage ( {
361+ message : this . options . successMessageText ,
362+ onRemove : ( ) => {
363+ if ( timeoutId ) {
364+ clearTimeout ( timeoutId ) ;
365+ }
366+ this . showActor ( ) ;
367+ } ,
368+ theme : THEME ,
369+ } ) ;
370+
371+ this . _shadow . appendChild ( success . $el ) ;
372+
373+ const timeoutId = setTimeout ( ( ) => {
374+ if ( success ) {
375+ success . remove ( ) ;
331376 }
332- this . showActor ( ) ;
333- } ,
334- theme : THEME ,
335- } ) ;
336-
337- this . _shadow . appendChild ( success . $el ) ;
338-
339- const timeoutId = setTimeout ( ( ) => {
340- if ( success ) {
341- success . remove ( ) ;
342- }
343- } , 5000 ) ;
377+ } , 5000 ) ;
378+ } catch ( err ) {
379+ // TODO: error handling
380+ console . error ( err ) ;
381+ }
344382 }
345383
346384 /**
@@ -368,31 +406,12 @@ export class Feedback implements Integration {
368406 * create and send the feedback message as an event.
369407 */
370408 protected _handleFeedbackSubmit = async ( feedback : FeedbackFormData ) : Promise < void > => {
371- console . log ( 'ahndle feedback submit' ) ;
372- if ( ! this . _dialog ) {
373- // Not sure when this would happen
374- return ;
375- }
409+ const result = await handleFeedbackSubmit ( this . _dialog , feedback ) ;
376410
377- try {
378- this . _dialog . hideError ( ) ;
379- this . _dialog . setSubmitDisabled ( ) ;
380- const resp = await sendFeedback ( feedback ) ;
381- console . log ( { resp } ) ;
382- if ( resp ) {
383- // Success!
411+ // Success
412+ if ( result ) {
384413 this . removeDialog ( ) ;
385414 this . _showSuccessMessage ( ) ;
386- return ;
387- }
388-
389- // Errored... re-enable submit button
390- this . _dialog . setSubmitEnabled ( ) ;
391- this . _dialog . showError ( 'There was a problem submitting feedback, please wait and try again.' ) ;
392- } catch {
393- // Errored... re-enable submit button
394- this . _dialog . setSubmitEnabled ( ) ;
395- this . _dialog . showError ( 'There was a problem submitting feedback, please wait and try again.' ) ;
396415 }
397416 } ;
398417}
0 commit comments