mirror of
https://github.com/jason5ng32/MyIP.git
synced 2026-11-04 08:55:47 +00:00
Frontend (@sentry/vue, loaded via build-time-gated dynamic import — no VITE_SENTRY_DSN_FRONTEND, no Sentry in the bundle; init joins the mount gate so instrumentation precedes the first render): - uncaught errors, route-change tracing / Web Vitals, error-only masked Session Replay, console.error capture fingerprinted per message prefix so each fallback source stays a distinct issue - explicit ip-source:exhausted signal via the app-events bus - hidden source maps uploaded (then deleted) by @sentry/vite-plugin, gated on SENTRY_AUTH_TOKEN Backend (@sentry/node, bootstrapped by sentry-instrument.js via node --import so ESM loader hooks land before express; never loaded without SENTRY_DSN_BACKEND): - Express error capture + per-route tracing - logger hook mirrors warn+ lines to Sentry Logs and elevates error+ to grouped, alertable Issues - Sentry Crons check-ins for the MaxMind / CAIDA / service-status jobs via the no-op-without-DSN wrapper common/sentry-cron.js Delivery & ops: - first-party tunnel /api/monitoring relays envelopes past ad blockers; DSN-allowlisted (never an open relay), exempt from the shared rate limiter with its own generous ceiling - rate-limit ledger file becomes opt-in now that the warn line reaches Sentry Logs; ecosystem.config.cjs carries the --import flag for pm2 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
65 lines
2.3 KiB
JavaScript
65 lines
2.3 KiB
JavaScript
// api/sentry-tunnel.js — envelope-header parsing and the guard branches that
|
|
// return before any upstream forward (method gate / unconfigured / invalid
|
|
// envelope / foreign DSN). No real Sentry traffic is ever sent.
|
|
import { test, beforeEach } from 'node:test';
|
|
import assert from 'node:assert';
|
|
|
|
import tunnelHandler, { parseEnvelopeDsn } from '../api/sentry-tunnel.js';
|
|
|
|
const OUR_DSN = 'https://publickey@o111.ingest.sentry.io/222';
|
|
|
|
const makeRes = () => {
|
|
const res = {
|
|
statusCode: null,
|
|
body: null,
|
|
status(code) { this.statusCode = code; return this; },
|
|
json(payload) { this.body = payload; return this; },
|
|
send(payload) { this.body = payload; return this; },
|
|
};
|
|
return res;
|
|
};
|
|
|
|
const envelopeFor = (dsn) => Buffer.from(`${JSON.stringify({ dsn })}\n{"type":"event"}\n{}`);
|
|
|
|
beforeEach(() => {
|
|
delete process.env.VITE_SENTRY_DSN_FRONTEND;
|
|
});
|
|
|
|
test('parseEnvelopeDsn extracts the DSN from the envelope header', () => {
|
|
const dsn = parseEnvelopeDsn(envelopeFor(OUR_DSN));
|
|
assert.strictEqual(dsn.host, 'o111.ingest.sentry.io');
|
|
assert.strictEqual(dsn.pathname, '/222');
|
|
});
|
|
|
|
test('parseEnvelopeDsn returns null for garbage input', () => {
|
|
assert.strictEqual(parseEnvelopeDsn(Buffer.from('not json')), null);
|
|
assert.strictEqual(parseEnvelopeDsn(Buffer.from('{"no_dsn":1}\n{}')), null);
|
|
});
|
|
|
|
test('rejects non-POST methods with 405', async () => {
|
|
const res = makeRes();
|
|
await tunnelHandler({ method: 'GET' }, res);
|
|
assert.strictEqual(res.statusCode, 405);
|
|
});
|
|
|
|
test('returns 404 when no frontend DSN is configured', async () => {
|
|
const res = makeRes();
|
|
await tunnelHandler({ method: 'POST', body: envelopeFor(OUR_DSN) }, res);
|
|
assert.strictEqual(res.statusCode, 404);
|
|
});
|
|
|
|
test('returns 400 on an unparseable envelope', async () => {
|
|
process.env.VITE_SENTRY_DSN_FRONTEND = OUR_DSN;
|
|
const res = makeRes();
|
|
await tunnelHandler({ method: 'POST', body: Buffer.from('garbage') }, res);
|
|
assert.strictEqual(res.statusCode, 400);
|
|
});
|
|
|
|
test('returns 403 when the envelope targets a foreign DSN', async () => {
|
|
process.env.VITE_SENTRY_DSN_FRONTEND = OUR_DSN;
|
|
const res = makeRes();
|
|
const foreign = envelopeFor('https://otherkey@o999.ingest.sentry.io/888');
|
|
await tunnelHandler({ method: 'POST', body: foreign }, res);
|
|
assert.strictEqual(res.statusCode, 403);
|
|
});
|