ac-geoip 1.3.0 → 2.0.0

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  module.exports = {
2
2
  repository: {
3
- url: 'https://github.com/mmpro/ac-geoip'
3
+ url: 'https://github.com/admiralcloud/ac-geoip'
4
4
  },
5
5
  changelogFile: __dirname + '/CHANGELOG.md',
6
6
  sections: [
package/CHANGELOG.md CHANGED
@@ -1,3 +1,68 @@
1
+ <a name="2.0.0"></a>
2
+
3
+ # [2.0.0](https://github.com/admiralcloud/ac-geoip/compare/v1.4.2..v2.0.0) (2022-01-29 11:56:17)
4
+
5
+
6
+ ### Bug Fix
7
+
8
+ * **App:** Open geolite DB during initialisation | VD | [37b6981913f08798c1b764021d528497f8cc238f](https://github.com/admiralcloud/ac-geoip/commit/37b6981913f08798c1b764021d528497f8cc238f)
9
+ Open geolite DB during initialisation
10
+ Related issues: [undefined/undefined#AC-2500](undefined/browse/AC-2500)
11
+ * **App:** Cache geoip2 Reader object | VD | [54a67bb2d186ccf10ec72e8b370ce9a51c2ab89c](https://github.com/admiralcloud/ac-geoip/commit/54a67bb2d186ccf10ec72e8b370ce9a51c2ab89c)
12
+ Store object to re-use instead of opening DB file each time it's needed.
13
+ Related issues: [undefined/undefined#AC-2500](undefined/browse/AC-2500)
14
+ * **App:** Remove callbacks from async functions | VD | [6c4a6ecd0ce8f8350eb206b41be0d90ddaf09ee5](https://github.com/admiralcloud/ac-geoip/commit/6c4a6ecd0ce8f8350eb206b41be0d90ddaf09ee5)
15
+ Mixing async and callback functions makes code read hard, misunderstand the results and make function behave unpredictably.
16
+ Related issues: [undefined/undefined#AC-2500](undefined/browse/AC-2500)
17
+ ### Style
18
+
19
+ * **App:** Get rid of semicollons, code re-aligning | VD | [e27005f2001a0da06d12fe272a3e007937f1604b](https://github.com/admiralcloud/ac-geoip/commit/e27005f2001a0da06d12fe272a3e007937f1604b)
20
+ Get rid of semicollons, code re-aligning
21
+ ### Chores
22
+
23
+ * **App:** Updated packages | MP | [49f68ab1575163f6b9e9a845a23cd779195d9a41](https://github.com/admiralcloud/ac-geoip/commit/49f68ab1575163f6b9e9a845a23cd779195d9a41)
24
+ Updated packages
25
+ ## BREAKING CHANGES
26
+ * **App:** * lookupLocal, lookup functions fully async and doesn't have callback parameters
27
+ <a name="1.4.2"></a>
28
+
29
+ ## [1.4.2](https://github.com/admiralcloud/ac-geoip/compare/v1.4.1..v1.4.2) (2021-10-09 10:25:40)
30
+
31
+
32
+ ### Bug Fix
33
+
34
+ * **App:** Package updates | MP | [08dd237ecc7274cfb083f3f6279036fa94d17398](https://github.com/admiralcloud/ac-geoip/commit/08dd237ecc7274cfb083f3f6279036fa94d17398)
35
+ Package updates
36
+ <a name="1.4.1"></a>
37
+
38
+ ## [1.4.1](https://github.com/mmpro/ac-geoip/compare/v1.4.0..v1.4.1) (2021-05-02 09:42:55)
39
+
40
+
41
+ ### Bug Fix
42
+
43
+ * **App:** Improved debug log | MP | [e5985e0a10d08979ab2f1ddadcc51217f43e0342](https://github.com/mmpro/ac-geoip/commit/e5985e0a10d08979ab2f1ddadcc51217f43e0342)
44
+ Improved debug log
45
+ ### Tests
46
+
47
+ * **App:** Minor fix | MP | [42f5ffec32d0fa9d54306ddc1281feef7ef6e3df](https://github.com/mmpro/ac-geoip/commit/42f5ffec32d0fa9d54306ddc1281feef7ef6e3df)
48
+ Minor fix
49
+ ### Chores
50
+
51
+ * **App:** Updated packages | MP | [39b1e855d0d8b7419868f84038163f142f5a4b26](https://github.com/mmpro/ac-geoip/commit/39b1e855d0d8b7419868f84038163f142f5a4b26)
52
+ Updated packages
53
+ <a name="1.4.0"></a>
54
+
55
+ # [1.4.0](https://github.com/mmpro/ac-geoip/compare/v1.3.0..v1.4.0) (2021-04-12 09:36:41)
56
+
57
+
58
+ ### Feature
59
+
60
+ * **App:** Use node-cache per default | MP | [1a5481a30245fc53ccb6333a60f4ae38a8245eb5](https://github.com/mmpro/ac-geoip/commit/1a5481a30245fc53ccb6333a60f4ae38a8245eb5)
61
+ If Redis is not defined, use node-cache (memory) to improve performance.
62
+ ### Bug Fix
63
+
64
+ * **App:** Allow Redis usage when using local database | MP | [943ab0cf914245cceab0c23da1e03078783a1b66](https://github.com/mmpro/ac-geoip/commit/943ab0cf914245cceab0c23da1e03078783a1b66)
65
+ To improve performance it makes sense to use Redis even for local database. Lookup is a little slow and consumes a lot of CPU
1
66
  <a name="1.3.0"></a>
2
67
 
3
68
  # [1.3.0](https://github.com/mmpro/ac-geoip/compare/v1.2.1..v1.3.0) (2021-04-02 09:52:27)
package/index.js CHANGED
@@ -1,17 +1,23 @@
1
1
  const _ = require('lodash')
2
2
  const ipPackage = require('ip')
3
+ const fs = require('fs')
3
4
 
4
- const WebServiceClient = require('@maxmind/geoip2-node').WebServiceClient;
5
- const Reader = require('@maxmind/geoip2-node').Reader;
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 })
6
10
 
7
11
  const acgeoip = () => {
8
-
12
+
9
13
  let geoip = {
10
14
  userId: 'userId',
11
15
  licenseKey: 'licenseKey',
12
16
  environment: 'development',
13
17
  // redis, // instance of redis
18
+ // reader // initiated if useBuffer with local database
14
19
  geolite: {
20
+ useBuffer: false,
15
21
  enabled: false,
16
22
  path: '/path/to/GeoLite2-City.mmdb'
17
23
  },
@@ -23,6 +29,8 @@ const acgeoip = () => {
23
29
  { response: 'isp', geoIP: 'traits.isp' },
24
30
  { response: 'organization', geoIP: 'traits.organization' },
25
31
  { response: 'domain', geoIP: 'traits.domain' },
32
+ { response: 'latitude', geoIP: 'location.latitude' },
33
+ { response: 'longitude', geoIP: 'location.longitude' }
26
34
  ]
27
35
  }
28
36
 
@@ -32,46 +40,90 @@ const acgeoip = () => {
32
40
  if (_.has(params, 'env')) _.set(geoip, 'environment', _.get(params, 'env'))
33
41
  if (_.has(params, 'redis')) _.set(geoip, 'redis', _.get(params, 'redis'))
34
42
  if (_.has(params, 'geolite')) _.set(geoip, 'geolite', _.get(params, 'geolite'))
35
- }
36
-
37
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
+ else {
49
+ Reader.open(_.get(geoip, 'geolite.path')).then(geoipReader => {
50
+ geoip.geolite.reader = geoipReader
51
+ })
52
+ }
53
+ }
38
54
 
39
- const lookupLocal = async(params, cb) => {
55
+ const lookupLocal = async (params) => {
56
+ const functionName = 'ac-geoip | lookupLocal'
40
57
  if (!_.get(geoip, 'geolite.enabled')) {
41
- let message = 'acgeoip_geolite_notEnabled'
42
- if (_.isFunction(cb)) return cb({ message })
58
+ const message = 'acgeoip_geolite_notEnabled'
43
59
  throw Error(message)
44
60
  }
61
+
45
62
  const ip = _.get(params, 'ip')
46
- if (ipPackage.isPrivate(ip)) {
47
- if (_.isFunction(cb)) return cb()
48
- return
49
- }
63
+ if (ipPackage.isPrivate(ip)) return
50
64
 
51
65
  const mapping = _.get(params, 'mapping', geoip.mapping)
52
66
  const debug = _.get(params, 'debug')
67
+ const debugPerformance = _.get(params, 'debugPerforance')
68
+ const start = process.hrtime()
53
69
 
54
70
  let response = {
55
71
  ip
56
72
  }
57
73
  let geoipResponse
58
74
 
59
- try {
60
- if (_.get(geoip, 'geolite.enabled')) {
61
- geoipResponse = await new Promise((resolve, reject) => {
62
- Reader.open(_.get(geoip, 'geolite.path')).then(reader => {
63
- const response = reader.city(ip)
64
- resolve(response)
65
- }).catch(reject)
66
- })
75
+ if (geoip.redis) {
76
+ try {
77
+ geoipResponse = await checkRedis(params)
67
78
  }
68
-
69
- if (debug) {
70
- console.log('AC-GEOIP | From Geolite | %s', JSON.stringify(geoipResponse, null, 2))
79
+ catch (err) {
80
+ console.error('AC-GEOIP | From Geolite | Failed | %j', err)
71
81
  }
72
82
  }
73
- catch(e) {
74
- console.error('AC-GEOIP | From Geolite | Failed | %j', e)
83
+ else {
84
+ geoipResponse = getFromMemory({ ip })
85
+ }
86
+
87
+ if (debugPerformance) console.log('%s | getFromCache %d', functionName, performanceHelper(start, process.hrtime()))
88
+
89
+ if (!geoipResponse) {
90
+ if (_.get(geoip, 'geolite.useBuffer') && geoip.reader) {
91
+ geoipResponse = geoip.reader.city(ip)
92
+ if (debugPerformance) console.log('%s | readFromBuffer %d', functionName, performanceHelper(start, process.hrtime()))
93
+ }
94
+ else {
95
+ try {
96
+ if (_.get(geoip, 'geolite.enabled')) {
97
+ let geoipReader = _.get(geoip, 'geolite.reader')
98
+ if (!geoipReader) {
99
+ geoipReader = await Reader.open(_.get(geoip, 'geolite.path'))
100
+ _.set(geoip, 'geolite.reader', geoipReader)
101
+ }
102
+ if (geoipReader) geoipResponse = geoipReader.city(ip)
103
+ }
104
+
105
+ if (debugPerformance) console.log('%s | readFromDB %d', functionName, performanceHelper(start, process.hrtime()))
106
+
107
+ if (debug) {
108
+ console.log('AC-GEOIP | From Geolite | %j', geoipResponse)
109
+ }
110
+ }
111
+ catch (e) {
112
+ console.error('AC-GEOIP | From Geolite | Failed | %j', e)
113
+ }
114
+ }
115
+
116
+ if (geoipResponse) {
117
+ _.set(geoipResponse, 'origin', 'db')
118
+ if (geoip.redis) {
119
+ await storeRedis({ ip, geoipResponse })
120
+ }
121
+ else {
122
+ storeInMemory({ ip, geoipResponse })
123
+ }
124
+
125
+ if (debugPerformance) console.log('%s | storeInCache %d', functionName, performanceHelper(start, process.hrtime()))
126
+ }
75
127
  }
76
128
 
77
129
  // prepare response
@@ -83,72 +135,73 @@ const acgeoip = () => {
83
135
  else {
84
136
  response = geoipResponse
85
137
  }
86
- if (_.isFunction(cb)) return cb(null, response)
138
+
139
+ _.set(response, 'origin', _.get(geoipResponse, 'origin'))
140
+ if (_.get(geoipResponse, 'fromCache')) _.set(response, 'fromCache', true)
141
+
142
+ if (debugPerformance) console.log('%s | Finished %d', functionName, performanceHelper(start, process.hrtime()))
143
+
87
144
  return response
88
145
  }
89
146
 
90
-
91
- const lookup = async(params, cb) => {
147
+ const lookup = async (params) => {
148
+ const functionName = 'ac-geoip | lookup'
92
149
  if (!_.get(geoip, 'licenseKey') || _.get(geoip, 'licenseKey') === 'licenseKey') {
93
- let message = 'acgeoip_licenseKey_missing'
94
- if (_.isFunction(cb)) return cb({ message })
150
+ const message = 'acgeoip_licenseKey_missing'
95
151
  throw Error(message)
96
152
  }
97
153
  const ip = _.get(params, 'ip')
98
154
  if (ipPackage.isPrivate(ip)) {
99
- if (_.isFunction(cb)) return cb()
100
155
  return
101
156
  }
102
157
 
103
- const refresh = _.get(params, 'refresh')
104
- const redisKey = _.get(geoip, 'environment') + ':geoip:' + ip
105
158
  const mapping = _.get(params, 'mapping', geoip.mapping)
106
159
  const debug = _.get(params, 'debug')
160
+ const debugPerformance = _.get(params, 'debugPerforance')
161
+ const start = process.hrtime()
107
162
 
108
163
  let response = {
109
164
  ip
110
165
  }
111
166
  let geoipResponse
112
167
 
113
- // try redis
114
- if (geoip.redis && !refresh) {
115
- try {
116
- geoipResponse = await geoip.redis.get(redisKey)
117
- geoipResponse = JSON.parse(geoipResponse)
118
- if (_.isPlainObject(geoipResponse)) {
119
- geoipResponse.fromCache = true
120
- }
121
- if (debug) {
122
- console.log('AC-GEOIP | From Cache | %j', JSON.stringify(geoipResponse, null, 2))
123
- }
124
- }
125
- catch(e) {
126
- console.error('AC-GEOIP | From Cache | Failed | %j', e)
127
- }
168
+ if (geoip.redis) {
169
+ geoipResponse = await checkRedis(params)
128
170
  }
171
+ else {
172
+ geoipResponse = getFromMemory({ ip })
173
+ }
174
+ if (debugPerformance) console.log('%s | getFromCache %d', functionName, performanceHelper(start, process.hrtime()))
129
175
 
130
176
  // fetch fresh
131
- if (refresh || !_.get(geoipResponse, 'country')) {
177
+ if (!_.get(geoipResponse, 'country')) {
132
178
  try {
133
179
  const client = new WebServiceClient(geoip.userId, geoip.licenseKey)
134
- geoipResponse = await new Promise((resolve, reject) => {
135
- client.city(ip).then(result => {
136
- return resolve(result)
137
- }).catch(reject)
138
- })
180
+ geoipResponse = await client.city(ip)
181
+ if (debugPerformance) console.log('%s | readFromWebservice %d', functionName, performanceHelper(start, process.hrtime()))
182
+ if (geoipResponse) {
183
+ _.set(geoipResponse, 'origin', 'webservice')
184
+ }
139
185
 
140
186
  if (debug) {
141
- console.log('AC-GEOIP | From Maxmind | %s', JSON.stringify(geoipResponse, null, 2))
187
+ console.log('AC-GEOIP | From Maxmind | %j', geoipResponse)
142
188
  }
143
- if (geoip.redis) {
144
- await geoip.redis.setex(redisKey, geoip.cacheTime, JSON.stringify(geoipResponse))
145
- }
146
189
  }
147
- catch(e) {
190
+ catch (e) {
148
191
  console.error('AC-GEOIP | From Maxmind | Failed | %j', e)
149
192
  }
150
193
  }
151
194
 
195
+ if (geoipResponse) {
196
+ if (geoip.redis) {
197
+ await storeRedis({ ip, geoipResponse })
198
+ }
199
+ else {
200
+ storeInMemory({ ip, geoipResponse })
201
+ }
202
+ if (debugPerformance) console.log('%s | storeInCache %d', functionName, performanceHelper(start, process.hrtime()))
203
+ }
204
+
152
205
  // prepare response
153
206
  if (!_.isEmpty(mapping)) {
154
207
  _.forEach(mapping, item => {
@@ -158,12 +211,70 @@ const acgeoip = () => {
158
211
  else {
159
212
  response = geoipResponse
160
213
  }
214
+
215
+ _.set(response, 'origin', _.get(geoipResponse, 'origin'))
161
216
  if (_.get(geoipResponse, 'fromCache')) _.set(response, 'fromCache', true)
162
217
 
163
- if (_.isFunction(cb)) return cb(null, response)
164
218
  return response
165
219
  }
166
220
 
221
+ const storeInMemory = (params) => {
222
+ const geoipResponse = _.get(params, 'geoipResponse')
223
+ const ip = _.get(params, 'ip')
224
+ const storageKey = _.get(geoip, 'environment') + ':geoip:' + ip
225
+ geoCache.set(storageKey, geoipResponse)
226
+ }
227
+
228
+ const getFromMemory = (params) => {
229
+ const ip = _.get(params, 'ip')
230
+ const storageKey = _.get(geoip, 'environment') + ':geoip:' + ip
231
+ return geoCache.get(storageKey)
232
+ }
233
+
234
+ const checkRedis = async (params) => {
235
+ const refresh = _.get(params, 'refresh')
236
+ if (!geoip.redis || refresh) 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
+ }
167
278
 
168
279
  return {
169
280
  init,
package/package.json CHANGED
@@ -1,20 +1,21 @@
1
1
  {
2
2
  "name": "ac-geoip",
3
- "author": "Mark Poepping (https://www.mmpro.de)",
3
+ "author": "Mark Poepping (https://www.admiralcloud.com)",
4
4
  "license": "MIT",
5
- "repository": "mmpro/ac-geoip",
6
- "version": "1.3.0",
5
+ "repository": "admiralcloud/ac-geoip",
6
+ "version": "2.0.0",
7
7
  "dependencies": {
8
- "@maxmind/geoip2-node": "^2.3.1",
8
+ "@maxmind/geoip2-node": "^3.4.0",
9
9
  "ip": "^1.1.5",
10
- "lodash": "^4.17.21"
10
+ "lodash": "^4.17.21",
11
+ "node-cache": "^5.1.2"
11
12
  },
12
13
  "devDependencies": {
13
- "ac-semantic-release": "^0.2.5",
14
- "chai": "^4.3.4",
15
- "eslint": "^7.22.0",
16
- "ioredis": "^4.24.2",
17
- "mocha": "^8.3.2"
14
+ "ac-semantic-release": "^0.2.7",
15
+ "chai": "^4.3.6",
16
+ "eslint": "^8.8.0",
17
+ "ioredis": "^4.28.3",
18
+ "mocha": "^9.2.0"
18
19
  },
19
20
  "scripts": {
20
21
  "test": "./node_modules/.bin/mocha --bail --exit --slow 1000 ./test/test.js || :"
package/test/test.js CHANGED
@@ -23,6 +23,7 @@ const expectedValue = {
23
23
  }
24
24
 
25
25
 
26
+
26
27
  describe('Test Webservice', () => {
27
28
 
28
29
  it('Webservice is not yet enabled - should fail', async function() {
@@ -30,7 +31,7 @@ describe('Test Webservice', () => {
30
31
  try {
31
32
  await acgeoip.lookup({ ip, debug: false })
32
33
  }
33
- catch (e) {
34
+ catch (e) {
34
35
  expect(e).to.be.instanceOf(Error)
35
36
  expect(e.message).to.eql('acgeoip_licenseKey_missing')
36
37
  }
@@ -46,24 +47,9 @@ describe('Test Webservice', () => {
46
47
  return done()
47
48
  })
48
49
 
49
- it('Shoud be tested with callback', function(done) {
50
- this.timeout(5000)
51
- acgeoip.lookup({
52
- ip,
53
- debug: false
54
- }, (err, result) => {
55
- if (err) return done(err)
56
- //console.log(51, result)
57
- _.forOwn(expectedValue, (val, key) => {
58
- expect(result).to.have.property(key, val)
59
- })
60
- return done()
61
- })
62
- })
63
-
64
50
  it('Shoud be tested with async/await', async function() {
65
51
  this.timeout(5000)
66
- const result = await acgeoip.lookup({ ip, debug: false })
52
+ const result = await acgeoip.lookup({ ip, debug: false, refresh: true })
67
53
  _.forOwn(expectedValue, (val, key) => {
68
54
  expect(result).to.have.property(key, val)
69
55
  })
@@ -101,7 +87,7 @@ describe('Test Geolite2 local database', () => {
101
87
 
102
88
  it('Init geolite', done => {
103
89
  const geoip = {
104
- redis: undefined,
90
+ redis,
105
91
  userId: undefined,
106
92
  licenseKey: undefined,
107
93
  geolite: {
@@ -121,13 +107,27 @@ describe('Test Geolite2 local database', () => {
121
107
 
122
108
  it('Shoud be tested with async/await', async function() {
123
109
  this.timeout(5000)
124
- const result = await acgeoip.lookupLocal({ ip, debug: false })
110
+ const result = await acgeoip.lookupLocal({ ip, debug: false, refresh: true })
125
111
  let fields = ['iso2', 'city', 'region']
126
112
  _.forEach(fields, key => {
127
113
  let val = _.get(expectedValue, key)
128
114
  expect(result).to.have.property(key, val)
129
115
  })
116
+ expect(result).to.have.property('origin', 'db')
130
117
  return
131
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
132
  })
133
133