Skip to content

Commit 2c48efd

Browse files
committed
fix: apply most linter suggestions
1 parent d588d19 commit 2c48efd

File tree

4 files changed

+109
-64
lines changed

4 files changed

+109
-64
lines changed

packages/sdk-analytics/src/analytics.test.ts

+19-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import nock from 'nock';
2+
/* eslint-disable-next-line id-length */
13
import * as t from 'vitest';
4+
25
import Analytics from './analytics';
3-
import nock from 'nock';
4-
import * as schema from './schema';
6+
import type * as schema from './schema';
57

68
t.describe('Analytics Integration', () => {
79
let analytics: Analytics;
@@ -17,6 +19,7 @@ t.describe('Analytics Integration', () => {
1719
};
1820

1921
t.afterAll(() => {
22+
/* eslint-disable-next-line import-x/no-named-as-default-member */
2023
nock.cleanAll();
2124
});
2225

@@ -28,7 +31,11 @@ t.describe('Analytics Integration', () => {
2831
return true; // Accept any body to proceed with the intercept
2932
})
3033
.optionally()
31-
.reply(200, { status: 'success' }, { 'Content-Type': 'application/json' });
34+
.reply(
35+
200,
36+
{ status: 'success' },
37+
{ 'Content-Type': 'application/json' },
38+
);
3239

3340
analytics = new Analytics('http://127.0.0.1');
3441
analytics.track(event.name, { ...event });
@@ -49,7 +56,11 @@ t.describe('Analytics Integration', () => {
4956
captured = body; // Capture the request body directly
5057
return true; // Accept any body to proceed with the intercept
5158
})
52-
.reply(200, { status: 'success' }, { 'Content-Type': 'application/json' });
59+
.reply(
60+
200,
61+
{ status: 'success' },
62+
{ 'Content-Type': 'application/json' },
63+
);
5364

5465
analytics = new Analytics('http://127.0.0.2');
5566
analytics.enable();
@@ -63,8 +74,10 @@ t.describe('Analytics Integration', () => {
6374
await new Promise((resolve) => setTimeout(resolve, 300));
6475

6576
// Verify the captured payload
66-
t.expect(captured).toEqual([{ ...event, dapp_id: 'some-non-global-property' }]);
77+
t.expect(captured).toEqual([
78+
{ ...event, dapp_id: 'some-non-global-property' },
79+
]);
6780

6881
scope.done();
6982
});
70-
});
83+
});

packages/sdk-analytics/src/analytics.ts

+20-13
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
1+
/* eslint-disable no-restricted-syntax */
12
import createClient from 'openapi-fetch';
2-
import * as schema from './schema';
3+
4+
import type * as schema from './schema';
35
import Sender from './sender';
46

57
type Event = schema.components['schemas']['Event'];
68

79
class Analytics {
8-
private enabled: boolean = false;
9-
private sender: Sender<Event>;
10+
private enabled = false;
11+
12+
private readonly sender: Sender<Event>;
13+
1014
private properties: Record<string, string> = {};
1115

1216
constructor(baseUrl: string) {
1317
const client = createClient<schema.paths>({ baseUrl });
1418

15-
const sendFn = async (batch: Event[]) => {
19+
const sendFn = async (batch: Event[]): Promise<void> => {
1620
const res = await client.POST('/v1/events', { body: batch });
1721
if (res.response.status !== 200) {
1822
throw new Error(res.error);
@@ -22,21 +26,24 @@ class Analytics {
2226
this.sender = new Sender({ batchSize: 100, baseIntervalMs: 200, sendFn });
2327
}
2428

25-
public enable() {
26-
if (this.enabled) return;
27-
this.enabled = true;
28-
this.sender.start();
29+
public enable(): void {
30+
if (!this.enabled) {
31+
this.enabled = true;
32+
this.sender.start();
33+
}
2934
}
3035

31-
public setGlobalProperty(key: string, value: string) {
36+
public setGlobalProperty(key: string, value: string): void {
3237
this.properties[key] = value;
3338
}
3439

35-
public track<T extends Event>(name: T['name'], properties: Partial<T>) {
36-
if (!this.enabled) return;
40+
public track<T extends Event>(name: T['name'], properties: Partial<T>): void {
41+
if (!this.enabled) {
42+
return;
43+
}
3744

38-
const event: Event = {
39-
name: name,
45+
const event = {
46+
name,
4047
...this.properties,
4148
...properties,
4249
} as T;
+43-30
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
/* eslint-disable-next-line id-length */
12
import * as t from 'vitest';
3+
24
import Sender from './sender';
35

46
t.describe('Sender', () => {
@@ -24,60 +26,71 @@ t.describe('Sender', () => {
2426
t.it('should flush periodically', async () => {
2527
sender.start();
2628
sender.enqueue('event1');
27-
await new Promise(resolve => setTimeout(resolve, 100));
29+
await new Promise((resolve) => setTimeout(resolve, 100));
2830
t.expect(sendFn).toHaveBeenCalledWith(['event1']);
2931
});
3032

31-
t.it('should handle failure and reset interval after successful send', async () => {
32-
let sendCount = 0;
33-
sendFn = t.vi.fn().mockImplementation(() => {
34-
sendCount++;
35-
return sendCount === 1 ? Promise.reject(new Error('Failed')) : Promise.resolve();
36-
});
37-
sender = new Sender({ batchSize: 100, baseIntervalMs: 50, sendFn }); // Short interval
38-
sender.start();
33+
t.it(
34+
'should handle failure and reset interval after successful send',
35+
async () => {
36+
let sendCount = 0;
37+
sendFn = t.vi.fn().mockImplementation(async () => {
38+
sendCount += 1;
39+
return sendCount === 1
40+
? Promise.reject(new Error('Failed'))
41+
: Promise.resolve();
42+
});
43+
sender = new Sender({ batchSize: 100, baseIntervalMs: 50, sendFn }); // Short interval
44+
sender.start();
3945

40-
sender.enqueue('event1');
41-
t.expect(sendFn).toHaveBeenCalledTimes(0);
46+
sender.enqueue('event1');
47+
t.expect(sendFn).toHaveBeenCalledTimes(0);
4248

43-
// Wait for initial send
44-
await new Promise(resolve => setTimeout(resolve, 51));
45-
t.expect(sendFn).toHaveBeenCalledTimes(1);
49+
// Wait for initial send
50+
await new Promise((resolve) => setTimeout(resolve, 51));
51+
t.expect(sendFn).toHaveBeenCalledTimes(1);
4652

47-
// Wait for first attempt + retry
48-
await new Promise(resolve => setTimeout(resolve, 101));
49-
t.expect(sendFn).toHaveBeenCalledTimes(2); // First attempt fails, then succeeds
53+
// Wait for first attempt + retry
54+
await new Promise((resolve) => setTimeout(resolve, 101));
55+
t.expect(sendFn).toHaveBeenCalledTimes(2); // First attempt fails, then succeeds
5056

51-
// Wait for next scheduled send (should happen at original interval)
52-
sender.enqueue('event2');
53-
await new Promise(resolve => setTimeout(resolve, 51));
54-
t.expect(sendFn).toHaveBeenCalledTimes(3);
55-
t.expect(sendFn).toHaveBeenCalledWith(['event2']);
56-
});
57+
// Wait for next scheduled send (should happen at original interval)
58+
sender.enqueue('event2');
59+
await new Promise((resolve) => setTimeout(resolve, 51));
60+
t.expect(sendFn).toHaveBeenCalledTimes(3);
61+
t.expect(sendFn).toHaveBeenCalledWith(['event2']);
62+
},
63+
);
5764

5865
t.it('should not send when batch is empty', async () => {
5966
sender.start();
60-
await new Promise(resolve => setTimeout(resolve, 51));
67+
await new Promise((resolve) => setTimeout(resolve, 51));
6168
t.expect(sendFn).not.toHaveBeenCalled(); // No send if batch is empty
6269
});
6370

6471
t.it('should handle concurrent sends properly', async () => {
65-
let resolveSend: (value?: unknown) => void;
66-
sendFn = t.vi.fn().mockImplementation(() => new Promise(resolve => { resolveSend = resolve; }));
72+
let resolveSend!: (value?: unknown) => void;
73+
sendFn = t.vi.fn().mockImplementation(
74+
// eslint-disable-next-line @typescript-eslint/promise-function-async
75+
() =>
76+
new Promise((resolve) => {
77+
resolveSend = resolve;
78+
}),
79+
);
6780
sender = new Sender({ batchSize: 100, baseIntervalMs: 1000, sendFn });
6881
sender.start();
6982

7083
sender.enqueue('event1');
71-
await new Promise(resolve => setTimeout(resolve, 1000));
84+
await new Promise((resolve) => setTimeout(resolve, 1000));
7285
t.expect(sendFn).toHaveBeenCalledWith(['event1']);
7386

7487
sender.enqueue('event2'); // Enqueue while sending
75-
resolveSend!(); // Finish the first send
88+
resolveSend(); // Finish the first send
7689
await Promise.resolve(); // Allow async flush to complete
7790

7891
// Wait for next scheduled send
79-
await new Promise(resolve => setTimeout(resolve, 1000));
92+
await new Promise((resolve) => setTimeout(resolve, 1000));
8093
t.expect(sendFn).toHaveBeenCalledWith(['event2']);
8194
t.expect(sendFn).toHaveBeenCalledTimes(2);
8295
});
83-
});
96+
});

packages/sdk-analytics/src/sender.ts

+27-15
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable no-restricted-syntax */
12
type SenderOptions<T> = {
23
batchSize: number;
34
baseIntervalMs: number;
@@ -9,14 +10,17 @@ type SenderOptions<T> = {
910
* It also uses exponential backoff to handle errors.
1011
*/
1112
class Sender<T> {
12-
private sendFn: (batch: T[]) => Promise<void>;
13+
private readonly sendFn: (batch: T[]) => Promise<void>;
1314

1415
private batch: T[] = [];
1516

16-
private batchSize: number;
17-
private baseIntervalMs: number;
17+
private readonly batchSize: number;
18+
19+
private readonly baseIntervalMs: number;
20+
1821
private currentIntervalMs: number;
19-
private maxIntervalMs: number = 30_000; // 30 seconds
22+
23+
private readonly maxIntervalMs: number = 30_000; // 30 seconds
2024

2125
private intervalId: NodeJS.Timeout | null = null;
2226

@@ -29,40 +33,48 @@ class Sender<T> {
2933
this.sendFn = options.sendFn;
3034
}
3135

32-
public enqueue(item: T) {
36+
public enqueue(item: T): void {
3337
this.batch.push(item);
3438
if (this.batch.length >= this.batchSize && !this.isSending) {
39+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
3540
this.flush();
3641
}
3742
}
3843

39-
public start() {
40-
if (this.intervalId) return;
44+
public start(): void {
45+
if (this.intervalId) {
46+
return;
47+
}
4148
this.scheduleNextSend();
4249
}
4350

44-
private scheduleNextSend() {
51+
private scheduleNextSend(): void {
4552
if (this.intervalId) {
4653
clearTimeout(this.intervalId);
4754
}
48-
this.intervalId = setTimeout(() => this.flush(), this.currentIntervalMs);
55+
this.intervalId = setTimeout(() => {
56+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
57+
this.flush();
58+
}, this.currentIntervalMs);
4959
}
5060

51-
private async flush() {
52-
if (this.isSending || this.batch.length === 0) return;
53-
61+
private async flush(): Promise<void> {
62+
if (this.isSending || this.batch.length === 0) {
63+
return;
64+
}
5465
this.isSending = true;
55-
5666
const current = [...this.batch];
57-
5867
this.batch = [];
5968

6069
try {
6170
await this.sendFn(current);
6271
this.currentIntervalMs = this.baseIntervalMs; // reset on success
6372
} catch {
6473
this.batch = [...current, ...this.batch];
65-
this.currentIntervalMs = Math.min(this.currentIntervalMs * 2, this.maxIntervalMs); // exponential backoff on error
74+
this.currentIntervalMs = Math.min(
75+
this.currentIntervalMs * 2,
76+
this.maxIntervalMs,
77+
); // exponential backoff on error
6678
} finally {
6779
this.isSending = false;
6880
this.scheduleNextSend();

0 commit comments

Comments
 (0)