Skip to content

Commit d9cafec

Browse files
committed
First commit
0 parents  commit d9cafec

14 files changed

+603
-0
lines changed

.bumpedrc

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
files:
2+
- package.json
3+
plugins:
4+
prerelease:
5+
Linting config files:
6+
plugin: bumped-finepack
7+
postrelease:
8+
Generating CHANGELOG file:
9+
plugin: bumped-changelog
10+
Committing new version:
11+
plugin: bumped-terminal
12+
command: 'git add CHANGELOG.md package.json && git commit -m "Release $newVersion"'
13+
Detecting problems before publish:
14+
plugin: bumped-terminal
15+
command: 'git-dirty && npm test'
16+
Publishing tag to GitHub:
17+
plugin: bumped-terminal
18+
command: 'git tag $newVersion && git push && git push --tags'
19+
Publishing to NPM:
20+
plugin: bumped-terminal
21+
command: npm publish

.editorconfig

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# http://editorconfig.org
2+
3+
root = true
4+
5+
[*]
6+
indent_style = space
7+
indent_size = 2
8+
end_of_line = lf
9+
charset = utf-8
10+
trim_trailing_whitespace = true
11+
insert_final_newline = true
12+
max_line_length = 100
13+
indent_brace_style = 1TBS
14+
spaces_around_operators = true
15+
quote_type = auto
16+
17+
[package.json]
18+
indent_style = space
19+
indent_size = 2
20+
21+
[*.md]
22+
trim_trailing_whitespace = false

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* text=auto

.gitignore

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
############################
2+
# npm
3+
############################
4+
node_modules
5+
npm-debug.log
6+
.node_history
7+
yarn.lock
8+
package-lock.json
9+
10+
############################
11+
# tmp, editor & OS files
12+
############################
13+
.tmp
14+
*.swo
15+
*.swp
16+
*.swn
17+
*.swm
18+
.DS_Store
19+
*#
20+
*~
21+
.idea
22+
*sublime*
23+
nbproject
24+
25+
############################
26+
# Tests
27+
############################
28+
testApp
29+
coverage
30+
.nyc_output
31+
32+
############################
33+
# Other
34+
############################

.npmrc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
unsafe-perm=true
2+
save-prefix=~
3+
shrinkwrap=false
4+
save=false

.travis.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
language: node_js
2+
services:
3+
- redis-server
4+
node_js:
5+
- "node"
6+
- "lts/*"
7+
after_success: npm run coveralls

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright © 2018 microlink.io <hello@microlink.io> (microlink.io)
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

README.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# async-ratelimiter
2+
3+
![Last version](https://img.shields.io/github/tag/microlinkhq/async-ratelimiter.svg?style=flat-square)
4+
[![Build Status](https://img.shields.io/travis/microlinkhq/async-ratelimiter/master.svg?style=flat-square)](https://travis-ci.org/microlinkhq/async-ratelimiter)
5+
[![Coverage Status](https://img.shields.io/coveralls/microlinkhq/async-ratelimiter.svg?style=flat-square)](https://coveralls.io/github/microlinkhq/async-ratelimiter)
6+
[![Dependency status](https://img.shields.io/david/microlinkhq/async-ratelimiter.svg?style=flat-square)](https://david-dm.org/microlinkhq/async-ratelimiter)
7+
[![Dev Dependencies Status](https://img.shields.io/david/dev/microlinkhq/async-ratelimiter.svg?style=flat-square)](https://david-dm.org/microlinkhq/async-ratelimiter#info=devDependencies)
8+
[![NPM Status](https://img.shields.io/npm/dm/async-ratelimiter.svg?style=flat-square)](https://www.npmjs.org/package/async-ratelimiter)
9+
[![Donate](https://img.shields.io/badge/donate-paypal-blue.svg?style=flat-square)](https://paypal.me/microlinkhq)
10+
11+
> Rate limit made simple, easy, async.
12+
13+
> NOTE: It requires Redis 2.6.12+
14+
15+
## Install
16+
17+
```bash
18+
$ npm install async-ratelimiter --save
19+
```
20+
21+
## Usage
22+
23+
A simple middleware implementation for whatever HTTP server:
24+
25+
```js
26+
'use strict'
27+
28+
const RateLimiter = require('async-ratelimiter')
29+
const Redis = require('ioredis')
30+
31+
const limit = new RateLimiter({
32+
db: new Redis()
33+
})
34+
35+
const apiQuota = async (req, res, handler) => {
36+
const limit = await rateLimiter.get({ id: req.clientIp })
37+
38+
if (!res.finished && !res.headersSent) {
39+
res.setHeader('X-Rate-Limit-Limit', limit.total)
40+
res.setHeader('X-Rate-Limit-Remaining', Math.max(0, limit.remaining - 1))
41+
res.setHeader('X-Rate-Limit-Reset', limit.reset)
42+
}
43+
44+
return !limit.remaining
45+
? sendFail({req,
46+
res,
47+
code: HTTPStatus.TOO_MANY_REQUESTS,
48+
message: MESSAGES.RATE_LIMIT_EXCEDEED()
49+
})
50+
: handler(req, res)
51+
}
52+
```
53+
54+
## API
55+
56+
### constructor(options)
57+
58+
#### options
59+
60+
##### db
61+
62+
*Required*<br>
63+
Type: `object`
64+
65+
The redis connection instance.
66+
67+
##### max
68+
69+
Type: `number`<br>
70+
Default: `2500`
71+
72+
The maximum number of requests within `duration`.
73+
74+
##### duration
75+
76+
Type: `number`<br>
77+
Default: `3600000`
78+
79+
How long keep records of requests in milliseconds.
80+
81+
##### id
82+
83+
Type: `string`
84+
85+
The identifier to limit against (typically a user id).
86+
87+
You can pass this value using when you use `.get` method as well.
88+
89+
### .get(options)
90+
91+
Given an `id`, returns a Promise with the status of the limit with the following structure:
92+
- `total`: `max` value.
93+
- `remaining`: number of calls left in current `duration` without decreasing current `get`.
94+
- `reset`: time since epoch in seconds that the rate limiting period will end (or already ended).
95+
96+
#### options
97+
98+
##### id
99+
100+
Type: `string`
101+
102+
The identifier to limit against (typically a user id).
103+
104+
## License
105+
106+
**async-ratelimiter** © [microlink.io](https://microlink.io), released under the [MIT](https://github.com/microlinkhq/async-ratelimiter/blob/master/LICENSE.md) License.<br>
107+
Authored and maintained by microlink.io with help from [contributors](https://github.com/microlinkhq/async-ratelimiter/contributors).
108+
109+
> [microlink.io](https://microlink.io) · GitHub [microlink.io](https://github.com/microlinkhq) · Twitter [@microlinkhq](https://twitter.com/microlinkhq)

dump.rdb

92 Bytes
Binary file not shown.

package.json

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
{
2+
"name": "async-ratelimiter",
3+
"description": "Rate limit made simple, easy, async.",
4+
"homepage": "https://documentup.com/microlinkhq/async-ratelimiter",
5+
"version": "0.0.0",
6+
"main": "src/index.js",
7+
"author": {
8+
"email": "hello@microlink.io",
9+
"name": "microlink.io",
10+
"url": "https://microlink.io"
11+
},
12+
"repository": {
13+
"type": "git",
14+
"url": "git+https://github.com/microlinkhq/async-ratelimiter.git"
15+
},
16+
"bugs": {
17+
"url": "https://github.com/microlinkhq/async-ratelimiter/issues"
18+
},
19+
"keywords": [
20+
"async",
21+
"limit",
22+
"limiter",
23+
"promise",
24+
"rate",
25+
"ratelimit"
26+
],
27+
"devDependencies": {
28+
"coveralls": "latest",
29+
"finepack": "latest",
30+
"git-dirty": "latest",
31+
"husky": "latest",
32+
"ioredis": "latest",
33+
"lint-staged": "latest",
34+
"mocha": "latest",
35+
"nyc": "latest",
36+
"prettier-standard": "latest",
37+
"should": "latest",
38+
"standard": "latest",
39+
"standard-markdown": "latest"
40+
},
41+
"engines": {
42+
"node": ">= 8"
43+
},
44+
"files": [
45+
"src"
46+
],
47+
"scripts": {
48+
"clean": "rm -rf node_modules",
49+
"coveralls": "nyc report --reporter=text-lcov | coveralls",
50+
"lint": "standard-markdown && standard",
51+
"precommit": "lint-staged",
52+
"pretest": "npm run lint",
53+
"pretty": "prettier-standard index.js {core,test,bin,scripts}/**/*.js --single-quote --print-width 100",
54+
"test": "nyc mocha"
55+
},
56+
"license": "MIT",
57+
"lint-staged": {
58+
"package.json": [
59+
"finepack",
60+
"git add"
61+
],
62+
"*.js": [
63+
"prettier-standard",
64+
"git add"
65+
],
66+
"*.md": [
67+
"standard-markdown",
68+
"git add"
69+
]
70+
},
71+
"standard": {
72+
"env": [
73+
"mocha"
74+
]
75+
}
76+
}

src/index.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
'use strict'
2+
3+
const assert = require('assert')
4+
5+
const microtime = require('./microtime')
6+
7+
module.exports = class Limiter {
8+
constructor ({ id, db, max = 2500, duration = 3600000, namespace = 'limit' }) {
9+
assert(db, 'db required')
10+
this.db = db
11+
this.id = id
12+
this.max = max
13+
this.duration = duration
14+
this.namespace = namespace
15+
}
16+
17+
async get ({ id = this.id } = {}) {
18+
assert(id, 'id required')
19+
20+
const { db, duration, max } = this
21+
const key = `${this.namespace}:${this.id}`
22+
const now = microtime.now()
23+
const start = now - duration * 1000
24+
25+
const res = await db
26+
.multi()
27+
.zrange([key, 0, start, 'WITHSCORES'])
28+
.zcard([key])
29+
.zadd([key, now, now])
30+
.zrange([key, 0, 0])
31+
.pexpire([key, duration])
32+
.exec()
33+
34+
const count = parseInt(Array.isArray(res[0]) ? res[1][1] : res[1])
35+
const oldest = parseInt(Array.isArray(res[0]) ? res[3][1] : res[3])
36+
37+
return {
38+
remaining: count < max ? max - count : 0,
39+
reset: Math.floor((oldest + duration * 1000) / 1000000),
40+
total: max
41+
}
42+
}
43+
}

src/microtime.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
'use strict'
2+
3+
const time = Date.now() * 1e3
4+
const start = process.hrtime()
5+
6+
module.exports.now = function () {
7+
const diff = process.hrtime(start)
8+
return time + diff[0] * 1e6 + Math.round(diff[1] * 1e-3)
9+
}

0 commit comments

Comments
 (0)