@netloc8/nextjs 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,3 +1,3 @@
1
1
 
2
- import { GeoContext, GeoGate, NetLoc8Provider, useGeo } from "@netloc8/react";
3
- export { GeoContext, GeoGate, NetLoc8Provider, useGeo };
2
+ import { GeoContext, GeoGate, GeoGateProps, NetLoc8Provider, useGeo } from "@netloc8/react";
3
+ export { GeoContext, GeoGate, type GeoGateProps, NetLoc8Provider, useGeo };
package/dist/proxy.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { NextRequest, NextResponse } from "next/server";
2
- import { Geo } from "@netloc8/netloc8-js";
2
+ import { Geo } from "@netloc8/core";
3
3
 
4
4
  //#region src/proxy.d.ts
5
5
  interface CreateProxyOptions {
package/dist/proxy.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { NextResponse } from "next/server";
2
- import { COOKIE_NAME, COOKIE_OPTIONS, fetchGeo, getClientIp, getGeoFromPlatformHeaders, isPublicIp, normalizeApiResponse, parseCookie, reconcileGeo, serializeCookie } from "@netloc8/netloc8-js";
2
+ import { COOKIE_NAME, COOKIE_OPTIONS, fetchGeo, getClientIp, getGeoFromPlatformHeaders, isPublicIp, normalizeApiResponse, parseCookie, reconcileGeo, serializeCookie } from "@netloc8/core";
3
3
  //#region src/proxy.ts
4
4
  /**
5
5
  * Header-to-Geo field mapping for setting request headers.
@@ -64,7 +64,7 @@ function createProxy(options) {
64
64
  apiKey,
65
65
  apiUrl,
66
66
  timeout,
67
- clientId: `@netloc8/nextjs/0.1.2`
67
+ clientId: `@netloc8/nextjs/0.2.0`
68
68
  });
69
69
  if (raw) apiGeo = normalizeApiResponse(raw, clientIp);
70
70
  }
@@ -1 +1 @@
1
- {"version":3,"file":"proxy.mjs","names":[],"sources":["../src/proxy.ts"],"sourcesContent":["declare const __PKG_NAME__: string;\ndeclare const __PKG_VERSION__: string;\n\nimport type { Geo } from '@netloc8/netloc8-js';\nimport type { NextRequest } from 'next/server';\nimport { NextResponse } from 'next/server';\nimport {\n getClientIp,\n isPublicIp,\n getGeoFromPlatformHeaders,\n fetchGeo,\n normalizeApiResponse,\n parseCookie,\n serializeCookie,\n reconcileGeo,\n COOKIE_NAME,\n COOKIE_OPTIONS,\n} from '@netloc8/netloc8-js';\n\ninterface CreateProxyOptions {\n timeout?: number;\n apiKey?: string;\n apiUrl?: string;\n testIp?: string;\n handler?: (\n request: NextRequest,\n geo: Geo\n ) => NextResponse | undefined | Promise<NextResponse | undefined>;\n}\n\ninterface GeoRedirectOptions {\n defaultLocale: string;\n localeMap: Record<string, string>;\n excludePaths?: string[];\n}\n\n/**\n * Header-to-Geo field mapping for setting request headers.\n */\nconst GEO_HEADER_MAP: Array<[keyof Geo, string]> = [\n ['ip', 'x-netloc8-ip'],\n ['ipVersion', 'x-netloc8-ip-version'],\n ['continent', 'x-netloc8-continent'],\n ['continentName', 'x-netloc8-continent-name'],\n ['country', 'x-netloc8-country'],\n ['countryName', 'x-netloc8-country-name'],\n ['isEU', 'x-netloc8-is-eu'],\n ['region', 'x-netloc8-region'],\n ['regionName', 'x-netloc8-region-name'],\n ['city', 'x-netloc8-city'],\n ['postalCode', 'x-netloc8-postal-code'],\n ['latitude', 'x-netloc8-latitude'],\n ['longitude', 'x-netloc8-longitude'],\n ['timezone', 'x-netloc8-timezone'],\n ['accuracyRadius', 'x-netloc8-accuracy-radius'],\n ['precision', 'x-netloc8-precision'],\n ['isLimited', 'x-netloc8-is-limited'],\n ['limitReason', 'x-netloc8-limit-reason'],\n ['timezoneFromClient', 'x-netloc8-timezone-from-client'],\n];\n\n/**\n * Set x-netloc8-* request headers from a Geo object.\n */\nfunction setGeoHeaders(requestHeaders: Headers, geo: Geo): void {\n for (const [field, header] of GEO_HEADER_MAP) {\n const value = geo[field];\n if (value !== undefined && value !== null) {\n requestHeaders.set(header, encodeURIComponent(String(value)));\n }\n }\n}\n\n/**\n * Create a Next.js 16 proxy function that resolves geolocation for every\n * matching request.\n *\n * Returns a standard proxy function that can be exported directly from the\n * user's proxy.ts / proxy.js file, or composed with other proxy logic.\n */\nexport function createProxy(options?: CreateProxyOptions):\n (request: NextRequest) => Promise<NextResponse> {\n\n return async (request: NextRequest): Promise<NextResponse> => {\n const apiKey = options?.apiKey ?? process.env.NETLOC8_API_KEY;\n const apiUrl = options?.apiUrl ?? process.env.NETLOC8_API_URL;\n const timeout = options?.timeout ?? 1500;\n\n // Security: Remove any incoming spoofed headers\n const requestHeaders = new Headers(request.headers);\n for (const [, headerName] of GEO_HEADER_MAP) {\n requestHeaders.delete(headerName);\n }\n\n // 1. Determine client IP\n let clientIp: string | undefined;\n\n if (process.env.NODE_ENV !== 'production') {\n clientIp = options?.testIp ?? process.env.NETLOC8_TEST_IP;\n }\n\n if (!clientIp) {\n clientIp = getClientIp(request.headers);\n }\n\n // 2. Check the cookie cache (fast path)\n const cookieValue = request.cookies.get(COOKIE_NAME)?.value;\n const cookieGeo = parseCookie(cookieValue);\n\n // Cookie fast path: only trust timezone/timezoneFromClient from the\n // client-controlled cookie. Re-resolve other geo fields to prevent\n // spoofing of country/region/city via cookie manipulation.\n const cookieTimezone = (\n cookieGeo.timezoneFromClient === true &&\n cookieGeo.ip === clientIp\n ) ? { timezone: cookieGeo.timezone, timezoneFromClient: cookieGeo.timezoneFromClient } : undefined;\n\n // 3. Extract platform headers (zero-cost)\n const platformGeo = getGeoFromPlatformHeaders(request.headers);\n\n // 4. Decide whether to call the API\n let apiGeo: Geo | undefined;\n\n if (clientIp && isPublicIp(clientIp) && !platformGeo.timezone && !cookieTimezone) {\n const raw = await fetchGeo(clientIp, { apiKey, apiUrl, timeout, clientId: typeof __PKG_NAME__ !== 'undefined' ? `${__PKG_NAME__}/${__PKG_VERSION__}` : undefined });\n if (raw) {\n apiGeo = normalizeApiResponse(raw, clientIp);\n }\n }\n\n // 5. Reconcile all sources — cookie is lowest priority in\n // reconcileGeo, so platform headers and API data overwrite it.\n // Pass the full cookie so self-hosted deployments (no platform\n // headers, API call skipped) still have city/country/region.\n const geo = reconcileGeo({\n cookie: cookieGeo.ip ? cookieGeo : undefined,\n platform: platformGeo,\n api: apiGeo,\n ip: clientIp,\n });\n\n // Apply trusted cookie timezone if available\n if (cookieTimezone) {\n geo.timezone = cookieTimezone.timezone;\n geo.timezoneFromClient = cookieTimezone.timezoneFromClient;\n }\n\n // 6. Set request headers\n setGeoHeaders(requestHeaders, geo);\n\n // 7. Build the response — use sanitized headers in the handler\n let handlerResponse: NextResponse | undefined;\n if (options?.handler) {\n const sanitizedRequest = new Request(request.nextUrl.toString(), {\n method: request.method ?? 'GET',\n headers: requestHeaders,\n body: request.body,\n // @ts-expect-error -- NextRequest supports duplex but TS doesn't expose it\n duplex: 'half',\n });\n handlerResponse = await options.handler(\n Object.assign(sanitizedRequest, { nextUrl: request.nextUrl, cookies: request.cookies }) as NextRequest,\n geo\n );\n }\n\n const response = handlerResponse ?? NextResponse.next({\n request: { headers: requestHeaders },\n });\n\n // 8. Set/update the cookie if needed\n if (!cookieValue || cookieGeo.ip !== clientIp) {\n response.cookies.set(COOKIE_NAME, serializeCookie(geo), {\n path: COOKIE_OPTIONS.path,\n httpOnly: COOKIE_OPTIONS.httpOnly,\n secure: COOKIE_OPTIONS.secure,\n sameSite: COOKIE_OPTIONS.sameSite,\n maxAge: COOKIE_OPTIONS.maxAge,\n });\n }\n\n return response;\n };\n}\n\n/**\n * Create a geo-redirect handler for use with createProxy.\n */\nexport function withGeoRedirect(\n options: GeoRedirectOptions\n): (request: NextRequest, geo: Geo) => NextResponse | undefined {\n const { defaultLocale, localeMap, excludePaths = [] } = options;\n const validLocales = new Set(Object.values(localeMap));\n validLocales.add(defaultLocale);\n\n return (request: NextRequest, geo: Geo): NextResponse | undefined => {\n const pathname = request.nextUrl.pathname;\n\n // Skip excluded paths\n for (const prefix of excludePaths) {\n if (pathname.startsWith(prefix)) {\n return undefined;\n }\n }\n\n // Extract current locale prefix from path\n const segments = pathname.split('/').filter(Boolean);\n const currentPrefix = segments[0];\n\n // If path already has a valid locale prefix, don't redirect\n if (currentPrefix && validLocales.has(currentPrefix)) {\n return undefined;\n }\n\n // Look up locale for the user's country\n const locale = (geo.country && localeMap[geo.country]) || defaultLocale;\n\n // If resolved locale is the default and path has no locale prefix, no redirect needed\n if (locale === defaultLocale) {\n return undefined;\n }\n\n // Redirect to locale-prefixed path\n const url = request.nextUrl.clone();\n url.pathname = `/${locale}${pathname}`;\n return NextResponse.redirect(url, 307);\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"proxy.mjs","names":[],"sources":["../src/proxy.ts"],"sourcesContent":["declare const __PKG_NAME__: string;\ndeclare const __PKG_VERSION__: string;\n\nimport type { Geo } from '@netloc8/core';\nimport type { NextRequest } from 'next/server';\nimport { NextResponse } from 'next/server';\nimport {\n getClientIp,\n isPublicIp,\n getGeoFromPlatformHeaders,\n fetchGeo,\n normalizeApiResponse,\n parseCookie,\n serializeCookie,\n reconcileGeo,\n COOKIE_NAME,\n COOKIE_OPTIONS,\n} from '@netloc8/core';\n\ninterface CreateProxyOptions {\n timeout?: number;\n apiKey?: string;\n apiUrl?: string;\n testIp?: string;\n handler?: (\n request: NextRequest,\n geo: Geo\n ) => NextResponse | undefined | Promise<NextResponse | undefined>;\n}\n\ninterface GeoRedirectOptions {\n defaultLocale: string;\n localeMap: Record<string, string>;\n excludePaths?: string[];\n}\n\n/**\n * Header-to-Geo field mapping for setting request headers.\n */\nconst GEO_HEADER_MAP: Array<[keyof Geo, string]> = [\n ['ip', 'x-netloc8-ip'],\n ['ipVersion', 'x-netloc8-ip-version'],\n ['continent', 'x-netloc8-continent'],\n ['continentName', 'x-netloc8-continent-name'],\n ['country', 'x-netloc8-country'],\n ['countryName', 'x-netloc8-country-name'],\n ['isEU', 'x-netloc8-is-eu'],\n ['region', 'x-netloc8-region'],\n ['regionName', 'x-netloc8-region-name'],\n ['city', 'x-netloc8-city'],\n ['postalCode', 'x-netloc8-postal-code'],\n ['latitude', 'x-netloc8-latitude'],\n ['longitude', 'x-netloc8-longitude'],\n ['timezone', 'x-netloc8-timezone'],\n ['accuracyRadius', 'x-netloc8-accuracy-radius'],\n ['precision', 'x-netloc8-precision'],\n ['isLimited', 'x-netloc8-is-limited'],\n ['limitReason', 'x-netloc8-limit-reason'],\n ['timezoneFromClient', 'x-netloc8-timezone-from-client'],\n];\n\n/**\n * Set x-netloc8-* request headers from a Geo object.\n */\nfunction setGeoHeaders(requestHeaders: Headers, geo: Geo): void {\n for (const [field, header] of GEO_HEADER_MAP) {\n const value = geo[field];\n if (value !== undefined && value !== null) {\n requestHeaders.set(header, encodeURIComponent(String(value)));\n }\n }\n}\n\n/**\n * Create a Next.js 16 proxy function that resolves geolocation for every\n * matching request.\n *\n * Returns a standard proxy function that can be exported directly from the\n * user's proxy.ts / proxy.js file, or composed with other proxy logic.\n */\nexport function createProxy(options?: CreateProxyOptions):\n (request: NextRequest) => Promise<NextResponse> {\n\n return async (request: NextRequest): Promise<NextResponse> => {\n const apiKey = options?.apiKey ?? process.env.NETLOC8_API_KEY;\n const apiUrl = options?.apiUrl ?? process.env.NETLOC8_API_URL;\n const timeout = options?.timeout ?? 1500;\n\n // Security: Remove any incoming spoofed headers\n const requestHeaders = new Headers(request.headers);\n for (const [, headerName] of GEO_HEADER_MAP) {\n requestHeaders.delete(headerName);\n }\n\n // 1. Determine client IP\n let clientIp: string | undefined;\n\n if (process.env.NODE_ENV !== 'production') {\n clientIp = options?.testIp ?? process.env.NETLOC8_TEST_IP;\n }\n\n if (!clientIp) {\n clientIp = getClientIp(request.headers);\n }\n\n // 2. Check the cookie cache (fast path)\n const cookieValue = request.cookies.get(COOKIE_NAME)?.value;\n const cookieGeo = parseCookie(cookieValue);\n\n // Cookie fast path: only trust timezone/timezoneFromClient from the\n // client-controlled cookie. Re-resolve other geo fields to prevent\n // spoofing of country/region/city via cookie manipulation.\n const cookieTimezone = (\n cookieGeo.timezoneFromClient === true &&\n cookieGeo.ip === clientIp\n ) ? { timezone: cookieGeo.timezone, timezoneFromClient: cookieGeo.timezoneFromClient } : undefined;\n\n // 3. Extract platform headers (zero-cost)\n const platformGeo = getGeoFromPlatformHeaders(request.headers);\n\n // 4. Decide whether to call the API\n let apiGeo: Geo | undefined;\n\n if (clientIp && isPublicIp(clientIp) && !platformGeo.timezone && !cookieTimezone) {\n const raw = await fetchGeo(clientIp, { apiKey, apiUrl, timeout, clientId: typeof __PKG_NAME__ !== 'undefined' ? `${__PKG_NAME__}/${__PKG_VERSION__}` : undefined });\n if (raw) {\n apiGeo = normalizeApiResponse(raw, clientIp);\n }\n }\n\n // 5. Reconcile all sources — cookie is lowest priority in\n // reconcileGeo, so platform headers and API data overwrite it.\n // Pass the full cookie so self-hosted deployments (no platform\n // headers, API call skipped) still have city/country/region.\n const geo = reconcileGeo({\n cookie: cookieGeo.ip ? cookieGeo : undefined,\n platform: platformGeo,\n api: apiGeo,\n ip: clientIp,\n });\n\n // Apply trusted cookie timezone if available\n if (cookieTimezone) {\n geo.timezone = cookieTimezone.timezone;\n geo.timezoneFromClient = cookieTimezone.timezoneFromClient;\n }\n\n // 6. Set request headers\n setGeoHeaders(requestHeaders, geo);\n\n // 7. Build the response — use sanitized headers in the handler\n let handlerResponse: NextResponse | undefined;\n if (options?.handler) {\n const sanitizedRequest = new Request(request.nextUrl.toString(), {\n method: request.method ?? 'GET',\n headers: requestHeaders,\n body: request.body,\n // @ts-expect-error -- NextRequest supports duplex but TS doesn't expose it\n duplex: 'half',\n });\n handlerResponse = await options.handler(\n Object.assign(sanitizedRequest, { nextUrl: request.nextUrl, cookies: request.cookies }) as NextRequest,\n geo\n );\n }\n\n const response = handlerResponse ?? NextResponse.next({\n request: { headers: requestHeaders },\n });\n\n // 8. Set/update the cookie if needed\n if (!cookieValue || cookieGeo.ip !== clientIp) {\n response.cookies.set(COOKIE_NAME, serializeCookie(geo), {\n path: COOKIE_OPTIONS.path,\n httpOnly: COOKIE_OPTIONS.httpOnly,\n secure: COOKIE_OPTIONS.secure,\n sameSite: COOKIE_OPTIONS.sameSite,\n maxAge: COOKIE_OPTIONS.maxAge,\n });\n }\n\n return response;\n };\n}\n\n/**\n * Create a geo-redirect handler for use with createProxy.\n */\nexport function withGeoRedirect(\n options: GeoRedirectOptions\n): (request: NextRequest, geo: Geo) => NextResponse | undefined {\n const { defaultLocale, localeMap, excludePaths = [] } = options;\n const validLocales = new Set(Object.values(localeMap));\n validLocales.add(defaultLocale);\n\n return (request: NextRequest, geo: Geo): NextResponse | undefined => {\n const pathname = request.nextUrl.pathname;\n\n // Skip excluded paths\n for (const prefix of excludePaths) {\n if (pathname.startsWith(prefix)) {\n return undefined;\n }\n }\n\n // Extract current locale prefix from path\n const segments = pathname.split('/').filter(Boolean);\n const currentPrefix = segments[0];\n\n // If path already has a valid locale prefix, don't redirect\n if (currentPrefix && validLocales.has(currentPrefix)) {\n return undefined;\n }\n\n // Look up locale for the user's country\n const locale = (geo.country && localeMap[geo.country]) || defaultLocale;\n\n // If resolved locale is the default and path has no locale prefix, no redirect needed\n if (locale === defaultLocale) {\n return undefined;\n }\n\n // Redirect to locale-prefixed path\n const url = request.nextUrl.clone();\n url.pathname = `/${locale}${pathname}`;\n return NextResponse.redirect(url, 307);\n };\n}\n"],"mappings":";;;;;;AAuCA,MAAM,iBAA6C;CAC/C,CAAC,MAAM,eAAe;CACtB,CAAC,aAAa,uBAAuB;CACrC,CAAC,aAAa,sBAAsB;CACpC,CAAC,iBAAiB,2BAA2B;CAC7C,CAAC,WAAW,oBAAoB;CAChC,CAAC,eAAe,yBAAyB;CACzC,CAAC,QAAQ,kBAAkB;CAC3B,CAAC,UAAU,mBAAmB;CAC9B,CAAC,cAAc,wBAAwB;CACvC,CAAC,QAAQ,iBAAiB;CAC1B,CAAC,cAAc,wBAAwB;CACvC,CAAC,YAAY,qBAAqB;CAClC,CAAC,aAAa,sBAAsB;CACpC,CAAC,YAAY,qBAAqB;CAClC,CAAC,kBAAkB,4BAA4B;CAC/C,CAAC,aAAa,sBAAsB;CACpC,CAAC,aAAa,uBAAuB;CACrC,CAAC,eAAe,yBAAyB;CACzC,CAAC,sBAAsB,iCAAiC;CAC3D;;;;AAKD,SAAS,cAAc,gBAAyB,KAAgB;AAC5D,MAAK,MAAM,CAAC,OAAO,WAAW,gBAAgB;EAC1C,MAAM,QAAQ,IAAI;AAClB,MAAI,UAAU,KAAA,KAAa,UAAU,KACjC,gBAAe,IAAI,QAAQ,mBAAmB,OAAO,MAAM,CAAC,CAAC;;;;;;;;;;AAYzE,SAAgB,YAAY,SACwB;AAEhD,QAAO,OAAO,YAAgD;EAC1D,MAAM,SAAS,SAAS,UAAU,QAAQ,IAAI;EAC9C,MAAM,SAAS,SAAS,UAAU,QAAQ,IAAI;EAC9C,MAAM,UAAU,SAAS,WAAW;EAGpC,MAAM,iBAAiB,IAAI,QAAQ,QAAQ,QAAQ;AACnD,OAAK,MAAM,GAAG,eAAe,eACzB,gBAAe,OAAO,WAAW;EAIrC,IAAI;AAEJ,MAAI,QAAQ,IAAI,aAAa,aACzB,YAAW,SAAS,UAAU,QAAQ,IAAI;AAG9C,MAAI,CAAC,SACD,YAAW,YAAY,QAAQ,QAAQ;EAI3C,MAAM,cAAc,QAAQ,QAAQ,IAAI,YAAY,EAAE;EACtD,MAAM,YAAY,YAAY,YAAY;EAK1C,MAAM,iBACF,UAAU,uBAAuB,QACjC,UAAU,OAAO,WACjB;GAAE,UAAU,UAAU;GAAU,oBAAoB,UAAU;GAAoB,GAAG,KAAA;EAGzF,MAAM,cAAc,0BAA0B,QAAQ,QAAQ;EAG9D,IAAI;AAEJ,MAAI,YAAY,WAAW,SAAS,IAAI,CAAC,YAAY,YAAY,CAAC,gBAAgB;GAC9E,MAAM,MAAM,MAAM,SAAS,UAAU;IAAE;IAAQ;IAAQ;IAAS,UAAgD;IAAkD,CAAC;AACnK,OAAI,IACA,UAAS,qBAAqB,KAAK,SAAS;;EAQpD,MAAM,MAAM,aAAa;GACrB,QAAQ,UAAU,KAAK,YAAY,KAAA;GACnC,UAAU;GACV,KAAK;GACL,IAAI;GACP,CAAC;AAGF,MAAI,gBAAgB;AAChB,OAAI,WAAW,eAAe;AAC9B,OAAI,qBAAqB,eAAe;;AAI5C,gBAAc,gBAAgB,IAAI;EAGlC,IAAI;AACJ,MAAI,SAAS,SAAS;GAClB,MAAM,mBAAmB,IAAI,QAAQ,QAAQ,QAAQ,UAAU,EAAE;IAC7D,QAAQ,QAAQ,UAAU;IAC1B,SAAS;IACT,MAAM,QAAQ;IAEd,QAAQ;IACX,CAAC;AACF,qBAAkB,MAAM,QAAQ,QAC5B,OAAO,OAAO,kBAAkB;IAAE,SAAS,QAAQ;IAAS,SAAS,QAAQ;IAAS,CAAC,EACvF,IACH;;EAGL,MAAM,WAAW,mBAAmB,aAAa,KAAK,EAClD,SAAS,EAAE,SAAS,gBAAgB,EACvC,CAAC;AAGF,MAAI,CAAC,eAAe,UAAU,OAAO,SACjC,UAAS,QAAQ,IAAI,aAAa,gBAAgB,IAAI,EAAE;GACpD,MAAM,eAAe;GACrB,UAAU,eAAe;GACzB,QAAQ,eAAe;GACvB,UAAU,eAAe;GACzB,QAAQ,eAAe;GAC1B,CAAC;AAGN,SAAO;;;;;;AAOf,SAAgB,gBACZ,SAC4D;CAC5D,MAAM,EAAE,eAAe,WAAW,eAAe,EAAE,KAAK;CACxD,MAAM,eAAe,IAAI,IAAI,OAAO,OAAO,UAAU,CAAC;AACtD,cAAa,IAAI,cAAc;AAE/B,SAAQ,SAAsB,QAAuC;EACjE,MAAM,WAAW,QAAQ,QAAQ;AAGjC,OAAK,MAAM,UAAU,aACjB,KAAI,SAAS,WAAW,OAAO,CAC3B;EAMR,MAAM,gBADW,SAAS,MAAM,IAAI,CAAC,OAAO,QAAQ,CACrB;AAG/B,MAAI,iBAAiB,aAAa,IAAI,cAAc,CAChD;EAIJ,MAAM,SAAU,IAAI,WAAW,UAAU,IAAI,YAAa;AAG1D,MAAI,WAAW,cACX;EAIJ,MAAM,MAAM,QAAQ,QAAQ,OAAO;AACnC,MAAI,WAAW,IAAI,SAAS;AAC5B,SAAO,aAAa,SAAS,KAAK,IAAI"}
package/dist/server.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { Geo } from "@netloc8/netloc8-js";
1
+ import { Geo } from "@netloc8/core";
2
2
 
3
3
  //#region src/server.d.ts
4
4
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"server.mjs","names":[],"sources":["../src/server.ts"],"sourcesContent":["import type { Geo } from '@netloc8/netloc8-js';\n\n/**\n * Header-to-Geo field mapping.\n */\nconst HEADER_MAP: Array<[string, keyof Geo, 'string' | 'number' | 'boolean']> = [\n ['x-netloc8-ip', 'ip', 'string'],\n ['x-netloc8-ip-version', 'ipVersion', 'number'],\n ['x-netloc8-continent', 'continent', 'string'],\n ['x-netloc8-continent-name', 'continentName', 'string'],\n ['x-netloc8-country', 'country', 'string'],\n ['x-netloc8-country-name', 'countryName', 'string'],\n ['x-netloc8-is-eu', 'isEU', 'boolean'],\n ['x-netloc8-region', 'region', 'string'],\n ['x-netloc8-region-name', 'regionName', 'string'],\n ['x-netloc8-city', 'city', 'string'],\n ['x-netloc8-postal-code', 'postalCode', 'string'],\n ['x-netloc8-latitude', 'latitude', 'number'],\n ['x-netloc8-longitude', 'longitude', 'number'],\n ['x-netloc8-timezone', 'timezone', 'string'],\n ['x-netloc8-accuracy-radius', 'accuracyRadius', 'number'],\n ['x-netloc8-precision', 'precision', 'string'],\n ['x-netloc8-is-limited', 'isLimited', 'boolean'],\n ['x-netloc8-limit-reason', 'limitReason', 'string'],\n ['x-netloc8-timezone-from-client', 'timezoneFromClient', 'boolean'],\n];\n\n/**\n * Get the full geolocation data for the current request.\n * Must be called from a Server Component, Route Handler, or Server Action.\n *\n * Returns the Geo object populated by the proxy. If the proxy did not run\n * (e.g. the route is excluded from the matcher), returns an empty object.\n */\nexport async function getGeo(): Promise<Geo> {\n try {\n const { headers } = await import('next/headers');\n const h = await headers();\n const geo: Geo = {};\n\n for (const [header, field, type] of HEADER_MAP) {\n const raw = h.get(header);\n if (raw === null) {\n continue;\n }\n\n try {\n const decoded = decodeURIComponent(raw);\n\n if (type === 'number') {\n const num = parseFloat(decoded);\n if (!isNaN(num)) {\n (geo as Record<string, unknown>)[field] = num;\n }\n } else if (type === 'boolean') {\n (geo as Record<string, unknown>)[field] = decoded === 'true';\n } else {\n (geo as Record<string, unknown>)[field] = decoded;\n }\n } catch {\n // Skip this header if decodeURIComponent throws\n }\n }\n\n return geo;\n } catch {\n return {};\n }\n}\n\n/**\n * Get the timezone for the current request.\n * Shorthand for (await getGeo()).timezone.\n */\nexport async function getTimezone(): Promise<string | undefined> {\n const geo = await getGeo();\n return geo.timezone;\n}\n"],"mappings":";;;;AAKA,MAAM,aAA0E;CAC5E;EAAC;EAAgB;EAAM;EAAS;CAChC;EAAC;EAAwB;EAAa;EAAS;CAC/C;EAAC;EAAuB;EAAa;EAAS;CAC9C;EAAC;EAA4B;EAAiB;EAAS;CACvD;EAAC;EAAqB;EAAW;EAAS;CAC1C;EAAC;EAA0B;EAAe;EAAS;CACnD;EAAC;EAAmB;EAAQ;EAAU;CACtC;EAAC;EAAoB;EAAU;EAAS;CACxC;EAAC;EAAyB;EAAc;EAAS;CACjD;EAAC;EAAkB;EAAQ;EAAS;CACpC;EAAC;EAAyB;EAAc;EAAS;CACjD;EAAC;EAAsB;EAAY;EAAS;CAC5C;EAAC;EAAuB;EAAa;EAAS;CAC9C;EAAC;EAAsB;EAAY;EAAS;CAC5C;EAAC;EAA6B;EAAkB;EAAS;CACzD;EAAC;EAAuB;EAAa;EAAS;CAC9C;EAAC;EAAwB;EAAa;EAAU;CAChD;EAAC;EAA0B;EAAe;EAAS;CACnD;EAAC;EAAkC;EAAsB;EAAU;CACtE;;;;;;;;AASD,eAAsB,SAAuB;AACzC,KAAI;EACA,MAAM,EAAE,YAAY,MAAM,OAAO;EACjC,MAAM,IAAI,MAAM,SAAS;EACzB,MAAM,MAAW,EAAE;AAEnB,OAAK,MAAM,CAAC,QAAQ,OAAO,SAAS,YAAY;GAC5C,MAAM,MAAM,EAAE,IAAI,OAAO;AACzB,OAAI,QAAQ,KACR;AAGJ,OAAI;IACA,MAAM,UAAU,mBAAmB,IAAI;AAEvC,QAAI,SAAS,UAAU;KACnB,MAAM,MAAM,WAAW,QAAQ;AAC/B,SAAI,CAAC,MAAM,IAAI,CACV,KAAgC,SAAS;eAEvC,SAAS,UACf,KAAgC,SAAS,YAAY;QAErD,KAAgC,SAAS;WAE1C;;AAKZ,SAAO;SACH;AACJ,SAAO,EAAE;;;;;;;AAQjB,eAAsB,cAA2C;AAE7D,SADY,MAAM,QAAQ,EACf"}
1
+ {"version":3,"file":"server.mjs","names":[],"sources":["../src/server.ts"],"sourcesContent":["import type { Geo } from '@netloc8/core';\n\n/**\n * Header-to-Geo field mapping.\n */\nconst HEADER_MAP: Array<[string, keyof Geo, 'string' | 'number' | 'boolean']> = [\n ['x-netloc8-ip', 'ip', 'string'],\n ['x-netloc8-ip-version', 'ipVersion', 'number'],\n ['x-netloc8-continent', 'continent', 'string'],\n ['x-netloc8-continent-name', 'continentName', 'string'],\n ['x-netloc8-country', 'country', 'string'],\n ['x-netloc8-country-name', 'countryName', 'string'],\n ['x-netloc8-is-eu', 'isEU', 'boolean'],\n ['x-netloc8-region', 'region', 'string'],\n ['x-netloc8-region-name', 'regionName', 'string'],\n ['x-netloc8-city', 'city', 'string'],\n ['x-netloc8-postal-code', 'postalCode', 'string'],\n ['x-netloc8-latitude', 'latitude', 'number'],\n ['x-netloc8-longitude', 'longitude', 'number'],\n ['x-netloc8-timezone', 'timezone', 'string'],\n ['x-netloc8-accuracy-radius', 'accuracyRadius', 'number'],\n ['x-netloc8-precision', 'precision', 'string'],\n ['x-netloc8-is-limited', 'isLimited', 'boolean'],\n ['x-netloc8-limit-reason', 'limitReason', 'string'],\n ['x-netloc8-timezone-from-client', 'timezoneFromClient', 'boolean'],\n];\n\n/**\n * Get the full geolocation data for the current request.\n * Must be called from a Server Component, Route Handler, or Server Action.\n *\n * Returns the Geo object populated by the proxy. If the proxy did not run\n * (e.g. the route is excluded from the matcher), returns an empty object.\n */\nexport async function getGeo(): Promise<Geo> {\n try {\n const { headers } = await import('next/headers');\n const h = await headers();\n const geo: Geo = {};\n\n for (const [header, field, type] of HEADER_MAP) {\n const raw = h.get(header);\n if (raw === null) {\n continue;\n }\n\n try {\n const decoded = decodeURIComponent(raw);\n\n if (type === 'number') {\n const num = parseFloat(decoded);\n if (!isNaN(num)) {\n (geo as Record<string, unknown>)[field] = num;\n }\n } else if (type === 'boolean') {\n (geo as Record<string, unknown>)[field] = decoded === 'true';\n } else {\n (geo as Record<string, unknown>)[field] = decoded;\n }\n } catch {\n // Skip this header if decodeURIComponent throws\n }\n }\n\n return geo;\n } catch {\n return {};\n }\n}\n\n/**\n * Get the timezone for the current request.\n * Shorthand for (await getGeo()).timezone.\n */\nexport async function getTimezone(): Promise<string | undefined> {\n const geo = await getGeo();\n return geo.timezone;\n}\n"],"mappings":";;;;AAKA,MAAM,aAA0E;CAC5E;EAAC;EAAgB;EAAM;EAAS;CAChC;EAAC;EAAwB;EAAa;EAAS;CAC/C;EAAC;EAAuB;EAAa;EAAS;CAC9C;EAAC;EAA4B;EAAiB;EAAS;CACvD;EAAC;EAAqB;EAAW;EAAS;CAC1C;EAAC;EAA0B;EAAe;EAAS;CACnD;EAAC;EAAmB;EAAQ;EAAU;CACtC;EAAC;EAAoB;EAAU;EAAS;CACxC;EAAC;EAAyB;EAAc;EAAS;CACjD;EAAC;EAAkB;EAAQ;EAAS;CACpC;EAAC;EAAyB;EAAc;EAAS;CACjD;EAAC;EAAsB;EAAY;EAAS;CAC5C;EAAC;EAAuB;EAAa;EAAS;CAC9C;EAAC;EAAsB;EAAY;EAAS;CAC5C;EAAC;EAA6B;EAAkB;EAAS;CACzD;EAAC;EAAuB;EAAa;EAAS;CAC9C;EAAC;EAAwB;EAAa;EAAU;CAChD;EAAC;EAA0B;EAAe;EAAS;CACnD;EAAC;EAAkC;EAAsB;EAAU;CACtE;;;;;;;;AASD,eAAsB,SAAuB;AACzC,KAAI;EACA,MAAM,EAAE,YAAY,MAAM,OAAO;EACjC,MAAM,IAAI,MAAM,SAAS;EACzB,MAAM,MAAW,EAAE;AAEnB,OAAK,MAAM,CAAC,QAAQ,OAAO,SAAS,YAAY;GAC5C,MAAM,MAAM,EAAE,IAAI,OAAO;AACzB,OAAI,QAAQ,KACR;AAGJ,OAAI;IACA,MAAM,UAAU,mBAAmB,IAAI;AAEvC,QAAI,SAAS,UAAU;KACnB,MAAM,MAAM,WAAW,QAAQ;AAC/B,SAAI,CAAC,MAAM,IAAI,CACV,KAAgC,SAAS;eAEvC,SAAS,UACf,KAAgC,SAAS,YAAY;QAErD,KAAgC,SAAS;WAE1C;;AAKZ,SAAO;SACH;AACJ,SAAO,EAAE;;;;;;;AAQjB,eAAsB,cAA2C;AAE7D,SADY,MAAM,QAAQ,EACf"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netloc8/nextjs",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "license": "Elastic-2.0",
6
6
  "exports": {
@@ -35,7 +35,7 @@
35
35
  "react": ">=19.0.0"
36
36
  },
37
37
  "dependencies": {
38
- "@netloc8/netloc8-js": "workspace:*",
39
- "@netloc8/react": "workspace:*"
38
+ "@netloc8/core": "0.2.0",
39
+ "@netloc8/react": "0.2.0"
40
40
  }
41
41
  }