hono-geo-middleware
Version:
A geo middleware for Hono
241 lines (232 loc) • 7.59 kB
JavaScript
// src/index.ts
import { createMiddleware } from "hono/factory";
// src/util.ts
function createInsensitiveHeaderProxy(header) {
return new Proxy(header, {
get(target, prop) {
for (const key in target) {
if (key.toLowerCase() === prop.toLowerCase()) {
return target[key];
}
}
return void 0;
}
});
}
function tryDecodeURIText(text) {
if (text) {
return decodeURIComponent(text);
}
return void 0;
}
var EMOJI_FLAG_UNICODE_STARTING_POSITION = 127397;
function getFlagFromCountryCode(countryCode) {
const regex = new RegExp("^[A-Z]{2}$").test(countryCode);
if (!countryCode || !regex) return void 0;
return String.fromCodePoint(
...countryCode.split("").map((char) => EMOJI_FLAG_UNICODE_STARTING_POSITION + char.charCodeAt(0))
);
}
// src/extractor/vercel.ts
var CITY_HEADER_NAME = "x-vercel-ip-city";
var COUNTRY_HEADER_NAME = "x-vercel-ip-country";
var IP_HEADER_NAME = "x-real-ip";
var LATITUDE_HEADER_NAME = "x-vercel-ip-latitude";
var LONGITUDE_HEADER_NAME = "x-vercel-ip-longitude";
var REGION_HEADER_NAME = "x-vercel-ip-country-region";
var REQUEST_ID_HEADER_NAME = "x-vercel-id";
var vercel = (headers) => {
if (!headers[REQUEST_ID_HEADER_NAME]) {
return null;
}
return {
reqId: headers[REQUEST_ID_HEADER_NAME],
ip: headers[IP_HEADER_NAME],
city: tryDecodeURIText(headers[CITY_HEADER_NAME]),
// country: ?,
countryCode: headers[COUNTRY_HEADER_NAME],
// region: ?,
regionCode: headers[REGION_HEADER_NAME],
latitude: headers[LATITUDE_HEADER_NAME],
longitude: headers[LONGITUDE_HEADER_NAME],
// continent: ?
// postalCode: ?
// metroCode: ?
// timezone: ?
// asn: ?
flag: getFlagFromCountryCode(headers[COUNTRY_HEADER_NAME]),
// Vercel Edge Network region name
idcRegion: getRegionFromRequestId(headers[REQUEST_ID_HEADER_NAME])
};
};
function getRegionFromRequestId(requestId) {
if (!requestId) {
return "dev1";
}
return requestId.split(":")[0];
}
// src/extractor/cloudflare-worker.ts
import { getRuntimeKey } from "hono/adapter";
var REQUEST_ID_HEADER_NAME2 = "CF-ray";
var IP_HEADER_NAME2 = "CF-Connecting-IP";
var cloudflareWorker = (headers, c) => {
if (getRuntimeKey() !== "workerd" && !headers[REQUEST_ID_HEADER_NAME2]) {
return null;
}
const req = c.req.raw;
return {
reqId: headers[REQUEST_ID_HEADER_NAME2],
ip: headers[IP_HEADER_NAME2],
city: tryDecodeURIText(req.cf?.city),
/* ISO 3166-2 code */
countryCode: req.cf?.country,
continent: req.cf?.continent,
region: req.cf?.region,
regionCode: req.cf?.regionCode,
latitude: req.cf?.latitude,
longitude: req.cf?.longitude,
postalCode: req.cf?.postalCode,
metroCode: req.cf?.metroCode,
timezone: req.cf?.timezone,
asn: req.cf?.asn?.toString(),
/** flag emoji */
flag: getFlagFromCountryCode(req.cf?.country)
};
};
// src/extractor/cloudflare.ts
var REQUEST_ID_HEADER_NAME3 = "CF-ray";
var IP_HEADER_NAME3 = "CF-Connecting-IP";
var COUNTRY_HEADER_NAME2 = "CF-IPCountry";
var CITY_HEADER_NAME2 = "CF-ipcity";
var CONTINENT_HEADER_NAME = "cf-ipcontinent";
var LATITUDE_HEADER_NAME2 = "cf-iplatitude";
var LONGITUDE_HEADER_NAME2 = "cf-iplongitude";
var REGION_HEADER_NAME2 = "cf-region";
var REGION_CODE_HEADER_NAME = "cf-region-code";
var TIMEZONE_HEADER_NAME = "cf-timezone";
var METRO_HEADER_NAME = "cf-metro-code";
var POSTAL_HEADER_NAME = "cf-postal-code";
var cloudflare = (headers) => {
if (!headers[REQUEST_ID_HEADER_NAME3]) {
return null;
}
return {
reqId: headers[REQUEST_ID_HEADER_NAME3],
ip: headers[IP_HEADER_NAME3],
city: tryDecodeURIText(headers[CITY_HEADER_NAME2]),
countryCode: headers[COUNTRY_HEADER_NAME2],
region: headers[REGION_HEADER_NAME2],
regionCode: headers[REGION_CODE_HEADER_NAME],
latitude: headers[LATITUDE_HEADER_NAME2],
longitude: headers[LONGITUDE_HEADER_NAME2],
continent: headers[CONTINENT_HEADER_NAME],
metroCode: headers[METRO_HEADER_NAME],
postalCode: headers[POSTAL_HEADER_NAME],
timezone: headers[TIMEZONE_HEADER_NAME],
flag: getFlagFromCountryCode(headers[COUNTRY_HEADER_NAME2])
};
};
// src/extractor/netlify.ts
var REQUEST_ID_HEADER_NAME4 = "X-Nf-request-id";
var IP_HEADER_NAME4 = "X-Nf-Client-Connection-Ip";
var netlify = (headers, c) => {
if (!headers[REQUEST_ID_HEADER_NAME4]) {
return null;
}
const ctx = c.env.context;
const geo = c.env.context?.geo;
return {
reqId: headers[REQUEST_ID_HEADER_NAME4],
ip: ctx?.ip ?? headers[IP_HEADER_NAME4],
city: tryDecodeURIText(geo?.city),
country: geo?.country?.name,
countryCode: geo?.country?.code,
region: geo?.subdivision?.name,
regionCode: geo?.subdivision?.code,
latitude: geo?.latitude?.toString(),
longitude: geo?.longitude?.toString(),
// metroCode: ,
postalCode: geo?.postalCode,
timezone: geo?.timezone,
flag: getFlagFromCountryCode(geo?.country?.code),
idcRegion: ctx?.server.region
};
};
// src/extractor/cloudfront.ts
var CITY_HEADER_NAME3 = "CloudFront-Viewer-City";
var COUNTRY_CODE_HEADER_NAME = "CloudFront-Viewer-Country";
var COUNTRY_NAME_HEADER_NAME = "CloudFront-Viewer-Country-Name";
var REGION_CODE_HEADER_NAME2 = "CloudFront-Viewer-Country-Region";
var REGION_NAME_HEADER_NAME = "CloudFront-Viewer-Country-Region-Name";
var LATITUDE_HEADER_NAME3 = "CloudFront-Viewer-Latitude";
var LONGITUDE_HEADER_NAME3 = "CloudFront-Viewer-Longitude";
var TIMEZONE_HEADER_NAME2 = "CloudFront-Viewer-Time-Zone";
var METRO_CODE_HEADER_NAME = "CloudFront-Viewer-Metro-Code";
var POSTAL_CODE_HEADER_NAME = "CloudFront-Viewer-Postal-Code";
var ASN_HEADER_NAME = "CloudFront-Viewer-ASN";
var cloudfront = (headers, c) => {
const reqId = c.env?.context?.awsRequestId;
if (!reqId) {
return null;
}
return {
reqId,
ip: c.env?.request?.clientIp,
city: tryDecodeURIText(headers[CITY_HEADER_NAME3]),
country: headers[COUNTRY_NAME_HEADER_NAME],
countryCode: headers[COUNTRY_CODE_HEADER_NAME],
region: headers[REGION_NAME_HEADER_NAME],
regionCode: headers[REGION_CODE_HEADER_NAME2],
latitude: headers[LATITUDE_HEADER_NAME3],
longitude: headers[LONGITUDE_HEADER_NAME3],
// continent?: string;
postalCode: headers[POSTAL_CODE_HEADER_NAME],
metroCode: headers[METRO_CODE_HEADER_NAME],
timezone: headers[TIMEZONE_HEADER_NAME2],
asn: headers[ASN_HEADER_NAME],
// idcRegion?
flag: getFlagFromCountryCode(headers[COUNTRY_CODE_HEADER_NAME])
};
};
// src/extractor/index.ts
var extractorFuncs = [
vercel,
cloudflareWorker,
cloudflare,
netlify,
cloudfront
];
var extractorFuncsMap = {
"vercel": vercel,
"cloudflare": cloudflare,
"cloudflare-worker": cloudflareWorker,
"netlify": netlify,
"cloudfront": cloudfront
};
// src/index.ts
var GeoMiddleware = (options) => {
const extractors = options?.extractors ?? extractorFuncs;
const availableExtractorFuncs = extractors.map((it) => typeof it === "string" ? extractorFuncsMap[it] : it);
return createMiddleware(async (c, next) => {
const geo = extractGeo(c, availableExtractorFuncs, options?.extractorOption);
c.set("geo", geo);
await next();
});
};
function extractGeo(c, extractorFuncs2, options) {
const headers = c.req.header();
let ans = null;
const h = createInsensitiveHeaderProxy(headers);
for (const func of extractorFuncs2) {
ans = func(h, c, options);
if (ans) break;
}
return ans || {};
}
var getGeo = (c) => {
return c.get("geo");
};
export {
GeoMiddleware,
getGeo
};