UNPKG

geo-ip-service

Version:

GeoIP middleware to provide ip based info leveraging maxmind Geo City databases

97 lines (88 loc) 3.11 kB
import { createReadStream } from 'fs' import { writeFile, readFile, unlink } from 'fs/promises' import https from 'https' import path from 'path' import concat from 'concat-stream' import tarfs from 'tar-fs' import zlib from 'zlib' import { maxMindHost, maxMindPath, vendorDir, dateFile, finalDest, dbPath } from './constants.js' export { download } async function lastModifiedAtOrigin () { const lm = await (new Promise((resolve, reject) => { const req = https.request({ host: maxMindHost, path: maxMindPath, method: 'HEAD' }, res => resolve(res.headers['last-modified'])) req.on('error', err => reject(err)) req.end() })) return new Date(lm) } async function getLocalLastModifiedInSeconds () { try { const localModified = await readFile(dateFile) return parseInt(localModified) } catch (error) { return null } } async function download () { let lastModified const localValue = await getLocalLastModifiedInSeconds() const lmOrigin = await lastModifiedAtOrigin() const originValueSeconds = lmOrigin.getTime() if (localValue && localValue >= originValueSeconds) { // local date value exists and it's equal with what we see at origin, no need to update here // gt case is only used to ease testing this part // this should never be the case otherwise and we could test for equality and early return return { lastModified: lmOrigin } } const buffer = await (new Promise((resolve, reject) => { let filename const onResponse = res => { lastModified = res.headers['last-modified'] res .pipe(zlib.createGunzip()) .pipe(tarfs.extract(vendorDir, { ignore: name => path.extname(name) !== '.mmdb', map: header => { header.name = header.name.split('/')[1] return header }, mapStream: (fileStream, header) => { if (path.extname(header.name) === '.mmdb') { filename = header.name } return fileStream } })) res.on('end', () => { createReadStream(path.join(vendorDir, filename)) .pipe(concat(resolve)) .on('error', reject) }) } const req = https.request({ host: maxMindHost, path: maxMindPath }, onResponse) req.on('error', reject) req.end() })) // all this is because we want to use maxmind.open with a watcher // so it updates reader accoringly when new db arrives // problem with unpacking directly, is that zlib->tar streaming into destination is slow, // and reader will see a corrupted file, so using fs instead to copy into destination is much much faster // and allows reader watch events to find non-corrupted file on it's cycle await writeFile(dateFile, `${new Date(lastModified).getTime()}`) // save new date modified header file await writeFile(finalDest, buffer) // overwrite file at final dest where reader is watching try { await unlink(dbPath) // delete maxmind named file } catch (_) {} // just cleanup but ignore errors if file is not there return { buffer, lastModified } }