@@ -2,8 +2,8 @@ import { getCurrentHub } from '@sentry/core';
2
2
import type { Integration } from '@sentry/types' ;
3
3
import { isNodeEnv , logger } from '@sentry/utils' ;
4
4
5
- import { sendFeedback } from './sendFeedback' ;
6
5
import type { FeedbackConfigurationWithDefaults , FeedbackFormData } from './types' ;
6
+ import { handleFeedbackSubmit } from './util/handleFeedbackSubmit' ;
7
7
import { sendFeedbackRequest } from './util/sendFeedbackRequest' ;
8
8
import { Actor } from './widget/Actor' ;
9
9
import { createActorStyles } from './widget/Actor.css' ;
@@ -43,7 +43,9 @@ const THEME = {
43
43
} ;
44
44
45
45
/**
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.
47
49
*/
48
50
export class Feedback implements Integration {
49
51
/**
@@ -89,6 +91,7 @@ export class Feedback implements Integration {
89
91
90
92
public constructor ( {
91
93
attachTo = null ,
94
+ autoInject = true ,
92
95
showEmail = true ,
93
96
showName = true ,
94
97
useSentryUser = {
@@ -124,6 +127,7 @@ export class Feedback implements Integration {
124
127
125
128
this . options = {
126
129
attachTo,
130
+ autoInject,
127
131
isAnonymous,
128
132
isEmailRequired,
129
133
isNameRequired,
@@ -159,7 +163,48 @@ export class Feedback implements Integration {
159
163
return ;
160
164
}
161
165
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
+ }
163
208
}
164
209
165
210
/**
@@ -175,43 +220,55 @@ export class Feedback implements Integration {
175
220
* Opens the Feedback dialog form
176
221
*/
177
222
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
+ }
182
230
183
- if ( ! this . _shadow ) {
184
- this . _shadow = this . _createShadowHost ( ) ;
185
- }
231
+ try {
232
+ this . _shadow = this . _createShadowHost ( ) ;
233
+ } catch {
234
+ return ;
235
+ }
186
236
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
+ }
191
242
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
+ }
215
272
}
216
273
217
274
/**
@@ -220,6 +277,7 @@ export class Feedback implements Integration {
220
277
public hideDialog = ( ) : void => {
221
278
if ( this . _dialog ) {
222
279
this . _dialog . close ( ) ;
280
+ this . _isDialogOpen = false ;
223
281
}
224
282
} ;
225
283
@@ -244,57 +302,27 @@ export class Feedback implements Integration {
244
302
} ;
245
303
246
304
/**
247
- *
305
+ * Creates the host element of widget's shadow DOM. Returns null if not supported.
248
306
*/
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.' )
275
312
}
276
313
277
- if ( ! this . _host ) {
278
- return ;
314
+ // Don't create if it already exists
315
+ if ( this . _shadow ) {
316
+ return this . _shadow ;
279
317
}
280
318
281
- document . body . appendChild ( this . _host ) ;
282
- }
283
-
284
- /**
285
- * Creates the host element of widget's shadow DOM
286
- */
287
- protected _createShadowHost ( ) : ShadowRoot {
288
319
// Create the host
289
320
this . _host = document . createElement ( 'div' ) ;
290
321
this . _host . id = 'sentry-feedback' ;
291
322
292
323
// Create the shadow root
293
324
const shadow = this . _host . attachShadow ( { mode : 'open' } ) ;
294
325
295
- // Insert styles for actor
296
- shadow . appendChild ( createActorStyles ( document , THEME ) ) ;
297
-
298
326
return shadow ;
299
327
}
300
328
@@ -307,12 +335,17 @@ export class Feedback implements Integration {
307
335
return ;
308
336
}
309
337
310
- this . _shadow . appendChild ( createActorStyles ( document , THEME ) ) ;
338
+ try {
339
+ this . _shadow . appendChild ( createActorStyles ( document , THEME ) ) ;
311
340
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 } ) ;
314
343
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
+ }
316
349
}
317
350
318
351
/**
@@ -323,24 +356,29 @@ export class Feedback implements Integration {
323
356
return ;
324
357
}
325
358
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 ( ) ;
331
376
}
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
+ }
344
382
}
345
383
346
384
/**
@@ -368,31 +406,12 @@ export class Feedback implements Integration {
368
406
* create and send the feedback message as an event.
369
407
*/
370
408
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 ) ;
376
410
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 ) {
384
413
this . removeDialog ( ) ;
385
414
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.' ) ;
396
415
}
397
416
} ;
398
417
}
0 commit comments