Skip to content

Commit f38d433

Browse files
authored
Merge pull request #3 from getsentry/sig-add-event-proxy-server
feat(proxy): Add base for event proxy server
2 parents b3fc0ac + f3f744e commit f38d433

File tree

8 files changed

+201
-3
lines changed

8 files changed

+201
-3
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
_This repository is a work in progress_
44

55
The main purpose of this repository is to visualize the differences between the
6-
[Sentry JS SDK](https://github.com/getsentry/sentry-javascript) version 7 and version 8. Those example applications can
7-
also be used as a reference for using the JS SDKs.
6+
[Sentry JS SDK](https://github.com/getsentry/sentry-javascript) version 7 and version 8. Those
7+
example applications can also be used as a reference for using the JS SDKs.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@
88
},
99
"packageManager": "[email protected]",
1010
"scripts": {
11+
"start:proxy-server": "yarn workspace event-proxy-server run start",
1112
"start:express": "yarn workspace express-test-application run start",
1213
"fix:prettier": "prettier . --write",
1314
"fix:lint": "yarn run eslint --fix",
1415
"lint": "yarn run eslint"
1516
},
1617
"workspaces": [
18+
"utils/event-proxy-server",
1719
"apps/express"
1820
],
1921
"devDependencies": {
@@ -32,7 +34,7 @@
3234
},
3335
"prettier": {
3436
"arrowParens": "avoid",
35-
"printWidth": 120,
37+
"printWidth": 100,
3638
"proseWrap": "always",
3739
"singleQuote": true,
3840
"trailingComma": "all"

utils/event-proxy-server/package.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"private": true,
3+
"version": "8.0.0-alpha.7",
4+
"name": "event-proxy-server",
5+
"author": "Sentry",
6+
"license": "MIT",
7+
"sideEffects": false,
8+
"engines": {
9+
"node": ">=14.18"
10+
},
11+
"scripts": {
12+
"start": "ts-node start-event-proxy.ts",
13+
"fix": "eslint . --format stylish --fix",
14+
"lint": "eslint . --format stylish",
15+
"build:dev": "yarn build",
16+
"clean": "rimraf -g ./node_modules ./build"
17+
},
18+
"dependencies": {
19+
"@sentry/types": "7.109.0",
20+
"@sentry/utils": "7.109.0"
21+
},
22+
"volta": {
23+
"extends": "../../package.json"
24+
}
25+
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import * as fs from 'fs';
2+
import * as http from 'http';
3+
import * as https from 'https';
4+
import type { AddressInfo } from 'net';
5+
import * as os from 'os';
6+
import * as path from 'path';
7+
import * as util from 'util';
8+
import * as zlib from 'zlib';
9+
import type { Envelope } from '@sentry/types';
10+
import { parseEnvelope } from '@sentry/utils';
11+
12+
const writeFile = util.promisify(fs.writeFile);
13+
14+
interface EventProxyServerOptions {
15+
/** Port to start the event proxy server at. */
16+
port: number;
17+
/** The name for the proxy server used for referencing it with listener functions */
18+
proxyServerName: string;
19+
}
20+
21+
interface SentryRequestCallbackData {
22+
envelope: Envelope;
23+
rawProxyRequestBody: string;
24+
rawSentryResponseBody: string;
25+
sentryResponseStatusCode?: number;
26+
}
27+
28+
/**
29+
* Starts an event proxy server that will proxy events to sentry when the `tunnel` option is used. Point the `tunnel`
30+
* option to this server (like this `tunnel: http://localhost:${port option}/`).
31+
*
32+
*/
33+
export async function startEventProxyServer(options: EventProxyServerOptions): Promise<void> {
34+
const eventCallbackListeners: Set<(data: string) => void> = new Set();
35+
36+
const proxyServer = http.createServer((proxyRequest, proxyResponse) => {
37+
const proxyRequestChunks: Uint8Array[] = [];
38+
39+
proxyRequest.addListener('data', (chunk: Buffer) => {
40+
proxyRequestChunks.push(chunk);
41+
});
42+
43+
proxyRequest.addListener('error', err => {
44+
throw err;
45+
});
46+
47+
proxyRequest.addListener('end', () => {
48+
const proxyRequestBody =
49+
proxyRequest.headers['content-encoding'] === 'gzip'
50+
? zlib.gunzipSync(Buffer.concat(proxyRequestChunks)).toString()
51+
: Buffer.concat(proxyRequestChunks).toString();
52+
53+
let envelopeHeader = JSON.parse(proxyRequestBody.split('\n')[0]);
54+
55+
if (!envelopeHeader.dsn) {
56+
throw new Error(
57+
'[event-proxy-server] No dsn on envelope header. Please set tunnel option.',
58+
);
59+
}
60+
61+
const { origin, pathname, host } = new URL(envelopeHeader.dsn);
62+
63+
const projectId = pathname.substring(1);
64+
const sentryIngestUrl = `${origin}/api/${projectId}/envelope/`;
65+
66+
proxyRequest.headers.host = host;
67+
68+
const sentryResponseChunks: Uint8Array[] = [];
69+
70+
const sentryRequest = https.request(
71+
sentryIngestUrl,
72+
{ headers: proxyRequest.headers, method: proxyRequest.method },
73+
sentryResponse => {
74+
sentryResponse.addListener('data', (chunk: Buffer) => {
75+
proxyResponse.write(chunk, 'binary');
76+
sentryResponseChunks.push(chunk);
77+
});
78+
79+
sentryResponse.addListener('end', () => {
80+
eventCallbackListeners.forEach(listener => {
81+
const rawSentryResponseBody = Buffer.concat(sentryResponseChunks).toString();
82+
83+
const data: SentryRequestCallbackData = {
84+
envelope: parseEnvelope(proxyRequestBody, new TextEncoder(), new TextDecoder()),
85+
rawProxyRequestBody: proxyRequestBody,
86+
rawSentryResponseBody,
87+
sentryResponseStatusCode: sentryResponse.statusCode,
88+
};
89+
90+
listener(Buffer.from(JSON.stringify(data)).toString('base64'));
91+
});
92+
proxyResponse.end();
93+
});
94+
95+
sentryResponse.addListener('error', err => {
96+
throw err;
97+
});
98+
99+
proxyResponse.writeHead(sentryResponse.statusCode || 500, sentryResponse.headers);
100+
},
101+
);
102+
103+
sentryRequest.write(Buffer.concat(proxyRequestChunks), 'binary');
104+
sentryRequest.end();
105+
});
106+
});
107+
108+
const proxyServerStartupPromise = new Promise<void>(resolve => {
109+
proxyServer.listen(options.port, () => {
110+
resolve();
111+
});
112+
});
113+
114+
const eventCallbackServer = http.createServer((eventCallbackRequest, eventCallbackResponse) => {
115+
eventCallbackResponse.statusCode = 200;
116+
eventCallbackResponse.setHeader('connection', 'keep-alive');
117+
118+
const callbackListener = (data: string): void => {
119+
eventCallbackResponse.write(data.concat('\n'), 'utf8');
120+
};
121+
122+
eventCallbackListeners.add(callbackListener);
123+
124+
eventCallbackRequest.on('close', () => {
125+
eventCallbackListeners.delete(callbackListener);
126+
});
127+
128+
eventCallbackRequest.on('error', () => {
129+
eventCallbackListeners.delete(callbackListener);
130+
});
131+
});
132+
133+
const eventCallbackServerStartupPromise = new Promise<void>(resolve => {
134+
eventCallbackServer.listen(0, () => {
135+
const port = String((eventCallbackServer.address() as AddressInfo).port);
136+
void registerCallbackServerPort(options.proxyServerName, port).then(resolve);
137+
});
138+
});
139+
140+
await eventCallbackServerStartupPromise;
141+
await proxyServerStartupPromise;
142+
return;
143+
}
144+
145+
const TEMP_FILE_PREFIX = 'event-proxy-server-';
146+
147+
async function registerCallbackServerPort(serverName: string, port: string): Promise<void> {
148+
const tmpFilePath = path.join(os.tmpdir(), `${TEMP_FILE_PREFIX}${serverName}`);
149+
await writeFile(tmpFilePath, port, { encoding: 'utf8' });
150+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { startEventProxyServer } from './event-proxy-server';
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { startEventProxyServer } from './src/event-proxy-server';
2+
3+
startEventProxyServer({
4+
port: 3031,
5+
proxyServerName: 'event-proxy-server',
6+
});
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"extends": "../../tsconfig.json",
3+
"compilerOptions": {},
4+
"include": ["src/**/*.ts"]
5+
}

yarn.lock

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,6 +1259,15 @@ __metadata:
12591259
languageName: node
12601260
linkType: hard
12611261

1262+
"event-proxy-server@workspace:utils/event-proxy-server":
1263+
version: 0.0.0-use.local
1264+
resolution: "event-proxy-server@workspace:utils/event-proxy-server"
1265+
dependencies:
1266+
"@sentry/types": "npm:7.109.0"
1267+
"@sentry/utils": "npm:7.109.0"
1268+
languageName: unknown
1269+
linkType: soft
1270+
12621271
"exponential-backoff@npm:^3.1.1":
12631272
version: 3.1.1
12641273
resolution: "exponential-backoff@npm:3.1.1"

0 commit comments

Comments
 (0)