Skip to content

Commit 12f07fe

Browse files
committed
add LimitRate settings
1 parent c3f5d21 commit 12f07fe

File tree

13 files changed

+219
-8
lines changed

13 files changed

+219
-8
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { it, expect } from '@jest/globals';
2+
3+
import { EmailJSResponseStatus } from '../../models/EmailJSResponseStatus';
4+
import { limitRateError } from './limitRateError';
5+
6+
it('should return EmailJSResponseStatus', () => {
7+
expect(limitRateError()).toBeInstanceOf(EmailJSResponseStatus);
8+
});
9+
10+
it('should return status 451', () => {
11+
expect(limitRateError()).toEqual({
12+
status: 429,
13+
text: 'Too Many Requests',
14+
});
15+
});
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { EmailJSResponseStatus } from '../../models/EmailJSResponseStatus';
2+
3+
export const limitRateError = () => {
4+
return new EmailJSResponseStatus(429, 'Too Many Requests');
5+
};

src/methods/init/init.spec.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { store } from '../../store/store';
55

66
beforeEach(() => {
77
store.origin = 'https://api.emailjs.com';
8-
store.limitRate = 0;
98
store.publicKey = undefined;
109
});
1110

@@ -16,7 +15,6 @@ describe('sdk v3', () => {
1615
expect(store).toEqual({
1716
origin: 'https://api.emailjs.com',
1817
publicKey: 'C2JWGTestKeySomething',
19-
limitRate: 0,
2018
});
2119
});
2220
});
@@ -27,7 +25,6 @@ describe('sdk v4', () => {
2725

2826
expect(store).toEqual({
2927
origin: 'https://api.emailjs.com',
30-
limitRate: 0,
3128
});
3229
});
3330

@@ -37,7 +34,9 @@ describe('sdk v4', () => {
3734
blockList: {
3835
list: ['block@email.com'],
3936
},
40-
limitRate: 10000,
37+
limitRate: {
38+
throttle: 10000,
39+
},
4140
});
4241

4342
expect(store).toEqual({
@@ -46,7 +45,9 @@ describe('sdk v4', () => {
4645
blockList: {
4746
list: ['block@email.com'],
4847
},
49-
limitRate: 10000,
48+
limitRate: {
49+
throttle: 10000,
50+
},
5051
});
5152
});
5253
});

src/methods/init/init.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,6 @@ export const init = (
1919
store.publicKey = opts.publicKey;
2020
store.blockHeadless = opts.blockHeadless;
2121
store.blockList = opts.blockList;
22-
store.limitRate = opts.limitRate || store.limitRate;
22+
store.limitRate = opts.limitRate;
2323
store.origin = opts.origin || origin;
2424
};

src/methods/send/send.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { isHeadless } from '../../utils/isHeadless/isHeadless';
1010
import { headlessError } from '../../errors/headlessError/headlessError';
1111
import { isBlockedValueInParams } from '../../utils/isBlockedValue/isBlockedValue';
1212
import { blockedEmailError } from '../../errors/blockedEmailError/blockedEmailError';
13+
import { isLimitRateHit } from '../../utils/isLimitRateHit/isLimitRateHit';
14+
import { limitRateError } from '../../errors/limitRateError/limitRateError';
1315

1416
/**
1517
* Send a template to the specific EmailJS service
@@ -29,6 +31,7 @@ export const send = (
2931
const publicKey = opts.publicKey || store.publicKey;
3032
const blockHeadless = opts.blockHeadless || store.blockHeadless;
3133
const blockList = { ...store.blockList, ...opts.blockList };
34+
const limitRate = { ...store.limitRate, ...opts.limitRate };
3235

3336
if (blockHeadless && isHeadless(navigator)) {
3437
return Promise.reject(headlessError());
@@ -41,6 +44,10 @@ export const send = (
4144
return Promise.reject(blockedEmailError());
4245
}
4346

47+
if (isLimitRateHit(localStorage, location.pathname, limitRate)) {
48+
return Promise.reject(limitRateError());
49+
}
50+
4451
const params = {
4552
lib_version: process.env.npm_package_version,
4653
user_id: publicKey,

src/methods/sendForm/sendForm.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { isHeadless } from '../../utils/isHeadless/isHeadless';
1010
import { headlessError } from '../../errors/headlessError/headlessError';
1111
import { isBlockedValueInParams } from '../../utils/isBlockedValue/isBlockedValue';
1212
import { blockedEmailError } from '../../errors/blockedEmailError/blockedEmailError';
13+
import { isLimitRateHit } from '../../utils/isLimitRateHit/isLimitRateHit';
14+
import { limitRateError } from '../../errors/limitRateError/limitRateError';
1315

1416
const findHTMLForm = (form: string | HTMLFormElement): HTMLFormElement | null => {
1517
return typeof form === 'string' ? document.querySelector<HTMLFormElement>(form) : form;
@@ -33,6 +35,7 @@ export const sendForm = (
3335
const publicKey = opts.publicKey || store.publicKey;
3436
const blockHeadless = opts.blockHeadless || store.blockHeadless;
3537
const blockList = { ...store.blockList, ...opts.blockList };
38+
const limitRate = { ...store.limitRate, ...opts.limitRate };
3639

3740
if (blockHeadless && isHeadless(navigator)) {
3841
return Promise.reject(headlessError());
@@ -49,6 +52,10 @@ export const sendForm = (
4952
return Promise.reject(blockedEmailError());
5053
}
5154

55+
if (isLimitRateHit(localStorage, location.pathname, limitRate)) {
56+
return Promise.reject(limitRateError());
57+
}
58+
5259
formData.append('lib_version', process.env.npm_package_version!);
5360
formData.append('service_id', serviceID);
5461
formData.append('template_id', templateID);

src/store/store.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,4 @@ import type { Options } from '../types/Options';
33
export const store: Options = {
44
origin: 'https://api.emailjs.com',
55
blockHeadless: false,
6-
limitRate: 0,
76
};

src/types/LimitRate.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export interface LimitRate {
2+
id?: string;
3+
throttle?: number; // in miliseconds
4+
}

src/types/Options.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import type { BlockList } from './BlockList';
2+
import type { LimitRate } from './LimitRate';
23

34
export interface Options {
45
origin?: string;
56
publicKey?: string;
67
blockHeadless?: boolean;
78
blockList?: BlockList;
8-
limitRate?: number;
9+
limitRate?: LimitRate;
910
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { it, describe, expect, beforeEach } from '@jest/globals';
2+
import { isLimitRateHit } from './isLimitRateHit';
3+
import type { LimitRate } from '../../types/LimitRate';
4+
5+
beforeEach(() => {
6+
localStorage.clear();
7+
});
8+
9+
describe('limit rate is disabed', () => {
10+
it('empty limit rate options', () => {
11+
const limitRate: LimitRate = {};
12+
13+
expect(isLimitRateHit(localStorage, location.pathname, limitRate)).toBeFalsy();
14+
});
15+
16+
it('throttle is 0', () => {
17+
const limitRate: LimitRate = {
18+
throttle: 0,
19+
};
20+
21+
expect(isLimitRateHit(localStorage, location.pathname, limitRate)).toBeFalsy();
22+
});
23+
24+
it('no record', () => {
25+
const limitRate: LimitRate = {
26+
id: 'app',
27+
throttle: 1000,
28+
};
29+
30+
expect(isLimitRateHit(localStorage, location.pathname, limitRate)).toBeFalsy();
31+
});
32+
33+
it('no hit limit', async () => {
34+
const limitRate: LimitRate = {
35+
id: 'app',
36+
throttle: 100,
37+
};
38+
39+
expect(isLimitRateHit(localStorage, location.pathname, limitRate)).toBeFalsy();
40+
41+
await new Promise((r) => setTimeout(r, 150));
42+
43+
expect(isLimitRateHit(localStorage, location.pathname, limitRate)).toBeFalsy();
44+
});
45+
46+
it('not same page or ID', async () => {
47+
const limitRate: LimitRate = {
48+
throttle: 100,
49+
};
50+
51+
expect(isLimitRateHit(localStorage, location.pathname, limitRate)).toBeFalsy();
52+
53+
location.replace('/new-form');
54+
55+
expect(isLimitRateHit(localStorage, location.pathname, limitRate)).toBeFalsy();
56+
});
57+
});
58+
59+
describe('limit rate is enabled', () => {
60+
it('hit limit', async () => {
61+
const limitRate: LimitRate = {
62+
id: 'app',
63+
throttle: 100,
64+
};
65+
66+
expect(isLimitRateHit(localStorage, location.pathname, limitRate)).toBeFalsy();
67+
expect(isLimitRateHit(localStorage, location.pathname, limitRate)).toBeTruthy();
68+
});
69+
70+
it('restore after page refresh and hit limit', () => {
71+
const limitRate: LimitRate = {
72+
throttle: 100,
73+
};
74+
75+
expect(isLimitRateHit(localStorage, location.pathname, limitRate)).toBeFalsy();
76+
77+
location.reload();
78+
79+
expect(isLimitRateHit(localStorage, location.pathname, limitRate)).toBeTruthy();
80+
});
81+
82+
it('next page refresh and hit limit', () => {
83+
const limitRate: LimitRate = {
84+
id: 'app',
85+
throttle: 100,
86+
};
87+
88+
expect(isLimitRateHit(localStorage, location.pathname, limitRate)).toBeFalsy();
89+
90+
location.replace('/new-form');
91+
92+
expect(isLimitRateHit(localStorage, location.pathname, limitRate)).toBeTruthy();
93+
});
94+
});

0 commit comments

Comments
 (0)