ac-geoip 1.4.2
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/.acsemver.js +9 -0
- package/.eslintrc.js +27 -0
- package/CHANGELOG.md +154 -0
- package/Makefile +16 -0
- package/README.md +118 -0
- package/index.js +289 -0
- package/package.json +26 -0
- package/test/test.js +133 -0
package/.acsemver.js
ADDED
package/.eslintrc.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const config = {
|
|
2
|
+
root: true,
|
|
3
|
+
'env': {
|
|
4
|
+
'commonjs': true,
|
|
5
|
+
'es6': true,
|
|
6
|
+
'node': true
|
|
7
|
+
},
|
|
8
|
+
'extends': 'eslint:recommended',
|
|
9
|
+
"rules": {
|
|
10
|
+
"space-before-function-paren": 0,
|
|
11
|
+
"no-extra-semi": 0,
|
|
12
|
+
"object-curly-spacing": ["error", "always"],
|
|
13
|
+
"brace-style": ["error", "stroustrup", { "allowSingleLine": true }],
|
|
14
|
+
"no-useless-escape": 0,
|
|
15
|
+
"standard/no-callback-literal": 0,
|
|
16
|
+
"new-cap": 0
|
|
17
|
+
},
|
|
18
|
+
globals: {
|
|
19
|
+
describe: true,
|
|
20
|
+
it: true
|
|
21
|
+
},
|
|
22
|
+
'parserOptions': {
|
|
23
|
+
'ecmaVersion': 2018
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = config
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
<a name="1.4.2"></a>
|
|
2
|
+
|
|
3
|
+
## [1.4.2](https://github.com/admiralcloud/ac-geoip/compare/v1.4.1..v1.4.2) (2021-10-09 10:25:40)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fix
|
|
7
|
+
|
|
8
|
+
* **App:** Package updates | MP | [08dd237ecc7274cfb083f3f6279036fa94d17398](https://github.com/admiralcloud/ac-geoip/commit/08dd237ecc7274cfb083f3f6279036fa94d17398)
|
|
9
|
+
Package updates
|
|
10
|
+
<a name="1.4.1"></a>
|
|
11
|
+
|
|
12
|
+
## [1.4.1](https://github.com/mmpro/ac-geoip/compare/v1.4.0..v1.4.1) (2021-05-02 09:42:55)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fix
|
|
16
|
+
|
|
17
|
+
* **App:** Improved debug log | MP | [e5985e0a10d08979ab2f1ddadcc51217f43e0342](https://github.com/mmpro/ac-geoip/commit/e5985e0a10d08979ab2f1ddadcc51217f43e0342)
|
|
18
|
+
Improved debug log
|
|
19
|
+
### Tests
|
|
20
|
+
|
|
21
|
+
* **App:** Minor fix | MP | [42f5ffec32d0fa9d54306ddc1281feef7ef6e3df](https://github.com/mmpro/ac-geoip/commit/42f5ffec32d0fa9d54306ddc1281feef7ef6e3df)
|
|
22
|
+
Minor fix
|
|
23
|
+
### Chores
|
|
24
|
+
|
|
25
|
+
* **App:** Updated packages | MP | [39b1e855d0d8b7419868f84038163f142f5a4b26](https://github.com/mmpro/ac-geoip/commit/39b1e855d0d8b7419868f84038163f142f5a4b26)
|
|
26
|
+
Updated packages
|
|
27
|
+
<a name="1.4.0"></a>
|
|
28
|
+
|
|
29
|
+
# [1.4.0](https://github.com/mmpro/ac-geoip/compare/v1.3.0..v1.4.0) (2021-04-12 09:36:41)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
### Feature
|
|
33
|
+
|
|
34
|
+
* **App:** Use node-cache per default | MP | [1a5481a30245fc53ccb6333a60f4ae38a8245eb5](https://github.com/mmpro/ac-geoip/commit/1a5481a30245fc53ccb6333a60f4ae38a8245eb5)
|
|
35
|
+
If Redis is not defined, use node-cache (memory) to improve performance.
|
|
36
|
+
### Bug Fix
|
|
37
|
+
|
|
38
|
+
* **App:** Allow Redis usage when using local database | MP | [943ab0cf914245cceab0c23da1e03078783a1b66](https://github.com/mmpro/ac-geoip/commit/943ab0cf914245cceab0c23da1e03078783a1b66)
|
|
39
|
+
To improve performance it makes sense to use Redis even for local database. Lookup is a little slow and consumes a lot of CPU
|
|
40
|
+
<a name="1.3.0"></a>
|
|
41
|
+
|
|
42
|
+
# [1.3.0](https://github.com/mmpro/ac-geoip/compare/v1.2.1..v1.3.0) (2021-04-02 09:52:27)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
### Feature
|
|
46
|
+
|
|
47
|
+
* **App:** LookupLocal for local database lookup | MP | [d6821229d6475e6bd2bb83822bee01bf474f1219](https://github.com/mmpro/ac-geoip/commit/d6821229d6475e6bd2bb83822bee01bf474f1219)
|
|
48
|
+
LookupLocal is now available as dedicated function to lookup a local geolite database. Lookup only uses webservice. You can use both functions in parallel (e.g. to compare results).
|
|
49
|
+
<a name="1.2.1"></a>
|
|
50
|
+
|
|
51
|
+
## [1.2.1](https://github.com/mmpro/ac-geoip/compare/v1.2.0..v1.2.1) (2021-04-02 06:49:54)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
### Bug Fix
|
|
55
|
+
|
|
56
|
+
* **App:** Improved logging | MP | [7d664460d4109fc078e656a0fe264a23c7e2cf9d](https://github.com/mmpro/ac-geoip/commit/7d664460d4109fc078e656a0fe264a23c7e2cf9d)
|
|
57
|
+
Improved logging
|
|
58
|
+
### Tests
|
|
59
|
+
|
|
60
|
+
* **App:** Added tests for geolite database | MP | [f6da987068a63c4cb43fb84729e4816875de9283](https://github.com/mmpro/ac-geoip/commit/f6da987068a63c4cb43fb84729e4816875de9283)
|
|
61
|
+
Added tests for geolite database
|
|
62
|
+
### Chores
|
|
63
|
+
|
|
64
|
+
* **App:** Updated packages | MP | [66e8056c2dab7377b6040cb35551ee3bf352e98d](https://github.com/mmpro/ac-geoip/commit/66e8056c2dab7377b6040cb35551ee3bf352e98d)
|
|
65
|
+
Updated packages
|
|
66
|
+
<a name="1.2.0"></a>
|
|
67
|
+
|
|
68
|
+
# [1.2.0](https://github.com/mmpro/ac-geoip/compare/v1.1.0..v1.2.0) (2021-03-21 07:35:20)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
### Feature
|
|
72
|
+
|
|
73
|
+
* **App:** You can now use geolite2 local database | MP | [dd671ab0b6bcc19cee5aefeb3cf421c15974e864](https://github.com/mmpro/ac-geoip/commit/dd671ab0b6bcc19cee5aefeb3cf421c15974e864)
|
|
74
|
+
It is now possible to use geolite2 downloadble database instead of web service.
|
|
75
|
+
### Chores
|
|
76
|
+
|
|
77
|
+
* **App:** Do not commit folder geolite2 | MP | [8e20cbba069222d9f25abe79111d604bc827b3e6](https://github.com/mmpro/ac-geoip/commit/8e20cbba069222d9f25abe79111d604bc827b3e6)
|
|
78
|
+
Do not commit folder geolite2
|
|
79
|
+
### Chores
|
|
80
|
+
|
|
81
|
+
* **App:** Updated packages | MP | [8c44382bb0f0c60973fccd6f7e25feb01993e32b](https://github.com/mmpro/ac-geoip/commit/8c44382bb0f0c60973fccd6f7e25feb01993e32b)
|
|
82
|
+
Updated packages
|
|
83
|
+
<a name="1.1.0"></a>
|
|
84
|
+
|
|
85
|
+
# [1.1.0](https://github.com/mmpro/ac-geoip/compare/v1.0.4..v1.1.0) (2020-12-12 14:43:41)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
### Feature
|
|
89
|
+
|
|
90
|
+
* **App:** Function now supports async/await | MP | [f6b65c568ed34fb8149ee9e00d23b8946cb83256](https://github.com/mmpro/ac-geoip/commit/f6b65c568ed34fb8149ee9e00d23b8946cb83256)
|
|
91
|
+
You can use async/await in addition to classic callback
|
|
92
|
+
### Chores
|
|
93
|
+
|
|
94
|
+
* **App:** Remove husky | MP | [cb52b5d3f8c38c6e8a9437725b5aeba35cf6de33](https://github.com/mmpro/ac-geoip/commit/cb52b5d3f8c38c6e8a9437725b5aeba35cf6de33)
|
|
95
|
+
Remove husky
|
|
96
|
+
* **App:** Package cleanup and updates | MP | [f661f118bd7897bce87de6c862d8497a19ec72f8](https://github.com/mmpro/ac-geoip/commit/f661f118bd7897bce87de6c862d8497a19ec72f8)
|
|
97
|
+
Package cleanup and updates
|
|
98
|
+
* **App:** Use ac-semantic-release | MP | [66c9797b3db0072559d62f16dcaa93acdad6859a](https://github.com/mmpro/ac-geoip/commit/66c9797b3db0072559d62f16dcaa93acdad6859a)
|
|
99
|
+
Use ac-semantic-release
|
|
100
|
+
<a name="1.0.4"></a>
|
|
101
|
+
## [1.0.4](https://github.com/mmpro/ac-geoip/compare/v1.0.3...v1.0.4) (2019-10-13 06:40)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
### Bug Fixes
|
|
105
|
+
|
|
106
|
+
* **GeoIP:** Only log if debug parameter is set | MP ([b5466de](https://github.com/mmpro/ac-geoip/commit/b5466de))
|
|
107
|
+
Only log if debug parameter is set
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
<a name="1.0.3"></a>
|
|
112
|
+
## [1.0.3](https://github.com/mmpro/ac-geoip/compare/v1.0.2...v1.0.3) (2019-10-06 09:57)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
### Bug Fixes
|
|
116
|
+
|
|
117
|
+
* **GeoIP:** Minor fixes: Redis, debugging | MP ([d692f62](https://github.com/mmpro/ac-geoip/commit/d692f62))
|
|
118
|
+
Fixed using Redis, added debug option per request
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
<a name="1.0.2"></a>
|
|
123
|
+
## [1.0.2](https://github.com/mmpro/ac-geoip/compare/v1.0.1...v1.0.2) (2019-10-06 09:10)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
### Bug Fixes
|
|
127
|
+
|
|
128
|
+
* **GeoIP:** Improved functionality | MP ([000654f](https://github.com/mmpro/ac-geoip/commit/000654f))
|
|
129
|
+
Check for private IP, fixed using Redis as cache, allow mapping per request.
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
<a name="1.0.1"></a>
|
|
134
|
+
## [1.0.1](https://github.com/mmpro/ac-geoip/compare/v1.0.0...v1.0.1) (2019-10-03 11:09)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
### Bug Fixes
|
|
138
|
+
|
|
139
|
+
* **GeoIP:** Removed location from default mapping | MP ([6c25ed4](https://github.com/mmpro/ac-geoip/commit/6c25ed4))
|
|
140
|
+
Removed location from default mapping
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
<a name="1.0.0"></a>
|
|
145
|
+
# 1.0.0 (2019-10-03 08:36)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
### Features
|
|
149
|
+
|
|
150
|
+
* **GeoIP:** Initial version | MP ([89771ac](https://github.com/mmpro/ac-geoip/commit/89771ac))
|
|
151
|
+
Initial version
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
|
package/Makefile
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
MOCHA_OPTS= --slow 0 -A
|
|
2
|
+
REPORTER = spec
|
|
3
|
+
|
|
4
|
+
lint-fix:
|
|
5
|
+
./node_modules/.bin/eslint --fix index.js test/test.js
|
|
6
|
+
|
|
7
|
+
lint-check:
|
|
8
|
+
./node_modules/.bin/eslint index.js test/test.js
|
|
9
|
+
|
|
10
|
+
commit:
|
|
11
|
+
@node ./node_modules/ac-semantic-release/lib/commit.js
|
|
12
|
+
|
|
13
|
+
release:
|
|
14
|
+
@node ./node_modules/ac-semantic-release/lib/release.js
|
|
15
|
+
|
|
16
|
+
.PHONY: check
|
package/README.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# AC GEOIP
|
|
2
|
+
Lookup IP addresses at Maxmind GEOIP services or local Maxmind (Geolite) database and prepare the response with custom field mapping. Optionally, you can store the response in Redis to improve performance (of your app).
|
|
3
|
+
|
|
4
|
+
GEOIP web service requires an account at Maxmind.
|
|
5
|
+
|
|
6
|
+
You can also use the Geolite2 database from Maxmind: https://dev.maxmind.com/geoip/geoip2/geolite2/
|
|
7
|
+
|
|
8
|
+
## Usage
|
|
9
|
+
|
|
10
|
+
### Using Webservice
|
|
11
|
+
```
|
|
12
|
+
const acgeoip = require('./index')
|
|
13
|
+
|
|
14
|
+
let geoip = {
|
|
15
|
+
userId: 123456778,
|
|
16
|
+
licenseKey: 'abc-licensekey'
|
|
17
|
+
}
|
|
18
|
+
acgeoip.init(geoip)
|
|
19
|
+
|
|
20
|
+
// ASYNC/AWAIT
|
|
21
|
+
async() {
|
|
22
|
+
let response = await acgeoip.lookup({ ip: '1.2.3.4' })
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Using local (Geolite) database
|
|
27
|
+
```
|
|
28
|
+
const acgeoip = require('./index')
|
|
29
|
+
|
|
30
|
+
let geoip = {
|
|
31
|
+
geolite: {
|
|
32
|
+
enabled: true,
|
|
33
|
+
path: '/path/to/GeoLite2-City.mmdb'
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
acgeoip.init(geoip)
|
|
37
|
+
|
|
38
|
+
// ASYNC/AWAIT
|
|
39
|
+
async() {
|
|
40
|
+
let response = await acgeoip.lookupLocal({ ip: '1.2.3.4' })
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Combined usage
|
|
45
|
+
```
|
|
46
|
+
const acgeoip = require('./index')
|
|
47
|
+
|
|
48
|
+
let geoip = {
|
|
49
|
+
userId: 123456778,
|
|
50
|
+
licenseKey: 'abc-licensekey',
|
|
51
|
+
geolite: {
|
|
52
|
+
enabled: true,
|
|
53
|
+
path: '/path/to/GeoLite2-City.mmdb'
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
acgeoip.init(geoip)
|
|
57
|
+
|
|
58
|
+
// lookup using web service
|
|
59
|
+
async() {
|
|
60
|
+
let response = await acgeoip.lookup({ ip: '1.2.3.4' })
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// lookup using local database
|
|
64
|
+
async() {
|
|
65
|
+
let response = await acgeoip.lookupLocal({ ip: '1.2.3.4' })
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Using callbacks
|
|
70
|
+
We recommend using modern async/await approach, but you can also use classic callbacks.
|
|
71
|
+
```
|
|
72
|
+
// TRADITONAL CALLBACK is available for lookup and lookupLocal
|
|
73
|
+
acgeoip.lookup({
|
|
74
|
+
ip: '8.8.8.8'
|
|
75
|
+
}, (err, response) => {
|
|
76
|
+
console.log(response)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Init Parameters
|
|
82
|
+
**Required**
|
|
83
|
+
Initiate the function with the required "userId" and "licenseKey" if you want to use the webservice. If you want to use local database, make sure to set geolite.enabled to true and provide the location of the database.
|
|
84
|
+
|
|
85
|
+
**Mapping**
|
|
86
|
+
Define how your response looks like using a mapping array. The array contains objects with properties "response" which is the property name in the response and "geoIP" which is the path to the GeoIP response.
|
|
87
|
+
|
|
88
|
+
See this default setup as example
|
|
89
|
+
```
|
|
90
|
+
mapping: [
|
|
91
|
+
{ response: 'iso2', geoIP: 'country.iso_code' },
|
|
92
|
+
{ response: 'city', geoIP: 'city.names.en' },
|
|
93
|
+
{ response: 'region', geoIP: 'subdivisions[0].names.en' },
|
|
94
|
+
// the following properties are only available using the paid webservice
|
|
95
|
+
{ response: 'isp', geoIP: 'traits.isp' },
|
|
96
|
+
{ response: 'organization', geoIP: 'traits.organization' },
|
|
97
|
+
{ response: 'domain', geoIP: 'traits.domain' },
|
|
98
|
+
{ response: 'location', geoIP: 'location' },
|
|
99
|
+
]
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Caching**
|
|
103
|
+
In order to cache the data, you need to provide:
|
|
104
|
+
+ redis - a redis instance (from ioredis)
|
|
105
|
+
+ environment - prefix (default development) for the redisKey (e.g development:geoip:8.8.8.8)
|
|
106
|
+
+ cacheTime - seconds to cache the GeoIP response (default 7 days)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
## Links
|
|
110
|
+
- [Website](https://www.admiralcloud.com/)
|
|
111
|
+
- [Twitter (@admiralcloud)](https://twitter.com/admiralcloud)
|
|
112
|
+
- [Facebook](https://www.facebook.com/MediaAssetManagement/)
|
|
113
|
+
|
|
114
|
+
## Thanks
|
|
115
|
+
Thanks to https://github.com/maxmind/GeoIP2-node
|
|
116
|
+
|
|
117
|
+
## License
|
|
118
|
+
[MIT License](https://opensource.org/licenses/MIT) Copyright © 2009-present, AdmiralCloud AG, Mark Poepping
|
package/index.js
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
const _ = require('lodash')
|
|
2
|
+
const ipPackage = require('ip')
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
|
|
5
|
+
const WebServiceClient = require('@maxmind/geoip2-node').WebServiceClient;
|
|
6
|
+
const Reader = require('@maxmind/geoip2-node').Reader;
|
|
7
|
+
|
|
8
|
+
const NodeCache = require( "node-cache" );
|
|
9
|
+
const geoCache = new NodeCache( { stdTTL: 7*86400, checkperiod: 3600 } );
|
|
10
|
+
|
|
11
|
+
const acgeoip = () => {
|
|
12
|
+
|
|
13
|
+
let geoip = {
|
|
14
|
+
userId: 'userId',
|
|
15
|
+
licenseKey: 'licenseKey',
|
|
16
|
+
environment: 'development',
|
|
17
|
+
// redis, // instance of redis
|
|
18
|
+
// reader // initiated if useBuffer with local database
|
|
19
|
+
geolite: {
|
|
20
|
+
useBuffer: false,
|
|
21
|
+
enabled: false,
|
|
22
|
+
path: '/path/to/GeoLite2-City.mmdb'
|
|
23
|
+
},
|
|
24
|
+
cacheTime: 7 * 86400, // cache GEOIP response for 1 week
|
|
25
|
+
mapping: [
|
|
26
|
+
{ response: 'iso2', geoIP: 'country.isoCode' },
|
|
27
|
+
{ response: 'city', geoIP: 'city.names.en' },
|
|
28
|
+
{ response: 'region', geoIP: 'subdivisions[0].names.en' },
|
|
29
|
+
{ response: 'isp', geoIP: 'traits.isp' },
|
|
30
|
+
{ response: 'organization', geoIP: 'traits.organization' },
|
|
31
|
+
{ response: 'domain', geoIP: 'traits.domain' },
|
|
32
|
+
{ response: 'latitude', geoIP: 'location.latitude' },
|
|
33
|
+
{ response: 'longitude', geoIP: 'location.longitude' }
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const init = (params) => {
|
|
38
|
+
if (_.has(params, 'userId')) _.set(geoip, 'userId', _.get(params, 'userId'))
|
|
39
|
+
if (_.has(params, 'licenseKey')) _.set(geoip, 'licenseKey', _.get(params, 'licenseKey'))
|
|
40
|
+
if (_.has(params, 'env')) _.set(geoip, 'environment', _.get(params, 'env'))
|
|
41
|
+
if (_.has(params, 'redis')) _.set(geoip, 'redis', _.get(params, 'redis'))
|
|
42
|
+
if (_.has(params, 'geolite')) _.set(geoip, 'geolite', _.get(params, 'geolite'))
|
|
43
|
+
|
|
44
|
+
if (_.get(params, 'geolite.enabled') && _.get(params, 'geolite.useBuffer')) {
|
|
45
|
+
const dbBuffer = fs.readFileSync(_.get(geoip, 'geolite.path'))
|
|
46
|
+
geoip.reader = Reader.openBuffer(dbBuffer)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
const lookupLocal = async(params, cb) => {
|
|
53
|
+
const functionName = 'ac-geoip | lookupLocal'
|
|
54
|
+
if (!_.get(geoip, 'geolite.enabled')) {
|
|
55
|
+
let message = 'acgeoip_geolite_notEnabled'
|
|
56
|
+
if (_.isFunction(cb)) return cb({ message })
|
|
57
|
+
throw Error(message)
|
|
58
|
+
}
|
|
59
|
+
const ip = _.get(params, 'ip')
|
|
60
|
+
if (ipPackage.isPrivate(ip)) {
|
|
61
|
+
if (_.isFunction(cb)) return cb()
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const mapping = _.get(params, 'mapping', geoip.mapping)
|
|
66
|
+
const debug = _.get(params, 'debug')
|
|
67
|
+
const debugPerformance = _.get(params, 'debugPerforance')
|
|
68
|
+
const start = process.hrtime()
|
|
69
|
+
|
|
70
|
+
let response = {
|
|
71
|
+
ip
|
|
72
|
+
}
|
|
73
|
+
let geoipResponse
|
|
74
|
+
|
|
75
|
+
if (geoip.redis) {
|
|
76
|
+
geoipResponse = await checkRedis(params)
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
geoipResponse = getFromMemory({ ip })
|
|
80
|
+
}
|
|
81
|
+
if (debugPerformance) console.log('%s | getFromCache %d', functionName, performanceHelper(start, process.hrtime()))
|
|
82
|
+
|
|
83
|
+
if (!geoipResponse) {
|
|
84
|
+
if (_.get(geoip, 'geolite.useBuffer') && geoip.reader) {
|
|
85
|
+
geoipResponse = geoip.reader.city(ip)
|
|
86
|
+
if (debugPerformance) console.log('%s | readFromBuffer %d', functionName, performanceHelper(start, process.hrtime()))
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
try {
|
|
90
|
+
if (_.get(geoip, 'geolite.enabled')) {
|
|
91
|
+
geoipResponse = await new Promise((resolve, reject) => {
|
|
92
|
+
Reader.open(_.get(geoip, 'geolite.path')).then(reader => {
|
|
93
|
+
const response = reader.city(ip)
|
|
94
|
+
resolve(response)
|
|
95
|
+
}).catch(reject)
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
if (debugPerformance) console.log('%s | readFromDB %d', functionName, performanceHelper(start, process.hrtime()))
|
|
99
|
+
|
|
100
|
+
if (debug) {
|
|
101
|
+
console.log('AC-GEOIP | From Geolite | %j', geoipResponse)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch(e) {
|
|
105
|
+
console.error('AC-GEOIP | From Geolite | Failed | %j', e)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (geoipResponse) {
|
|
109
|
+
_.set(geoipResponse, 'origin', 'db')
|
|
110
|
+
if (geoip.redis) {
|
|
111
|
+
await storeRedis({ ip, geoipResponse })
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
storeInMemory({ ip, geoipResponse })
|
|
115
|
+
}
|
|
116
|
+
if (debugPerformance) console.log('%s | storeInCache %d', functionName, performanceHelper(start, process.hrtime()))
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// prepare response
|
|
121
|
+
if (!_.isEmpty(mapping)) {
|
|
122
|
+
_.forEach(mapping, item => {
|
|
123
|
+
if (_.get(geoipResponse, item.geoIP)) _.set(response, item.response, _.get(geoipResponse, item.geoIP))
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
response = geoipResponse
|
|
128
|
+
}
|
|
129
|
+
_.set(response, 'origin', _.get(geoipResponse, 'origin'))
|
|
130
|
+
if (_.get(geoipResponse, 'fromCache')) _.set(response, 'fromCache', true)
|
|
131
|
+
|
|
132
|
+
if (debugPerformance) console.log('%s | Finished %d', functionName, performanceHelper(start, process.hrtime()))
|
|
133
|
+
if (_.isFunction(cb)) return cb(null, response)
|
|
134
|
+
return response
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const lookup = async(params, cb) => {
|
|
138
|
+
const functionName = 'ac-geoip | lookup'
|
|
139
|
+
if (!_.get(geoip, 'licenseKey') || _.get(geoip, 'licenseKey') === 'licenseKey') {
|
|
140
|
+
let message = 'acgeoip_licenseKey_missing'
|
|
141
|
+
if (_.isFunction(cb)) return cb({ message })
|
|
142
|
+
throw Error(message)
|
|
143
|
+
}
|
|
144
|
+
const ip = _.get(params, 'ip')
|
|
145
|
+
if (ipPackage.isPrivate(ip)) {
|
|
146
|
+
if (_.isFunction(cb)) return cb()
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const mapping = _.get(params, 'mapping', geoip.mapping)
|
|
151
|
+
const debug = _.get(params, 'debug')
|
|
152
|
+
const debugPerformance = _.get(params, 'debugPerforance')
|
|
153
|
+
const start = process.hrtime()
|
|
154
|
+
|
|
155
|
+
let response = {
|
|
156
|
+
ip
|
|
157
|
+
}
|
|
158
|
+
let geoipResponse
|
|
159
|
+
|
|
160
|
+
if (geoip.redis) {
|
|
161
|
+
geoipResponse = await checkRedis(params)
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
geoipResponse = getFromMemory({ ip })
|
|
165
|
+
}
|
|
166
|
+
if (debugPerformance) console.log('%s | getFromCache %d', functionName, performanceHelper(start, process.hrtime()))
|
|
167
|
+
|
|
168
|
+
// fetch fresh
|
|
169
|
+
if (!_.get(geoipResponse, 'country')) {
|
|
170
|
+
try {
|
|
171
|
+
const client = new WebServiceClient(geoip.userId, geoip.licenseKey)
|
|
172
|
+
geoipResponse = await new Promise((resolve, reject) => {
|
|
173
|
+
client.city(ip).then(result => {
|
|
174
|
+
return resolve(result)
|
|
175
|
+
}).catch(reject)
|
|
176
|
+
})
|
|
177
|
+
if (debugPerformance) console.log('%s | readFromWebservice %d', functionName, performanceHelper(start, process.hrtime()))
|
|
178
|
+
if (geoipResponse) {
|
|
179
|
+
_.set(geoipResponse, 'origin', 'webservice')
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (debug) {
|
|
183
|
+
console.log('AC-GEOIP | From Maxmind | %j', geoipResponse)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
catch(e) {
|
|
187
|
+
console.error('AC-GEOIP | From Maxmind | Failed | %j', e)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (geoipResponse) {
|
|
192
|
+
if (geoip.redis) {
|
|
193
|
+
await storeRedis({ ip, geoipResponse })
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
storeInMemory({ ip, geoipResponse })
|
|
197
|
+
}
|
|
198
|
+
if (debugPerformance) console.log('%s | storeInCache %d', functionName, performanceHelper(start, process.hrtime()))
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// prepare response
|
|
202
|
+
if (!_.isEmpty(mapping)) {
|
|
203
|
+
_.forEach(mapping, item => {
|
|
204
|
+
if (_.get(geoipResponse, item.geoIP)) _.set(response, item.response, _.get(geoipResponse, item.geoIP))
|
|
205
|
+
})
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
response = geoipResponse
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
_.set(response, 'origin', _.get(geoipResponse, 'origin'))
|
|
212
|
+
if (_.get(geoipResponse, 'fromCache')) _.set(response, 'fromCache', true)
|
|
213
|
+
|
|
214
|
+
if (_.isFunction(cb)) return cb(null, response)
|
|
215
|
+
return response
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const storeInMemory = (params) => {
|
|
219
|
+
const geoipResponse = _.get(params, 'geoipResponse')
|
|
220
|
+
const ip = _.get(params, 'ip')
|
|
221
|
+
const storageKey = _.get(geoip, 'environment') + ':geoip:' + ip
|
|
222
|
+
geoCache.set(storageKey, geoipResponse)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const getFromMemory = (params) => {
|
|
226
|
+
const ip = _.get(params, 'ip')
|
|
227
|
+
const storageKey = _.get(geoip, 'environment') + ':geoip:' + ip
|
|
228
|
+
return geoCache.get(storageKey)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
const checkRedis = async(params, cb) => {
|
|
233
|
+
const refresh = _.get(params, 'refresh')
|
|
234
|
+
if (!geoip.redis || refresh) {
|
|
235
|
+
if (_.isFunction(cb)) return cb()
|
|
236
|
+
return
|
|
237
|
+
}
|
|
238
|
+
const ip = _.get(params, 'ip')
|
|
239
|
+
const redisKey = _.get(geoip, 'environment') + ':geoip:' + ip
|
|
240
|
+
const debug = _.get(params, 'debug')
|
|
241
|
+
|
|
242
|
+
let geoipResponse
|
|
243
|
+
try {
|
|
244
|
+
geoipResponse = await geoip.redis.get(redisKey)
|
|
245
|
+
geoipResponse = JSON.parse(geoipResponse)
|
|
246
|
+
if (_.isPlainObject(geoipResponse)) {
|
|
247
|
+
geoipResponse.fromCache = true
|
|
248
|
+
}
|
|
249
|
+
if (debug) {
|
|
250
|
+
console.log('AC-GEOIP | From Cache | %j', geoipResponse)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
catch(e) {
|
|
254
|
+
console.log(e)
|
|
255
|
+
console.error('AC-GEOIP | From Cache | Failed | %j', e)
|
|
256
|
+
}
|
|
257
|
+
return geoipResponse
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const storeRedis = async(params) => {
|
|
261
|
+
const refresh = _.get(params, 'refresh')
|
|
262
|
+
if (!geoip.redis || refresh) {
|
|
263
|
+
return
|
|
264
|
+
}
|
|
265
|
+
const ip = _.get(params, 'ip')
|
|
266
|
+
const geoipResponse = _.get(params, 'geoipResponse')
|
|
267
|
+
const redisKey = _.get(geoip, 'environment') + ':geoip:' + ip
|
|
268
|
+
|
|
269
|
+
await geoip.redis.setex(redisKey, geoip.cacheTime, JSON.stringify(geoipResponse))
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const performanceHelper = (t1, t2, options) => {
|
|
273
|
+
const accuracy = _.get(options, 'accuracy', 1e6)
|
|
274
|
+
const s = t2[0] - t1[0]
|
|
275
|
+
const mms = t2[1] - t1[1]
|
|
276
|
+
return (s*1e9 + mms)/accuracy
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
init,
|
|
282
|
+
lookup,
|
|
283
|
+
lookupLocal
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
module.exports = acgeoip()
|
|
288
|
+
|
|
289
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ac-geoip",
|
|
3
|
+
"author": "Mark Poepping (https://www.admiralcloud.com)",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"repository": "admiralcloud/ac-geoip",
|
|
6
|
+
"version": "1.4.2",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"@maxmind/geoip2-node": "^3.2.0",
|
|
9
|
+
"ip": "^1.1.5",
|
|
10
|
+
"lodash": "^4.17.21",
|
|
11
|
+
"node-cache": "^5.1.2"
|
|
12
|
+
},
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"ac-semantic-release": "^0.2.6",
|
|
15
|
+
"chai": "^4.3.4",
|
|
16
|
+
"eslint": "^7.32.0",
|
|
17
|
+
"ioredis": "^4.27.10",
|
|
18
|
+
"mocha": "^9.1.2"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"test": "./node_modules/.bin/mocha --bail --exit --slow 1000 ./test/test.js || :"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=8.0.0"
|
|
25
|
+
}
|
|
26
|
+
}
|
package/test/test.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
const acgeoip = require('../index')
|
|
2
|
+
const _ = require('lodash')
|
|
3
|
+
|
|
4
|
+
const { expect } = require('chai');
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
const Redis = require("ioredis");
|
|
9
|
+
const redis = new Redis()
|
|
10
|
+
|
|
11
|
+
const credentials = require('./../credentials');
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
const ip = '35.184.130.59'
|
|
15
|
+
const expectedValue = {
|
|
16
|
+
ip: '35.184.130.59',
|
|
17
|
+
iso2: 'US',
|
|
18
|
+
city: 'Council Bluffs',
|
|
19
|
+
region: 'Iowa',
|
|
20
|
+
isp: 'Google Cloud',
|
|
21
|
+
organization: 'Google Cloud',
|
|
22
|
+
domain: 'googleusercontent.com'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
describe('Test Webservice', () => {
|
|
28
|
+
|
|
29
|
+
it('Webservice is not yet enabled - should fail', async function() {
|
|
30
|
+
this.timeout(5000)
|
|
31
|
+
try {
|
|
32
|
+
await acgeoip.lookup({ ip, debug: false })
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
expect(e).to.be.instanceOf(Error)
|
|
36
|
+
expect(e.message).to.eql('acgeoip_licenseKey_missing')
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('Init', done => {
|
|
41
|
+
const geoip = {
|
|
42
|
+
redis,
|
|
43
|
+
userId: credentials.userId,
|
|
44
|
+
licenseKey: credentials.licenseKey
|
|
45
|
+
}
|
|
46
|
+
acgeoip.init(geoip)
|
|
47
|
+
return done()
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('Shoud be tested with async/await', async function() {
|
|
51
|
+
this.timeout(5000)
|
|
52
|
+
const result = await acgeoip.lookup({ ip, debug: false, refresh: true })
|
|
53
|
+
_.forOwn(expectedValue, (val, key) => {
|
|
54
|
+
expect(result).to.have.property(key, val)
|
|
55
|
+
})
|
|
56
|
+
return
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('Shoud be tested with async/await - should be from cache', async function() {
|
|
60
|
+
this.timeout(5000)
|
|
61
|
+
const result = await acgeoip.lookup({ ip, debug: false })
|
|
62
|
+
_.forOwn(expectedValue, (val, key) => {
|
|
63
|
+
expect(result).to.have.property(key, val)
|
|
64
|
+
})
|
|
65
|
+
expect(result).to.have.property('fromCache', true)
|
|
66
|
+
return
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('End', done => {
|
|
70
|
+
return done()
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
describe('Test Geolite2 local database', () => {
|
|
76
|
+
|
|
77
|
+
it('Geolite local is not yet enabled - should fail', async function() {
|
|
78
|
+
this.timeout(5000)
|
|
79
|
+
try {
|
|
80
|
+
await acgeoip.lookupLocal({ ip, debug: false })
|
|
81
|
+
}
|
|
82
|
+
catch (e) {
|
|
83
|
+
expect(e).to.be.instanceOf(Error)
|
|
84
|
+
expect(e.message).to.eql('acgeoip_geolite_notEnabled')
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('Init geolite', done => {
|
|
89
|
+
const geoip = {
|
|
90
|
+
redis,
|
|
91
|
+
userId: undefined,
|
|
92
|
+
licenseKey: undefined,
|
|
93
|
+
geolite: {
|
|
94
|
+
enabled: true,
|
|
95
|
+
path: './geolite2/GeoLite2-City.mmdb'
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
acgeoip.init(geoip)
|
|
99
|
+
return done()
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('Test local ip async/await', async function() {
|
|
103
|
+
this.timeout(5000)
|
|
104
|
+
const result = await acgeoip.lookupLocal({ ip: '127.0.0.1', debug: false })
|
|
105
|
+
expect(result).to.be.undefined
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('Shoud be tested with async/await', async function() {
|
|
109
|
+
this.timeout(5000)
|
|
110
|
+
const result = await acgeoip.lookupLocal({ ip, debug: false, refresh: true })
|
|
111
|
+
let fields = ['iso2', 'city', 'region']
|
|
112
|
+
_.forEach(fields, key => {
|
|
113
|
+
let val = _.get(expectedValue, key)
|
|
114
|
+
expect(result).to.have.property(key, val)
|
|
115
|
+
})
|
|
116
|
+
expect(result).to.have.property('origin', 'db')
|
|
117
|
+
return
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('Test again - should be from cache', async function() {
|
|
121
|
+
this.timeout(5000)
|
|
122
|
+
const result = await acgeoip.lookupLocal({ ip, debug: false })
|
|
123
|
+
let fields = ['iso2', 'city', 'region']
|
|
124
|
+
_.forEach(fields, key => {
|
|
125
|
+
let val = _.get(expectedValue, key)
|
|
126
|
+
expect(result).to.have.property(key, val)
|
|
127
|
+
})
|
|
128
|
+
expect(result).to.have.property('origin', 'db')
|
|
129
|
+
expect(result).to.have.property('fromCache', true)
|
|
130
|
+
return
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
|