Skip to content

Commit cffb6c2

Browse files
committed
Update
1 parent e4ebe85 commit cffb6c2

File tree

6 files changed

+130
-97
lines changed

6 files changed

+130
-97
lines changed

fsWatcher.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,13 @@ const FSWatcher = {};
1414
* Takes an FSWatcher object and closes it.
1515
* @param {string} name - The name of the watcher to close.
1616
*/
17-
const stopWatching = name => FSWatcher[name].close();
17+
const stopWatching = name => {
18+
const watcher = FSWatcher[name];
19+
if (!watcher) return;
20+
21+
watcher.close();
22+
delete FSWatcher[name];
23+
};
1824

1925
// ============================================================================
2026
// File System Watch with Debounce
@@ -82,4 +88,4 @@ const makeFsWatchFilter = (name, directory, filename, cdDelay, callback) => {
8288
// Exports
8389
// ============================================================================
8490

85-
module.exports = { makeFsWatchFilter, stopWatching };
91+
module.exports = { makeFsWatchFilter, stopWatching };

index.d.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ interface GeoIp2Location {
1313
export function lookup(ip: string | number): GeoIp2Location | null;
1414
export function pretty(n: string | number | any[]): string;
1515
export function reloadDataSync(): void;
16-
export function reloadData(callback?: (err?: Error | null) => void): Promise<void>;
17-
export function startWatchingDataUpdate(callback: () => void): void;
16+
export function reloadData(callback: (err?: Error | null) => void): void;
17+
export function reloadData(): Promise<void>;
18+
export function startWatchingDataUpdate(callback?: (err?: Error | null) => void): void;
1819
export function stopWatchingDataUpdate(): void;
1920
export function clear(): void;
20-
export const version: string;
21+
export const version: string;

index.js

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ const removeNullTerminator = str => {
7474

7575
const readIp6 = (buffer, line, recordSize, offset) => {
7676
const ipArray = [];
77-
for (let i = 0; i < 2; i++) {
77+
for (let i = 0; i < 4; i++) {
7878
ipArray.push(buffer.readUInt32BE((line * recordSize) + (offset * 16) + (i * 4)));
7979
}
8080
return ipArray;
@@ -87,8 +87,8 @@ const readIp6 = (buffer, line, recordSize, offset) => {
8787
const lookup4 = ip => {
8888
let fline = 0;
8989
let cline = cache4.lastLine;
90-
let floor = cache4.lastIP;
91-
let ceil = cache4.firstIP;
90+
let floor;
91+
let ceil;
9292
let line, locId;
9393

9494
const buffer = cache4.mainBuffer;
@@ -187,8 +187,8 @@ const lookup6 = ip => {
187187

188188
let fline = 0;
189189
let cline = cache6.lastLine;
190-
let floor = cache6.lastIP;
191-
let ceil = cache6.firstIP;
190+
let floor;
191+
let ceil;
192192
let line, locId;
193193

194194
if (cmp6(ip, cache6.lastIP) > 0 || cmp6(ip, cache6.firstIP) < 0) return null;
@@ -518,14 +518,19 @@ module.exports = {
518518
// complete before triggering the callback.
519519
startWatchingDataUpdate: callback => {
520520
fsWatcher.makeFsWatchFilter(watcherName, geoDataDir, 60 * 1000, () => {
521-
// Reload data
522-
async.series([
521+
const tasks = [
523522
cb => {
524523
preload(cb);
525524
}, cb => {
526525
preload6(cb);
527526
},
528-
], callback);
527+
];
528+
529+
if (typeof callback === 'function') {
530+
async.series(tasks, callback);
531+
} else {
532+
async.series(tasks);
533+
}
529534
});
530535
},
531536

@@ -546,15 +551,31 @@ module.exports = {
546551

547552
// Reload data asynchronously
548553
reloadData: callback => {
549-
// Reload data
550-
async.series([
551-
cb => {
552-
preload(cb);
553-
},
554-
cb => {
555-
preload6(cb);
556-
},
557-
], callback);
554+
if (typeof callback === 'function') {
555+
async.series([
556+
cb => {
557+
preload(cb);
558+
},
559+
cb => {
560+
preload6(cb);
561+
},
562+
], callback);
563+
return;
564+
}
565+
566+
return new Promise((resolve, reject) => {
567+
async.series([
568+
cb => {
569+
preload(cb);
570+
},
571+
cb => {
572+
preload6(cb);
573+
},
574+
], err => {
575+
if (err) reject(err);
576+
else resolve();
577+
});
578+
});
558579
},
559580

560581
version,
@@ -568,4 +589,4 @@ preload();
568589
preload6();
569590

570591
// lookup4 = gen_lookup('geoip-country.dat', 4);
571-
// lookup6 = gen_lookup('geoip-country6.dat', 16);
592+
// lookup6 = gen_lookup('geoip-country6.dat', 16);

test/index.test.js

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,14 +169,45 @@ describe('GeoIP2', () => {
169169
const none6 = geoIp.lookup('::ffff:173.185.182.82');
170170
expect(none6).toBeNull();
171171

172-
geoIp.reloadData(() => {
172+
const result = geoIp.reloadData(() => {
173173
const after4 = geoIp.lookup('75.82.117.180');
174174
expect(before4).toEqual(after4);
175175
const after6 = geoIp.lookup('::ffff:173.185.182.82');
176176
expect(before6).toEqual(after6);
177177

178178
done();
179179
});
180+
181+
expect(result).toBeUndefined();
182+
});
183+
184+
it('should reload data and resolve a promise when callback is omitted', async () => {
185+
const before4 = geoIp.lookup('75.82.117.180');
186+
expect(before4).not.toBeNull();
187+
const before6 = geoIp.lookup('::ffff:173.185.182.82');
188+
expect(before6).not.toBeNull();
189+
190+
geoIp.clear();
191+
192+
expect(geoIp.lookup('75.82.117.180')).toBeNull();
193+
expect(geoIp.lookup('::ffff:173.185.182.82')).toBeNull();
194+
195+
const result = geoIp.reloadData();
196+
expect(result).toBeDefined();
197+
expect(typeof result.then).toBe('function');
198+
199+
await result;
200+
201+
const after4 = geoIp.lookup('75.82.117.180');
202+
expect(before4).toEqual(after4);
203+
const after6 = geoIp.lookup('::ffff:173.185.182.82');
204+
expect(before6).toEqual(after6);
205+
});
206+
});
207+
208+
describe('#testWatcherLifecycle', () => {
209+
it('should not throw when stopping watcher before it starts', () => {
210+
expect(() => geoIp.stopWatchingDataUpdate()).not.toThrow();
180211
});
181212
});
182213

@@ -225,4 +256,4 @@ describe('GeoIP2', () => {
225256
expect(actual).toBeNull();
226257
});
227258
});
228-
});
259+
});

test/utils.test.js

Lines changed: 23 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,18 @@ describe('Utility Functions', () => {
6565
expect(result).toBeInstanceOf(Array);
6666
expect(result.length).toBe(4);
6767
});
68+
69+
it('should return identical output for equivalent compressed and full IPv6 forms', () => {
70+
const compressed = utils.aton6('2607:f0d0:1002:51::4');
71+
const expanded = utils.aton6('2607:f0d0:1002:0051:0000:0000:0000:0004');
72+
expect(compressed).toEqual(expanded);
73+
});
74+
75+
it('should correctly expand omitted middle zero groups', () => {
76+
const actual = utils.aton6('2001:db8:1::2');
77+
const expected = utils.aton6('2001:0db8:0001:0000:0000:0000:0000:0002');
78+
expect(actual).toEqual(expected);
79+
});
6880
});
6981

7082
describe('#ntoa6', () => {
@@ -94,7 +106,7 @@ describe('Utility Functions', () => {
94106
});
95107

96108
it('should delegate to cmp6 for arrays', () => {
97-
const result = utils.cmp([1, 2], [1, 3]);
109+
const result = utils.cmp([0, 0, 0, 1], [0, 0, 0, 2]);
98110
expect(result).toBe(-1);
99111
});
100112

@@ -106,44 +118,24 @@ describe('Utility Functions', () => {
106118

107119
describe('#cmp6', () => {
108120
it('should compare IPv6 arrays correctly', () => {
109-
expect(utils.cmp6([0, 0], [0, 1])).toBe(-1);
110-
expect(utils.cmp6([0, 1], [0, 0])).toBe(1);
111-
expect(utils.cmp6([5, 10], [5, 10])).toBe(0);
121+
expect(utils.cmp6([0, 0, 0, 0], [0, 0, 0, 1])).toBe(-1);
122+
expect(utils.cmp6([0, 0, 0, 1], [0, 0, 0, 0])).toBe(1);
123+
expect(utils.cmp6([5, 10, 15, 20], [5, 10, 15, 20])).toBe(0);
112124
});
113125

114126
it('should compare first element priority', () => {
115-
expect(utils.cmp6([1, 100], [2, 0])).toBe(-1);
116-
expect(utils.cmp6([2, 0], [1, 100])).toBe(1);
127+
expect(utils.cmp6([1, 100, 999, 999], [2, 0, 0, 0])).toBe(-1);
128+
expect(utils.cmp6([2, 0, 0, 0], [1, 100, 999, 999])).toBe(1);
117129
});
118130

119131
it('should handle equal first elements', () => {
120-
expect(utils.cmp6([1, 2], [1, 3])).toBe(-1);
121-
expect(utils.cmp6([1, 3], [1, 2])).toBe(1);
122-
});
123-
});
124-
125-
describe('#isPrivateIP', () => {
126-
it('should detect private IPv4 addresses', () => {
127-
expect(utils.isPrivateIP('10.0.0.1')).toBe(true);
128-
expect(utils.isPrivateIP('10.255.255.255')).toBe(true);
129-
expect(utils.isPrivateIP('192.168.0.1')).toBe(true);
130-
expect(utils.isPrivateIP('192.168.255.255')).toBe(true);
131-
expect(utils.isPrivateIP('172.16.0.1')).toBe(true);
132-
expect(utils.isPrivateIP('127.0.0.1')).toBe(true);
133-
expect(utils.isPrivateIP('169.254.1.1')).toBe(true);
134-
});
135-
136-
it('should detect private IPv6 addresses', () => {
137-
expect(utils.isPrivateIP('fc00::1')).toBe(true);
138-
expect(utils.isPrivateIP('fe80::1')).toBe(true);
132+
expect(utils.cmp6([1, 2, 0, 0], [1, 3, 0, 0])).toBe(-1);
133+
expect(utils.cmp6([1, 3, 0, 0], [1, 2, 0, 0])).toBe(1);
139134
});
140135

141-
it('should not detect public IP addresses as private', () => {
142-
expect(utils.isPrivateIP('8.8.8.8')).toBe(false);
143-
expect(utils.isPrivateIP('1.1.1.1')).toBe(false);
144-
expect(utils.isPrivateIP('172.15.0.1')).toBe(false);
145-
expect(utils.isPrivateIP('172.32.0.1')).toBe(false);
146-
expect(utils.isPrivateIP('2001:db8::1')).toBe(false);
136+
it('should compare lower 64 bits when upper 64 bits are equal', () => {
137+
expect(utils.cmp6([638054608, 268566609, 0, 4], [638054608, 268566609, 0, 5])).toBe(-1);
138+
expect(utils.cmp6([638054608, 268566609, 4, 4], [638054608, 268566609, 0, 4])).toBe(1);
147139
});
148140
});
149141
});

utils.js

Lines changed: 23 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -22,34 +22,29 @@ utils.ntoa4 = n => {
2222
// ============================================================================
2323

2424
utils.aton6 = a => {
25-
a = a.replace(/"/g, '').split(':');
26-
27-
const l = a.length - 1;
28-
let i;
29-
30-
if (a[l] === '') {
31-
a[l] = 0;
32-
}
33-
34-
if (l < 7) {
35-
a.length = 8;
36-
37-
for (i = l; i >= 0 && a[i] !== ''; i--) {
38-
a[7 - l + i] = a[i];
39-
}
25+
a = a.replace(/"/g, '');
26+
27+
let parts;
28+
const omitStart = a.indexOf('::');
29+
if (omitStart >= 0) {
30+
const left = a.slice(0, omitStart);
31+
const right = a.slice(omitStart + 2);
32+
const leftParts = left ? left.split(':') : [];
33+
const rightParts = right ? right.split(':') : [];
34+
const omitted = 8 - leftParts.length - rightParts.length;
35+
36+
parts = leftParts.concat(new Array(Math.max(omitted, 0)).fill('0'), rightParts);
37+
} else {
38+
parts = a.split(':');
4039
}
4140

42-
for (i = 0; i < 8; i++) {
43-
if (!a[i]) {
44-
a[i] = 0;
45-
} else {
46-
a[i] = parseInt(a[i], 16);
47-
}
41+
for (let i = 0; i < 8; i++) {
42+
parts[i] = parseInt(parts[i] || '0', 16);
4843
}
4944

5045
const r = [];
51-
for (i = 0; i < 4; i++) {
52-
r.push(((a[2 * i] << 16) + a[2 * i + 1]) >>> 0);
46+
for (let i = 0; i < 4; i++) {
47+
r.push(((parts[2 * i] << 16) + parts[(2 * i) + 1]) >>> 0);
5348
}
5449

5550
return r;
@@ -79,25 +74,12 @@ utils.cmp = (a, b) => {
7974
};
8075

8176
utils.cmp6 = (a, b) => {
82-
for (let ii = 0; ii < 2; ii++) {
83-
if (a[ii] < b[ii]) return -1;
84-
if (a[ii] > b[ii]) return 1;
77+
for (let ii = 0; ii < 4; ii++) {
78+
const av = a[ii] ?? 0;
79+
const bv = b[ii] ?? 0;
80+
if (av < bv) return -1;
81+
if (av > bv) return 1;
8582
}
8683

8784
return 0;
8885
};
89-
90-
// ============================================================================
91-
// IP Address Validation
92-
// ============================================================================
93-
94-
utils.isPrivateIP = addr => {
95-
const str = addr.toString();
96-
return str.startsWith('10.') ||
97-
str.startsWith('192.168.') ||
98-
str.startsWith('172.16.') ||
99-
str.startsWith('127.') ||
100-
str.startsWith('169.254.') ||
101-
str.startsWith('fc00:') ||
102-
str.startsWith('fe80:');
103-
};

0 commit comments

Comments
 (0)