1
- import { getCurrentHub } from '@sentry/core' ;
2
- import { dsnToString } from '@sentry/utils ' ;
1
+ import { createEventEnvelope , getCurrentHub } from '@sentry/core' ;
2
+ import type { FeedbackEvent , TransportMakeRequestResponse } from '@sentry/types ' ;
3
3
4
4
import type { SendFeedbackData } from '../types' ;
5
5
import { prepareFeedbackEvent } from './prepareFeedbackEvent' ;
6
6
7
7
/**
8
- * Send feedback using `fetch()`
8
+ * Send feedback using transport
9
9
*/
10
10
export async function sendFeedbackRequest ( {
11
11
feedback : { message, email, name, replay_id, url } ,
12
- } : SendFeedbackData ) : Promise < Response | null > {
12
+ } : SendFeedbackData ) : Promise < void | TransportMakeRequestResponse > {
13
13
const hub = getCurrentHub ( ) ;
14
-
15
- if ( ! hub ) {
16
- return null ;
17
- }
18
-
19
14
const client = hub . getClient ( ) ;
20
15
const scope = hub . getScope ( ) ;
21
16
const transport = client && client . getTransport ( ) ;
22
17
const dsn = client && client . getDsn ( ) ;
23
18
24
19
if ( ! client || ! transport || ! dsn ) {
25
- return null ;
20
+ return ;
26
21
}
27
22
28
- const baseEvent = {
29
- feedback : {
30
- contact_email : email ,
31
- name,
32
- message,
33
- replay_id,
34
- url,
23
+ const baseEvent : FeedbackEvent = {
24
+ contexts : {
25
+ feedback : {
26
+ contact_email : email ,
27
+ name,
28
+ message,
29
+ replay_id,
30
+ url,
31
+ } ,
35
32
} ,
36
- // type: 'feedback_event ',
33
+ type : 'feedback ' ,
37
34
} ;
38
35
39
36
const feedbackEvent = await prepareFeedbackEvent ( {
@@ -42,72 +39,80 @@ export async function sendFeedbackRequest({
42
39
event : baseEvent ,
43
40
} ) ;
44
41
45
- if ( ! feedbackEvent ) {
46
- // Taken from baseclient's `_processEvent` method, where this is handled for errors/transactions
47
- // client.recordDroppedEvent('event_processor', 'feedback', baseEvent);
48
- return null ;
42
+ if ( feedbackEvent === null ) {
43
+ return ;
49
44
}
50
45
51
46
/*
52
47
For reference, the fully built event looks something like this:
53
48
{
54
- "data": {
55
- "dist": "abc123",
49
+ "type": "feedback",
50
+ "event_id": "d2132d31b39445f1938d7e21b6bf0ec4",
51
+ "timestamp": 1597977777.6189718,
52
+ "dist": "1.12",
53
+ "platform": "javascript",
56
54
"environment": "production",
57
- "feedback": {
58
- "contact_email": "colton.allen@sentry .io",
59
- "message": "I really like this user-feedback feature!",
60
- "replay_id": "ec3b4dc8b79f417596f7a1aa4fcca5d2",
61
- "url": "https://docs.sentry.io/platforms/javascript/"
55
+ "release": 42,
56
+ "tags": {"transaction": "/organizations/:orgId/performance/:eventSlug/"},
57
+ "sdk": {"name": "name", "version": "version"},
58
+ "user": {
59
+ "id": "123",
60
+ "username": "user",
61
+ "email": "user@site .com",
62
+ "ip_address": "192.168.11.12",
62
63
},
63
- "id": "1ffe0775ac0f4417aed9de36d9f6f8dc",
64
- "platform": "javascript",
65
-
66
64
"request": {
67
- "headers": {
68
- "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
69
- }
70
- },
71
- "sdk": {
72
- "name": "sentry.javascript.react",
73
- "version": "6.18.1"
65
+ "url": None,
66
+ "headers": {
67
+ "user-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Safari/605.1.15"
68
+ },
74
69
},
75
- "tags": {
76
- "key": "value"
70
+ "contexts": {
71
+ "feedback": {
72
+ "message": "test message",
73
+ "contact_email": "test@example .com",
74
+ "type": "feedback",
75
+ },
76
+ "trace": {
77
+ "trace_id": "4C79F60C11214EB38604F4AE0781BFB2",
78
+ "span_id": "FA90FDEAD5F74052",
79
+ "type": "trace",
80
+ },
81
+ "replay": {
82
+ "replay_id": "e2d42047b1c5431c8cba85ee2a8ab25d",
83
+ },
77
84
},
78
- "timestamp": "2023-08-31T14:10:34.954048",
79
- "user": {
80
- "email": "username@example .com",
81
- "id": "123",
82
- "ip_address": "127.0.0.1",
83
- "name": "user",
84
- "username": "user2270129"
85
- }
86
85
}
87
- }
88
86
*/
89
87
90
- // Prevent this data (which, if it exists, was used in earlier steps in the processing pipeline) from being sent to
91
- // sentry. (Note: Our use of this property comes and goes with whatever we might be debugging, whatever hacks we may
92
- // have temporarily added, etc. Even if we don't happen to be using it at some point in the future, let's not get rid
93
- // of this `delete`, lest we miss putting it back in the next time the property is in use.)
94
- delete feedbackEvent . sdkProcessingMetadata ;
88
+ const envelope = createEventEnvelope ( feedbackEvent , dsn , client . getOptions ( ) . _metadata , client . getOptions ( ) . tunnel ) ;
89
+
90
+ let response : void | TransportMakeRequestResponse ;
95
91
96
92
try {
97
- const path = 'https://sentry.io/api/0/feedback/' ;
98
- const response = await fetch ( path , {
99
- method : 'POST' ,
100
- headers : {
101
- 'Content-Type' : 'application/json' ,
102
- Authorization : `DSN ${ dsnToString ( dsn ) } ` ,
103
- } ,
104
- body : JSON . stringify ( feedbackEvent ) ,
105
- } ) ;
106
- if ( ! response . ok ) {
107
- return null ;
93
+ response = await transport . send ( envelope ) ;
94
+ } catch ( err ) {
95
+ const error = new Error ( 'Unable to send Feedback' ) ;
96
+
97
+ try {
98
+ // In case browsers don't allow this property to be writable
99
+ // @ts -expect-error This needs lib es2022 and newer
100
+ error . cause = err ;
101
+ } catch {
102
+ // nothing to do
108
103
}
104
+ throw error ;
105
+ }
106
+
107
+ // TODO (v8): we can remove this guard once transport.send's type signature doesn't include void anymore
108
+ if ( ! response ) {
109
109
return response ;
110
- } catch ( err ) {
111
- return null ;
112
110
}
111
+
112
+ // Require valid status codes, otherwise can assume feedback was not sent successfully
113
+ if ( typeof response . statusCode === 'number' && ( response . statusCode < 200 || response . statusCode >= 300 ) ) {
114
+ throw new Error ( 'Unable to send Feedback' ) ;
115
+ }
116
+
117
+ return response ;
113
118
}
0 commit comments