Skip to content

Commit b19e7c8

Browse files
committed
inline _handleFeedbackSubmit and handleFeedbackSubmit and simplify return types
1 parent 3bd5ddc commit b19e7c8

File tree

6 files changed

+147
-205
lines changed

6 files changed

+147
-205
lines changed
Lines changed: 112 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
import { createEventEnvelope, getClient, withScope } from '@sentry/core';
2+
import type { FeedbackEvent, TransportMakeRequestResponse } from '@sentry/types';
13
import { getLocationHref } from '@sentry/utils';
2-
3-
import { FEEDBACK_API_SOURCE } from './constants';
4+
import { FEEDBACK_API_SOURCE, FEEDBACK_WIDGET_SOURCE } from './constants';
45
import type { SendFeedbackOptions } from './types';
5-
import { sendFeedbackRequest } from './util/sendFeedbackRequest';
6+
import { prepareFeedbackEvent } from './util/prepareFeedbackEvent';
67

78
interface SendFeedbackParams {
89
message: string;
@@ -15,24 +16,124 @@ interface SendFeedbackParams {
1516
/**
1617
* Public API to send a Feedback item to Sentry
1718
*/
18-
export function sendFeedback(
19+
export async function sendFeedback(
1920
{ name, email, message, source = FEEDBACK_API_SOURCE, url = getLocationHref() }: SendFeedbackParams,
20-
options: SendFeedbackOptions = {},
21-
): ReturnType<typeof sendFeedbackRequest> {
21+
{ includeReplay = true }: SendFeedbackOptions = {},
22+
): Promise<TransportMakeRequestResponse> {
2223
if (!message) {
2324
throw new Error('Unable to submit feedback with empty message');
2425
}
2526

26-
return sendFeedbackRequest(
27-
{
27+
const client = getClient();
28+
const transport = client && client.getTransport();
29+
const dsn = client && client.getDsn();
30+
31+
if (!client || !transport || !dsn) {
32+
throw new Error('Invalid Sentry client');
33+
}
34+
35+
const baseEvent: FeedbackEvent = {
36+
contexts: {
2837
feedback: {
38+
contact_email: email,
2939
name,
30-
email,
3140
message,
3241
url,
3342
source,
3443
},
3544
},
36-
options,
37-
);
45+
type: 'feedback',
46+
};
47+
48+
return withScope(async scope => {
49+
// No use for breadcrumbs in feedback
50+
scope.clearBreadcrumbs();
51+
52+
if ([FEEDBACK_API_SOURCE, FEEDBACK_WIDGET_SOURCE].includes(String(source))) {
53+
scope.setLevel('info');
54+
}
55+
56+
const feedbackEvent = await prepareFeedbackEvent({
57+
scope,
58+
client,
59+
event: baseEvent,
60+
});
61+
62+
if (client.emit) {
63+
client.emit('beforeSendFeedback', feedbackEvent, { includeReplay: Boolean(includeReplay) });
64+
}
65+
66+
const envelope = createEventEnvelope(feedbackEvent, dsn, client.getOptions()._metadata, client.getOptions().tunnel);
67+
68+
let response: void | TransportMakeRequestResponse;
69+
70+
try {
71+
response = await transport.send(envelope);
72+
} catch (err) {
73+
const error = new Error('Unable to send Feedback');
74+
75+
try {
76+
// In case browsers don't allow this property to be writable
77+
// @ts-expect-error This needs lib es2022 and newer
78+
error.cause = err;
79+
} catch {
80+
// nothing to do
81+
}
82+
throw error;
83+
}
84+
85+
// TODO (v8): we can remove this guard once transport.send's type signature doesn't include void anymore
86+
if (!response) {
87+
throw new Error('Unable to send Feedback');
88+
}
89+
90+
// Require valid status codes, otherwise can assume feedback was not sent successfully
91+
if (typeof response.statusCode === 'number' && (response.statusCode < 200 || response.statusCode >= 300)) {
92+
throw new Error('Unable to send Feedback. Invalid response from server.');
93+
}
94+
95+
return response;
96+
});
3897
}
98+
99+
/*
100+
* For reference, the fully built event looks something like this:
101+
* {
102+
* "type": "feedback",
103+
* "event_id": "d2132d31b39445f1938d7e21b6bf0ec4",
104+
* "timestamp": 1597977777.6189718,
105+
* "dist": "1.12",
106+
* "platform": "javascript",
107+
* "environment": "production",
108+
* "release": 42,
109+
* "tags": {"transaction": "/organizations/:orgId/performance/:eventSlug/"},
110+
* "sdk": {"name": "name", "version": "version"},
111+
* "user": {
112+
* "id": "123",
113+
* "username": "user",
114+
* "email": "user@site.com",
115+
* "ip_address": "192.168.11.12",
116+
* },
117+
* "request": {
118+
* "url": None,
119+
* "headers": {
120+
* "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"
121+
* },
122+
* },
123+
* "contexts": {
124+
* "feedback": {
125+
* "message": "test message",
126+
* "contact_email": "test@example.com",
127+
* "type": "feedback",
128+
* },
129+
* "trace": {
130+
* "trace_id": "4C79F60C11214EB38604F4AE0781BFB2",
131+
* "span_id": "FA90FDEAD5F74052",
132+
* "type": "trace",
133+
* },
134+
* "replay": {
135+
* "replay_id": "e2d42047b1c5431c8cba85ee2a8ab25d",
136+
* },
137+
* },
138+
* }
139+
*/

packages/feedback-async/src/util/handleFeedbackSubmit.ts

Lines changed: 0 additions & 41 deletions
This file was deleted.

packages/feedback-async/src/util/prepareFeedbackEvent.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export async function prepareFeedbackEvent({
1515
client,
1616
scope,
1717
event,
18-
}: PrepareFeedbackEventParams): Promise<FeedbackEvent | null> {
18+
}: PrepareFeedbackEventParams): Promise<FeedbackEvent> {
1919
const eventHint = {};
2020
if (client.emit) {
2121
client.emit('preprocessEvent', event, eventHint);
@@ -33,7 +33,7 @@ export async function prepareFeedbackEvent({
3333
if (preparedEvent === null) {
3434
// Taken from baseclient's `_processEvent` method, where this is handled for errors/transactions
3535
client.recordDroppedEvent('event_processor', 'feedback', event);
36-
return null;
36+
throw new Error('Unable to prepare event');
3737
}
3838

3939
// This normally happens in browser client "_prepareEvent"

packages/feedback-async/src/util/sendFeedbackRequest.ts

Lines changed: 0 additions & 131 deletions
This file was deleted.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { FeedbackFormData, FeedbackInternalOptions } from '../types';
2+
3+
/**
4+
* Validate that a given feedback submission has the required fields
5+
*/
6+
export function getMissingFields(feedback: FeedbackFormData, options: FeedbackInternalOptions): string[] {
7+
const emptyFields = [];
8+
if (options.isNameRequired && !feedback.name) {
9+
emptyFields.push(options.nameLabel);
10+
}
11+
if (options.isEmailRequired && !feedback.email) {
12+
emptyFields.push(options.emailLabel);
13+
}
14+
if (!feedback.message) {
15+
emptyFields.push(options.messageLabel);
16+
}
17+
18+
return emptyFields;
19+
}

0 commit comments

Comments
 (0)