Skip to content

Commit fdfb9aa

Browse files
authored
Merge pull request #5 from getsentry/sig-save-payload
feat(proxy): Save payload in json
2 parents f38d433 + ecd1ebf commit fdfb9aa

File tree

3 files changed

+117
-3
lines changed

3 files changed

+117
-3
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,15 @@ dist
122122
# Stores VSCode versions used for testing VSCode extensions
123123
.vscode-test
124124

125+
# JetBrains
126+
.idea
127+
125128
# yarn v2
126129
.yarn/cache
127130
.yarn/unplugged
128131
.yarn/build-state.yml
129132
.yarn/install-state.gz
130133
.pnp.*
134+
135+
# Mac
136+
.DS_Store
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
When running the event-proxy-server, the request are saved in a json file. This folder is where all
2+
the generated files go.

utils/event-proxy-server/src/event-proxy-server.ts

Lines changed: 109 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ import * as os from 'os';
66
import * as path from 'path';
77
import * as util from 'util';
88
import * as zlib from 'zlib';
9-
import type { Envelope } from '@sentry/types';
9+
import type { Envelope, EnvelopeItem } from '@sentry/types';
1010
import { parseEnvelope } from '@sentry/utils';
1111

12+
const readFile = util.promisify(fs.readFile);
1213
const writeFile = util.promisify(fs.writeFile);
14+
const unlink = util.promisify(fs.unlink);
1315

1416
interface EventProxyServerOptions {
1517
/** Port to start the event proxy server at. */
@@ -25,6 +27,99 @@ interface SentryRequestCallbackData {
2527
sentryResponseStatusCode?: number;
2628
}
2729

30+
const TEMPORARY_FILE_PATH = 'payload-files/temporary.json';
31+
32+
function isDateLikeString(str: string): boolean {
33+
// matches strings in the format "YYYY-MM-DD"
34+
const datePattern = /^\d{4}-\d{2}-\d{2}/;
35+
return datePattern.test(str);
36+
}
37+
38+
function extractPathFromUrl(url: string): string {
39+
const localhost = 'http://localhost:3030/';
40+
return url.replace(localhost, '');
41+
}
42+
43+
function addCommaAfterEachLine(data: string): string {
44+
const jsonData = data.trim().split('\n');
45+
46+
const jsonDataWithCommas = jsonData.map((item, index) =>
47+
index < jsonData.length - 1 ? item + ',' : item,
48+
);
49+
50+
return jsonDataWithCommas.join('\n');
51+
}
52+
53+
let idCounter = 1;
54+
const idMap = new Map();
55+
56+
function recursivelyReplaceData(obj: any) {
57+
for (let key in obj) {
58+
if (typeof obj[key] === 'string' && isDateLikeString(obj[key])) {
59+
obj[key] = `[[ISODateString]]`;
60+
} else if (key.includes('timestamp')) {
61+
obj[key] = `[[timestamp]]`;
62+
} else if (typeof obj[key] === 'number' && obj[key] > 1000) {
63+
obj[key] = `[[highNumber]]`;
64+
} else if (key.includes('_id')) {
65+
if (idMap.has(obj[key])) {
66+
// give the same ID replacement to the same value
67+
obj[key] = idMap.get(obj[key]);
68+
} else {
69+
const newId = `[[ID${idCounter++}]]`;
70+
idMap.set(obj[key], newId);
71+
obj[key] = newId;
72+
}
73+
} else if (typeof obj[key] === 'object' && obj[key] !== null) {
74+
recursivelyReplaceData(obj[key]);
75+
}
76+
}
77+
}
78+
79+
function replaceDynamicValues(data: string): string[] {
80+
const jsonData = JSON.parse(data);
81+
82+
recursivelyReplaceData(jsonData);
83+
84+
// change remaining dynamic values
85+
jsonData.forEach((item: any) => {
86+
if (item.trace?.public_key) {
87+
item.trace.public_key = '[[publicKey]]';
88+
}
89+
});
90+
91+
return jsonData;
92+
}
93+
94+
/** This function transforms all dynamic data (like timestamps) from the temporarily saved file.
95+
* The new content is saved into a new file with the url as the filename.
96+
* The temporary file is deleted in the end.
97+
*/
98+
async function transformSavedJSON() {
99+
try {
100+
const data = await readFile(TEMPORARY_FILE_PATH, 'utf8');
101+
102+
const jsonData = addCommaAfterEachLine(data);
103+
const transformedJSON = replaceDynamicValues(jsonData);
104+
const objWithReq = transformedJSON[2] as unknown as { request: { url: string } };
105+
106+
if ('request' in objWithReq) {
107+
const url = objWithReq.request.url;
108+
const filepath = `payload-files/${extractPathFromUrl(url)}.json`;
109+
110+
writeFile(filepath, JSON.stringify(transformedJSON, null, 2)).then(() => {
111+
console.log(`Successfully replaced data and saved file in ${filepath}`);
112+
113+
unlink(TEMPORARY_FILE_PATH).then(() =>
114+
console.log(`Successfully deleted ${TEMPORARY_FILE_PATH}`),
115+
);
116+
});
117+
}
118+
} catch (err) {
119+
console.error('Error', err);
120+
}
121+
}
122+
28123
/**
29124
* Starts an event proxy server that will proxy events to sentry when the `tunnel` option is used. Point the `tunnel`
30125
* option to this server (like this `tunnel: http://localhost:${port option}/`).
@@ -33,6 +128,8 @@ interface SentryRequestCallbackData {
33128
export async function startEventProxyServer(options: EventProxyServerOptions): Promise<void> {
34129
const eventCallbackListeners: Set<(data: string) => void> = new Set();
35130

131+
console.log(`Proxy server "${options.proxyServerName}" running. Waiting for events...`);
132+
36133
const proxyServer = http.createServer((proxyRequest, proxyResponse) => {
37134
const proxyRequestChunks: Uint8Array[] = [];
38135

@@ -50,15 +147,24 @@ export async function startEventProxyServer(options: EventProxyServerOptions): P
50147
? zlib.gunzipSync(Buffer.concat(proxyRequestChunks)).toString()
51148
: Buffer.concat(proxyRequestChunks).toString();
52149

53-
let envelopeHeader = JSON.parse(proxyRequestBody.split('\n')[0]);
150+
// save the JSON payload into a file
151+
try {
152+
writeFile(TEMPORARY_FILE_PATH, `[${proxyRequestBody}]`).then(() => {
153+
transformSavedJSON();
154+
});
155+
} catch (err) {
156+
console.error(`Error writing file ${TEMPORARY_FILE_PATH}`, err);
157+
}
158+
159+
const envelopeHeader: EnvelopeItem[0] = JSON.parse(proxyRequestBody.split('\n')[0]);
54160

55161
if (!envelopeHeader.dsn) {
56162
throw new Error(
57163
'[event-proxy-server] No dsn on envelope header. Please set tunnel option.',
58164
);
59165
}
60166

61-
const { origin, pathname, host } = new URL(envelopeHeader.dsn);
167+
const { origin, pathname, host } = new URL(envelopeHeader.dsn as string);
62168

63169
const projectId = pathname.substring(1);
64170
const sentryIngestUrl = `${origin}/api/${projectId}/envelope/`;

0 commit comments

Comments
 (0)