@harshror77/rate-limiter-sdk 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@harshror77/rate-limiter-sdk",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "type": "module",
5
5
  "main": "src/index.js",
6
6
  "dependencies": {
@@ -1,23 +1,42 @@
1
1
  import axios from 'axios';
2
+ import fs from 'fs';
3
+ import path from 'path';
2
4
 
3
- export class RateLimiterClient{
4
- constructor({serviceUrl, apiKey, timeout=3000}){
5
+ // Extract the project name from the host environment ONCE when the SDK is initialized
6
+ let autoProjectName = 'Unknown Project';
7
+ try {
8
+ const pkgPath = path.resolve(process.cwd(), 'package.json');
9
+ if (fs.existsSync(pkgPath)) {
10
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
11
+ if (pkg.name) {
12
+ autoProjectName = pkg.name;
13
+ }
14
+ }
15
+ } catch (e) {
16
+ // Fail silently if package.json cannot be read, default to 'Unknown Project'
17
+ }
18
+
19
+ export class RateLimiterClient {
20
+ constructor({ serviceUrl, apiKey, timeout = 3000 }) {
5
21
  this.serviceUrl = serviceUrl;
6
22
  this.apiKey = apiKey;
7
23
  this.http = axios.create({
8
- baseURL:serviceUrl,
24
+ baseURL: serviceUrl,
9
25
  timeout
10
26
  });
11
27
  }
12
28
 
13
- async check(request={}){
14
- try{
15
- const response = await this.http.post('/api/check', request,{
16
- headers:{'x-api-key':this.apiKey},
17
- })
29
+ async check(request = {}) {
30
+ try {
31
+ const response = await this.http.post('/api/check', request, {
32
+ headers: {
33
+ 'x-api-key': this.apiKey,
34
+ 'x-client-name': autoProjectName // <-- Send the extracted name
35
+ },
36
+ });
18
37
  return response.data;
19
- }catch(err){
20
- if(err.response){
38
+ } catch (err) {
39
+ if (err.response) {
21
40
  return err.response.data;
22
41
  }
23
42
  throw err;
@@ -1,29 +1,66 @@
1
1
  import { RateLimiterClient } from './RateLimiterClient.js';
2
2
 
3
- export function RateLimiterMiddleware({ serviceUrl, apiKey, timeout }) {
3
+ export function RateLimiterMiddleware({ serviceUrl, apiKey, timeout, onLimitExceeded } = {}) {
4
4
  const client = new RateLimiterClient({ serviceUrl, apiKey, timeout });
5
5
 
6
6
  return async function (req, res, next) {
7
7
  try {
8
8
  const result = await client.check({
9
9
  ip: req.ip,
10
- userId: req.userId,
10
+ userId: req.user?.id || req.userId,
11
11
  });
12
12
 
13
+ res.setHeader('X-RateLimit-Remaining', result.remaining ?? 0);
14
+ res.setHeader('X-RateLimit-Reset', result.resetAt ?? 0);
15
+ res.setHeader('X-RateLimit-Denied-By', result.deniedBy || '');
16
+
13
17
  if (!result.allowed) {
14
- res.setHeader('X-RateLimit-Remaining', result.remaining ?? 0);
18
+ const resetTime = result.resetAt
19
+ ? new Date(result.resetAt).toISOString()
20
+ : null;
21
+
22
+ const retryAfterSeconds = result.resetAt
23
+ ? Math.ceil((result.resetAt - Date.now()) / 1000)
24
+ : 60;
25
+
26
+ res.setHeader('Retry-After', retryAfterSeconds);
27
+
28
+ if (onLimitExceeded) {
29
+ return onLimitExceeded(req, res, result);
30
+ }
31
+
15
32
  return res.status(429).json({
16
- error: 'Rate limit exceeded',
33
+ error: 'Too Many Requests',
34
+ message: buildMessage(result.deniedBy, retryAfterSeconds),
17
35
  deniedBy: result.deniedBy,
18
- remaining: result.remaining,
36
+ remaining: result.remaining ?? 0,
19
37
  resetAt: result.resetAt,
38
+ retryAfter: `${retryAfterSeconds} seconds`,
39
+ retryAfterISO: resetTime,
20
40
  });
21
41
  }
22
42
 
23
- res.setHeader('X-RateLimit-Remaining', result.remaining ?? 0);
24
43
  next();
25
44
  } catch (err) {
45
+ console.warn('[RateShield] Service unreachable, failing open:', err.message);
26
46
  next();
27
47
  }
28
48
  };
49
+ }
50
+
51
+ function buildMessage(deniedBy, retryAfterSeconds) {
52
+ const retry = retryAfterSeconds > 60
53
+ ? `${Math.ceil(retryAfterSeconds / 60)} minute(s)`
54
+ : `${retryAfterSeconds} second(s)`;
55
+
56
+ switch (deniedBy) {
57
+ case 'plan':
58
+ return `You have exceeded your plan's request limit. Please try again in ${retry}.`;
59
+ case 'ip':
60
+ return `Too many requests from your IP address. Please try again in ${retry}.`;
61
+ case 'user':
62
+ return `You are sending requests too quickly. Please slow down and try again in ${retry}.`;
63
+ default:
64
+ return `Rate limit exceeded. Please try again in ${retry}.`;
65
+ }
29
66
  }