@@ -6,7 +6,7 @@ import * as os from 'os';
6
6
import * as path from 'path' ;
7
7
import * as util from 'util' ;
8
8
import * as zlib from 'zlib' ;
9
- import type { Envelope } from '@sentry/types' ;
9
+ import type { Envelope , EnvelopeItem } from '@sentry/types' ;
10
10
import { parseEnvelope } from '@sentry/utils' ;
11
11
12
12
const writeFile = util . promisify ( fs . writeFile ) ;
@@ -25,6 +25,98 @@ interface SentryRequestCallbackData {
25
25
sentryResponseStatusCode ?: number ;
26
26
}
27
27
28
+ const TEMPORARY_FILE_PATH = 'payload-files/temporary.json' ;
29
+
30
+ function isDateLikeString ( str : string ) : boolean {
31
+ // matches strings in the format "YYYY-MM-DD"
32
+ const datePattern = / ^ \d { 4 } - \d { 2 } - \d { 2 } / ;
33
+ return datePattern . test ( str ) ;
34
+ }
35
+
36
+ function extractPathFromUrl ( url : string ) : string {
37
+ const localhost = 'http://localhost:3030/' ;
38
+ return url . replace ( localhost , '' ) ;
39
+ }
40
+
41
+ function addCommaAfterEachLine ( data : string ) : string {
42
+ const jsonData = data . trim ( ) . split ( '\n' ) ;
43
+
44
+ const jsonDataWithCommas = jsonData . map ( ( item , index ) =>
45
+ index < jsonData . length - 1 ? item + ',' : item ,
46
+ ) ;
47
+
48
+ return jsonDataWithCommas . join ( '\n' ) ;
49
+ }
50
+
51
+ function recursivelyReplaceData ( obj : any ) {
52
+ for ( let key in obj ) {
53
+ if ( typeof obj [ key ] === 'string' && isDateLikeString ( obj [ key ] ) ) {
54
+ obj [ key ] = `[[ISODateString]]` ;
55
+ } else if ( key . includes ( 'timestamp' ) ) {
56
+ obj [ key ] = `[[timestamp]]` ;
57
+ } else if ( key . includes ( '_id' ) ) {
58
+ obj [ key ] = `[[ID]]` ;
59
+ } else if ( typeof obj [ key ] === 'number' && obj [ key ] > 1000 ) {
60
+ obj [ key ] = `[[highNumber]]` ;
61
+ } else if ( typeof obj [ key ] === 'object' && obj [ key ] !== null ) {
62
+ recursivelyReplaceData ( obj [ key ] ) ;
63
+ }
64
+ }
65
+ }
66
+
67
+ function replaceDynamicValues ( data : string ) : string [ ] {
68
+ const jsonData = JSON . parse ( data ) ;
69
+
70
+ recursivelyReplaceData ( jsonData ) ;
71
+
72
+ // change remaining dynamic values
73
+ jsonData . forEach ( ( item : any ) => {
74
+ if ( item . trace ?. public_key ) {
75
+ item . trace . public_key = '[[publicKey]]' ;
76
+ }
77
+ } ) ;
78
+
79
+ return jsonData ;
80
+ }
81
+
82
+ /** This function transforms all dynamic data (like timestamps) from the temporarily saved file.
83
+ * The new content is saved into a new file with the url as the filename.
84
+ * The temporary file is deleted in the end.
85
+ */
86
+ function transformSavedJSON ( ) {
87
+ fs . readFile ( TEMPORARY_FILE_PATH , 'utf8' , ( err , data ) => {
88
+ if ( err ) {
89
+ console . error ( 'Error reading file' , err ) ;
90
+ return ;
91
+ }
92
+
93
+ const jsonData = addCommaAfterEachLine ( data ) ;
94
+ const transformedJSON = replaceDynamicValues ( jsonData ) ;
95
+ const objWithReq = transformedJSON [ 2 ] as unknown as { request : { url : string } } ;
96
+
97
+ if ( 'request' in objWithReq ) {
98
+ const url = objWithReq . request . url ;
99
+ const filepath = `payload-files/${ extractPathFromUrl ( url ) } .json` ;
100
+
101
+ fs . writeFile ( filepath , JSON . stringify ( transformedJSON , null , 2 ) , err => {
102
+ if ( err ) {
103
+ console . error ( 'Error writing file' , err ) ;
104
+ } else {
105
+ console . log ( `Successfully modified timestamp in ${ filepath } ` ) ;
106
+ }
107
+ } ) ;
108
+ }
109
+ } ) ;
110
+
111
+ fs . unlink ( TEMPORARY_FILE_PATH , err => {
112
+ if ( err ) {
113
+ console . error ( 'Error deleting file' , err ) ;
114
+ } else {
115
+ console . log ( `Successfully deleted ${ TEMPORARY_FILE_PATH } ` ) ;
116
+ }
117
+ } ) ;
118
+ }
119
+
28
120
/**
29
121
* Starts an event proxy server that will proxy events to sentry when the `tunnel` option is used. Point the `tunnel`
30
122
* option to this server (like this `tunnel: http://localhost:${port option}/`).
@@ -33,6 +125,8 @@ interface SentryRequestCallbackData {
33
125
export async function startEventProxyServer ( options : EventProxyServerOptions ) : Promise < void > {
34
126
const eventCallbackListeners : Set < ( data : string ) => void > = new Set ( ) ;
35
127
128
+ console . log ( `Proxy server "${ options . proxyServerName } " running. Waiting for events...` ) ;
129
+
36
130
const proxyServer = http . createServer ( ( proxyRequest , proxyResponse ) => {
37
131
const proxyRequestChunks : Uint8Array [ ] = [ ] ;
38
132
@@ -50,15 +144,25 @@ export async function startEventProxyServer(options: EventProxyServerOptions): P
50
144
? zlib . gunzipSync ( Buffer . concat ( proxyRequestChunks ) ) . toString ( )
51
145
: Buffer . concat ( proxyRequestChunks ) . toString ( ) ;
52
146
53
- let envelopeHeader = JSON . parse ( proxyRequestBody . split ( '\n' ) [ 0 ] ) ;
147
+ fs . writeFile ( TEMPORARY_FILE_PATH , `[${ proxyRequestBody } ]` , err => {
148
+ if ( err ) {
149
+ console . error ( `Error writing file ${ TEMPORARY_FILE_PATH } ` , err ) ;
150
+ } else {
151
+ console . log ( `Successfully wrote to ${ TEMPORARY_FILE_PATH } ` ) ;
152
+ }
153
+ } ) ;
154
+
155
+ transformSavedJSON ( ) ;
156
+
157
+ const envelopeHeader : EnvelopeItem [ 0 ] = JSON . parse ( proxyRequestBody . split ( '\n' ) [ 0 ] ) ;
54
158
55
159
if ( ! envelopeHeader . dsn ) {
56
160
throw new Error (
57
161
'[event-proxy-server] No dsn on envelope header. Please set tunnel option.' ,
58
162
) ;
59
163
}
60
164
61
- const { origin, pathname, host } = new URL ( envelopeHeader . dsn ) ;
165
+ const { origin, pathname, host } = new URL ( envelopeHeader . dsn as string ) ;
62
166
63
167
const projectId = pathname . substring ( 1 ) ;
64
168
const sentryIngestUrl = `${ origin } /api/${ projectId } /envelope/` ;
0 commit comments