@smpx/koa-request 1.1.0 → 1.2.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/GeoIP.js +167 -55
- package/Request.js +18 -8
- package/package.json +2 -2
package/GeoIP.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
const mmdb = require('mmdb-lib');
|
|
2
2
|
const https = require('https');
|
|
3
3
|
const zlib = require('zlib');
|
|
4
4
|
const path = require('path');
|
|
@@ -9,17 +9,89 @@ const fileName = 'GeoLite2-City';
|
|
|
9
9
|
const fileUrl = `https://raw.githubusercontent.com/GitSquared/node-geolite2-redist/master/redist/${fileName}.tar.gz`;
|
|
10
10
|
const fileDir = __dirname;
|
|
11
11
|
const filePath = `${fileDir}/${fileName}.mmdb`;
|
|
12
|
+
const ONE_DAY = 24 * 3600 * 1000;
|
|
13
|
+
// eslint-disable-next-line max-len
|
|
14
|
+
const processMs = ((process.env.pm_id || process.pid || Math.floor(Math.random() * 60)) % 60) * 60 * 1000;
|
|
12
15
|
let geoInitPromise;
|
|
16
|
+
let geoInitSynced;
|
|
13
17
|
let geoip;
|
|
18
|
+
let updateTimer;
|
|
14
19
|
|
|
15
|
-
|
|
20
|
+
class LRU {
|
|
21
|
+
constructor() {
|
|
22
|
+
this.maxItems = 10000;
|
|
23
|
+
this.cache = new Map();
|
|
24
|
+
this.oldCache = new Map();
|
|
25
|
+
this._size = 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
_set(key, value) {
|
|
29
|
+
this.cache.set(key, value);
|
|
30
|
+
if (this.cache.size >= this.maxItems) {
|
|
31
|
+
this._size = this.cache.size;
|
|
32
|
+
this.oldCache = this.cache;
|
|
33
|
+
this.cache = new Map();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get(key) {
|
|
38
|
+
const value = this.cache.get(key);
|
|
39
|
+
if (value !== undefined) return value;
|
|
40
|
+
const oldValue = this.oldCache.get(key);
|
|
41
|
+
if (oldValue) {
|
|
42
|
+
this._set(key, oldValue);
|
|
43
|
+
return oldValue;
|
|
44
|
+
}
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
set(key, value) {
|
|
49
|
+
if (this.cache.has(key)) {
|
|
50
|
+
this.cache.set(key, value);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
this._size++;
|
|
54
|
+
this._set(key, value);
|
|
55
|
+
}
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const readFile = fs.promises.readFile;
|
|
61
|
+
async function mtime(file) {
|
|
62
|
+
try {
|
|
63
|
+
const stats = await fs.promises.stat(file);
|
|
64
|
+
return stats.mtimeMs || stats.ctimeMs;
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getMmdbReader(buffer) {
|
|
72
|
+
const cache = new LRU();
|
|
73
|
+
return new mmdb.Reader(buffer, {cache});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function validateReader(geoIPReader) {
|
|
77
|
+
try {
|
|
78
|
+
const country = geoIPReader.get('1.2.3.4')?.country?.iso_code;
|
|
79
|
+
return !!country;
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
console.error(e);
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function downloadNewDatabase() {
|
|
16
88
|
return new Promise((resolve, reject) => {
|
|
17
|
-
https.get(
|
|
89
|
+
https.get(fileUrl, (res) => {
|
|
18
90
|
try {
|
|
19
91
|
const untar = res.pipe(zlib.createGunzip({})).pipe(tar.t());
|
|
20
92
|
untar.on('entry', (entry) => {
|
|
21
93
|
if (entry.path.endsWith('.mmdb')) {
|
|
22
|
-
const dstFilename = path.join(
|
|
94
|
+
const dstFilename = path.join(fileDir, path.basename(entry.path) + '-tmp');
|
|
23
95
|
try {
|
|
24
96
|
entry.pipe(fs.createWriteStream(dstFilename));
|
|
25
97
|
}
|
|
@@ -30,88 +102,128 @@ function save(url, outDir) {
|
|
|
30
102
|
});
|
|
31
103
|
untar.on('error', e => reject(e));
|
|
32
104
|
untar.on('finish', () => {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
105
|
+
setTimeout(async () => {
|
|
106
|
+
try {
|
|
107
|
+
const newGeoIp = getMmdbReader(await readFile(filePath + '-tmp'));
|
|
108
|
+
if (validateReader(newGeoIp)) {
|
|
109
|
+
resolve(newGeoIp);
|
|
110
|
+
fs.promises.rename(filePath + '-tmp', filePath).catch(console.error);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
reject(new Error('Invalid GeoIP Database!'));
|
|
114
|
+
}
|
|
36
115
|
}
|
|
37
|
-
|
|
38
|
-
|
|
116
|
+
catch (e) {
|
|
117
|
+
reject(e);
|
|
39
118
|
}
|
|
40
|
-
});
|
|
119
|
+
}, 100);
|
|
41
120
|
});
|
|
42
121
|
}
|
|
43
122
|
catch (error) {
|
|
44
|
-
|
|
123
|
+
reject(error);
|
|
45
124
|
}
|
|
46
125
|
});
|
|
47
126
|
});
|
|
48
127
|
}
|
|
49
128
|
|
|
50
|
-
function mtime(file) {
|
|
51
|
-
return new Promise((resolve) => {
|
|
52
|
-
try {
|
|
53
|
-
fs.stat(file, (err, stats) => {
|
|
54
|
-
if (err) {
|
|
55
|
-
resolve(false);
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
58
|
-
resolve(stats.mtimeMs || stats.ctimeMs);
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
catch (e) {
|
|
63
|
-
resolve(false);
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
129
|
async function download() {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
await save(fileUrl, fileDir);
|
|
130
|
+
console.log(`[Request::GeoIP] Downloading ${fileName} ...`);
|
|
131
|
+
try {
|
|
132
|
+
const newGeoIp = await downloadNewDatabase();
|
|
73
133
|
console.log(`[Request::GeoIP] Downloaded ${fileName} !`);
|
|
134
|
+
return newGeoIp;
|
|
135
|
+
}
|
|
136
|
+
catch (e) {
|
|
137
|
+
console.error(e);
|
|
138
|
+
return null;
|
|
74
139
|
}
|
|
75
140
|
}
|
|
76
141
|
|
|
77
142
|
async function updateDb() {
|
|
143
|
+
clearTimeout(updateTimer);
|
|
78
144
|
const modifyTime = await mtime(filePath);
|
|
79
|
-
if (modifyTime < Date.now() -
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
145
|
+
if (modifyTime < Date.now() - 3 * ONE_DAY) {
|
|
146
|
+
const newGeoIp = await download();
|
|
147
|
+
if (newGeoIp) {
|
|
148
|
+
console.log('[Request::GeoIP] Updated GeoIP Database!');
|
|
149
|
+
geoip = newGeoIp;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// add some random time in timeout to avoid race condition between multiple processes
|
|
153
|
+
const randomMs = Math.floor(Math.random() * 10000000);
|
|
154
|
+
updateTimer = setTimeout(updateDb, 3 * ONE_DAY + randomMs + processMs);
|
|
155
|
+
updateTimer.unref();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async function updateDbOnInit() {
|
|
159
|
+
if (process.env.NODE_ENV !== 'production') return;
|
|
160
|
+
clearTimeout(updateTimer);
|
|
161
|
+
// add some random time in timeout to avoid race condition between multiple processes
|
|
162
|
+
const randomMs = Math.floor(Math.random() * 1000000);
|
|
163
|
+
updateTimer = setTimeout(updateDb, randomMs + processMs);
|
|
164
|
+
updateTimer.unref();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function _geoIpInit() {
|
|
168
|
+
try {
|
|
169
|
+
const newGeoIp = getMmdbReader(await readFile(filePath));
|
|
170
|
+
if (!validateReader(newGeoIp)) {
|
|
171
|
+
throw new Error('Invalid GeoIP file');
|
|
172
|
+
}
|
|
173
|
+
geoip = newGeoIp;
|
|
174
|
+
}
|
|
175
|
+
catch (e) {
|
|
176
|
+
console.error(e);
|
|
177
|
+
const newGeoIp = await download();
|
|
178
|
+
if (newGeoIp) {
|
|
179
|
+
geoip = newGeoIp;
|
|
180
|
+
}
|
|
84
181
|
}
|
|
85
|
-
|
|
86
|
-
timer.unref();
|
|
182
|
+
updateDbOnInit();
|
|
87
183
|
}
|
|
88
184
|
|
|
89
185
|
async function geoIpInit() {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
186
|
+
if (!geoInitPromise) {
|
|
187
|
+
geoInitPromise = _geoIpInit();
|
|
188
|
+
}
|
|
189
|
+
return geoInitPromise;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function _geoIpInitSync() {
|
|
193
|
+
try {
|
|
194
|
+
const newGeoIp = getMmdbReader(fs.readFileSync(filePath));
|
|
195
|
+
if (!validateReader(newGeoIp)) {
|
|
196
|
+
throw new Error('Invalid GeoIP file');
|
|
197
|
+
}
|
|
198
|
+
geoip = newGeoIp;
|
|
199
|
+
updateDbOnInit();
|
|
200
|
+
}
|
|
201
|
+
catch (e) {
|
|
202
|
+
console.error(e);
|
|
203
|
+
geoIpInit().catch((err) => {
|
|
204
|
+
console.error(err);
|
|
205
|
+
});
|
|
94
206
|
}
|
|
95
207
|
}
|
|
96
208
|
|
|
209
|
+
function geoIpInitSync() {
|
|
210
|
+
if (!geoInitSynced) {
|
|
211
|
+
geoInitSynced = true;
|
|
212
|
+
_geoIpInitSync();
|
|
213
|
+
}
|
|
214
|
+
return geoInitSynced;
|
|
215
|
+
}
|
|
216
|
+
|
|
97
217
|
async function getGeoIp() {
|
|
98
218
|
if (!geoip) {
|
|
99
|
-
|
|
100
|
-
geoInitPromise = geoIpInit();
|
|
101
|
-
}
|
|
102
|
-
await geoInitPromise;
|
|
103
|
-
geoInitPromise = null;
|
|
219
|
+
await geoIpInit();
|
|
104
220
|
}
|
|
105
221
|
return geoip;
|
|
106
222
|
}
|
|
107
223
|
|
|
108
224
|
function getGeoIpSync() {
|
|
109
225
|
if (!geoip) {
|
|
110
|
-
|
|
111
|
-
geoip = new maxmind.Reader(buffer);
|
|
112
|
-
if (process.env.NODE_ENV === 'production') {
|
|
113
|
-
updateDb();
|
|
114
|
-
}
|
|
226
|
+
geoIpInitSync();
|
|
115
227
|
}
|
|
116
228
|
return geoip;
|
|
117
229
|
}
|
|
@@ -119,7 +231,7 @@ function getGeoIpSync() {
|
|
|
119
231
|
class GeoIP {
|
|
120
232
|
static async get(ip) {
|
|
121
233
|
try {
|
|
122
|
-
return
|
|
234
|
+
return (await getGeoIp()).get(ip);
|
|
123
235
|
}
|
|
124
236
|
catch (e) {
|
|
125
237
|
console.error('Error getting geoip location', e);
|
package/Request.js
CHANGED
|
@@ -1394,19 +1394,19 @@ class Request {
|
|
|
1394
1394
|
const loc = this.geoipLoc();
|
|
1395
1395
|
const country = loc.country;
|
|
1396
1396
|
const city = loc.city;
|
|
1397
|
-
const state = loc.subdivisions
|
|
1397
|
+
const state = loc.subdivisions?.[0];
|
|
1398
1398
|
this._ipLoc = {
|
|
1399
1399
|
country: {
|
|
1400
|
-
name:
|
|
1401
|
-
isoCode:
|
|
1400
|
+
name: country?.names?.en || '',
|
|
1401
|
+
isoCode: country?.iso_code || '',
|
|
1402
1402
|
},
|
|
1403
1403
|
city: {
|
|
1404
|
-
name:
|
|
1405
|
-
isoCode:
|
|
1404
|
+
name: city?.names?.en || '',
|
|
1405
|
+
isoCode: city?.iso_code || '',
|
|
1406
1406
|
},
|
|
1407
1407
|
state: {
|
|
1408
|
-
name:
|
|
1409
|
-
isoCode:
|
|
1408
|
+
name: state?.names?.en || '',
|
|
1409
|
+
isoCode: state?.iso_code || '',
|
|
1410
1410
|
},
|
|
1411
1411
|
};
|
|
1412
1412
|
}
|
|
@@ -1414,8 +1414,18 @@ class Request {
|
|
|
1414
1414
|
return this._ipLoc;
|
|
1415
1415
|
}
|
|
1416
1416
|
|
|
1417
|
+
ipCountryISO() {
|
|
1418
|
+
if (this._ipciso === undefined) {
|
|
1419
|
+
this._ipciso = this.geoipLoc().country?.iso_code?.toLowerCase() || '';
|
|
1420
|
+
}
|
|
1421
|
+
return this._ipciso;
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1417
1424
|
ipCountry() {
|
|
1418
|
-
|
|
1425
|
+
if (this._ipcname === undefined) {
|
|
1426
|
+
this._ipcname = this.geoipLoc().country?.names?.en || '';
|
|
1427
|
+
}
|
|
1428
|
+
return this._ipcname;
|
|
1419
1429
|
}
|
|
1420
1430
|
|
|
1421
1431
|
ipCity() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smpx/koa-request",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Handle basic tasks for koajs",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"koa-body": "^5.0.0",
|
|
36
36
|
"koa-send": "^5.0.1",
|
|
37
37
|
"koa2-ratelimit": "^1.1.3",
|
|
38
|
-
"
|
|
38
|
+
"mmdb-lib": "^3.0.1",
|
|
39
39
|
"tar": "^7.4.3",
|
|
40
40
|
"ua-parser-js": "^1.0.40"
|
|
41
41
|
},
|