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 ' ;
33
44import type { SendFeedbackData } from '../types' ;
55import { prepareFeedbackEvent } from './prepareFeedbackEvent' ;
66
77/**
8- * Send feedback using `fetch()`
8+ * Send feedback using transport
99 */
1010export async function sendFeedbackRequest ( {
1111 feedback : { message, email, name, replay_id, url } ,
12- } : SendFeedbackData ) : Promise < Response | null > {
12+ } : SendFeedbackData ) : Promise < void | TransportMakeRequestResponse > {
1313 const hub = getCurrentHub ( ) ;
14-
15- if ( ! hub ) {
16- return null ;
17- }
18-
1914 const client = hub . getClient ( ) ;
2015 const scope = hub . getScope ( ) ;
2116 const transport = client && client . getTransport ( ) ;
2217 const dsn = client && client . getDsn ( ) ;
2318
2419 if ( ! client || ! transport || ! dsn ) {
25- return null ;
20+ return ;
2621 }
2722
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+ } ,
3532 } ,
36- // type: 'feedback_event ',
33+ type : 'feedback ' ,
3734 } ;
3835
3936 const feedbackEvent = await prepareFeedbackEvent ( {
@@ -42,72 +39,80 @@ export async function sendFeedbackRequest({
4239 event : baseEvent ,
4340 } ) ;
4441
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 ;
4944 }
5045
5146 /*
5247 For reference, the fully built event looks something like this:
5348 {
54- "data": {
55- "dist": "abc123",
49+ "type": "feedback",
50+ "event_id": "d2132d31b39445f1938d7e21b6bf0ec4",
51+ "timestamp": 1597977777.6189718,
52+ "dist": "1.12",
53+ "platform": "javascript",
5654 "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",
6263 },
63- "id": "1ffe0775ac0f4417aed9de36d9f6f8dc",
64- "platform": "javascript",
65- "release": "version@1.3",
6664 "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+ },
7469 },
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+ },
7784 },
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- }
8685 }
87- }
8886 */
8987
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 ;
9591
9692 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
108103 }
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 ) {
109109 return response ;
110- } catch ( err ) {
111- return null ;
112110 }
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 ;
113118}
0 commit comments