Skip to content

Commit 2fc6632

Browse files
authored
feat(feedback): use custom prepare/send, re-use createEventEnvelope from core (#9432)
Remove the use of our temporary ingest endpoint, and use our normal ingestion instead.
1 parent 7bb2161 commit 2fc6632

14 files changed

+145
-148
lines changed

packages/core/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export type { OfflineStore, OfflineTransportOptions } from './transports/offline
44
export type { ServerRuntimeClientOptions } from './server-runtime-client';
55

66
export * from './tracing';
7+
export { createEventEnvelope } from './envelope';
78
export {
89
addBreadcrumb,
910
captureCheckIn,

packages/feedback/src/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
export { sendFeedbackRequest } from './util/sendFeedbackRequest';
1+
export { sendFeedback } from './sendFeedback';
22
export { Feedback } from './integration';

packages/feedback/src/types/index.ts

+1-17
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,10 @@
1-
import type { Event, Primitive } from '@sentry/types';
1+
import type { Primitive } from '@sentry/types';
22

33
import type { ActorComponent } from '../widget/Actor';
44
import type { DialogComponent } from '../widget/Dialog';
55

66
export type SentryTags = { [key: string]: Primitive } | undefined;
77

8-
/**
9-
* NOTE: These types are still considered Beta and subject to change.
10-
* @hidden
11-
*/
12-
export interface FeedbackEvent extends Event {
13-
feedback: {
14-
message: string;
15-
url: string;
16-
contact_email?: string;
17-
name?: string;
18-
replay_id?: string;
19-
};
20-
// TODO: Add this event type to Event
21-
// type: 'feedback_event';
22-
}
23-
248
export interface SendFeedbackData {
259
feedback: {
2610
message: string;
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
1+
import type { TransportMakeRequestResponse } from '@sentry/types';
2+
import { logger } from '@sentry/utils';
3+
14
import { sendFeedback } from '../sendFeedback';
25
import type { FeedbackFormData, SendFeedbackOptions } from '../types';
36
import type { DialogComponent } from '../widget/Dialog';
47

58
/**
6-
* Calls `sendFeedback` to send feedback, handles UI behavior of dialog.
9+
* Handles UI behavior of dialog when feedback is submitted, calls
10+
* `sendFeedback` to send feedback.
711
*/
812
export async function handleFeedbackSubmit(
913
dialog: DialogComponent | null,
1014
feedback: FeedbackFormData,
1115
options?: SendFeedbackOptions,
12-
): Promise<Response | false> {
16+
): Promise<TransportMakeRequestResponse | void> {
1317
if (!dialog) {
1418
// Not sure when this would happen
15-
return false;
19+
return;
1620
}
1721

1822
const showFetchError = (): void => {
@@ -22,21 +26,15 @@ export async function handleFeedbackSubmit(
2226
dialog.showError('There was a problem submitting feedback, please wait and try again.');
2327
};
2428

29+
dialog.hideError();
30+
2531
try {
26-
dialog.hideError();
2732
const resp = await sendFeedback(feedback, options);
2833

29-
if (!resp) {
30-
// Errored... re-enable submit button
31-
showFetchError();
32-
return false;
33-
}
34-
3534
// Success!
3635
return resp;
37-
} catch {
38-
// Errored... re-enable submit button
36+
} catch (err) {
37+
__DEBUG_BUILD__ && logger.error(err);
3938
showFetchError();
40-
return false;
4139
}
4240
}
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import type { Scope } from '@sentry/core';
22
import { prepareEvent } from '@sentry/core';
3-
import type { Client } from '@sentry/types';
4-
5-
import type { FeedbackEvent } from '../types';
3+
import type { Client, FeedbackEvent } from '@sentry/types';
64

75
interface PrepareFeedbackEventParams {
86
client: Client;
@@ -17,20 +15,22 @@ export async function prepareFeedbackEvent({
1715
scope,
1816
event,
1917
}: PrepareFeedbackEventParams): Promise<FeedbackEvent | null> {
20-
const eventHint = { integrations: undefined };
18+
const eventHint = {};
2119
if (client.emit) {
2220
client.emit('preprocessEvent', event, eventHint);
2321
}
2422

2523
const preparedEvent = (await prepareEvent(
2624
client.getOptions(),
2725
event,
28-
{ integrations: undefined },
26+
eventHint,
2927
scope,
28+
client,
3029
)) as FeedbackEvent | null;
3130

32-
// If e.g. a global event processor returned null
33-
if (!preparedEvent) {
31+
if (preparedEvent === null) {
32+
// Taken from baseclient's `_processEvent` method, where this is handled for errors/transactions
33+
client.recordDroppedEvent('event_processor', 'feedback', event);
3434
return null;
3535
}
3636

@@ -39,14 +39,5 @@ export async function prepareFeedbackEvent({
3939
// we need to do this manually.
4040
preparedEvent.platform = preparedEvent.platform || 'javascript';
4141

42-
// extract the SDK name because `client._prepareEvent` doesn't add it to the event
43-
const metadata = client.getSdkMetadata && client.getSdkMetadata();
44-
const { name, version } = (metadata && metadata.sdk) || {};
45-
46-
preparedEvent.sdk = {
47-
...preparedEvent.sdk,
48-
name: name || 'sentry.javascript.unknown',
49-
version: version || '0.0.0',
50-
};
5142
return preparedEvent;
5243
}
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,36 @@
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

44
import type { SendFeedbackData } from '../types';
55
import { prepareFeedbackEvent } from './prepareFeedbackEvent';
66

77
/**
8-
* Send feedback using `fetch()`
8+
* Send feedback using transport
99
*/
1010
export 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": "[email protected]",
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

Comments
 (0)