diff --git a/lib/memoize/index.ts b/lib/memoize/index.ts index a363360c..c4c62f72 100644 --- a/lib/memoize/index.ts +++ b/lib/memoize/index.ts @@ -45,7 +45,7 @@ export function memoize(options: MemoizeOptions = {}): MethodDecorator { max = 50, ttl = 1000 * 60 * 30, strategy = 'concat', - skipCache = [] + skipCache = [], } = options; /* eslint-enable */ diff --git a/workers/archiver/src/index.ts b/workers/archiver/src/index.ts index 18d39de4..457f36c5 100644 --- a/workers/archiver/src/index.ts +++ b/workers/archiver/src/index.ts @@ -86,7 +86,7 @@ export default class ArchiverWorker extends Worker { const projects = await this.projectCollection.find({}).project({ _id: 1, - name: 1 + name: 1, }); const projectsData: ReportDataByProject[] = []; @@ -155,11 +155,11 @@ export default class ArchiverWorker extends Worker { await this.projectCollection.updateOne({ _id: project._id, }, - { - $inc: { - archivedEventsCount: deletedCount, - }, - }); + { + $inc: { + archivedEventsCount: deletedCount, + }, + }); } /** @@ -351,7 +351,7 @@ export default class ArchiverWorker extends Worker { this.logger.info('Report notification response:', { status: response?.status, statusText: response?.statusText, - data: response?.data + data: response?.data, }); } diff --git a/workers/email/scripts/emailOverview.ts b/workers/email/scripts/emailOverview.ts index ff36ed82..a108b86b 100644 --- a/workers/email/scripts/emailOverview.ts +++ b/workers/email/scripts/emailOverview.ts @@ -156,7 +156,7 @@ class EmailTestServer { tariffPlanId: '5f47f031ff71510040f433c1', password: '1as2eadd321a3cDf', plan: { - name: 'Корпоративный' + name: 'Корпоративный', }, workspaceName: workspace.name, }; diff --git a/workers/javascript/tests/index.test.ts b/workers/javascript/tests/index.test.ts index 6b57d1e4..531826e8 100644 --- a/workers/javascript/tests/index.test.ts +++ b/workers/javascript/tests/index.test.ts @@ -442,5 +442,4 @@ describe('JavaScript event worker', () => { await worker.finish(); }); - }); diff --git a/workers/limiter/tests/dbHelper.test.ts b/workers/limiter/tests/dbHelper.test.ts index 9b705878..cded599b 100644 --- a/workers/limiter/tests/dbHelper.test.ts +++ b/workers/limiter/tests/dbHelper.test.ts @@ -304,7 +304,7 @@ describe('DbHelper', () => { /** * Act */ - await dbHelper.updateWorkspacesEventsCountAndIsBlocked([updatedWorkspace]); + await dbHelper.updateWorkspacesEventsCountAndIsBlocked([ updatedWorkspace ]); /** * Assert diff --git a/workers/paymaster/src/index.ts b/workers/paymaster/src/index.ts index 333a3911..94be50d9 100644 --- a/workers/paymaster/src/index.ts +++ b/workers/paymaster/src/index.ts @@ -160,6 +160,8 @@ export default class PaymasterWorker extends Worker { /** * Finds plan by id from cached plans + * + * @param planId */ private findPlanById(planId: WorkspaceDBScheme['tariffPlanId']): PlanDBScheme | undefined { return this.plans.find((plan) => plan._id.toString() === planId.toString()); @@ -167,6 +169,8 @@ export default class PaymasterWorker extends Worker { /** * Returns workspace plan, refreshes cache when plan is missing + * + * @param workspace */ private async getWorkspacePlan(workspace: WorkspaceDBScheme): Promise { let currentPlan = this.findPlanById(workspace.tariffPlanId); @@ -413,7 +417,6 @@ export default class PaymasterWorker extends Worker { }); } - /** * Sends reminder emails to blocked workspace admins * diff --git a/workers/paymaster/tests/index.test.ts b/workers/paymaster/tests/index.test.ts index 8ad43b4d..51ff31fd 100644 --- a/workers/paymaster/tests/index.test.ts +++ b/workers/paymaster/tests/index.test.ts @@ -317,6 +317,7 @@ describe('PaymasterWorker', () => { } MockDate.reset(); + return addTaskSpy; }; diff --git a/workers/release/src/index.ts b/workers/release/src/index.ts index 93c2618f..ea9282dc 100644 --- a/workers/release/src/index.ts +++ b/workers/release/src/index.ts @@ -281,6 +281,7 @@ export default class ReleaseWorker extends Worker { /** * Some bundlers could skip file in the source map content since it duplicates in map name * Like map name bundle.js.map is a source map for a bundle.js + * * @see https://sourcemaps.info/spec.html - format */ originFileName: mapContent.file ?? file.name.replace(/\.map$/, ''), diff --git a/workers/sentry/src/index.ts b/workers/sentry/src/index.ts index b0594094..ff71217b 100644 --- a/workers/sentry/src/index.ts +++ b/workers/sentry/src/index.ts @@ -67,10 +67,12 @@ export default class SentryEventWorker extends Worker { /** * Filter out binary items that crash parseEnvelope + * Also filters out all Sentry Replay events (replay_event and replay_recording) */ private filterOutBinaryItems(rawEvent: string): string { const lines = rawEvent.split('\n'); const filteredLines = []; + let isInReplayBlock = false; for (let i = 0; i < lines.length; i++) { const line = lines[i]; @@ -90,17 +92,42 @@ export default class SentryEventWorker extends Worker { // Try to parse as JSON to check if it's a header const parsed = JSON.parse(line); - // If it's a replay header, skip this line and the next one (payload) + // Check if this is a replay event type if (parsed.type === 'replay_recording' || parsed.type === 'replay_event') { - // Skip the next line too (which would be the payload) - i++; + // Mark that we're in a replay block and skip this line + isInReplayBlock = true; continue; } - // Keep valid headers and other JSON data - filteredLines.push(line); + // If we're in a replay block, check if this is still part of it + if (isInReplayBlock) { + // Check if this line is part of replay data (segment_id, length, etc.) + if ('segment_id' in parsed || ('length' in parsed && parsed.type !== 'event') || 'replay_id' in parsed) { + // Still in replay block, skip this line + continue; + } + + // If it's a new envelope item (like event), we've exited the replay block + if (parsed.type === 'event' || parsed.type === 'transaction' || parsed.type === 'session') { + isInReplayBlock = false; + } else { + // Unknown type, assume we're still in replay block + continue; + } + } + + // Keep valid headers and other JSON data (not in replay block) + if (!isInReplayBlock) { + filteredLines.push(line); + } } catch { - // If line doesn't parse as JSON, it might be binary data - skip it + // If line doesn't parse as JSON, it might be binary data + // If we're in a replay block, skip it (it's part of replay recording) + if (isInReplayBlock) { + continue; + } + + // If not in replay block and not JSON, it might be corrupted data - skip it continue; } } diff --git a/workers/sentry/tests/index.test.ts b/workers/sentry/tests/index.test.ts index e41c9fc5..acf44e96 100644 --- a/workers/sentry/tests/index.test.ts +++ b/workers/sentry/tests/index.test.ts @@ -806,12 +806,18 @@ describe('SentryEventWorker', () => { event_id: '4c40fee730194a989439a86bf75634111', sent_at: '2025-08-29T10:59:29.952Z', /* eslint-enable @typescript-eslint/naming-convention */ - sdk: { name: 'sentry.javascript.react', version: '9.10.1' }, + sdk: { + name: 'sentry.javascript.react', + version: '9.10.1', + }, }), // Event item header JSON.stringify({ type: 'event' }), // Event item payload - JSON.stringify({ message: 'Test event', level: 'error' }), + JSON.stringify({ + message: 'Test event', + level: 'error', + }), // Replay event item header - should be filtered out JSON.stringify({ type: 'replay_event' }), // Replay event item payload - should be filtered out @@ -822,7 +828,10 @@ describe('SentryEventWorker', () => { /* eslint-enable @typescript-eslint/naming-convention */ }), // Replay recording item header - should be filtered out - JSON.stringify({ type: 'replay_recording', length: 343 }), + JSON.stringify({ + type: 'replay_recording', + length: 343, + }), // Replay recording binary payload - should be filtered out 'binary-data-here-that-is-not-json', ]; @@ -841,6 +850,7 @@ describe('SentryEventWorker', () => { expect(mockedAmqpChannel.sendToQueue).toHaveBeenCalledTimes(1); const addedTaskPayload = getAddTaskPayloadFromLastCall(); + expect(addedTaskPayload).toMatchObject({ payload: expect.objectContaining({ addons: { @@ -852,6 +862,90 @@ describe('SentryEventWorker', () => { }), }); }); + + it('should ignore envelope with only replay_event and replay_recording items', async () => { + /** + * Test case based on real-world scenario where envelope contains only replay data + * This should not crash with "Unexpected end of JSON input" error + */ + const envelopeLines = [ + // Envelope header + JSON.stringify({ + /* eslint-disable @typescript-eslint/naming-convention */ + event_id: '62680958b3ab4497886375e06533d86a', + sent_at: '2025-12-24T13:16:34.580Z', + /* eslint-enable @typescript-eslint/naming-convention */ + sdk: { + name: 'sentry.javascript.react', + version: '10.22.0', + }, + }), + // Replay event item header - should be filtered out + JSON.stringify({ type: 'replay_event' }), + // Replay event item payload (large JSON) - should be filtered out + JSON.stringify({ + /* eslint-disable @typescript-eslint/naming-convention */ + type: 'replay_event', + replay_start_timestamp: 1766582182.757, + timestamp: 1766582194.579, + error_ids: [], + trace_ids: [], + urls: ['https://my.huntio.ru/applicants', 'https://my.huntio.ru/applicants/1270067'], + replay_id: '62680958b3ab4497886375e06533d86a', + segment_id: 1, + replay_type: 'session', + request: { + url: 'https://my.huntio.ru/applicants/1270067', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', + }, + }, + event_id: '62680958b3ab4497886375e06533d86a', + environment: 'production', + release: '1.0.7', + sdk: { + integrations: ['InboundFilters', 'FunctionToString', 'BrowserApiErrors', 'Breadcrumbs'], + name: 'sentry.javascript.react', + version: '10.22.0', + settings: { infer_ip: 'auto' }, + }, + user: { + id: 487, + email: 'npr@unicorn-resources.pro', + username: 'Прохорова Наталья', + }, + contexts: { react: { version: '19.1.0' } }, + transaction: '/applicants/1270067', + platform: 'javascript', + /* eslint-enable @typescript-eslint/naming-convention */ + }), + // Replay recording item header - should be filtered out + JSON.stringify({ + type: 'replay_recording', + length: 16385, + }), + /* eslint-disable @typescript-eslint/naming-convention */ + // Segment ID - should be filtered out + JSON.stringify({ segment_id: 1 }), + /* eslint-enable @typescript-eslint/naming-convention */ + // Binary data (simulated) - should be filtered out + 'xnFWy@v$xAlJ=&fS~¾˶IJ {