Files
MyIP/api/ipapi-is.js
T
jason5ng32andClaude Opus 4.7 2792185a53 refactor(common): unify fetch-with-timeout + fetch-upstream into one helper
The two wrappers (frontend/utils/fetch-with-timeout.js and
common/fetch-upstream.js) were ~95% identical — same AbortController +
setTimeout pattern, same caller-signal chaining, differing only in
default timeout (5s vs 8s) and which half of the app imported them.
Their tests were word-for-word duplicates too.

Consolidate to common/fetch-with-timeout.js with two named exports:

- fetchWithTimeout(url, init) — 5s default (front-end use)
- fetchUpstream(url, init)    — 8s preset around fetchWithTimeout (back-end)

- frontend/utils/fetch-with-timeout.js becomes a thin re-export of the
  common module (same pattern we use for valid-ip.js). Front-end imports
  don't change.
- All 12 back-end handler imports move from './common/fetch-upstream.js'
  to './common/fetch-with-timeout.js'.
- tests/fetch-upstream.test.js is deleted; tests/fetch-with-timeout.test.js
  now covers both exports (10 cases total).
- common/fetch-upstream.js deleted.

Net: -1 file, -130 lines of duplicate code & tests; single source of truth
for the timeout logic.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 20:05:20 +08:00

39 lines
1.3 KiB
JavaScript

import { fetchUpstream } from '../common/fetch-with-timeout.js';
export default async (req, res) => {
// IP presence + validity guaranteed by requireValidIP middleware.
const ipAddress = req.query.ip;
const keys = (process.env.IPAPIIS_API_KEY).split(',');
const key = keys[Math.floor(Math.random() * keys.length)];
const url = `https://api.ipapi.is?q=${ipAddress}&key=${key}`;
try {
const apiRes = await fetchUpstream(url);
const json = await apiRes.json();
res.json(modifyJsonForIPAPI(json));
} catch (e) {
res.status(500).json({ error: e.message });
}
};
function modifyJsonForIPAPI(json) {
let asn = json.asn || {};
const { ip, location, is_datacenter, is_proxy, is_vpn, is_tor } = json;
return {
ip: ip,
city: location.city || 'N/A',
region: location.state || 'N/A',
country: location.country_code || 'N/A',
country_name: location.country || 'N/A',
country_code: location.country_code || 'N/A',
latitude: location.latitude || 'N/A',
longitude: location.longitude || 'N/A',
asn: asn.asn === undefined ? 'N/A' : 'AS' + asn.asn,
org: asn.org || 'N/A',
isHosting: is_datacenter || false,
isProxy: is_proxy || is_vpn || is_tor || false
};
}