diff --git a/packages/edge/src/edge-headers.ts b/packages/edge/src/edge-headers.ts new file mode 100644 index 000000000..ffe7f1f94 --- /dev/null +++ b/packages/edge/src/edge-headers.ts @@ -0,0 +1,82 @@ +/** + * City of the original client IP calculated by Vercel Proxy. + */ +export const CITY_HEADER_NAME = 'x-vercel-ip-city'; +/** + * Country of the original client IP calculated by Vercel Proxy. + */ +export const COUNTRY_HEADER_NAME = 'x-vercel-ip-country'; +/** + * Ip from Vercel Proxy. Do not confuse it with the client Ip. + */ +export const IP_HEADER_NAME = 'x-real-ip'; +/** + * Latitude of the original client IP calculated by Vercel Proxy. + */ +export const LATITUDE_HEADER_NAME = 'x-vercel-ip-latitude'; +/** + * Longitude of the original client IP calculated by Vercel Proxy. + */ +export const LONGITUDE_HEADER_NAME = 'x-vercel-ip-longitude'; +/** + * Region of the original client IP calculated by Vercel Proxy. + */ +export const REGION_HEADER_NAME = 'x-vercel-ip-country-region'; + +/** + * We define a new type so this function can be reused with + * the global `Request`, `node-fetch` and other types. + */ +interface Request { + headers: { + get(name: string): string | null; + }; +} + +/** + * The location information of a given request + */ +export interface Geo { + /** The city that the request originated from */ + city?: string; + /** The country that the request originated from */ + country?: string; + /** The Vercel Edge Network region that received the request */ + region?: string; + /** The latitude of the client */ + latitude?: string; + /** The longitude of the client */ + longitude?: string; +} + +function getHeader(request: Request, key: string): string | undefined { + return request.headers.get(key) ?? undefined; +} + +/** + * Returns the IP address of the request from the headers. + * + * @see {@link IP_HEADER_NAME} + */ +export function ipAddress(request: Request): string | undefined { + return getHeader(request, IP_HEADER_NAME); +} + +/** + * Returns the location information from for the incoming request + * + * @see {@link CITY_HEADER_NAME} + * @see {@link COUNTRY_HEADER_NAME} + * @see {@link REGION_HEADER_NAME} + * @see {@link LATITUDE_HEADER_NAME} + * @see {@link LONGITUDE_HEADER_NAME} + */ +export function geolocation(request: Request): Geo { + return { + city: getHeader(request, CITY_HEADER_NAME), + country: getHeader(request, COUNTRY_HEADER_NAME), + region: getHeader(request, REGION_HEADER_NAME), + latitude: getHeader(request, LATITUDE_HEADER_NAME), + longitude: getHeader(request, LONGITUDE_HEADER_NAME), + }; +} diff --git a/packages/edge/src/index.ts b/packages/edge/src/index.ts index f34508182..c746582ce 100644 --- a/packages/edge/src/index.ts +++ b/packages/edge/src/index.ts @@ -1,2 +1,5 @@ export type { ExtraResponseInit } from './middleware-helpers'; -export { next, rewrite } from './middleware-helpers'; +export * from './middleware-helpers'; + +export type { Geo } from './edge-headers'; +export * from './edge-headers'; diff --git a/packages/edge/test/edge-headers.test.ts b/packages/edge/test/edge-headers.test.ts new file mode 100644 index 000000000..9af1e3002 --- /dev/null +++ b/packages/edge/test/edge-headers.test.ts @@ -0,0 +1,50 @@ +/** + * @jest-environment @edge-runtime/jest-environment + */ + +import { + CITY_HEADER_NAME, + COUNTRY_HEADER_NAME, + Geo, + geolocation, + ipAddress, + IP_HEADER_NAME, + LATITUDE_HEADER_NAME, + LONGITUDE_HEADER_NAME, + REGION_HEADER_NAME, +} from '../src'; + +test('`ipAddress` returns the value from the header', () => { + const req = new Request('https://example.vercel.sh', { + headers: { + [IP_HEADER_NAME]: '127.0.0.1', + }, + }); + expect(ipAddress(req)).toBe('127.0.0.1'); +}); + +describe('`geolocation`', () => { + test('returns an empty object if headers are not found', () => { + const req = new Request('https://example.vercel.sh'); + expect(geolocation(req)).toEqual({}); + }); + + test('reads values from headers', () => { + const req = new Request('https://example.vercel.sh', { + headers: { + [CITY_HEADER_NAME]: 'Tel Aviv', + [COUNTRY_HEADER_NAME]: 'Israel', + [LATITUDE_HEADER_NAME]: '32.109333', + [LONGITUDE_HEADER_NAME]: '34.855499', + [REGION_HEADER_NAME]: 'fra1', + }, + }); + expect(geolocation(req)).toEqual({ + city: 'Tel Aviv', + country: 'Israel', + latitude: '32.109333', + longitude: '34.855499', + region: 'fra1', + }); + }); +});