@streamr/geoip-location 100.2.4
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/.eslintignore +4 -0
- package/.eslintrc +3 -0
- package/LICENSE +174 -0
- package/README.md +72 -0
- package/dist/src/GeoIpLocator.d.ts +20 -0
- package/dist/src/GeoIpLocator.js +98 -0
- package/dist/src/GeoIpLocator.js.map +1 -0
- package/dist/src/downloadGeoIpDatabase.d.ts +3 -0
- package/dist/src/downloadGeoIpDatabase.js +169 -0
- package/dist/src/downloadGeoIpDatabase.js.map +1 -0
- package/dist/src/exports.d.ts +1 -0
- package/dist/src/exports.js +6 -0
- package/dist/src/exports.js.map +1 -0
- package/dist/src/tarHelper.d.ts +3 -0
- package/dist/src/tarHelper.js +40 -0
- package/dist/src/tarHelper.js.map +1 -0
- package/jest.config.js +2 -0
- package/package.json +39 -0
- package/src/GeoIpLocator.ts +109 -0
- package/src/downloadGeoIpDatabase.ts +185 -0
- package/src/exports.ts +1 -0
- package/src/tarHelper.ts +35 -0
- package/test/helpers/TestServer.ts +176 -0
- package/test/helpers/fetchFileToMemory.ts +7 -0
- package/test/unit/GeoIpLocator-intervals.test.ts +107 -0
- package/test/unit/GeoIpLocator-no-network-at-monthly.test.ts +55 -0
- package/test/unit/GeoIpLocator-no-network-at-start.test.ts +29 -0
- package/test/unit/GeoIpLocator.test.ts +127 -0
- package/test/unit/downloadGeoIpDatabase.test.ts +66 -0
- package/test/unit/tarHelper.test.ts +92 -0
- package/tsconfig.browser.json +12 -0
- package/tsconfig.jest.json +14 -0
- package/tsconfig.json +3 -0
- package/tsconfig.node.json +13 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { GeoIpLocator } from '../../src/GeoIpLocator'
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import { wait } from '@streamr/utils'
|
|
4
|
+
import { TestServer } from '../helpers/TestServer'
|
|
5
|
+
|
|
6
|
+
describe('GeoIpLocatorNoNetworkAtMonthly', () => {
|
|
7
|
+
let dirCounter = 0
|
|
8
|
+
const dbPath = '/tmp'
|
|
9
|
+
const serverPort = 31990
|
|
10
|
+
const serverUrl = 'http://localhost:' + serverPort + '/'
|
|
11
|
+
|
|
12
|
+
let testServer: TestServer
|
|
13
|
+
let dbDir: string
|
|
14
|
+
let locator: GeoIpLocator
|
|
15
|
+
|
|
16
|
+
const getDbDir = () => {
|
|
17
|
+
dirCounter++
|
|
18
|
+
return dbPath + '/geolite2-no-nw-monthly' + dirCounter
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
beforeAll(async () => {
|
|
22
|
+
testServer = new TestServer()
|
|
23
|
+
await testServer.start(serverPort)
|
|
24
|
+
dbDir = getDbDir()
|
|
25
|
+
locator = new GeoIpLocator(dbDir, 5000, 10000, serverUrl)
|
|
26
|
+
await locator.start()
|
|
27
|
+
}, 120000)
|
|
28
|
+
|
|
29
|
+
afterAll(async () => {
|
|
30
|
+
locator!.stop()
|
|
31
|
+
testServer!.stop()
|
|
32
|
+
fs.unlinkSync(dbDir + '/GeoLite2-City.mmdb')
|
|
33
|
+
fs.rmSync(dbDir!, { recursive: true })
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('does not crash if monthly database check fails because of fetch returning garbage', async () => {
|
|
37
|
+
const oldFetch = globalThis.fetch
|
|
38
|
+
const fetchMock = jest
|
|
39
|
+
.spyOn(globalThis, 'fetch')
|
|
40
|
+
.mockImplementation(() => oldFetch('https://streamr.network'))
|
|
41
|
+
|
|
42
|
+
await wait(10000)
|
|
43
|
+
|
|
44
|
+
fetchMock.mockRestore()
|
|
45
|
+
|
|
46
|
+
// suomi.fi
|
|
47
|
+
const location = locator!.lookup('62.241.198.245')
|
|
48
|
+
expect(location).toBeDefined()
|
|
49
|
+
|
|
50
|
+
// Helsinki, Finland
|
|
51
|
+
expect(location!.latitude).toBe(60.1797)
|
|
52
|
+
expect(location!.longitude).toBe(24.9344)
|
|
53
|
+
|
|
54
|
+
}, 60000)
|
|
55
|
+
})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { GeoIpLocator } from '../../src/GeoIpLocator'
|
|
2
|
+
|
|
3
|
+
describe('GeoIpLocator', () => {
|
|
4
|
+
let dirCounter = 0
|
|
5
|
+
const dbPath = '/tmp'
|
|
6
|
+
|
|
7
|
+
const getDbDir = () => {
|
|
8
|
+
dirCounter++
|
|
9
|
+
return dbPath + '/geolite2-no-nw-start' + dirCounter
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
it('start throws if no network connectivity', async () => {
|
|
13
|
+
const dbDir = getDbDir()
|
|
14
|
+
|
|
15
|
+
const fetchMock = jest
|
|
16
|
+
.spyOn(globalThis, 'fetch')
|
|
17
|
+
.mockImplementation(async () => {
|
|
18
|
+
throw new Error('API is down')
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const locator = new GeoIpLocator(dbDir)
|
|
22
|
+
|
|
23
|
+
await expect(locator.start()).rejects.toThrow()
|
|
24
|
+
|
|
25
|
+
fetchMock.mockRestore()
|
|
26
|
+
|
|
27
|
+
locator.stop()
|
|
28
|
+
})
|
|
29
|
+
})
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { GeoIpLocator } from '../../src/GeoIpLocator'
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import { wait } from '@streamr/utils'
|
|
4
|
+
import { TestServer } from '../helpers/TestServer'
|
|
5
|
+
|
|
6
|
+
describe('GeoIpLocator', () => {
|
|
7
|
+
let testServer: TestServer
|
|
8
|
+
let dirCounter = 0
|
|
9
|
+
const dbPath = '/tmp'
|
|
10
|
+
const serverPort = 31992
|
|
11
|
+
const serverUrl = 'http://127.0.0.1:' + serverPort + '/'
|
|
12
|
+
|
|
13
|
+
const getDbDir = () => {
|
|
14
|
+
dirCounter++
|
|
15
|
+
return dbPath + '/geolite2-' + dirCounter
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
beforeAll(async () => {
|
|
19
|
+
testServer = new TestServer()
|
|
20
|
+
await testServer.start(serverPort)
|
|
21
|
+
}, 120000)
|
|
22
|
+
|
|
23
|
+
afterAll(async () => {
|
|
24
|
+
testServer!.stop()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
describe('tests with normal startup and shutdown', () => {
|
|
28
|
+
let dbDir: string
|
|
29
|
+
let locator: GeoIpLocator
|
|
30
|
+
|
|
31
|
+
it('can locate an IP address', async () => {
|
|
32
|
+
dbDir = getDbDir()
|
|
33
|
+
locator = new GeoIpLocator(dbDir, 5000, 5000, serverUrl)
|
|
34
|
+
await locator.start()
|
|
35
|
+
|
|
36
|
+
// suomi.fi
|
|
37
|
+
const location = locator.lookup('62.241.198.245')
|
|
38
|
+
|
|
39
|
+
expect(location).toBeDefined()
|
|
40
|
+
|
|
41
|
+
// Helsinki, Finland
|
|
42
|
+
expect(location!.latitude).toBe(60.1797)
|
|
43
|
+
expect(location!.longitude).toBe(24.9344)
|
|
44
|
+
|
|
45
|
+
locator.stop()
|
|
46
|
+
fs.unlinkSync(dbDir + '/GeoLite2-City.mmdb')
|
|
47
|
+
fs.rmSync(dbDir, { recursive: true })
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('returns undefined with invalid IP address', async () => {
|
|
51
|
+
dbDir = getDbDir()
|
|
52
|
+
locator = new GeoIpLocator(dbDir, 5000, 5000, serverUrl)
|
|
53
|
+
await locator.start()
|
|
54
|
+
|
|
55
|
+
expect(locator.lookup('invalid')).toBeUndefined()
|
|
56
|
+
expect(locator.lookup('')).toBeUndefined()
|
|
57
|
+
expect(locator.lookup(undefined as unknown as string)).toBeUndefined()
|
|
58
|
+
expect(locator.lookup(null as unknown as string)).toBeUndefined()
|
|
59
|
+
expect(locator.lookup('127.0.0.1')).toBeUndefined()
|
|
60
|
+
|
|
61
|
+
locator.stop()
|
|
62
|
+
fs.unlinkSync(dbDir + '/GeoLite2-City.mmdb')
|
|
63
|
+
fs.rmSync(dbDir, { recursive: true })
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('works also after monthly check', async () => {
|
|
67
|
+
dbDir = getDbDir()
|
|
68
|
+
locator = new GeoIpLocator(dbDir, 5000, 5000, serverUrl)
|
|
69
|
+
await locator.start()
|
|
70
|
+
|
|
71
|
+
await wait(7000)
|
|
72
|
+
|
|
73
|
+
// suomi.fi
|
|
74
|
+
const location = locator.lookup('62.241.198.245')
|
|
75
|
+
expect(location).toBeDefined()
|
|
76
|
+
|
|
77
|
+
// Helsinki, Finland
|
|
78
|
+
expect(location!.latitude).toBe(60.1797)
|
|
79
|
+
expect(location!.longitude).toBe(24.9344)
|
|
80
|
+
|
|
81
|
+
locator.stop()
|
|
82
|
+
fs.unlinkSync(dbDir + '/GeoLite2-City.mmdb')
|
|
83
|
+
fs.rmSync(dbDir, { recursive: true })
|
|
84
|
+
}, 60000)
|
|
85
|
+
|
|
86
|
+
it('works also after monthly check if db gets deleted before the check', async () => {
|
|
87
|
+
dbDir = getDbDir()
|
|
88
|
+
locator = new GeoIpLocator(dbDir, 5000, 5000, serverUrl)
|
|
89
|
+
await locator.start()
|
|
90
|
+
|
|
91
|
+
fs.unlinkSync(dbDir + '/GeoLite2-City.mmdb')
|
|
92
|
+
|
|
93
|
+
await wait(10000)
|
|
94
|
+
|
|
95
|
+
// suomi.fi
|
|
96
|
+
const location = locator.lookup('62.241.198.245')
|
|
97
|
+
expect(location).toBeDefined()
|
|
98
|
+
|
|
99
|
+
// Helsinki, Finland
|
|
100
|
+
expect(location!.latitude).toBe(60.1797)
|
|
101
|
+
expect(location!.longitude).toBe(24.9344)
|
|
102
|
+
|
|
103
|
+
locator.stop()
|
|
104
|
+
fs.unlinkSync(dbDir + '/GeoLite2-City.mmdb')
|
|
105
|
+
fs.rmSync(dbDir, { recursive: true })
|
|
106
|
+
}, 60000)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
describe('tests with failing startup', () => {
|
|
110
|
+
it('returns undefined if not started', async () => {
|
|
111
|
+
const dbDir = getDbDir()
|
|
112
|
+
const locator = new GeoIpLocator(dbDir)
|
|
113
|
+
const location = locator.lookup('62.241.198.245')
|
|
114
|
+
expect(location).toBeUndefined()
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('start() throws if database path does not exist', async () => {
|
|
118
|
+
const locator = new GeoIpLocator('/nonexistent')
|
|
119
|
+
await expect(locator.start()).rejects.toThrow()
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('start() throws if database path is not writable', async () => {
|
|
123
|
+
const locator = new GeoIpLocator('/etc')
|
|
124
|
+
await expect(locator.start()).rejects.toThrow()
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
})
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { downloadGeoIpDatabase } from '../../src/downloadGeoIpDatabase'
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import { TestServer } from '../helpers/TestServer'
|
|
4
|
+
|
|
5
|
+
describe('downloadGeoIpDatabase', () => {
|
|
6
|
+
const serverPort = 31993
|
|
7
|
+
const mirrorUrl = 'http://127.0.0.1:' + serverPort + '/'
|
|
8
|
+
|
|
9
|
+
let testServer: TestServer
|
|
10
|
+
const abortController = new AbortController()
|
|
11
|
+
const path = '/tmp/downloadGeoIpDatabaseTest/'
|
|
12
|
+
|
|
13
|
+
beforeAll(async () => {
|
|
14
|
+
testServer = new TestServer()
|
|
15
|
+
await testServer.start(serverPort)
|
|
16
|
+
}, 120000)
|
|
17
|
+
|
|
18
|
+
afterAll(async () => {
|
|
19
|
+
testServer!.stop()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
try {
|
|
24
|
+
fs.rmSync(path, { recursive: true })
|
|
25
|
+
} catch (e) {
|
|
26
|
+
// ignore error when removing the test data
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('downloads the database with correct file permissions', async () => {
|
|
31
|
+
const reader = await downloadGeoIpDatabase(path, false, abortController.signal, mirrorUrl)
|
|
32
|
+
|
|
33
|
+
expect(fs.existsSync(path)).toBe(true)
|
|
34
|
+
expect(fs.existsSync(path + '/GeoLite2-City.mmdb')).toBe(true)
|
|
35
|
+
|
|
36
|
+
// https://www.martin-brennan.com/nodejs-file-permissions-fstat/
|
|
37
|
+
const permissions = fs.statSync(path + '/GeoLite2-City.mmdb').mode & 0o777
|
|
38
|
+
|
|
39
|
+
// on windows the permissions might be 0o666
|
|
40
|
+
expect(permissions === 0o600 || permissions === 0o666).toBe(true)
|
|
41
|
+
expect(reader).toBeDefined()
|
|
42
|
+
}, 60000)
|
|
43
|
+
|
|
44
|
+
it('throws if the path is not writable', async () => {
|
|
45
|
+
const path = '/etc/downloadGeoIpDatabaseTest/'
|
|
46
|
+
await expect(downloadGeoIpDatabase(path, false, abortController.signal, mirrorUrl)).rejects.toThrow()
|
|
47
|
+
}, 60000)
|
|
48
|
+
|
|
49
|
+
it('throws if the path does not exist', async () => {
|
|
50
|
+
const path = '/nonexistent/downloadGeoIpDatabaseTest/'
|
|
51
|
+
await expect(downloadGeoIpDatabase(path, false, abortController.signal, mirrorUrl)).rejects.toThrow()
|
|
52
|
+
}, 60000)
|
|
53
|
+
|
|
54
|
+
it('does not download the database if it is already up to date', async () => {
|
|
55
|
+
const path = '/tmp/downloadGeoIpDatabaseTest/'
|
|
56
|
+
|
|
57
|
+
const newReader = await downloadGeoIpDatabase(path, false, abortController.signal, mirrorUrl)
|
|
58
|
+
expect(newReader).toBeDefined()
|
|
59
|
+
|
|
60
|
+
const newReader2 = await downloadGeoIpDatabase(path, false, abortController.signal, mirrorUrl)
|
|
61
|
+
expect(newReader2).toBeUndefined()
|
|
62
|
+
|
|
63
|
+
const newReader3 = await downloadGeoIpDatabase(path, true, abortController.signal, mirrorUrl)
|
|
64
|
+
expect(newReader3).toBeDefined()
|
|
65
|
+
}, 60000)
|
|
66
|
+
})
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { waitForEvent3 } from '@streamr/utils'
|
|
2
|
+
import { extractFileFromTarStream } from '../../src/tarHelper'
|
|
3
|
+
import { TestServer, TestServerEvents } from '../helpers/TestServer'
|
|
4
|
+
|
|
5
|
+
describe('tarHelper', () => {
|
|
6
|
+
const serverUrl = 'http://127.0.0.1:'
|
|
7
|
+
const dbFileName = 'GeoLite2-City.mmdb'
|
|
8
|
+
const tarFileName = 'GeoLite2-City.tar.gz'
|
|
9
|
+
const hashFileName = 'GeoLite2-City.mmdb.sha384'
|
|
10
|
+
|
|
11
|
+
let testServer: TestServer
|
|
12
|
+
|
|
13
|
+
afterEach(async () => {
|
|
14
|
+
await testServer!.stop()
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
describe('testsWithNormalServer', () => {
|
|
18
|
+
const serverPort = 3197
|
|
19
|
+
|
|
20
|
+
beforeEach(async () => {
|
|
21
|
+
testServer = new TestServer()
|
|
22
|
+
await testServer.start(serverPort)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('happy path', async () => {
|
|
26
|
+
const url = serverUrl + serverPort + '/' + tarFileName
|
|
27
|
+
const result = await fetch(url, { keepalive: false })
|
|
28
|
+
|
|
29
|
+
await extractFileFromTarStream(dbFileName, result.body!, '/tmp')
|
|
30
|
+
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('throws asynchonously if the stream contains garbage', async () => {
|
|
34
|
+
const url = serverUrl + serverPort + '/' + hashFileName
|
|
35
|
+
const result = await fetch(url)
|
|
36
|
+
|
|
37
|
+
await expect(extractFileFromTarStream(dbFileName, result.body!, '/tmp'))
|
|
38
|
+
.rejects
|
|
39
|
+
.toThrow('TAR_BAD_ARCHIVE: Unrecognized archive format')
|
|
40
|
+
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('throws asynchonously if the stream does not contain the desired file', async () => {
|
|
44
|
+
const url = serverUrl + serverPort + '/' + tarFileName
|
|
45
|
+
const result = await fetch(url)
|
|
46
|
+
|
|
47
|
+
await expect(extractFileFromTarStream('nonexisting-filename', result.body!, '/tmp'))
|
|
48
|
+
.rejects
|
|
49
|
+
.toThrow('File not found in tarball: nonexisting-filename')
|
|
50
|
+
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
describe('testsWithThrottledServer', () => {
|
|
55
|
+
const serverPort = 3198
|
|
56
|
+
|
|
57
|
+
beforeEach(async () => {
|
|
58
|
+
testServer = new TestServer()
|
|
59
|
+
await testServer.start(serverPort, 1)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('throws asynchonously if the stream gets aborted', async () => {
|
|
63
|
+
const abortController = new AbortController()
|
|
64
|
+
|
|
65
|
+
setTimeout(() => {
|
|
66
|
+
abortController.abort()
|
|
67
|
+
}, 5000)
|
|
68
|
+
|
|
69
|
+
const url = serverUrl + serverPort + '/' + tarFileName
|
|
70
|
+
const result = await fetch(url, { signal: abortController.signal })
|
|
71
|
+
|
|
72
|
+
await expect(extractFileFromTarStream(dbFileName, result.body!, '/tmp'))
|
|
73
|
+
.rejects
|
|
74
|
+
.toThrow('AbortError: This operation was aborted')
|
|
75
|
+
|
|
76
|
+
}, 15 * 1000)
|
|
77
|
+
|
|
78
|
+
it('throws asynchonously if server gets shut down', async () => {
|
|
79
|
+
const closedPromise = waitForEvent3<TestServerEvents>(testServer!, 'closed', 10000)
|
|
80
|
+
setTimeout(async () => {
|
|
81
|
+
await testServer!.stop()
|
|
82
|
+
}, 5000)
|
|
83
|
+
|
|
84
|
+
const url = serverUrl + serverPort + '/' + tarFileName
|
|
85
|
+
const result = await fetch(url)
|
|
86
|
+
await expect(extractFileFromTarStream(dbFileName, result.body!, '/tmp'))
|
|
87
|
+
.rejects
|
|
88
|
+
.toThrow('Error extracting tarball')
|
|
89
|
+
await closedPromise
|
|
90
|
+
}, 15 * 1000)
|
|
91
|
+
})
|
|
92
|
+
})
|
package/tsconfig.json
ADDED