Skip to content

Commit

Permalink
MaxMind does redirects now so use fetch() to fetch the geoip database
Browse files Browse the repository at this point in the history
  • Loading branch information
statico committed Jun 16, 2024
1 parent be83623 commit a798d17
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 44 deletions.
73 changes: 39 additions & 34 deletions lib/geoip.ts
Original file line number Diff line number Diff line change
@@ -1,79 +1,82 @@
import dotenv from "dotenv";
import { existsSync, statSync, writeFileSync } from "fs";
import { get } from "https";
import { DateTime } from "luxon";
import maxmind from "maxmind";
import { basename } from "path";
import { gunzipSync } from "zlib";
import tar from "tar-stream";
import { createGunzip } from "zlib";
import { PassThrough } from "stream";

dotenv.config();

let hasLoggedError = false;

const filename = "GeoLite2-Country.mmdb";
const path = (process.env.DATA_DIR || "/tmp") + "/" + filename;
const url = process.env.MAXMIND_GEOLITE2_COUNTRY_URL;
const FILENAME = "GeoLite2-Country.mmdb";
const SRC_URL = process.env.MAXMIND_GEOLITE2_COUNTRY_URL;
const DEST_PATH = (process.env.DATA_DIR || "/tmp") + "/" + FILENAME;

const debug = (...args: any[]) => {
if (process.env.DEBUG) console.log("[DEBUG]", ...args);
};

const download = async () => {
if (!url) throw "Cannot download Maxmind DB: Missing env var";

return new Promise((resolve, reject) => {
const untar = (tarball: Buffer, filename: string) =>
new Promise<Buffer>((resolve, reject) => {
const extract = tar.extract();
const chunks: any[] = [];
const chunks: Buffer[] = [];

extract.on("entry", (header, stream, next) => {
if (basename(header.name) === filename) {
stream.on("data", (chunk) => {
stream.on("data", (chunk: Buffer) => {
chunks.push(chunk);
});
}
stream.on("end", () => {
next();
});
stream.on("end", next);
stream.resume();
});

extract.on("finish", () => {
if (chunks.length) {
const data = Buffer.concat(chunks);
writeFileSync(path, data);
console.log(`Wrote ${path} (${data.length} bytes)`);
console.log(`stat ${path}: ${JSON.stringify(statSync(path), null, 2)}`);
resolve(data);
}
});

console.log(`Downloading ${filename}...`);
debug(`GET ${url}`);
get(url, (res) => {
if (res.statusCode === 200) {
res.pipe(createGunzip()).pipe(extract);
} else {
reject(
`Failed to download Maxmind DB: ${res.statusCode} ${res.statusMessage}`,
);
}
});
extract.on("error", reject);

const stream = new PassThrough();
stream.pipe(extract);
stream.end(tarball);
});

const download = async () => {
if (!SRC_URL) throw "Cannot download Maxmind DB: Missing env var";

const res = await fetch(SRC_URL);
if (res.ok) {
const buf = await res.arrayBuffer();
const gzippedData = gunzipSync(buf);
const data = await untar(gzippedData, FILENAME);
writeFileSync(DEST_PATH, data);
console.log(`Wrote ${DEST_PATH} (${data.length} bytes)`);
debug(`stat ${DEST_PATH}: ${JSON.stringify(statSync(DEST_PATH), null, 2)}`);
} else {
throw `Failed to download Maxmind DB: ${res.status} ${res.statusText}`;
}
};

export const getCountryForIP = async (ip: string) => {
if (existsSync(path)) {
debug("Maxmind DB exists", path);
if (existsSync(DEST_PATH)) {
debug("Maxmind DB exists", DEST_PATH);
const maxAge = DateTime.now().minus({ weeks: 1 });
debug("Maxmind DB maxAge", maxAge.toISO());
const lastModified = DateTime.fromJSDate(statSync(path).mtime);
const lastModified = DateTime.fromJSDate(statSync(DEST_PATH).mtime);
debug("Maxmind DB lastModified", lastModified.toISO());
if (lastModified < maxAge) {
console.log("Maxmind DB is older than 1 week - redownloading");
await download();
}
} else {
if (!url) {
if (!SRC_URL) {
if (!hasLoggedError) {
console.error("Cannot lookup IP - Need MAXMIND_GEOLITE2_COUNTRY_URL");
hasLoggedError = true;
Expand All @@ -85,9 +88,11 @@ export const getCountryForIP = async (ip: string) => {
}

try {
const lookup = await maxmind.open(path);
const lookup = await maxmind.open(DEST_PATH);
const result = lookup.get(ip) as any;
return result?.country?.iso_code || null;
const code = result?.country?.iso_code || null;
debug(`Maxmind lookup for ${ip}: ${code}`);
return code;
} catch (err: any) {
console.error(`Error using Maxmind: ${err}`);
return null;
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"recoil-sync": "0.2.0",
"sqlite3": "5.1.7",
"swr": "2.2.5",
"tar-stream": "3.1.7",
"tar-stream": "^3.1.7",
"ua-parser-js": "1.0.38",
"uglify-js": "3.18.0",
"unfetch": "5.0.0",
Expand Down
26 changes: 17 additions & 9 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit a798d17

Please sign in to comment.