Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/memoize/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export function memoize(options: MemoizeOptions = {}): MethodDecorator {
max = 50,
ttl = 1000 * 60 * 30,
strategy = 'concat',
skipCache = []
skipCache = [],
} = options;
/* eslint-enable */

Expand Down
14 changes: 7 additions & 7 deletions workers/archiver/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[] = [];

Expand Down Expand Up @@ -155,11 +155,11 @@ export default class ArchiverWorker extends Worker {
await this.projectCollection.updateOne({
_id: project._id,
},
{
$inc: {
archivedEventsCount: deletedCount,
},
});
{
$inc: {
archivedEventsCount: deletedCount,
},
});
}

/**
Expand Down Expand Up @@ -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,
});
}

Expand Down
2 changes: 1 addition & 1 deletion workers/email/scripts/emailOverview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ class EmailTestServer {
tariffPlanId: '5f47f031ff71510040f433c1',
password: '1as2eadd321a3cDf',
plan: {
name: 'Корпоративный'
name: 'Корпоративный',
},
workspaceName: workspace.name,
};
Expand Down
1 change: 0 additions & 1 deletion workers/javascript/tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -442,5 +442,4 @@ describe('JavaScript event worker', () => {

await worker.finish();
});

});
2 changes: 1 addition & 1 deletion workers/limiter/tests/dbHelper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ describe('DbHelper', () => {
/**
* Act
*/
await dbHelper.updateWorkspacesEventsCountAndIsBlocked([updatedWorkspace]);
await dbHelper.updateWorkspacesEventsCountAndIsBlocked([ updatedWorkspace ]);

/**
* Assert
Expand Down
5 changes: 4 additions & 1 deletion workers/paymaster/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,13 +160,17 @@ 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());
}

/**
* Returns workspace plan, refreshes cache when plan is missing
*
* @param workspace
*/
private async getWorkspacePlan(workspace: WorkspaceDBScheme): Promise<PlanDBScheme> {
let currentPlan = this.findPlanById(workspace.tariffPlanId);
Expand Down Expand Up @@ -413,7 +417,6 @@ export default class PaymasterWorker extends Worker {
});
}


/**
* Sends reminder emails to blocked workspace admins
*
Expand Down
1 change: 1 addition & 0 deletions workers/paymaster/tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ describe('PaymasterWorker', () => {
}

MockDate.reset();

return addTaskSpy;
};

Expand Down
1 change: 1 addition & 0 deletions workers/release/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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$/, ''),
Expand Down
39 changes: 33 additions & 6 deletions workers/sentry/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand All @@ -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;
}
}
Expand Down
100 changes: 97 additions & 3 deletions workers/sentry/tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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',
];
Expand All @@ -841,6 +850,7 @@ describe('SentryEventWorker', () => {
expect(mockedAmqpChannel.sendToQueue).toHaveBeenCalledTimes(1);

const addedTaskPayload = getAddTaskPayloadFromLastCall();

expect(addedTaskPayload).toMatchObject({
payload: expect.objectContaining({
addons: {
Expand All @@ -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<Dڒ%8yX]]ˣ·9V|JGd!+%fF',
];

const envelopeString = envelopeLines.join('\n');

// Should not throw "Unexpected end of JSON input" error
await worker.handle({
payload: {
envelope: b64encode(envelopeString),
},
projectId: '123',
catcherType: 'external/sentry',
});

// Should not send any tasks since all items were replay-related and filtered out
expect(mockedAmqpChannel.sendToQueue).not.toHaveBeenCalled();
});
});

describe('envelope parsing', () => {
Expand Down
Loading