@k03mad/ip2geo 4.2.1 → 5.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/.github/workflows/lint.yml +3 -0
- package/.github/workflows/test.yml +3 -0
- package/README.md +2 -1
- package/app/lib/ip2geo.js +34 -9
- package/package.json +3 -3
- package/tests/{opts-assigned-cache-map.js → cache-map-entries.js} +6 -8
- package/tests/cache-map-max-entries.js +56 -0
- package/tests/cache-map-off.js +33 -0
- package/tests/{opts-assigned-multi-requests.js → ip-multi-requests-cache.js} +5 -0
- package/tests/{opts-assigned-no-data.js → ip-no-data.js} +15 -10
- package/tests/{opts-assigned-ipv6.js → ip-v6.js} +0 -1
- /package/tests/{opts-assigned-all.js → cache-file-modify.js} +0 -0
- /package/tests/{opts-assigned-subfolder.js → cache-file-subfolder.js} +0 -0
- /package/tests/{opts-default.js → default.js} +0 -0
package/README.md
CHANGED
|
@@ -45,7 +45,8 @@ const info = await ip2geo('1.1.1.1', {
|
|
|
45
45
|
cacheFileSeparator: ';;',
|
|
46
46
|
cacheFileNewline: '\n',
|
|
47
47
|
cacheMap: new Map(),
|
|
48
|
-
|
|
48
|
+
cacheMapMaxEntries: Number.POSITIVE_INFINITY, // store last N requests, 0 — turns cache map off
|
|
49
|
+
rps: 3, // API RPS, useful in Promise.all with IPs array
|
|
49
50
|
});
|
|
50
51
|
|
|
51
52
|
// info {
|
package/app/lib/ip2geo.js
CHANGED
|
@@ -26,6 +26,7 @@ const DEFAULT_CACHE_FILE_DIR = path.join(os.tmpdir(), '.ip2geo');
|
|
|
26
26
|
const DEFAULT_CACHE_FILE_NAME = 'ips.log';
|
|
27
27
|
const DEFAULT_CACHE_FILE_SEPARATOR = ';;';
|
|
28
28
|
const DEFAULT_CACHE_FILE_NEWLINE = '\n';
|
|
29
|
+
const DEFAULT_CACHE_MAP_MAX_ENTRIES = Number.POSITIVE_INFINITY;
|
|
29
30
|
const DEFAULT_RPS = 3;
|
|
30
31
|
|
|
31
32
|
export const cacheStorage = new Map();
|
|
@@ -111,8 +112,9 @@ const writeToFsCache = async (ip, data, cacheDir, cacheFileName, cacheFileSepara
|
|
|
111
112
|
* @param {string} [opts.cacheFileName]
|
|
112
113
|
* @param {string} [opts.cacheFileSeparator]
|
|
113
114
|
* @param {string} [opts.cacheFileNewline]
|
|
114
|
-
* @param {Map} [opts.cacheMap]
|
|
115
|
-
* @param {number} opts.
|
|
115
|
+
* @param {Map|false} [opts.cacheMap]
|
|
116
|
+
* @param {number} [opts.cacheMapMaxEntries]
|
|
117
|
+
* @param {number} [opts.rps]
|
|
116
118
|
* @returns {Promise<GeoIpOutput>}
|
|
117
119
|
*/
|
|
118
120
|
export const ip2geo = async (ip = '', {
|
|
@@ -120,15 +122,18 @@ export const ip2geo = async (ip = '', {
|
|
|
120
122
|
cacheFileName = DEFAULT_CACHE_FILE_NAME,
|
|
121
123
|
cacheFileSeparator = DEFAULT_CACHE_FILE_SEPARATOR,
|
|
122
124
|
cacheFileNewline = DEFAULT_CACHE_FILE_NEWLINE,
|
|
125
|
+
cacheMapMaxEntries = DEFAULT_CACHE_MAP_MAX_ENTRIES,
|
|
123
126
|
cacheMap = cacheStorage,
|
|
124
127
|
rps = DEFAULT_RPS,
|
|
125
128
|
} = {}) => {
|
|
126
129
|
if (ip) {
|
|
127
|
-
|
|
130
|
+
if (cacheMapMaxEntries > 0) {
|
|
131
|
+
const ipData = cacheMap.get(ip);
|
|
128
132
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
133
|
+
if (ipData) {
|
|
134
|
+
debug('get from map cache: %o', ipData);
|
|
135
|
+
return ipData;
|
|
136
|
+
}
|
|
132
137
|
}
|
|
133
138
|
|
|
134
139
|
const fsCache = await readFromFsCache(ip, cacheDir, cacheFileName);
|
|
@@ -143,7 +148,11 @@ export const ip2geo = async (ip = '', {
|
|
|
143
148
|
const outputData = collectOutputData(fileData);
|
|
144
149
|
debug('get from fs cache: %o', outputData);
|
|
145
150
|
|
|
146
|
-
|
|
151
|
+
if (cacheMapMaxEntries > 0) {
|
|
152
|
+
cacheMap.set(ip, outputData);
|
|
153
|
+
debug('set to map cache: %o', outputData);
|
|
154
|
+
}
|
|
155
|
+
|
|
147
156
|
return outputData;
|
|
148
157
|
}
|
|
149
158
|
}
|
|
@@ -170,8 +179,10 @@ export const ip2geo = async (ip = '', {
|
|
|
170
179
|
|
|
171
180
|
const outputData = collectOutputData(usedData);
|
|
172
181
|
|
|
173
|
-
|
|
174
|
-
|
|
182
|
+
if (cacheMapMaxEntries > 0) {
|
|
183
|
+
cacheMap.set(body.ip, outputData);
|
|
184
|
+
debug('set to map cache: %o', outputData);
|
|
185
|
+
}
|
|
175
186
|
|
|
176
187
|
await writeToFsCache(
|
|
177
188
|
body.ip, usedData,
|
|
@@ -179,5 +190,19 @@ export const ip2geo = async (ip = '', {
|
|
|
179
190
|
);
|
|
180
191
|
|
|
181
192
|
debug('set to fs cache: %o', outputData);
|
|
193
|
+
|
|
194
|
+
if (cacheMap.size > cacheMapMaxEntries) {
|
|
195
|
+
debug('remove from map cache by limit: size %o, limit: %o', cacheMap.size, cacheMapMaxEntries);
|
|
196
|
+
|
|
197
|
+
for (const [key] of cacheMap) {
|
|
198
|
+
debug('remove from map cache by limit: key %o', key);
|
|
199
|
+
cacheMap.delete(key);
|
|
200
|
+
|
|
201
|
+
if (cacheMap.size <= cacheMapMaxEntries) {
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
182
207
|
return outputData;
|
|
183
208
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@k03mad/ip2geo",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "GeoIP library",
|
|
5
5
|
"maintainers": [
|
|
6
6
|
"Kirill Molchanov <k03.mad@gmail.com"
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"debug": "4.3.4"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@k03mad/eslint-config": "19.
|
|
28
|
+
"@k03mad/eslint-config": "19.5.0",
|
|
29
29
|
"eslint": "8.56.0",
|
|
30
30
|
"husky": "8.0.3",
|
|
31
31
|
"mocha": "10.2.0"
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"clean": "npm run clean:modules && npm run clean:eslint:cache",
|
|
38
38
|
"clean:modules": "rm -rf ./node_modules || true",
|
|
39
39
|
"clean:eslint:cache": "rm -rf .eslintcache || true",
|
|
40
|
-
"setup": "npm run clean &&
|
|
40
|
+
"setup": "npm run clean && pnpm i",
|
|
41
41
|
"prepare": "husky install || true"
|
|
42
42
|
}
|
|
43
43
|
}
|
|
@@ -11,11 +11,9 @@ import {removeCacheFolder} from './shared/fs.js';
|
|
|
11
11
|
const testName = getCurrentFilename(import.meta.url);
|
|
12
12
|
|
|
13
13
|
describe(testName, () => {
|
|
14
|
-
const cacheMap = new Map();
|
|
15
|
-
|
|
16
14
|
const opts = {
|
|
17
15
|
cacheDir: getTestFolder(testName),
|
|
18
|
-
cacheMap,
|
|
16
|
+
cacheMap: new Map(),
|
|
19
17
|
};
|
|
20
18
|
|
|
21
19
|
it('should remove fs cache dir if exist', () => removeCacheFolder(opts.cacheDir));
|
|
@@ -26,8 +24,8 @@ describe(testName, () => {
|
|
|
26
24
|
});
|
|
27
25
|
|
|
28
26
|
it('should have 1 correct cache entry', () => {
|
|
29
|
-
assert.equal(cacheMap.size, 1);
|
|
30
|
-
assert.deepEqual(cacheMap.get(REQUEST_IPV4.ip), REQUEST_IPV4);
|
|
27
|
+
assert.equal(opts.cacheMap.size, 1);
|
|
28
|
+
assert.deepEqual(opts.cacheMap.get(REQUEST_IPV4.ip), REQUEST_IPV4);
|
|
31
29
|
});
|
|
32
30
|
|
|
33
31
|
it(`should return correct response for IP: "${REQUEST_IPV6.ip}"`, async () => {
|
|
@@ -36,8 +34,8 @@ describe(testName, () => {
|
|
|
36
34
|
});
|
|
37
35
|
|
|
38
36
|
it('should have 2 correct cache entries', () => {
|
|
39
|
-
assert.equal(cacheMap.size, 2);
|
|
40
|
-
assert.deepEqual(cacheMap.get(REQUEST_IPV4.ip), REQUEST_IPV4);
|
|
41
|
-
assert.deepEqual(cacheMap.get(REQUEST_IPV6.ip), REQUEST_IPV6);
|
|
37
|
+
assert.equal(opts.cacheMap.size, 2);
|
|
38
|
+
assert.deepEqual(opts.cacheMap.get(REQUEST_IPV4.ip), REQUEST_IPV4);
|
|
39
|
+
assert.deepEqual(opts.cacheMap.get(REQUEST_IPV6.ip), REQUEST_IPV6);
|
|
42
40
|
});
|
|
43
41
|
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
|
|
3
|
+
import {describe, it} from 'mocha';
|
|
4
|
+
|
|
5
|
+
import {ip2geo} from '../app/index.js';
|
|
6
|
+
|
|
7
|
+
import {getCurrentFilename, getTestFolder} from './helpers/path.js';
|
|
8
|
+
import {removeCacheFolder} from './shared/fs.js';
|
|
9
|
+
|
|
10
|
+
const testName = getCurrentFilename(import.meta.url);
|
|
11
|
+
|
|
12
|
+
describe(testName, () => {
|
|
13
|
+
const firstReqIps = [
|
|
14
|
+
'10.10.10.10',
|
|
15
|
+
'20.20.20.20',
|
|
16
|
+
'30.30.30.30',
|
|
17
|
+
'40.40.40.40',
|
|
18
|
+
'50.50.50.50',
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const secondReqIps = ['60.60.60.60'];
|
|
22
|
+
|
|
23
|
+
const opts = {
|
|
24
|
+
cacheDir: getTestFolder(testName),
|
|
25
|
+
cacheMap: new Map(),
|
|
26
|
+
cacheMapMaxEntries: 2,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
it('should remove fs cache dir if exist', () => removeCacheFolder(opts.cacheDir));
|
|
30
|
+
|
|
31
|
+
firstReqIps.forEach(ip => {
|
|
32
|
+
it(`should request geo for IP: "${ip}"`, async () => {
|
|
33
|
+
await ip2geo(ip, opts);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it(`should have ${opts.cacheMapMaxEntries} correct cache entries`, () => {
|
|
38
|
+
assert.equal(opts.cacheMap.size, opts.cacheMapMaxEntries);
|
|
39
|
+
assert.deepEqual([...opts.cacheMap.keys()], firstReqIps.slice(-opts.cacheMapMaxEntries));
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
secondReqIps.forEach(ip => {
|
|
43
|
+
it(`should request geo for IP: "${ip}"`, async () => {
|
|
44
|
+
await ip2geo(ip, opts);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it(`should have ${opts.cacheMapMaxEntries} correct cache entries`, () => {
|
|
49
|
+
assert.equal(opts.cacheMap.size, opts.cacheMapMaxEntries);
|
|
50
|
+
|
|
51
|
+
assert.deepEqual(
|
|
52
|
+
[...opts.cacheMap.keys()],
|
|
53
|
+
[firstReqIps, secondReqIps].flat().slice(-opts.cacheMapMaxEntries),
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
|
|
3
|
+
import {describe, it} from 'mocha';
|
|
4
|
+
|
|
5
|
+
import {cacheStorage, ip2geo} from '../app/index.js';
|
|
6
|
+
|
|
7
|
+
import {REQUEST_IPV4} from './helpers/consts.js';
|
|
8
|
+
import {getCurrentFilename, getTestFolder} from './helpers/path.js';
|
|
9
|
+
import {checkCacheFile, removeCacheFolder} from './shared/fs.js';
|
|
10
|
+
|
|
11
|
+
const testName = getCurrentFilename(import.meta.url);
|
|
12
|
+
|
|
13
|
+
describe(testName, () => {
|
|
14
|
+
const opts = {
|
|
15
|
+
cacheDir: getTestFolder(testName),
|
|
16
|
+
cacheMapMaxEntries: 0,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
it('should remove fs cache dir if exist', () => removeCacheFolder(opts.cacheDir));
|
|
20
|
+
|
|
21
|
+
it(`should return correct response for IP: "${REQUEST_IPV4.ip}"`, async () => {
|
|
22
|
+
const data = await ip2geo(REQUEST_IPV4.ip, opts);
|
|
23
|
+
assert.deepEqual(data, REQUEST_IPV4);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should have cache file', () => checkCacheFile({
|
|
27
|
+
response: REQUEST_IPV4,
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
it('should not have cache entries', () => {
|
|
31
|
+
assert.equal(cacheStorage.size, 0);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -11,13 +11,10 @@ import {removeCacheFolder} from './shared/fs.js';
|
|
|
11
11
|
const testName = getCurrentFilename(import.meta.url);
|
|
12
12
|
|
|
13
13
|
describe(testName, () => {
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
response[elem] = undefined;
|
|
19
|
-
}
|
|
20
|
-
});
|
|
14
|
+
const responses = [
|
|
15
|
+
{ip: '10.10.10.10'},
|
|
16
|
+
{ip: 'test'},
|
|
17
|
+
];
|
|
21
18
|
|
|
22
19
|
const opts = {
|
|
23
20
|
cacheDir: getTestFolder(testName),
|
|
@@ -26,8 +23,16 @@ describe(testName, () => {
|
|
|
26
23
|
|
|
27
24
|
it('should remove fs cache dir if exist', () => removeCacheFolder(opts.cacheDir));
|
|
28
25
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
responses.forEach(response => {
|
|
27
|
+
Object.keys(REQUEST_IPV4).forEach(elem => {
|
|
28
|
+
if (elem !== 'ip') {
|
|
29
|
+
response[elem] = undefined;
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it(`should return empty response for IP: "${response.ip}"`, async () => {
|
|
34
|
+
const data = await ip2geo(response.ip, opts);
|
|
35
|
+
assert.deepEqual(data, response);
|
|
36
|
+
});
|
|
32
37
|
});
|
|
33
38
|
});
|
|
File without changes
|
|
File without changes
|
|
File without changes
|